差别
这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录 前一修订版 后一修订版 | 前一修订版 后一修订版 两侧同时换到之后的修订记录 | ||
uart_verilog [2020/08/22 15:11] gongyu [Serial interface 2 - Baud generator] |
uart_verilog [2021/02/06 07:47] gongyusu |
||
---|---|---|---|
行 1: | 行 1: | ||
- | ## 串行接口RS-232 | + | ## 串行接口RS-232通信的Verilog代码 |
串口UART是PC和FPGA通信的最简单的方式,它是一种异步串行/全双工的通信方式,尤其是目前的PC都是通过USB端口来进行UART数据的传输,可以实现更高的传输速率,比如1.5Mbps。 | 串口UART是PC和FPGA通信的最简单的方式,它是一种异步串行/全双工的通信方式,尤其是目前的PC都是通过USB端口来进行UART数据的传输,可以实现更高的传输速率,比如1.5Mbps。 | ||
--- | --- | ||
- | #### 异步通信 | + | ### 1. 异步通信的原理 |
- | ##### 异步发送 | + | |
+ | #### 1.1 异步发送 | ||
{{ ::serialtxdmodule.gif |}} | {{ ::serialtxdmodule.gif |}} | ||
- | ##### 异步接收 | + | #### 1.2 异步接收 |
{{ ::SerialRxDmodule.gif |}} | {{ ::SerialRxDmodule.gif |}} | ||
行 13: | 行 14: | ||
--- | --- | ||
- | #### 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根最重要的信号: |
- | + | ||
- | RS-232有9根管脚, 有3根最重要的信号: | + | |
{{ ::SerialConnector.jpg |}} | {{ ::SerialConnector.jpg |}} | ||
- | * 管脚2: RxD(接收数据). | + | * 管脚2: RxD(接收数据). |
- | * 管脚3: TxD(发送数据). | + | * 管脚3: TxD(发送数据). |
- | * 管脚5: GND(地). | + | * 管脚5: GND(地). |
只需要3根线,就可以进行数据的收、发。 | 只需要3根线,就可以进行数据的收、发。 | ||
数据通常以多个8位(我们称之为一个Byte)来进行发送,先将其进行串行化:低位(数据的bit 0)先发送,接着是bit 1, ... 最后是最高位的bit 7。 | 数据通常以多个8位(我们称之为一个Byte)来进行发送,先将其进行串行化:低位(数据的bit 0)先发送,接着是bit 1, ... 最后是最高位的bit 7。 | ||
- | #### 异步通信: | ||
此接口采用异步协议,也就是没有时钟信号与数据一起发送,接收端必须能够对接收到的数据自行进行“定时”提取和判决。 | 此接口采用异步协议,也就是没有时钟信号与数据一起发送,接收端必须能够对接收到的数据自行进行“定时”提取和判决。 | ||
行 43: | 行 40: | ||
{{ ::SerialCommunication55.gif |}} | {{ ::SerialCommunication55.gif |}} | ||
- | |||
一个字节0x55的数值用二进制表示就是01010101,由于先发送最低位(bit-0),线路的变化为: 1-0-1-0-1-0-1-0. | 一个字节0x55的数值用二进制表示就是01010101,由于先发送最低位(bit-0),线路的变化为: 1-0-1-0-1-0-1-0. | ||
行 54: | 行 50: | ||
这些位数很难看出来,可以看出,让接收端知道数据的发送速率是非常重要的。 | 这些位数很难看出来,可以看出,让接收端知道数据的发送速率是非常重要的。 | ||
- | #### 数据发送能够多块? | + | #### 1.4 数据发送能够多快? |
- | 发送的速度是以波特(每秒都少个位)来标称的,例如1000波特意味着每秒1000位,或者说每一位持续时间为1毫秒 | + | 发送的速度是以波特(每秒多少个位)来标称的,例如1000波特意味着每秒1000位,或者说每一位持续时间为1毫秒 |
RS-232接口的传输速率不是任意的,它有一些固定的值: | RS-232接口的传输速率不是任意的,它有一些固定的值: | ||
行 73: | 行 69: | ||
- | ### 串行接口的Verilog实现 | + | ### 2. 串行接口的Verilog实现 |
这里我们用115200波特率,FPGA一般运行在更高的频率,远高于115200Hz,我们需要用FPGA的时钟产生每秒115200个脉冲。 | 这里我们用115200波特率,FPGA一般运行在更高的频率,远高于115200Hz,我们需要用FPGA的时钟产生每秒115200个脉冲。 | ||
- | 传统上RS-232芯片采用1.8432MHz的时钟,可以通过/16分频轻松的到115200Hz以及其它波特率的频率。 | + | 传统上RS-232芯片采用1.8432MHz的时钟,可以通过/16分频能够轻松得到115200Hz以及其它波特率的频率。 |
<code verilog> | <code verilog> | ||
行 88: | 行 84: | ||
</code> | </code> | ||
- | 如果时钟不是1.8432MHz,比如2MHz,为产生115200MHz的频率需要分频17.361111111,不是一个整数,解决的方法为时而/17,时而/18,以确保最后得到的平均数为17.361111111,串行接口能够容忍波特率一定的误差范围。 | + | 如果时钟不是1.8432MHz,比如2MHz,为产生115200MHz的频率需要分频17.361111111,不是一个整数,解决的方法为时而/17,时而/18,以确保最后得到的平均数为17.361111111,串行接口能够容忍波特率一定的误差范围 - 此方式类似于DDS中任意频率的生成机制。 |
<code verilog> | <code verilog> | ||
行 99: | 行 95: | ||
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; | ||
行 123: | 行 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. | ||
- | Serial interface 3 - RS-232 transmitter | + | #### 2.2 RS-232的数据发送 |
- | We are building an "async transmitter" with fixed parameters: 8 data bits, 2 stop bits, no-parity. | + | 异步发送的固定参数:8个数据位,2个停止位,无奇偶校验。 |
- | It works like that: | + | 发送端获取8位的数据,将其串行化(当Txd_start信号被断言的时候),当传输发生的时候“busy”信号会被拉高,在此期间“TxD_start”信号被忽略。 |
- | 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; | ||
行 165: | 行 156: | ||
</code> | </code> | ||
- | Now, we just need to generate the "TxD" output. | + | 现在,我们只需要产生"TxD"输出。 |
<code verilog> | <code verilog> | ||
reg muxbit; | reg muxbit; | ||
行 181: | 行 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 8 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 D 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; | ||
行 232: | 行 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; | ||
行 253: | 行 240: | ||
</code> | </code> | ||
- | Notice that we used a "next_bit" signal, to go from bit to bit. | + | 请注意,我们使用了“ next_bit”信号,从一个位到另一个位。 |
<code verilog> | <code verilog> | ||
行 268: | 行 255: | ||
</code> | </code> | ||
- | Finally a shift register collects the data bits as they come. | + | 最后,移位寄存器在数据位到来时收集它们。 |
<code verilog> | <code verilog> | ||
行 276: | 行 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> | ||
行 308: | 行 291: | ||
</code> | </code> | ||
- | Remember to grab the async_receiver and async_transmitter modules here, and to update the clock frequency values inside. |