欢迎光临
我们一直在努力

串口(UART)的FPGA实现(含源码工程)

文章目录

1、什么是串口(UART)?

2、串口的组成

2.1、串口的物理层

2.2、UART协议

3、串口发送模块

3.1、接口定义与整体设计

3.2、设计思路

3.3、Verilg代码

3.4、Testbench

3.5、仿真结果分析

3.6、上板实测

4、串口接收模块

4.1、接口定义与整体设计

4.2、设计思路

4.3、Verilg代码

4.4、Testbench

4.5、仿真结果分析

4.6、上板测试

5、总结


1、什么是串口(UART)?

串口作为常用的三大低速总线(UART、SPI、IIC)之一,在设计众多通信接口和调试时占有重要地位。

串口(UART)全称通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),主要用于数据间的串行传递,是一种全双工传输模式。它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。

“异步”两个字即意味着在数据传递的两个模块之间使用的不是同步时钟。实际上在异步串口的传输中是不需要时钟的,而是通过特定的时序来标志传输的开始(起始位--由高到低)和结束(结束位,拉高)。


2、串口的组成

2.1、串口的物理层

UART 通信只有两根信号线,一根是发送数据端口线叫 tx(Transmitter),一根是接收数据端口线叫 rx(Receiver),如图所示,对于 PC 来说它的 tx 要和对于 FPGA来说的 rx 连接,同样 PC 的 rx 要和 FPGA 的 tx 连接,如果是两个 tx 或者两个 rx 连接那数据就不能正常被发送出去和接收到。

串口(UART)的FPGA实现(含源码工程)

信号的传输由外部驱动电路实现。电信号的传输过程有着不同的电平标准和接口规范,针对异步串行通信的接口标准有RS232、RS422、RS485等,它们定义了接口不同的电气特性,如RS-232是单端输入输 出,而RS-422/485为差分输入输出等。

传输距离较短时(不超过15m),RS232是串行通信最常用的接口标准。RS-232标准的串口最常见的接口类型为DB9,样式如图所示,工业控制领域中用到的工控机一般都配备多个串口,很多老式台式机也都配有串口。但是笔记本电脑以及较新一点 的台式机都没有串口,它们一般通过USB转串口线来实现与外部设备的串口通信。

串口(UART)的FPGA实现(含源码工程)

DB9接口定义以及各引脚功能说明如图所示,我们一般只用到其中的2(RXD)、3 (TXD)、5(GND)引脚,其他引脚在普通串口模式下一般不使用:

串口(UART)的FPGA实现(含源码工程)


2.2、UART协议

UART 在发送或接收过程中的一帧数据由4部分组成,起始位、数据位、奇偶校验位和停止位,如图所示。其中,起始位标志着一帧数据的开始,停止位标志着一帧数据的结束,数据位是一帧数据中的有效数据。

校验位分为奇校验和偶校验,用于检验数据在传输过程中是否出错。

奇校验时,发送方应使数据位中1的个数与校验位中1的个数之和为奇数;接收方在接收数据时, 对1的个数进行检查,若不为奇数,则说明数据在传输过程中出了差错。同样,偶校验则检查1的个数是否为偶数。关于奇偶校验可参考:Verilgo实现的FPGA奇偶校验

串口(UART)的FPGA实现(含源码工程)

UART通信过程中的数据格式及传输速率是可设置的,为了正确的通信,收发双方应约定并遵循同样的设置。数据位可选择为5、6、7、8位,其中8位数据位是最常用的,在实际应用中一般都选择8位数据位;校验位可选择奇校验、偶校验或者无校验位;停止位可选择1位(默认), 1.5或2位。

串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,单位是bps(位 /秒),常用的波特率有9600、19200、38400、57600以及115200等。

如波特率9600则代表每秒传输9600bit数据,以串口发送1个字节10bit算(起始位1bit+数据8bit+停止位1bit+NO校验位),则传输1个字节需要的时间是1*10/9600秒。


3、串口发送模块

3.1、接口定义与整体设计

发送模块整体框图、输入输出信号如下所示:

串口(UART)的FPGA实现(含源码工程)

其中信号端口如下:

串口(UART)的FPGA实现(含源码工程)

需要说明的是,uart_tx_data为需要发送的一个字节的数据,uart_tx_en为发送使能位,当其拉高,则代表此时通过串口发送数据线发送数据uart_tx_data。


3.2、设计思路

  • 该模块支持任意波特率(理论上)的发送,但需要在使用该模块时使用参数将其例化,数据位8位,起始位和停止位各1位,无奇偶校验
  • 当使能信号有效后拉高发送标志信号,标志模块进入发送过程;当发送完10个bit后,拉低发送标志信号,标志发送过程结束。使能信号有效时将要发送的数据寄存。
  • 假设波特率为9600,则发送一个bit的时间为1s/9600,一个数据的传输共10bit(数据位8位,起始位和停止位各1位),则共需要1s/9600;假设系统时钟为50MHz(参数化以便适应不同的系统频率),则其周期为20ns,那么发送一个bit所需要的系统周期数为(1s/9600)/ 20ns ≈ 5208(个)。在发送过程中使用一个计数器计数,计数区间为(0~5208-1),这样的区间一共10个(一个字节需要发送10个bit);此外还需一个计数器对发送的bit数计数(每当上一个计数器计数到5207则表示发送完了一个bit),计数区间(0~9)
  • 在发送过程,根据计数器的值(发送bit计数器),对发送数据线进行操作。
    • 若发送bit计数器 = 0,则代表此时需要发送起始位;
    • 若发送bit计数器 = 1,则代表此时需要发送发送数据的最低位LSB(数据的发送总是低位在前,高位在后);
    • ······
    • 若发送bit计数器 = 8,则代表此时需要发送发送数据的最高位MSB;
    • 若发送bit计数器 = 9,则代表此时需要发送停止位;
  • 发送数据线在不处于发送状态时需拉高,以满足UART时序的空闲状态

3.3、Verilg代码

根据上述设计思路,发送模块代码如下:

 // ******************************************************************************************************* // ** 作者 : 孤独的单刀 // ** 邮箱 : zachary_wu93@163.com // ** 博客 : https://blog.csdn.net/wuzhikaidetb // ** 日期 : 2022/07/31 // ** 功能 : 1、基于FPGA的串口发送驱动模块; // 2、可设置波特率BPS、主时钟CLK_FRE; // 3、起始位1bit,数据位8bit,停止位1bit,无奇偶校验; // 4、每发送1个字节后拉高uart_tx_done一个周期,可用于后续发送多字节模块。 // ******************************************************************************************************* module uart_tx #( parameter integer BPS = 9_600 , //发送波特率 parameter integer CLK_FRE = 50_000_000 //主时钟频率 ) ( //系统接口 input sys_clk , //系统时钟 input sys_rst_n , //系统复位,低电平有效 //用户接口 input [7:0] uart_tx_data , //需要通过UART发送的数据,在uart_tx_en为高电平时有效 input uart_tx_en , //发送有效,当其为高电平时,代表此时需要发送的数据有效 //UART发送 output reg uart_tx_done , //成功发送1BYTE数据后拉高一个周期 output reg uart_txd //UART发送数据线tx ); //param define localparam integer BPS_CNT = CLK_FRE / BPS; //根据波特率计算传输每个bit需要计数多个系统时钟 localparam integer BITS_NUM = 10 ; //发送格式确定需要发送的bit数,10bit = 1起始位 + 8数据位 + 1停止位 //reg define reg tx_state ; //发送标志信号,拉高代表发送过程正在进行 reg [7:0] uart_tx_data_reg ; //寄存要发送的数据 reg [31:0] clk_cnt ; //计数器,用于计数发送一个bit数据所需要的时钟数 reg [3:0] bit_cnt ; //bit计数器,标志当前发送了多少个bit //当发送使能信号到达时,寄存待发送的数据以免后续变化、丢失 always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n) uart_tx_data_reg <=8'd0; else if(uart_tx_en) //要发送有效的数据 uart_tx_data_reg <= uart_tx_data; //寄存需要发送的数据 else uart_tx_data_reg <= uart_tx_data_reg; end //当发送使能信号到达时,进入发送过程 always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n) tx_state <=1'b0; else if(uart_tx_en) tx_state <= 1'b1; //发送信号有效则进入发送过程 //发送完了最后一个数据则退出发送过程 else if((bit_cnt == BITS_NUM - 1'b1) &amp;& (clk_cnt == BPS_CNT - 1'b1)) tx_state <= 1'b0; else tx_state <= tx_state; end //发送数据完毕后拉高发送完毕信号一个周期,指示一个字节发送完毕 always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n) uart_tx_done <=1'b0; //发送数据完毕后拉高发送完毕信号一个周期 else if((bit_cnt == BITS_NUM - 1'b1) && (clk_cnt == BPS_CNT - 1'b1)) uart_tx_done <=1'b1; else uart_tx_done <=1'b0; end //进入发送过程后,启动时钟计数器与发送个数bit计数器 always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n)begin clk_cnt <= 32'd0; bit_cnt <= 4'd0; end else if(tx_state) begin //在发送状态 if(clk_cnt < BPS_CNT - 1'd1)begin //一个bit数据没有发送完 clk_cnt <= clk_cnt + 1'b1; //时钟计数器+1 bit_cnt <= bit_cnt; //bit计数器不变 end else begin //一个bit数据发送完了 clk_cnt <= 32'd0; //清空时钟计数器,重新开始计时 bit_cnt <= bit_cnt+1'b1; //bit计数器+1,表示发送完了一个bit的数据 end end else begin //不在发送状态 clk_cnt <= 32'd0; //清零 bit_cnt <= 4'd0; //清零 end end //根据发送数据计数器来给uart发送端口赋值 always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n) uart_txd <= 1'b1; //默认为高状态,空闲状态 else if(tx_state) //处于发送状态 case(bit_cnt) //数据发送从低位到高位 4'd0: uart_txd <= 1'b0; //起始位,拉低 4'd1: uart_txd <= uart_tx_data_reg[0]; //发送最低位数据 4'd2: uart_txd <= uart_tx_data_reg[1]; // 4'd3: uart_txd <= uart_tx_data_reg[2]; // 4'd4: uart_txd <= uart_tx_data_reg[3]; // 4'd5: uart_txd <= uart_tx_data_reg[4]; // 4'd6: uart_txd <= uart_tx_data_reg[5]; // 4'd7: uart_txd <= uart_tx_data_reg[6]; // 4'd8: uart_txd <= uart_tx_data_reg[7]; //发送最高位数据 4'd9: uart_txd <= 1'b1; //终止位,拉高 default:uart_txd <= 1'b1; endcase else //不处于发送状态 uart_txd <= 1'b1; //默认为高状态,空闲状态 end endmodule 

3.4、Testbench

Testbench的设计如下:

  • 设定波特率230400(这样的目的是为了更方便的观察发送使能信号uart_tx_en,节约时间)
  • 3000ns后,拉高发送使能信号uart_tx_en一个周期,同时生成1个8bit的随机数据给uart_tx_data作为要发送的数据
  • 观察UART上TX线的时序是否满足要求
 // ******************************************************************************************************* // ** 作者 : 孤独的单刀 // ** 邮箱 : zachary_wu93@163.com // ** 博客 : https://blog.csdn.net/wuzhikaidetb // ** 日期 : 2022/07/29 // ** 功能 : 1、对基于FPGA的串口发送驱动模块的测试testbench // 2、发送一个8bit的随机数据,观测其波形是否符合UART时序 // ******************************************************************************************************* `timescale 1ns/1ns //定义时间刻度 module tb_uart_tx(); reg sys_clk ; reg sys_rst_n ; reg [7:0] uart_tx_data ; reg uart_tx_en ; wire uart_txd ; parameter integer BPS = 'd230400 ; //波特率 parameter integer CLK_FRE = 'd50_000_000 ; //系统频率50M localparam integer BIT_TIME = 'd1000_000_000 / BPS ; //计算出传输每个bit所需要的时间 initial begin sys_clk <=1'b0; sys_rst_n <=1'b0; uart_tx_en <=1'b0; uart_tx_data <=8'd0; #80 //系统开始工作 sys_rst_n <=1'b1; #200 @(posedge sys_clk); uart_tx_en <=1'b1; uart_tx_data <= ({$random} % 256); //发送8位随机数据 #20 uart_tx_en <=1'b0; #(BIT_TIME * 10) //发送1个BYTE需要10个bit #200 $finish; //结束仿真 end always #10 sys_clk=~sys_clk; //定义主时钟,周期20ns,频率50M //例化发送驱动模块 uart_tx #( .BPS (BPS ), .CLK_FRE (CLK_FRE ) ) uart_tx_inst( .sys_clk (sys_clk ), .sys_rst_n (sys_rst_n ), .uart_tx_data (uart_tx_data ), .uart_tx_en (uart_tx_en ), .uart_tx_done (uart_tx_done ), .uart_txd (uart_txd ) ); endmodule 

3.5、仿真结果分析

仿真结果如下图(注释很详细):

下图中可以看到发送模块发送了1个数据8'h24,一段时间后发送结束,并无法直接观察发送线TX上的时序。

串口(UART)的FPGA实现(含源码工程)
整体仿真时序

下图中可以看到发送模块发送了1个数据8'h24,一段时间后发送结束,且可以看到发送线TX在以符合UART时序的方式发送数据00100100(低位在前、高位在后),即8'h24。

串口(UART)的FPGA实现(含源码工程)
单次发送时序

可以看到仿真结果是符合预期设计要求的。


3.6、上板实测

至此已经顺利完成了发送模块的仿真验证,接下来使用一块Altera Cyclone IV E的开发板上板实测。

编写一个发送模块测试模块,该模块调用串口发送模块,并按一定间隔(默认1s)拉高发送使能信号和生成发送数据,发送数据从0x01开始累加1,直到0xFF(溢出到0x00)。同时在电脑上使用串口调试软件接收发送过来的数据。根据串口调试软件接收到的数据判断串口发送模块是否能成功工作。

发送模块验证模块代码如下:

 // ******************************************************************************************************* // ** 作者 : 孤独的单刀 // ** 邮箱 : zachary_wu93@163.com // ** 博客 : https://blog.csdn.net/wuzhikaidetb // ** 日期 : 2022/07/29 // ** 功能 : 1、基于FPGA的串口发送驱动模块的测试模块; // 2、每个1s发送1个递增1的数据到上位机。 // ******************************************************************************************************* module uart_tx_test ( //系统接口 input sys_clk , input sys_rst_n , //UART发送线 output uart_txd //UART发送线 ); parameter integer BPS = 'd230400 ; //波特率 parameter integer CLK_FRE = 'd50_000_000 ; //系统频率50M reg [31:0] cnt_time; reg uart_tx_en; //发送使能,当其为高电平时,代表此时需要发送数据 reg [7:0] uart_tx_data; //需要通过UART发送的数据,在uart_tx_en为高电平时有效 //1s计数模块,每隔1s发送一个数据和拉高发送使能信号一次;数据从0开始递增1 always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n)begin cnt_time <= 'd0; uart_tx_en <= 1'd0; uart_tx_data <= 8'd0; end else if(cnt_time == (50_000_000 - 1'b1))begin cnt_time <= 'd0; uart_tx_en <= 1'd1; //拉高发送使能 uart_tx_data <= uart_tx_data + 1'd1; //发送数据累加1 end else begin cnt_time <= cnt_time + 1'd1; uart_tx_en <= 1'd0; uart_tx_data <= uart_tx_data; end end //例化发送模块 uart_tx #( .BPS (BPS ), .CLK_FRE (CLK_FRE ) ) uart_tx_inst ( .sys_clk (sys_clk ), .sys_rst_n (sys_rst_n ), .uart_tx_en (uart_tx_en ), .uart_tx_data (uart_tx_data ), .uart_tx_done ( ), .uart_txd (uart_txd ) ); endmodule

串口调试软件结果如下:

依次接收到了数据01、02、03······。说明我们的发送模块工作正常。

串口(UART)的FPGA实现(含源码工程)


4、串口接收模块

4.1、接口定义与整体设计

接收模块整体框图、输入输出信号如下所示:

串口(UART)的FPGA实现(含源码工程)

其中信号描述如下:

串口(UART)的FPGA实现(含源码工程)

需要说明的是,uart_rx_data为接收的一个字节的数据,uart_rx_done为接收完成标志位,当其拉高,则代表此时接收到的串口数据uart_rx_data有效。


4.2、设计思路

  • 该模块支持任意波特率(理论上)的接收,但需要在使用该模块时使用参数将其例化,数据位8位,起始位和停止位各1位,无奇偶校验
  • 串口的传输是以起始位开始的,而起始位是将数据线拉低 ,所以我们需要捕捉数据线的下降沿,将接收数据线打拍3次,捕捉其下降沿。当捕捉到接收数据线的下降沿,拉高接收标志信号,标志模块进入接收过程;当接收完10个bit后,拉低接收标志信号,标志接收过程结束
  • 假设波特率为9600,则传输一个bit的时间为1s/9600,一个数据的传输共10bit(数据位8位,起始位和停止位各1位),则共需要1s/960;假设系统时钟为50MHz(参数化以便适应不同的系统频率),则其周期为20ns,那么传输一个bit所需要的系统周期数为(1s/960)/ 20ns ≈ 5208(个)。在接收过程中使用一个计数器计数,计数区间为(0~5208-1),这样的区间一共10个(一个字节需要传输10个bit);此外还需一个计数器对接收的bit数计数(每当上一个计数器计数到5207则表示接收完了一个bit),计数区间(0~9)。
  • 在接收过程,根据计数器的值(接收bit计数器),在每个bit计数器的中间接收数据,将其移位寄存(在电平中间数据最稳定)
    • 若接收bit计数器 = 0,则代表是起始位,不需要接收
    • 若接收bit计数器 = 1,则代表此时接收到数据的最低位LSB(数据的传输总是低位在前,高位在后),将其赋值给寄存数据的最低位;
    • ······
    • 若接收bit计数器 = 8,则代表此时接收到数据的最高位MSB,将其赋值给寄存数据的最高位;
    • 若接收bit计数器 = 9,则代表是停止位,不需要接收

4.3、Verilg代码

根据上述设计思路,代码如下:

 // ******************************************************************************************************* // ** 作者 : 孤独的单刀 // ** 邮箱 : zachary_wu93@163.com // ** 博客 : https://blog.csdn.net/wuzhikaidetb // ** 日期 : 2022/08/05 // ** 功能 : 1、基于FPGA的串口接收驱动模块; // 2、可重新设置波特率BPS、主时钟CLK_FRE; // 3、起始位1bit,数据位8bit,停止位1bit,无奇偶校验。 // ******************************************************************************************************* module uart_rx #( parameter integer BPS = 9_600 , //发送波特率 parameter integer CLK_FRE = 50_000_000 //输入时钟频率 ) ( //系统接口 input sys_clk , //50M系统时钟 input sys_rst_n , //系统复位 //UART接收线 input uart_rxd , //接收数据线 //用户接口 output reg uart_rx_done , //数据接收完成标志,当其为高电平时,代表接收数据有效 output reg [7:0] uart_rx_data //接收到的数据,在uart_rx_done为高电平时有效 ); //param define localparam integer BPS_CNT = CLK_FRE / BPS; //根据波特率计算传输每个bit需要多个系统时钟 //reg define reg uart_rx_d1 ; //寄存1拍 reg uart_rx_d2 ; //寄存2拍 reg uart_rx_d3 ; //寄存3拍 reg [31:0] clk_cnt ; //计数器,用于计数发送一个bit数据所需要的时钟数 reg [3:0] bit_cnt ; //bit计数器,标志当前发送了多少个bit reg rx_en ; //接收标志信号,拉高代表接收过程正在进行 reg [7:0] uart_rx_data_reg; //接收数据寄存 //wire define wire neg_uart_rxd ; //接收数据线的下降沿 assign neg_uart_rxd = uart_rx_d3 & (~uart_rx_d2); //捕获数据线的下降沿,用来标志数据传输开始 //将数据线打3拍,作用1:同步不同时钟域信号,防止亚稳态;作用2:捕获下降沿 always@(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n)begin uart_rx_d1 <= 1'b0; uart_rx_d2 <= 1'b0; uart_rx_d3 <= 1'b0; end else begin uart_rx_d1 <= uart_rxd; uart_rx_d2 <= uart_rx_d1; uart_rx_d3 <= uart_rx_d2; end end //捕获到数据下降沿(起始位0)后,拉高传输开始标志位,并在第9个数据(终止位)的传输过程正中(数据比较稳定)再将传输开始标志位拉低,标志传输结束 always@(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n) rx_en <= 1'b0; else begin if(neg_uart_rxd ) rx_en <= 1'b1; //接收完第9个数据(终止位)将传输开始标志位拉低,标志传输结束,判断高电平 else if((bit_cnt == 4'd9) && (clk_cnt == BPS_CNT >> 1'b1) && (uart_rx_d3 == 1'b1) ) rx_en <= 1'b0; else rx_en <= rx_en; end end //当数据传输到终止位时,拉高传输完成标志位,并将数据输出 always@(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n)begin uart_rx_done <= 1'b0; uart_rx_data <= 8'd0; end //结束接收后,将接收到的数据输出 else if((bit_cnt == 4'd9) && (clk_cnt == BPS_CNT >> 1'd1) && (uart_rx_d3 == 1'b1))begin uart_rx_done <= 1'b1; //仅仅拉高一个时钟周期 uart_rx_data <= uart_rx_data_reg; end else begin uart_rx_done <= 1'b0; //仅仅拉高一个时钟周期 uart_rx_data <= uart_rx_data; end end //时钟每计数一个BPS_CNT(传输一位数据所需要的时钟个数),即将数据计数器加1,并清零时钟计数器 always@(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n)begin bit_cnt <= 4'd0; clk_cnt <= 32'd0; end else if(rx_en)begin //在接收状态 if(clk_cnt < BPS_CNT - 1'b1)begin //一个bit数据没有接收完 clk_cnt <= clk_cnt + 1'b1; //时钟计数器+1 bit_cnt <= bit_cnt; //bit计数器不变 end else begin //一个bit数据接收完了 clk_cnt <= 32'd0; //清空时钟计数器,重新开始计时 bit_cnt <= bit_cnt + 1'b1; //bit计数器+1,表示接收完了一个bit的数据 end end else begin //不在接收状态 bit_cnt <= 4'd0; //清零 clk_cnt <= 32'd0; //清零 end end //在每个数据的传输过程正中(数据比较稳定)将数据线上的数据赋值给数据寄存器 always@(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n) uart_rx_data_reg <= 8'd0; //复位无接收数据 else if(rx_en) //处于接收状态 if(clk_cnt == BPS_CNT >> 1'b1) begin //传输过程正中(数据比较稳定) case(bit_cnt) //根据位数决定接收的内容是什么 4'd1:uart_rx_data_reg[0] <= uart_rx_d3; //LSB最低位 4'd2:uart_rx_data_reg[1] <= uart_rx_d3; // 4'd3:uart_rx_data_reg[2] <= uart_rx_d3; // 4'd4:uart_rx_data_reg[3] <= uart_rx_d3; // 4'd5:uart_rx_data_reg[4] <= uart_rx_d3; // 4'd6:uart_rx_data_reg[5] <= uart_rx_d3; // 4'd7:uart_rx_data_reg[6] <= uart_rx_d3; // 4'd8:uart_rx_data_reg[7] <= uart_rx_d3; //MSB最高位 default:; //1和9分别是起始位和终止位,不需要接收 endcase end else //数据不一定稳定就不接收 uart_rx_data_reg <= uart_rx_data_reg; else uart_rx_data_reg <= 8'd0; //不处于接收状态 end endmodule 

4.4、Testbench

仿真模块的Testbench设计如下:

  • 设定波特率230400(这样的目的是为了更方便的观察发送使能信号uart_tx_en)
  • 定义一个任务task,该任务将输入使用波特率230400一个bit一个bit的输出,模拟上位机发送数据给FPGA
  • 3000ns后,发送第1个随机数据
  • 发送完了第1个随机数据后发送第2个随机数据,一共发送4个随机数据
 // ******************************************************************************************************* // ** 作者 : 孤独的单刀 // ** 邮箱 : zachary_wu93@163.com // ** 博客 : https://blog.csdn.net/wuzhikaidetb // ** 日期 : 2022/07/29 // ** 功能 : 1、对基于FPGA的串口接收驱动模块的测试testbench // 2、通过构建一个task来模拟上位机时序发送数据给串口接收驱动,观察该模块能否成功接收数据。 // 3、依次发送4个随机的8bit数据 // ******************************************************************************************************* `timescale 1ns/1ns //定义时间刻度 //模块、接口定义 module tb_uart_rx(); reg sys_clk ; reg sys_rst_n ; reg uart_rxd ; wire uart_rx_done ; wire [7:0] uart_rx_data ; localparam integer BPS = 'd230400 ; //波特率 localparam integer CLK_FRE = 'd50_000_000 ; //系统频率50M localparam integer CNT = 1000_000_000 / BPS ; //计算出传输每个bit所需要的时间,单位:ns //初始时刻定义 initial begin $timeformat(-9, 0, " ns", 10); //定义时间显示格式 sys_clk =1'b0; sys_rst_n <=1'b0; uart_rxd <=1'b1; #20 //系统开始工作 sys_rst_n <=1'b1; #3000 rx_byte({$random} % 256); //生成8位随机数1 rx_byte({$random} % 256); //生成8位随机数2 rx_byte({$random} % 256); //生成8位随机数3 rx_byte({$random} % 256); //生成8位随机数4 #60 $finish(); end //每当成功接收一个BYTE的数据,就在测试端窗口打印出来 always @(posedge sys_clk)begin if(uart_rx_done)begin $display("@time%t", $time); $display("rx : 0x%h",uart_rx_data); end end //定义任务,每次发送的数据10 位(起始位1+数据位8+停止位1) task rx_byte( input [7:0] data ); integer i; //定义一个常量 //用 for 循环产生一帧数据,for 括号中最后执行的内容只能写 i=i+1 for(i=0; i<10; i=i+1) begin case(i) 0: uart_rxd <= 1'b0; //起始位 1: uart_rxd <= data[0]; //LSB 2: uart_rxd <= data[1]; 3: uart_rxd <= data[2]; 4: uart_rxd <= data[3]; 5: uart_rxd <= data[4]; 6: uart_rxd <= data[5]; 7: uart_rxd <= data[6]; 8: uart_rxd <= data[7]; //MSB 9: uart_rxd <= 1'b1; //停止位 endcase #CNT; //每发送 1 位数据延时 end endtask //任务结束 //设置主时钟 always #10 sys_clk <= ~sys_clk; //时钟20ns,50M //例化被测试的串口接收驱动 uart_rx #( .BPS (BPS ), .CLK_FRE (CLK_FRE ) ) uart_rx_inst( .sys_clk (sys_clk ), .sys_rst_n (sys_rst_n ), .uart_rxd (uart_rxd ), .uart_rx_done (uart_rx_done ), .uart_rx_data (uart_rx_data ) ); endmodule 

4.5、仿真结果分析

仿真结果如下图(注释很详细):

下图中分别发送了4个数据8'h24--8'h81--8'h09--8'h63;接收模块分别接收到了4个数据8'h24--8'h81--8'h09--8'h63。发送、接收数据一致。

串口(UART)的FPGA实现(含源码工程)
接收总体时序

下图是第1次接收数据(8'h24,即00100100)是的时序图。

串口(UART)的FPGA实现(含源码工程)
单个字节接收时序


4.6、上板测试

至此已经顺利完成了接收模块的仿真验证,接下来使用一块Altera Cyclone IV E的开发板上板测试。

首先生成一个IP核--ISSP(In-System Sources and Probes),这个IP核可以提供一个输出用来在线输出,相当于一个简单的信号发生器--Source,此外还可以提供探针Probes来在线监控信号的输出。

在本次设计中,我们使用Probes来观察串口接收数据。ISSP调用如下:

串口(UART)的FPGA实现(含源码工程)

编写一个接收模块验证模块,该模块调用接收模块,ISSP IP核。同时在电脑上使用串口调试软件发送数据,根据接收到的数据判断串口接收模块是否能成功工作。

接收模块验证模块uart_rx_test代码如下:

 // ******************************************************************************************************* // ** 作者 : 孤独的单刀 // ** 邮箱 : zachary_wu93@163.com // ** 博客 : https://blog.csdn.net/wuzhikaidetb // ** 日期 : 2022/07/29 // ** 功能 : 1、基于FPGA的串口接收驱动模块的测试模块; // 2、例化串口接收驱动与ISSP IP核; // 3、使用上位机发送随机数据到FPGA,通过观察ISSP监测到的接收驱动接收的数据来进行测试。 // ******************************************************************************************************* module uart_rx_test ( //系统接口 input sys_clk , input sys_rst_n , //UART接收线 input uart_rxd //接收数据线 ); parameter integer BPS = 'd230400 ; //波特率230400 parameter integer CLK_FRE = 'd50_000_000 ; //系统频率50MHZ wire [7:0] uart_rx_data; //例化接收模块 uart_rx #( .BPS (BPS ), .CLK_FRE (CLK_FRE ) ) uart_rx_isnt ( .sys_clk (sys_clk ), .sys_rst_n (sys_rst_n ), .uart_rxd (uart_rxd ), .uart_rx_done ( ), .uart_rx_data (uart_rx_data ) ); //例化ISSP作为观测手段 issp_uart_rx issp_uart_rx_inst ( .probe (uart_rx_data ), //观测接收数据 .source ( ) ); endmodule

下载程序后,在Quartus II中打开In-System Sources and Probes Editor,然后使用串口调试软件发送数据0x55--0xaa--0x88(随机选的3个),观察 In-System Sources and Probes Editor中寄存器的值,分别如下:

串口(UART)的FPGA实现(含源码工程)

串口(UART)的FPGA实现(含源码工程)

串口(UART)的FPGA实现(含源码工程)


5、总结

  • 串口作为一种常用的通信协议与调试手段,请一定要熟练掌握!
  • 基于FPGA的串口实现不难,只要注意根据波特率合理设计计数器即可,在此计数器的调动下可以实现数据的发送与接收
  • 源码工程点这里下载(提取码:pgel)

  • 📣博客主页:wuzhikai.blog.csdn.net
  • 📣本文由 孤独的单刀 原创,首发于CSDN平台🐵
  • 📣您有任何问题,都可以在评论区和我交流📞!
  • 📣创作不易,您的支持是我持续更新的最大动力!如果本文对您有帮助,还请多多点赞👍、评论💬和收藏⭐!

  • 海报
海报图正在生成中...
赞(0) 打赏
声明:
1、本博客不从事任何主机及服务器租赁业务,不参与任何交易,也绝非中介。博客内容仅记录博主个人感兴趣的服务器测评结果及一些服务器相关的优惠活动,信息均摘自网络或来自服务商主动提供;所以对本博客提及的内容不作直接、间接、法定、约定的保证,博客内容也不具备任何参考价值及引导作用,访问者需自行甄别。
2、访问本博客请务必遵守有关互联网的相关法律、规定与规则;不能利用本博客所提及的内容从事任何违法、违规操作;否则造成的一切后果由访问者自行承担。
3、未成年人及不能独立承担法律责任的个人及群体请勿访问本博客。
4、一旦您访问本博客,即表示您已经知晓并接受了以上声明通告。
文章名称:《串口(UART)的FPGA实现(含源码工程)》
文章链接:https://www.456zj.com/23499.html
本站资源仅供个人学习交流,请于下载后24小时内删除,不允许用于商业用途,否则法律问题自行承担。

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址