115200 uart接口_【接口时序】3、UART串口收发的原理与Verilog实现

日期: 2024-09-19 23:07:06|浏览: 10|编号: 96565

友情提醒:信息内容由网友发布,请自鉴内容实用性。

115200 uart接口_【接口时序】3、UART串口收发的原理与Verilog实现

欢迎FPGA工程师加入官方微信技术群

点击蓝色字关注我们 FPGA首页-中国最好最大的FPGA纯工程师社区

1.软件平台和硬件平台

软件平台:

1.操作系统:-8.1

2.开发套件:ISE14.7

3.仿真工具:-10.4-SE

硬件平台:

1. FPGA模型:

2. USB转UART芯片:实验室

2.原理介绍

串口是串行端口的简称,又称串行通信接口或COM接口。串口通信是指采用串行通信协议()在信号线上逐位传送数据的通信方式。串口按电气标准和协议不同分为RS-232、RS-422、RS485等,其中最常用的是RS-232接口。

RS-232接口有以下三个特点:

1. 使用 9 针连接器“DB-9”(早期计算机使用 25 针连接器“DB-25”)

2.允许全双工通信(即通过串口发送和接收数据可以同时进行)

3.最大通信速率约为/s

DB-9接口实物图如下(早期的电脑主机背面可以看到这种接口,现在一般都使用USB转串口线进行串口通讯):

虽然DB-9连接器总共有9根线,但实现串行通信只需要其中3根,即:

1. 引脚2:RXD(数据),接收串行数据

2. 引脚 3:TXD(数据),发送串行数据

3. 引脚 5:GND(),地线

串行通信中,数据是在一条宽度为1位的线上传输的。一个字节的数据被分成8份,按顺序从低到高一位一位地传输。这个过程就叫做数据的“串行化”。由于串行通信是异步通信协议,没有时钟信号随数据一起传输,而且在空闲状态(没有数据传输的状态)下,串行传输线处于高电平1。因此,发送方在发送一个字节的数据前,会先发送一个低电平0。接收方收到这个低电平0后,就知道有数据来了,准备开始接收数据,实现一次通信。串行通信时序如下图所示:

串行通信规格如下:

1.空闲状态(无数据传输)时,串行传输线为高电平1

2、发送方发送一个低电平0,表示开始传输数据,这个低电平表示传输的起始位。

3. 8位数据位(1 Byte)从最低位开始发送,最后发送最高位。

4、数据的最高位发送完之后,接下来的一位是校验位,可以省略,同时,当不发送校验位时,接收方也不会相应地收到校验位。

5、最后一位为停止位,用高电平1来指示停止位。

下面以发送字节0x55为例说明整个发送过程:

首先将0x55转换成二进制:显然0x55的最低位0为1,次低位1为0,最高位7为0。由于串口发送一个字节都是从最低位开始发送的,所以0x55各位的发送顺序为1-0-1-0-1-0-1-0。波形如下图所示。

下面是一个波形,根据上面的规则,很容易判断这是发送字节0x13的波形。

接下来最后一个问题是:串口传输速度是多少?

其实串口传输的速度是用波特率()来规定的。波特率表示每秒发送的位数,单位是bps(bits-per-)。比如1000波特就表示1秒发送1000个bit,或者每个bit持续1ms。串口传输的波特率是有一套标准的,并不是随便一个数就可以的。常用的波特率标准有:

1.1200 bps

2. 9600 bps(常用)

3.38400 bps

4.bps(常用,通常是我们能使用的最快的波特率)

当波特率为bps时,每个位的持续时间为(1/)=8.7us,所以发送8个位(1Byte)需要的时间为8*8.7us=69us。在不考虑奇偶校验位的情况下,发送一个字节还需要额外发送一个起始位和一个停止位,所以发送一个字节实际需要的最小时间为10*8.7us=87us,也就是说1s内可以发送的字节数()为(/87)=11494,所以当波特率为时,串口传输数据的速率大约为11.5KB/s。有些计算机串口有时要求停止位较长,比如1.5个或2个停止位,这样发送一个字节所需的时间比只有一个停止位时所消耗的时间要长,这种情况下串口传输速率就会低于10.5KB/s。

通过以上一系列的总结,可以得出FPGA与PC机的串口通信主要包括三个模块:波特率产生模块、发送模块、接收模块。

3.目标函数

1、编写发送模块代码,不断发送数据0x00~0xff到PC机,PC机使用串口调试助手接收并以16进制显示。

2、根据第一个函数编写接收模块的代码,接收模块接收到第一个函数中发送模块发来的数据后,利用接收到的并行数据的低四位来驱动板子上的四个LED灯。

3、编写顶层模块,例化发送模块和接收模块,然后把PC的串口调试助手的数据发送到FPGA,FPGA收到数据后,将收到的数据返回给串口调试助手进行显示。

4.设计思路与代码编写

4.1. 发送模块波特率时钟的设计与实现

本节以波特率为例说明波特率模块设计方法,其他波特率可依此类推。由于我的开发板上的时钟为50MHz,周期T=20ns,波特率为,所以1位的持续时间为8.7us,则每位所占的周期数N=(8.7us/20ns)=434,因此可以定义一个计数器,计数器每次从0计数到433时清零,然后在计数值为1时产生一个高脉冲(这个计数值最好小于433的一半,本博客最后部分会分析原因)。发送模块只要检测到这个高脉冲到来,就发送一位,这样就实现了波特率为的串口数据传输。

接收模块的波特率时钟产生逻辑与发送模块的波特率时钟稍有不同,不同之处在于当接收模块检测到下降沿时,表示有数据到来,准备开始接收数据。由于一个位的持续时间为434个时钟周期,为了保证接收模块接收到的数据的准确性,我们需要在434/2=217个周期,也就是在数据的中间位置接收并存储输入数据。也就是说,接收模块的波特率时钟比发送模块的波特率时钟滞后几个周期。

波特率产生模块框图如下

在:

I_clk是系统时钟;

这是系统重置;

发送模块波特率使能信号,只有为1时才输出时钟信号;

接收模块波特率使能信号,只有为1时才会输出时钟信号。

波特率模块完整代码如下:

module baudrate_gen
(input I_clk , // 系统50MHz时钟input I_rst_n , // 系统全局复位input I_bps_tx_clk_en , // 串口发送模块波特率时钟使能信号input I_bps_rx_clk_en , // 串口接收模块波特率时钟使能信号output O_bps_tx_clk , // 发送模块波特率产生时钟output O_bps_rx_clk // 接收模块波特率产生时钟);parameter C_BPS9600 = 5207 , //波特率为9600bps
C_BPS19200 = 2603 , //波特率为19200bps
C_BPS38400 = 1301 , //波特率为38400bps
C_BPS57600 = 867 , //波特率为57600bps
C_BPS115200 = 433 ; //波特率为115200bpsparameter C_BPS_SELECT = C_BPS115200 ; //波特率选择reg [12:0] R_bps_tx_cnt ;reg [12:0] R_bps_rx_cnt ;///// 功能:串口发送模块的波特率时钟产生逻辑///always @(posedge I_clk or negedge I_rst_n)beginif(!I_rst_n)
R_bps_tx_cnt <= 13'd0 ;else if(I_bps_tx_clk_en == 1'b1)beginif(R_bps_tx_cnt == C_BPS_SELECT)
R_bps_tx_cnt <= 13'd0 ;else
R_bps_tx_cnt <= R_bps_tx_cnt + 1'b1 ;end else
R_bps_tx_cnt <= 13'd0 ;endassign O_bps_tx_clk = (R_bps_tx_cnt == 13'd1) ? 1'b1 : 1'b0 ;///// 功能:串口接收模块的波特率时钟产生逻辑///always @(posedge I_clk or negedge I_rst_n)beginif(!I_rst_n)
R_bps_rx_cnt <= 13'd0 ;else if(I_bps_rx_clk_en == 1'b1)beginif(R_bps_rx_cnt == C_BPS_SELECT)
R_bps_rx_cnt <= 13'd0 ;else
R_bps_rx_cnt <= R_bps_rx_cnt + 1'b1 ;end else
R_bps_rx_cnt <= 13'd0 ;endassign O_bps_rx_clk = (R_bps_rx_cnt == C_BPS_SELECT >> 1'b1) ? 1'b1 : 1'b0 ;endmodule

波特率模块的仿真图为

4.2 发送模块设计与实现

有了波特率时钟之后就可以开始编写发送模块的内部逻辑了,发送模块的结构框图如下图所示

在:

I_clk是系统时钟;

这是系统重置;

它开始发送信号。当检测到高电平时,数据输入[7:0]立即被串行化为单个位并发送出去。

发送模块波特率时钟信号,检测到为高电平时,发送1bit;

[7:0]为并行8位数据;

它是一个串行的位数据流;

发送波特率时钟的起始信号,当其为1时,波特率产生模块可以产生发送模块的波特率时钟。

为标志位,表示1个字节数据发送完毕,每发送一个字节,产生一个高脉冲。

以发送字节0x55为例,发送模块几个关键信号时序图如下图所示

发送模块代码如下:

module uart_txd
(input I_clk , // 系统50MHz时钟input I_rst_n , // 系统全局复位input I_tx_start , // 发送使能信号input I_bps_tx_clk , // 发送波特率时钟input [7:0] I_para_data , // 要发送的并行数据output reg O_rs232_txd , // 发送的串行数据,在硬件上与串口相连output reg O_bps_tx_clk_en , // 波特率时钟使能信号output reg O_tx_done // 发送完成的标志);reg [3:0] R_state ;reg R_transmiting ; // 数据正在发送标志/////// 产生发送 R_transmiting 标志位/////always @(posedge I_clk or negedge I_rst_n)beginif(!I_rst_n)
R_transmiting <= 1'b0 ;else if(O_tx_done)
R_transmiting <= 1'b0 ;else if(I_tx_start)
R_transmiting <= 1'b1 ;end/////// 发送数据状态机/////always @(posedge I_clk or negedge I_rst_n)beginif(!I_rst_n)begin
R_state <= 4'd0 ;
O_rs232_txd <= 1'b1 ;
O_tx_done <= 1'b0 ;
O_bps_tx_clk_en <= 1'b0 ; // 关掉波特率时钟使能信号end else if(R_transmiting) // 检测发送标志被拉高,准备发送数据begin
O_bps_tx_clk_en <= 1'b1 ; // 发送数据前的第一件事就是打开波特率时钟使能信号if(I_bps_tx_clk) // 在波特率时钟的控制下把数据通过一个状态机发送出去,并产生发送完成信号begincase(R_state)4'd0 : // 发送起始位begin
O_rs232_txd <= 1'b0 ;
O_tx_done <= 1'b0 ;
R_state <= R_state + 1'b1 ;end4'd1 : // 发送 I_para_data[0]begin
O_rs232_txd <= I_para_data[0] ;
O_tx_done <= 1'b0 ;
R_state <= R_state + 1'b1 ;end 4'd2 : // 发送 I_para_data[1]begin
O_rs232_txd <= I_para_data[1] ;
O_tx_done <= 1'b0 ;
R_state <= R_state + 1'b1 ;end4'd3 : // 发送 I_para_data[2]begin
O_rs232_txd <= I_para_data[2] ;
O_tx_done <= 1'b0 ;
R_state <= R_state + 1'b1 ;end4'd4 : // 发送 I_para_data[3]begin
O_rs232_txd <= I_para_data[3] ;
O_tx_done <= 1'b0 ;
R_state <= R_state + 1'b1 ;end 4'd5 : // 发送 I_para_data[4]begin
O_rs232_txd <= I_para_data[4] ;
O_tx_done <= 1'b0 ;
R_state <= R_state + 1'b1 ;end4'd6 : // 发送 I_para_data[5]begin
O_rs232_txd <= I_para_data[5] ;
O_tx_done <= 1'b0 ;
R_state <= R_state + 1'b1 ;end4'd7 : // 发送 I_para_data[6]begin
O_rs232_txd <= I_para_data[6] ;
O_tx_done <= 1'b0 ;
R_state <= R_state + 1'b1 ;end4'd8 : // 发送 I_para_data[7]begin
O_rs232_txd <= I_para_data[7] ;
O_tx_done <= 1'b0 ;
R_state <= R_state + 1'b1 ;end 4'd9 : // 发送 停止位begin
O_rs232_txd <= 1'b1 ;
O_tx_done <= 1'b1 ;
R_state <= 4'd0      ;enddefault :R_state <= 4'd0 ;

          endcase         end       end     else       begin 
        O_bps_tx_clk_en    <= 1'b0 ; // 一帧数据发送完毕以后就关掉波特率时钟使能信号
        R_state        <= 4'd0 ;
        O_tx_done      <= 1'b0 ;
        O_rs232_txd      <= 1'b1 ;       end end endmodule

当检测到输入信号为高电平时,发送模块立即将该信号拉高,表示即将发送数据。高电平期间,打开波特率时钟使能信号并在波特率时钟的控制下通过状态机将并行数据发送出去,并产生发送完成信号,待其为高电平后,再次拉低,表示发送完成。

为了实现功能1的效果,还需要编写一个顶层模块,用于实例化波特率模块和发送模块并产生发送信号,顶层模块代码如下:

module uart_tx_top
(input I_clk , // 系统50MHz时钟input I_rst_n , // 系统全局复位output O_rs232_txd // 发送的串行数据,在硬件上与串口相连);wire W_bps_tx_clk ;wire W_bps_tx_clk_en ;wire W_tx_start ;wire W_tx_done ;wire [7:0] W_para_data ;reg [7:0] R_data_reg ;reg [31:0] R_cnt_1s ;reg R_tx_start_reg ;assign W_tx_start = R_tx_start_reg ;assign W_para_data = R_data_reg ;/// 产生要发送的数据/always @(posedge I_clk or negedge I_rst_n)beginif(!I_rst_n)begin
R_cnt_1s <= 31'd0 ;
R_data_reg <= 8'd0 ;
R_tx_start_reg  <= 1'b0 ;endelse if(R_cnt_1s == 31'd24_999_999)begin
R_cnt_1s <= 31'd0 ;
R_data_reg <= R_data_reg + 1'b1 ;
R_tx_start_reg <= 1'b1 ;endelsebegin
R_cnt_1s <= R_cnt_1s + 1'b1 ;
R_tx_start_reg <= 1'b0 ;endend
uart_txd U_uart_txd
(
.I_clk (I_clk ), // 系统50MHz时钟
.I_rst_n (I_rst_n ), // 系统全局复位
.I_tx_start (W_tx_start ), // 发送使能信号
.I_bps_tx_clk (W_bps_tx_clk ), // 波特率时钟
.I_para_data (W_para_data ), // 要发送的并行数据
.O_rs232_txd (O_rs232_txd ), // 发送的串行数据,在硬件上与串口相连
.O_bps_tx_clk_en (W_bps_tx_clk_en ), // 波特率时钟使能信号
.O_tx_done (W_tx_done ) // 发送完成的标志);
baudrate_gen U_baudrate_gen
(
.I_clk (I_clk ), // 系统50MHz时钟
.I_rst_n (I_rst_n ), // 系统全局复位
.I_bps_tx_clk_en (W_bps_tx_clk_en ), // 串口发送模块波特率时钟使能信号
.I_bps_rx_clk_en ( ), // 串口接收模块波特率时钟使能信号
.O_bps_tx_clk (W_bps_tx_clk ), // 发送模块波特率产生时钟
.O_bps_rx_clk ( ) // 接收模块波特率产生时钟);endmodule

下载到板子上之前,先用仿真检查一下逻辑是否正确。仿真之前,可以把这个参数的上限值设置成较小的值,比如5000,这样可以加快仿真速度。下图是仿真时序图,显然完全满足设计要求。

仿真结束之后绑定引脚并下载到FPGA中。然后打开电脑的串口调试助手。下图是我电脑上的显示效果:

4.3 接收模块设计与实现

波特率模块和发送模块都OK之后就可以开始编写接收模块的代码了,接收模块的结构框图如下图所示

在:

I_clk是系统时钟;

这是系统重置;

开始发送信号,当其一直处于高电平时,接收模块检测到有数据就会接收数据。

接收模块波特率时钟信号,检测到为高电平,表示接收到1bit;

它是一个串行的位数据流;

[7:0]为并行8位数据;

发送波特率时钟的起始信号,当其为1时,波特率产生模块可以产生接收模块的波特率时钟。

为标志位,表示1个字节数据接收完成,当接收到一个字节时,产生一个高脉冲。

接收模块的逻辑结构与发送模块非常相似,但由于接收模块需要判断串行数据流的起始位,因此增加了检测串行数据流下降沿的逻辑,检测串行数据流下降沿的代码如下:

////// 功能:把 I_rs232_rxd 打的前两拍,是为了消除亚稳态//      把 I_rs232_rxd 打的后两拍,是为了产生下降沿标志位////always @(posedge I_clk or negedge I_rst_n)beginif(!I_rst_n)begin
R_rs232_rx_reg0 <= 1'b0 ;
R_rs232_rx_reg1 <= 1'b0 ;
R_rs232_rx_reg2 <= 1'b0 ;
R_rs232_rx_reg3 <= 1'b0 ;end elsebegin
R_rs232_rx_reg0 <= I_rs232_rxd ;
R_rs232_rx_reg1 <= R_rs232_rx_reg0 ;
R_rs232_rx_reg2 <= R_rs232_rx_reg1 ;
R_rs232_rx_reg3 <= R_rs232_rx_reg2 ;end end// 产生I_rs232_rxd信号的下降沿标志位assign W_rs232_rxd_neg = (~R_rs232_rx_reg2) & R_rs232_rx_reg3 ;

这个逻辑一共对信号进行四次拍打,前两行用于消除亚稳态(亚稳态问题我们后面会讨论),后两行用于产生信号下降沿的标志位。

这里以0x55的接收为例,接收模块几个重要信号的时序图如下图所示:

接收模块完整代码如下:

module uart_rxd
(input I_clk , // 系统50MHz时钟input I_rst_n , // 系统全局复位input I_rx_start , // 接收使能信号input I_bps_rx_clk , // 接收波特率时钟input I_rs232_rxd , // 接收的串行数据,在硬件上与串口相连output reg O_bps_rx_clk_en , // 波特率时钟使能信号output reg O_rx_done , // 接收完成标志output reg [7:0] O_para_data // 接收到的8-bit并行数据);reg R_rs232_rx_reg0 ;reg R_rs232_rx_reg1 ;reg R_rs232_rx_reg2 ;reg R_rs232_rx_reg3 ;reg R_receiving ;reg [3:0] R_state ;reg [7:0] R_para_data_reg ;wire W_rs232_rxd_neg ;////// 功能:把 I_rs232_rxd 打的前两拍,是为了消除亚稳态// 把 I_rs232_rxd 打的后两拍,是为了产生下降沿标志位////always @(posedge I_clk or negedge I_rst_n)beginif(!I_rst_n)begin
R_rs232_rx_reg0 <= 1'b0 ;
R_rs232_rx_reg1 <= 1'b0 ;
R_rs232_rx_reg2 <= 1'b0 ;
R_rs232_rx_reg3 <= 1'b0 ;end elsebegin
R_rs232_rx_reg0 <= I_rs232_rxd ;
R_rs232_rx_reg1 <= R_rs232_rx_reg0 ;
R_rs232_rx_reg2 <= R_rs232_rx_reg1 ;
R_rs232_rx_reg3 <= R_rs232_rx_reg2 ;end end// 产生I_rs232_rxd信号的下降沿标志位assign W_rs232_rxd_neg = (~R_rs232_rx_reg2) & R_rs232_rx_reg3 ;////// 功能:产生发送信号R_receiving////always @(posedge I_clk or negedge I_rst_n)beginif(!I_rst_n)
R_receiving <= 1'b0 ;else if(O_rx_done)
R_receiving <= 1'b0 ;else if(I_rx_start && W_rs232_rxd_neg)
R_receiving <= 1'b1 ;end////// 功能:用状态机把串行的输入数据接收,并转化为并行数据输出////always @(posedge I_clk or negedge I_rst_n)beginif(!I_rst_n)begin
O_rx_done <= 1'b0 ;
R_state <= 4'd0 ;
R_para_data_reg <= 8'd0 ;
O_bps_rx_clk_en <= 1'b0 ;end else if(R_receiving)begin
O_bps_rx_clk_en <= 1'b1 ; // 打开波特率时钟使能信号if(I_bps_rx_clk)begincase(R_state)4'd0 : // 接收起始位,但不保存begin
R_para_data_reg <= 8'd0 ;
O_rx_done <= 1'b0 ;
R_state <= R_state + 1'b1 ;end4'd1 : // 接收第0位,保存到R_para_data_reg[0]begin
R_para_data_reg[0] <= I_rs232_rxd ;
O_rx_done <= 1'b0 ;
R_state <= R_state + 1'b1 ;end4'd2 : // 接收第1位,保存到R_para_data_reg[1]begin
R_para_data_reg[1] <= I_rs232_rxd ;
O_rx_done <= 1'b0 ;
R_state <= R_state + 1'b1 ;end4'd3 : // 接收第2位,保存到R_para_data_reg[2]begin
R_para_data_reg[2] <= I_rs232_rxd ;
O_rx_done <= 1'b0 ;
R_state <= R_state + 1'b1 ;end 4'd4 : // 接收第3位,保存到R_para_data_reg[3]begin
R_para_data_reg[3] <= I_rs232_rxd ;
O_rx_done <= 1'b0 ;
R_state <= R_state + 1'b1 ;end 4'd5 : // 接收第4位,保存到R_para_data_reg[4]begin
R_para_data_reg[4] <= I_rs232_rxd ;
O_rx_done <= 1'b0 ;
R_state <= R_state + 1'b1 ;end4'd6 : // 接收第5位,保存到R_para_data_reg[5]begin
R_para_data_reg[5] <= I_rs232_rxd ;
O_rx_done <= 1'b0 ;
R_state <= R_state + 1'b1 ;end4'd7 :// 接收第6位,保存到R_para_data_reg[6]begin
R_para_data_reg[6] <= I_rs232_rxd ;
O_rx_done <= 1'b0 ;
R_state <= R_state + 1'b1 ;end4'd8 : // 接收第7位,保存到R_para_data_reg[7]begin
R_para_data_reg[7] <= I_rs232_rxd ;
O_rx_done <= 1'b0 ;
R_state <= R_state + 1'b1 ;end 4'd9 : // 接收停止位,但不保存,并把R_para_data_reg给输出begin
O_para_data <= R_para_data_reg ;
O_rx_done <= 1'b1 ;
R_state <= 4'd0 ;end default:R_state <= 4'd0 ;endcase endendelsebegin
O_rx_done <= 1'b0 ;
R_state <= 4'd0 ;
R_para_data_reg <= 8'd0 ;
O_bps_rx_clk_en <= 1'b0 ; // 接收完毕以后关闭波特率时钟使能信号end endendmodule

在下载到开发板测试之前,可以先用软件对模块进行功能仿真,方法是直接将接收模块例化成上一节测试发送模块的例子,例化的顶层代码如下:

module uart_top
(input I_clk , // 系统50MHz时钟input I_rst_n , // 系统全局复位output [3:0] O_led_out ,output O_rs232_txd // 发送的串行数据,在硬件上与串口相连);wire W_bps_tx_clk ;wire W_bps_tx_clk_en ;wire W_bps_rx_clk ;wire W_bps_rx_clk_en ;wire W_tx_start ;wire W_tx_done ;wire W_rx_done ;wire [7:0] W_para_data ;wire [7:0] W_rx_para_data ;reg [7:0] R_data_reg ;reg [31:0] R_cnt_1s ;reg R_tx_start_reg ;assign W_tx_start = R_tx_start_reg ;assign W_para_data = R_data_reg ;assign O_led_out  = W_rx_para_data[3:0] ;/// 产生要发送的数据/always @(posedge I_clk or negedge I_rst_n)beginif(!I_rst_n)begin
R_cnt_1s <= 31'd0 ;
R_data_reg <= 8'd0 ;
R_tx_start_reg <= 1'b0 ;endelse if(R_cnt_1s == 31'd5000)begin
R_cnt_1s <= 31'd0 ;
R_data_reg <= R_data_reg + 1'b1 ;
R_tx_start_reg <= 1'b1 ;endelsebegin
R_cnt_1s <= R_cnt_1s + 1'b1 ;
R_tx_start_reg <= 1'b0 ;endend
uart_txd U_uart_txd
(
.I_clk (I_clk ), // 系统50MHz时钟
.I_rst_n (I_rst_n ), // 系统全局复位
.I_tx_start (W_tx_start ), // 发送使能信号
.I_bps_tx_clk (W_bps_tx_clk ), // 波特率时钟
.I_para_data (W_para_data ), // 要发送的并行数据
.O_rs232_txd (O_rs232_txd ), // 发送的串行数据,在硬件上与串口相连
.O_bps_tx_clk_en (W_bps_tx_clk_en ), // 波特率时钟使能信号
.O_tx_done (W_tx_done ) // 发送完成的标志);
baudrate_gen U_baudrate_gen
(
.I_clk (I_clk ), // 系统50MHz时钟
.I_rst_n (I_rst_n ), // 系统全局复位
.I_bps_tx_clk_en (W_bps_tx_clk_en ), // 串口发送模块波特率时钟使能信号
.I_bps_rx_clk_en (W_bps_rx_clk_en ), // 串口接收模块波特率时钟使能信号
.O_bps_tx_clk (W_bps_tx_clk ), // 发送模块波特率产生时钟
.O_bps_rx_clk (W_bps_rx_clk ) // 接收模块波特率产生时钟);
uart_rxd U_uart_rxd
(
.I_clk (I_clk ), // 系统50MHz时钟
.I_rst_n (I_rst_n ), // 系统全局复位
.I_rx_start (1'b1 ), // 接收使能信号
.I_bps_rx_clk (W_bps_rx_clk ), // 接收波特率时钟
.I_rs232_rxd (O_rs232_txd ), // 接收的串行数据,在硬件上与串口相连
.O_bps_rx_clk_en (W_bps_rx_clk_en ), // 波特率时钟使能信号
.O_rx_done (W_rx_done ), // 接收完成标志
.O_para_data (W_rx_para_data ) // 接收到的8-bit并行数据);endmodule

模拟图如下

从图中我们可以看到接收的数据和发送的数据一模一样,说明逻辑没有问题。接下来我们绑定引脚,下载代码到FPGA中看效果。正常的效果是PC串口调试助手依次显示数据00~FF,板子上LED的状态和数据低四位的状态一致。至此,功能2已经完全实现。

4.4. 串口回显功能的设计与实现

有了发送器模块和接收器模块之后,函数三的要求就很简单了,只需要写一个顶层模块,并实例化串口的发送器模块和接收器模块即可。唯一需要做的就是将接收器模块的接收完成标志连接到发送器模块,并将接收器模块的 8 位并行输出总线连接到发送器模块的 8 位并行输入总线。下面直接给出顶层代码:

module uart_top
(input I_clk , // 系统50MHz时钟input I_rst_n , // 系统全局复位input I_rs232_rxd , // 接收的串行数据,在硬件上与串口相连output O_rs232_txd , // 发送的串行数据,在硬件上与串口相连output [3:0] O_led_out
);wire W_bps_tx_clk ;wire W_bps_tx_clk_en ;wire W_bps_rx_clk ;wire W_bps_rx_clk_en ;wire W_rx_done ;wire W_tx_done ;wire [7:0] W_para_data ;assign O_led_out = W_para_data[3:0] ;
baudrate_gen U_baudrate_gen
(
.I_clk (I_clk ), // 系统50MHz时钟
.I_rst_n (I_rst_n ), // 系统全局复位
.I_bps_tx_clk_en (W_bps_tx_clk_en ), // 串口发送模块波特率时钟使能信号
.I_bps_rx_clk_en (W_bps_rx_clk_en ), // 串口接收模块波特率时钟使能信号
.O_bps_tx_clk (W_bps_tx_clk ), // 发送模块波特率产生时钟
.O_bps_rx_clk (W_bps_rx_clk ) // 接收模块波特率产生时钟);
uart_txd U_uart_txd
(
.I_clk (I_clk ), // 系统50MHz时钟
.I_rst_n (I_rst_n ), // 系统全局复位
.I_tx_start (W_rx_done ), // 发送使能信号
.I_bps_tx_clk (W_bps_tx_clk ), // 波特率时钟
.I_para_data (W_para_data ), // 要发送的并行数据
.O_rs232_txd (O_rs232_txd ), // 发送的串行数据,在硬件上与串口相连
.O_bps_tx_clk_en (W_bps_tx_clk_en ), // 波特率时钟使能信号
.O_tx_done (W_tx_done ) // 发送完成的标志);
uart_rxd U_uart_rxd
(
.I_clk (I_clk ), // 系统50MHz时钟
.I_rst_n (I_rst_n ), // 系统全局复位
.I_rx_start (1'b1 ), // 接收使能信号
.I_bps_rx_clk (W_bps_rx_clk ), // 接收波特率时钟
.I_rs232_rxd (I_rs232_rxd ), // 接收的串行数据,在硬件上与串口相连
.O_bps_rx_clk_en (W_bps_rx_clk_en ), // 波特率时钟使能信号
.O_rx_done (W_rx_done ), // 接收完成标志
.O_para_data (W_para_data ) // 接收到的8-bit并行数据);endmodule

建好工程绑定好引脚后,下载到开发板。利用串口调试助手的自动发送功能(自动发送周期最好在200ms以上),我分别测试了波特率 和 的情况下,共发送了 个字节,全部正确接收。波特率 的情况下,共发送了 个字节,全部正确接收。逻辑基本稳定,欢迎继续测试。我最开始写串口代码的时候, 的情况下,发送的字节数达到10万以上的时候,出现了一个bit错误,原因下节讲。上面的代码已经修复了这个问题,原因在于波特率模块。至此,功能三已经完成。

5. 进一步思考

5.1. 波特率模块滞后可能引起的问题

我最先写的波特率模块如下:

module baudrate_gen
(input I_clk , // 系统50MHz时钟input I_rst_n , // 系统全局复位input I_bps_tx_clk_en , // 串口发送模块波特率时钟使能信号input I_bps_rx_clk_en , // 串口接收模块波特率时钟使能信号output O_bps_tx_clk , // 发送模块波特率产生时钟output O_bps_rx_clk // 接收模块波特率产生时钟);parameter C_BPS9600 = 5207 , //波特率为9600bps
C_BPS19200 = 2603 , //波特率为19200bps
C_BPS38400 = 1301 , //波特率为38400bps
C_BPS57600 = 867 , //波特率为57600bps
C_BPS115200 = 433 ; //波特率为115200bpsparameter C_BPS_SELECT = C_BPS115200 ; //波特率选择reg [12:0] R_bps_tx_cnt ;reg R_bps_tx_clk_reg ;reg [12:0] R_bps_rx_cnt ;///// 功能:串口发送模块的波特率时钟产生逻辑///always @(posedge I_clk or negedge I_rst_n)beginif(!I_rst_n)begin
R_bps_tx_cnt <= 13'd0 ;
R_bps_tx_clk_reg <= 1'b0 ;end else if(I_bps_tx_clk_en == 1'b1)beginif(R_bps_tx_cnt == C_BPS_SELECT)begin
R_bps_tx_cnt <= 13'd0 ;
R_bps_tx_clk_reg <= 1'b1 ;end elsebegin
R_bps_tx_cnt <= R_bps_tx_cnt + 1'b1 ;
R_bps_tx_clk_reg <= 1'b0 ;end end elsebegin
R_bps_tx_cnt <= 13'd0 ;
R_bps_tx_clk_reg <= 1'b0 ;end endassign O_bps_tx_clk = R_bps_tx_clk_reg ;///// 功能:串口接收模块的波特率时钟产生逻辑///always @(posedge I_clk or negedge I_rst_n)beginif(!I_rst_n)
R_bps_rx_cnt <= 13'd0 ;else if(I_bps_rx_clk_en == 1'b1)beginif(R_bps_rx_cnt == C_BPS_SELECT)
R_bps_rx_cnt <= 13'd0 ;else
R_bps_rx_cnt <= R_bps_rx_cnt + 1'b1 ;end else
R_bps_rx_cnt <= 13'd0 ;endassign O_bps_rx_clk = (R_bps_rx_cnt == C_BPS_SELECT >> 1'b1) ? 1'b1 : 1'b0 ;endmodule

模拟如下所示:

当打开发送波特率时钟使能信号时,只有计数值满后才会产生发送时钟脉冲,而接收波特率时钟只需要计数值的一半就能产生时钟脉冲,这就造成了在echo实验中数据会滞后。在echo实验中我们直接把接收完成标志和发送开始标志连在一起,所以这样可能会导致数据在最后一个数据发送完之前就到了。经过我的测试,使用上面的波特率逻辑,如果不做echo实验,一般不会有问题。如果做echo实验,在波特率高的情况下,比如和,数据量小的时候不会出错,数据量大的时候一般会有数据丢失。在波特率低的情况下,比如和,数据直接是一帧一帧的接收,漏掉一帧,比如发一个字符串,收到的回来就是ace。我刚刚才找到原因,以后使用的时候注意这个问题。

5.2. 发送数据的状态机和接收数据的状态机可以通过移位来实现

其实状态机中发送和接收8位数据的部分可以通过移位来实现,这样可以让代码更短更简洁,以后有时间我会重新写一下。

欢迎FPGA工程、嵌入式工程师关注公众号

国内最大的FPGA微信技术群

欢迎加入全国FPGA微信技术群,这个群里有上万热爱技术的工程师,这里的FPGA工程师们互相帮助,互相分享,技术氛围浓厚!赶紧叫上你的小伙伴们一起加入吧!!

只需轻轻一按,即可加入FPGA国家技术集团

FPGA 主页 元件 核心城市

优势组件服务,有需求请扫码联系群主:金娟邮箱:欢迎推荐购买

ACTEL、AD部分优势订单(操作全系列):

、现货或订购优势(全系列):

(以上设备为部分型号,更多型号请咨询群主金娟)

服务理念:FPGA之家自营元器件城,旨在方便工程师快速便捷地采购器件服务。经过几年的专注服务,我们的客服人员遍布国内大型上市公司、军工科研单位、中小企业。最大的优势就是强调服务第一的理念,交货迅速、价格优惠!

直销品牌:ADI、TI、NXP、ST、E2V、等百余个元器件品牌,尤其擅长欧美禁运的元器件。欢迎各位工程师朋友推荐购买或亲临咨询!我们将一如既往的提供行业内最优质的服务!

FPGA技术组正式感谢品牌:Intel()、Actel()、e等。

提醒:请联系我时一定说明是从浚耀商务生活网上看到的!