差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

两侧同时换到之前的修订记录 前一修订版
后一修订版
前一修订版
uart_verilog [2020/03/22 12:52]
gongyu
uart_verilog [2021/02/06 16:25] (当前版本)
gongyusu
行 1: 行 1:
-##串行RS-232+## 异步收发器UART的Verilog代码 
 +串口[[UART]]是PC和FPGA通信的最简单的方式,它是一种异步串行/​全双工的通信方式,尤其是目前的PC都是通过USB端来进行UART数据的传输,可以实现更高的传输速率,比如1.5Mbps。
  
 --- ---
-###异步通信 +### 1. 异步通信的原理 
-####异步发送+ 
 +#### 1.1 异步发送
 {{ ::​serialtxdmodule.gif |}} {{ ::​serialtxdmodule.gif |}}
  
-####异步接收+#### 1.2 异步接收 
 +{{ ::​SerialRxDmodule.gif |}}
  
 +更多关于UART串行通信的信息,可以阅读[[uart|异步串行通信]]
  
 --- ---
-#### RS-232的串行接口是如何工作的?+#### 1.3 RS-232的串行接口是如何工作的?
 一个RS-232接口有如下的特性: 一个RS-232接口有如下的特性:
   * 使用9针的连接器"​DB-9"​ (更老的PCs使用25管脚的"​DB-25"​),现在都在使用USB-UART的接口方式   * 使用9针的连接器"​DB-9"​ (更老的PCs使用25管脚的"​DB-25"​),现在都在使用USB-UART的接口方式
   * 允许双向全双工通信(PC可以同时接收、发送数据).   * 允许双向全双工通信(PC可以同时接收、发送数据).
   * 最高的通信速率可以达到10KBytes/​s.   * 最高的通信速率可以达到10KBytes/​s.
-  * DB-9连接器 +  * DB-9连接器, 它有3根最重要的信号:​ 
- +{{ ::​SerialConnector.jpg |}} 
-RS-232有9根管脚, ​有3根最重要的信号:​ +    * 管脚2: RxD(接收数据). 
- +    * 管脚3: TxD(发送数据). 
-  * 管脚2: RxD(接收数据). +    * 管脚5: GND(地).
-  * 管脚3: TxD(发送数据). +
-  * 管脚5: GND(地). +
 只需要3根线,就可以进行数据的收、发。 只需要3根线,就可以进行数据的收、发。
  
 数据通常以多个8位(我们称之为一个Byte)来进行发送,先将其进行串行化:低位(数据的bit 0)先发送,接着是bit 1, ... 最后是最高位的bit 7。 数据通常以多个8位(我们称之为一个Byte)来进行发送,先将其进行串行化:低位(数据的bit 0)先发送,接着是bit 1, ... 最后是最高位的bit 7。
  
-#### 异步: +此接口采用异步协议,也就是没有时钟号与数据一起发送,接收端必须能够对接收到的数据自行进行“定时”提取和判决。
-This interface uses an asynchronous protocol. That means that no clock signal is transmitted along the data. The receiver has to have a way to "​time"​ itself to the incoming data bits.+
  
-In the case of RS-232, that's done this way:+RS-232是这样处理的: 
 +  - 收、发两端采用事先约定好的同样的通信参数(速率、格式。。。),这要在通信之前手动设定 
 +  - 只要是线路闲置,发送端就发出"​idle"​ (="​1"​) 
 +  - 每一次要发送一个字节的数据,发送端都要先发一个"​start"​ (="​0"​),这样接收端就可以判别出有一个字节要来了 
 +  - 然后发送一个字节的8个位 
 +  - 每发完一个字节以后发送端就发"​stop"​ (="​1"​)
  
-  - Both side of the cable agree in advance on the communication parameters (speed, format...). That's done manually before communication starts. +我们以0x55来看看是如何发送的
-  - The transmitter sends "​idle"​ (="​1"​) when and as long as the line is idle. +
-  - The transmitter sends "​start"​ (="​0"​) before each byte transmitted,​ so that the receiver can figure out that a byte is coming. +
-  - The 8 bits of the byte data are sent. +
-  - The transmitter sends "​stop"​ (="​1"​) after each byte. +
-Let's see how looks the byte 0x55 when transmitted:​+
  
 +{{ ::​SerialCommunication55.gif |}}
  
 +一个字节0x55的数值用二进制表示就是01010101,由于先发送最低位(bit-0),线路的变化为:​ 1-0-1-0-1-0-1-0.
  
-Byte 0x55 is 01010101 in binary. +这里有另外一个例子:​ 
-But since it is transmitted LSB (bit-0) first, the line toggles like that1-0-1-0-1-0-1-0.+{{ ::SerialCommunication.gif |}}
  
-Here's another example:+传输的数据为0xC4,能不能看出来?
  
-Here the data is 0xC4, can you see it? +这些位数很难看出来,可以看出,让接收端知道数据的发送速率是非常重要的。
-The bits are harder to see. That illustrates how important it is for the receiver to know at which speed the data is sent.+
  
-How fast can we send data? +#### 1.4 数据发送能够多快? 
-The speed is specified in baud, i.e. how many bits-per-seconds can be sent. For example, ​1000 bauds would mean 1000 bits-per-seconds,​ or that each bit lasts one millisecond. +发送的速度是以波特(每秒多少个位)来标称的,例如1000波特意味着每秒1000位,或者说每一位持续时间为1毫秒
- +
-Common implementations of the RS-232 interface (like the one used in PCs) don't allow just any speed to be used. If you want to use 123456 bauds, you're out of luck. You have to settle to some "​standard"​ speed. Common values are:+
  
 +RS-232接口的传输速率不是任意的,它有一些固定的值:
   * 1200 bauds.   * 1200 bauds.
   * 9600 bauds.   * 9600 bauds.
   * 38400 bauds.   * 38400 bauds.
-  * 115200 bauds (usually the fastest you can go). +  * 115200 bauds (一般来讲这是能用到的最快的速率). 
-At 115200 ​bauds, each bit lasts (1/115200) = 8.7µs. ​If you transmit ​8-bits data, that lasts 8 x 8.7µs = 69µs. ​But each byte requires an extra start and stop bit, so you actually need 10 x 8.7µs = 87µs. That translates to a maximum speed of 11.5KBytes per second.+ 
 +如果传输速率为115200波特,每一位持续时间为(1/115200) = 8.7µs. ​如果你要传输8位一个字节的数据,持续时间为8 x 8.7µs = 69µs.但每一个字节还需要额外的开始、停止位,所以你实际需要10 x 8.7µs = 87µs的时间,也就是最大的传输速率为每秒11.5K字节。
  
-At 115200 bauds, some PCs with buggy chips require a "​long"​ stop bit (1.5 or 2 bits long...) which make the maximum speed drop to around 10.5KBytes per second. 
  
-Physical layer +物理层
-The signals on the wires use a positive/​negative voltage scheme.+
  
-  ​* "​1" ​is sent using -10V (or between ​-5V and -15V). +在传输的导线上,信号采用正/​负电压的机制: 
-  * "​0" ​is sent using +10V (or between ​5V and 15V)+  ​* "​1" ​使用-10V来发送 ​(或介于-5V-15V之间). 
-So an idle line carries something like -10V.+  * "​0" ​使用+10V来发送 ​(或介于5V15V之间).
  
-Links 
-An Introduction to RS232 Serial Communications 
-Voltage Waveshapes, part of this huge Serial HOWTO 
  
-### Serial interface ​- Baud generator +### 2. 串行接口的Verilog实现 
-Here we want to use the serial link at maximum speed, i.e. 115200 ​bauds (slower speeds would also be easy to generate). FPGAs usually run at MHz speeds, well above 115200Hz ​(RS-232 is pretty slow by today'​s standards). We need to find a way to generate (from the FPGA clock) a "​tick"​ as close as possible to 115200 ​times a second.+这里我们用115200波特率,FPGA一般运行在更高的频率,远高于115200Hz,我们需要用FPGA的时钟产生每秒115200个脉冲。
  
-Traditionally, ​RS-232 ​chips use a 1.8432MHz ​clock, because that makes generating the standard baud frequencies very easy... 1.8432MHz divided by 16 gives 115200Hz.+传统上RS-232芯片采用1.8432MHz的时钟,可以通过/​16分频能够轻松得到115200Hz以及其它波特率的频率。
  
 <code verilog> <code verilog>
-// let's assume the FPGA clock signal runs at 1.8432MHz +// 假设FPGA的时钟为1.8432MHz 
-// we create a 4-bit counter+// 我们创建一个4位的计数器
 reg [3:0] BaudDivCnt; reg [3:0] BaudDivCnt;
-always @(posedge clk) BaudDivCnt <= BaudDivCnt + 1; // count forever from to 15+always @(posedge clk) BaudDivCnt <= BaudDivCnt + 1; // 015计数
  
-// and a tick signal that is asserted once every 16 clocks ​(so 115200 ​times a second)+// 16个时钟就会产生一个脉冲信号(每秒115200个脉冲信号)
 wire BaudTick = (BaudDivCnt==15);​ wire BaudTick = (BaudDivCnt==15);​
 </​code>​ </​code>​
  
-That was easy. But what do you do if instead of 1.8432MHz, you have a 2MHz clock? To generate 115200Hz from a 2MHz clock, we need to divide the clock by "17.361111111..." Not exactly a round number. The solution is to divide sometimes by 17, sometimes by 18, making sure the ratio stays "17.361111111". That's actually easy to do. +如果时钟不是1.8432MHz,比如2MHz,为产生115200MHz的频率需要分频17.361111111,不是一个整数,解决的方法为时而/​17,时而/18,以确保最后得到的平均数为17.361111111,串行接口能够容忍波特率一定的误差范围 ​此方式类似于DDS中任意频率的生成机制。
- +
-Look at the following "​C"​ code: +
-<code c> +
-while(1) // repeat forever +
-+
-  acc += 115200; +
-  if(acc>​=2000000) printf("​*"​);​ else printf("​ "); +
- +
-  acc %= 2000000; +
-+
-</​code>​ +
- +
-That prints the "​*"​ in the exact ratio, once every "​17.361111111..."​ loops on average. +
- +
-To obtain the same thing efficiently in an FPGA, we rely on the fact that the serial interface can tolerate a few % of error in the baud frequency generator. +
- +
-It is desirable that the 2000000 be a power of two. Obviously 2000000 is not. So we change the ratio... Instead of "​2000000/​115200",​ let's use "​1024/​59"​ = 17.356. That's very close to our ideal ratio, and makes an efficient FPGA implementation:​ we use a 10-bit accumulator incremented by 59, with a tick marked everytime the accumulator overflows.+
  
 <code verilog> <code verilog>
-// let's assume the FPGA clock signal runs at 2.0000MHz +// 假设FPGA的时钟为2.0000MHz 
-// we use a 10-bit accumulator plus an extra bit for the accumulator carry-out +// 使用10位的累加器再附加额外的位用于累加器的进位,总计11位 
-reg [10:0] acc;   // 11 bits total!+reg [10:0] acc;   // ​总计11
  
-// add 59 to the accumulator at each clock+// 每一个时钟累加器增加59
 always @(posedge clk) always @(posedge clk)
   acc <= acc[9:0] + 59; // use 10 bits from the previous accumulator result, but save the full 11 bits result   acc <= acc[9:0] + 59; // use 10 bits from the previous accumulator result, but save the full 11 bits result
  
-wire BaudTick = acc[10]; // so that the 11th bit is the accumulator carry-out+wire BaudTick = acc[10]; // 用最高位作为波特率的时钟输出
 </​code>​ </​code>​
  
-Using our 2MHz clock, "​BaudTick" ​is asserted ​115234 ​times a second, a 0.03% error from the ideal 115200.+2MHz的情况下, "​BaudTick"​每秒钟变动115234次,与115200的误差为0.03%
  
-Parameterized FPGA baud generator 
-The previous design was using a 10 bits accumulator,​ but as the clock frequency increases, more bits are required. 
  
-Here's a design with a 25MHz clock and a 16 bits accumulator. The design is parameterized,​ so easy to customize.+#### 2.1 参数化的FPGA波特率发生器 
 +下面的设计为25MHz的系统时钟,使用一个16位的累加器,代码可以通过简单地配置一下参数就可以灵活定制,适用不同的系统时钟。
  
 <code verilog> <code verilog>
  
-parameter ClkFrequency = 25000000; // 25MHz +parameter ClkFrequency = 25000000; // 此处为25MHz,使用不同的系统只需要修改这个参数 
-parameter Baud = 115200;+parameter Baud = 115200; ​ //​此处设置波特率为115200,如果使用不同的波特率只需要修改这个参数
 parameter BaudGeneratorAccWidth = 16; parameter BaudGeneratorAccWidth = 16;
 parameter BaudGeneratorInc = (Baud<<​BaudGeneratorAccWidth)/​ClkFrequency;​ parameter BaudGeneratorInc = (Baud<<​BaudGeneratorAccWidth)/​ClkFrequency;​
行 140: 行 118:
 </​code>​ </​code>​
  
-One last implementation issue: the "​BaudGeneratorInc"​ calculation is wrong, due to the fact that Verilog uses 32 bits intermediate results, and the calculation exceeds that. Change the line as follow for a workaround. 
  
-<code verilog>+如果系统时钟为12MHz,波特率115200Hz,波特率步进量为629,误差为0.02%
  
 +由于计算出来的BaudGeneratorInc结果超出了32位的中间结果,需要做一些调整:
 +
 +<code verilog>
 parameter BaudGeneratorInc = ((Baud<<​(BaudGeneratorAccWidth-4))+(ClkFrequency>>​5))/​(ClkFrequency>>​4);​ parameter BaudGeneratorInc = ((Baud<<​(BaudGeneratorAccWidth-4))+(ClkFrequency>>​5))/​(ClkFrequency>>​4);​
 </​code>​ </​code>​
  
-This line has also the added advantage to round the result instead of truncating. 
  
-Now that we have a precise enough Baud generator, we can go ahead with the RS-232 ​transmitter and receiver modules.+#### 2.2 RS-232的数据发送 
 +异步发送的固定参数:8个数据位,2个停止位,无奇偶校验。
  
-Serial interface 3 - RS-232 transmitter +发送端获取8位的数据,将其串行化(当Txd_start信号被断言的时候),当传输发生的时候“busy”信号会被拉高,在此期间“TxD_start”信号被忽略。
-We are building an "async transmitter"​ with fixed parameters: ​data bits, 2 stop bits, no-parity.+
  
-It works like that: +采用状态机进行发送比较合适:
- +
-The transmitter takes an 8-bits data inside the FPGA and serializes it (starting when the "​TxD_start"​ signal is asserted). +
-The "​busy"​ signal is asserted while a transmission occurs (the "​TxD_start"​ signal is ignored during that time). +
-Serializing the data +
-To go through the start bit, the 8 data bits, and the stop bits, a state machine seems appropriate.+
 <code verilog> <code verilog>
 reg [3:0] state; reg [3:0] state;
行 182: 行 156:
 </​code>​ </​code>​
  
-Now, we just need to generate the "​TxD" ​output.+现在,我们只需要产生"​TxD"​输出。
 <code verilog> <code verilog>
 reg muxbit; reg muxbit;
行 198: 行 172:
 endcase endcase
  
-// combine start, data, and stop bits together+//将起始位、数据位、停止位结合在一起
 assign TxD = (state<​4) | (state[3] & muxbit); assign TxD = (state<​4) | (state[3] & muxbit);
 </​code>​ </​code>​
  
-The complete code can be found here.+下面是完整的代码[[async_verilog_source|异步串行通信的Verilog源代码]]也可以直接下载解压使用:{{:​async.zip|异步通信的Verilog和C程序完整源代码}}。
  
-### Serial interface 4 - RS-232 ​receiver +#### 2.3 RS-232的数据接收 
-We are building an "async receiver":​ +现在我们来看看数据的接收端。
-Our implementation works like that:+
  
-The module assembles data from the RxD line as it comes. +该模块从RxD线上收集数据。当接收到一个字节时,它出现在“数据”总线上。 一旦接收到一个完整的字节,“data_ready”就会被拉高一个时钟。 
-As a byte is being received, it appears on the "​data"​ bus. Once a complete byte has been received, "data_ready" is asserted for one clock. +注意,只有在断言“ ​data_ready”时,“ ​data”才有效。 其余时间,请不要使用它,因为可能会出现新数据,从而使数据混乱。
-Note that "​data"​ is valid only when "data_ready" is asserted. The rest of the time, don't use it as new data may come that shuffles it.+
  
-Oversampling +过采样 
-An asynchronous receiver has to somehow get in-sync with the incoming signal (it normally doesn'​t have access to the clock used by the transmitter).+异步接收器必须以某种方式与输入信号同步(它通常无法访问发送器使用的时钟)。为了确定何时会有新的数据字节,我们通过以波特率频率的倍数对信号进行过采样来寻找“开始”位。一旦检测到“开始”位,我们就以已知的波特率对线路进行采样以获取数据位。
  
-To determine when a new data byte is coming, we look for the "​start"​ bit by oversampling the signal at a multiple of the baud rate frequency. +接收器通常以16倍波特率对输入信号进行过采样。 我们在这里使用了8...对于115200波特,采样率为921600Hz
-Once the "​start"​ bit is detected, we sample the line at the known baud rate to acquire the data bits. +
-Receivers typically oversample the incoming signal at 16 times the baud rate. We use times here... For 115200 ​bauds, that gives a sampling rate of 921600Hz.+
  
-Let's assume that we have a "Baud8Tick" signal available, asserted ​921600 ​times a second.+假设我们有一个可用的“ ​Baud8Tick”信号,该信号每秒拉高921600次。
  
-The design +具体的设计 
-First, the incoming "RxD" signal has no relationship with our clock. +首先,传入的“ ​RxD”信号与我们的时钟无关。我们使用两个D触发器对其进行过采样,并将其同步到我们的时钟域。
-We use two flip-flops to oversample it, and synchronize it to our clock domain.+
  
 <code verilog> <code verilog>
- 
 reg [1:0] RxD_sync; reg [1:0] RxD_sync;
 always @(posedge clk) if(Baud8Tick) RxD_sync <= {RxD_sync[0],​ RxD}; always @(posedge clk) if(Baud8Tick) RxD_sync <= {RxD_sync[0],​ RxD};
 </​code>​ </​code>​
  
-We filter the data, so that short spikes on the RxD line aren't mistaken with start bits.+我们对数据进行过滤,以免RxD线上的短尖峰误认为起始位。 
 <code verilog> <code verilog>
 reg [1:0] RxD_cnt; reg [1:0] RxD_cnt;
行 249: 行 218:
 </​code>​ </​code>​
  
-A state machine allows us to go through each bit received, once a "​start"​ is detected.+一旦检测到“开始”,状态机就允许我们遍历接收到的每个位。 
 <code verilog> <code verilog>
 reg [3:0] state; reg [3:0] state;
行 270: 行 240:
 </​code>​ </​code>​
  
-Notice that we used a "next_bit" signal, to go from bit to bit.+请注意,我们使用了“ ​next_bit”信号,从一个位到另一个位。
  
 <code verilog> <code verilog>
行 285: 行 255:
 </​code>​ </​code>​
  
-Finally a shift register collects the data bits as they come.+最后,移位寄存器在数据位到来时收集它们。
  
 <code verilog> <code verilog>
行 293: 行 263:
 </​code>​ </​code>​
  
-The complete code can be found here. +下面是完整的代码[[async_verilog_source|异步串行通信的Verilog源代码]]也可以直接下载解压使用:{{:​async.zip|异步通信的Verilog和C程序完整源代码}}。
-It has a few improvements;​ follow the comments in the code.+
  
-Links +#### 2.4 使用RS-232发送和接收数据 
-More details on Asynchronous Communication+在这里我们设计允许通过PC(通过PC的串行端口)控制几个FPGA引脚。
  
-Serial interface 5 - How to use the RS-232 transmitter and receiver +它在FPGA(端口名为“ GPout”)上创建8个输出,GPout由FPGA接收的任何字符更新。FPGA上还有8个输入(端口名为“ GPin”)。 每当FPGA接收到字符时,便发送GPin。
-This design allows controlling a few FPGA pins from your PC (through your PC's serial port).+
  
-It create 8 outputs on the FPGA (port named "​GPout"​). GPout is updated by any character that the FPGA receives. +GP输出可用于从PC远程控制任何东西,可能是LED或咖啡机...等等
-Also 8 inputs on the FPGA (port named "​GPin"​). GPin is transmitted every time the FPGA receives a character. +
-The GP outputs can be used to control anything remotely from your PC, might be LEDs or a coffee machine...+
  
 <code verilog> <code verilog>
行 325: 行 291:
 </​code>​ </​code>​
  
-Remember to grab the async_receiver and async_transmitter modules here, and to update the clock frequency values inside.