1、什么是串口(UART)?
串口,作为三大低速总线(UART、SPI、IIC)中的关键一员,其在设计阶段和调试过程中的作用举足轻重。
串口,即通用异步收发传输器(UART),其主要功能是进行数据的串行传输,它采用了一种全双工的传输方式。
在数据传输过程中,该设备会将并行数据转化为串行数据以进行发送;而在数据接收阶段,则会将所接收的串行数据转换回并行数据。
“异步”这一表述揭示了数据传递的两个模块间并非采用相同的时钟进行同步。
实际上,在进行异步串口数据传输时,并不需要依赖时钟信号。相反,传输的启动和终止是通过特定的时序信号来实现的,即通过起始位(电平从高变低)来标记传输的起始,以及通过结束位(电平拉高)来标识传输的结束。
2、串口的组成
2.1、串口的物理层
UART通信系统仅包含两根信号导线,其中一根负责发送数据,被称为tx(发射器),另一根则用于接收数据,称作rx(接收器)。
如图所示,针对PC设备,其传输数据的功能模块(tx)需与FPGA的接收数据模块(rx)相连接;同理,PC的接收数据模块(rx)也应与FPGA的传输数据模块(tx)相连。若两个tx或两个rx模块直接相连,则会导致数据无法正常传输和接收。

信号的传输是通过外部驱动电路来完成的。在这一过程中,电信号遵循着不同的电平标准以及接口规范。特别地,对于异步串行通信,其接口标准包括RS232、RS422、RS485等多种类型。
这些规范界定了接口所具备的各自独特的电气特性,例如,RS-232采用的是单端输入输出方式,而RS-422和RS-485则分别采用了差分输入输出的技术。
当传输距离较短,即不超过15米时,RS232成为串行通信中应用最为广泛的接口规范。RS-232标准的串行端口,其最常见的接口形式为DB9型,具体样式可参照图示。在工业控制领域,多数工控机都配备了多个此类串口,而许多老款台式电脑同样装备了串行接口。
然而,现有的笔记本电脑以及更新的台式机均不具备串口功能,通常它们依赖USB转串口线来达成与外部设备之间的串口数据交流。
DB9接口的规格和各个引脚的作用已在图中详细展示,通常情况下,我们仅会利用其中的2号引脚(RXD)、3号引脚(TXD)以及5号引脚(GND),而在常规的串口操作中,其他引脚则很少被用到。

2.2、UART协议
UART在数据传输的每个阶段,都会涉及一帧数据的构成,该帧数据由四个主要部分构成,分别是起始位、数据位、奇偶校验位以及停止位,具体结构如图所示。
起始位标志着数据帧的起始,而停止位则标志着数据帧的终结,数据位则构成了数据帧中的实际有效信息。
校验位分为奇校验和偶校验两种类型,其主要作用在于检测数据在传输过程中是否出现了错误。
在执行奇校验操作时,发送端需确保数据位中1的数量与校验位中1的数量加起来构成奇数;而在接收数据的过程中,接收端需要对1的数量进行核实。
若数据非奇数,则表明在传输环节出现了错误。此外,偶校验机制还会核对其中1的数量是否为偶数。欲了解奇偶校验的更多内容,请参阅Verilgo在FPGA上实现的奇偶校验方法。

在UART通信过程中,数据的格式和传输速率是可以调整的。为确保通信的准确性,收发双方必须达成一致,并共同遵守这些设定。
数据位的选择包括5、6、7、8位几种,其中以8位数据位使用最为频繁,通常情况下,人们更倾向于采用8位数据位;至于校验位,可以选奇校验、偶校验,或者不设置校验位;而停止位则有1位(为默认选项)、1.5位或2位可供选择。
串口通信的速率通常以波特率来衡量,这一指标反映了每秒钟传输的二进制数据位数,其计量单位为bps,即位每秒。在通信领域,常见的波特率数值包括9600、19200、38400、57600以及115200等。
若波特率为9600,则意味着每秒钟可以传输9600比特的数据。按照串口发送一个字节需要10比特来计算——包括1比特起始位、8比特数据位、1比特停止位以及无校验位——那么传输一个字节所需的时间便是10比特除以9600比特/秒,即1/960秒。
3、串口发送模块
3.1、接口定义与整体设计
发送模块整体框图、输入输出信号如下所示:

其中信号端口如下:

需特别指出,uart_tx_data指的是待传输的单字节信息,而uart_tx_en则是控制发送的使能信号,一旦该信号被置高,便意味着当前正通过串行口的数据线将uart_tx_data内容发送出去。
3.2、设计思路
此模块能够适应(在理论层面)各种波特率的传输需求,然而,在应用此模块时,必须通过参数化操作来对其进行具体化配置;具体而言,数据位数为8位,起始位和停止位均为1位,且不涉及奇偶校验设置。
一旦使能信号生效,便提升发送状态指示,此时标志模块启动发送流程;发送完毕10比特信息后,降低发送状态指示,标志着发送阶段的终结。在使能信号激活的条件下,数据寄存器中存储的待发信息将被传输。
若波特率为9600,那么发送一个比特所需的时间是1秒除以9600,而一个数据包的传输包括10个比特(其中数据位有8位,起始位和停止位各占1位),因此,整个数据传输过程需要的时间是1秒除以9600。
若系统时钟频率设定为50MHz,相应地,其周期长度为20纳秒,据此计算,发送一个比特所需占用系统周期数量约为(1秒除以9600)再除以20纳秒,结果大约为5208个周期。
在发送数据时,我们采用了一个计数器来进行计数,其计数范围设定在0至5208减1之间,换言之,这个区间包含了10个不同的数值;每个数值代表一个字节,而一个字节由10个比特组成。
此外,我们还需要设置一个专门的计数装置来记录传输的比特数,每当该装置的数值达到5207,即意味着一个比特的传输已经完成;同时,该计数装置的工作范围设定在0到9之间。
在数据传输过程中,依据发送比特计数器的数值,对传输的数据线路执行相应的操作。
若发送bit计数器 = 0,则代表此时需要发送起始位;
当bit计数器的值为1时,表明当前必须传输数据的最低有效位LSB;在数据传输过程中,低位信息始终先于高位信息被发送。
······
当bit计数器达到8时,即表明此刻应发送数据的最高位,即MSB。
若发送bit计数器 = 9云开·全站体育app登录,则代表此时需要发送停止位;
当数据线未处于发送状态时,必须将其拉高,以确保UART的空闲时序得到满足。
3.3、Verilg代码
根据上述设计思路,部分发送模块代码如下:
禁止对专有名词进行修改,确保内容的一致性,同时需避免使用原文中已出现的词汇。在改写过程中,可以对句子的结构和用词进行适当的调整和丰富,力求在保持原意的基础上,实现最大化的差异。在改写时,应保持原文的风格,并注意将长句拆分成若干个小分句,用逗号进行分隔。此外,还需确保不遗漏任何句子末尾的标点符号。 // ** 作者 : 孤独的单刀 // ** 邮箱 : zachary_wu93@163.com 禁止对博客内容进行修改,请勿擅自更改专有名词,确保引用准确无误。aidetb // ** 日期 : 2022/07/31 // ** 功能 : 1、基于FPGA的串口发送驱动模块; // 2、可设置波特率BPS、主时钟CLK_FRE; 起始位为1位比特,数据部分由8位比特组成,结束位为1位比特,且不进行奇偶校验。 发送每个字节后,uart_tx_done信号会上升一个周期,这一变化可用来触发后续多个字节的发送过程。 严禁对特定内容进行篡改,确保信息的真实性,维护数据的一致性,遵守相关法律法规,保障信息安全。 module uart_tx #( paramBPS值设定为9600,表示传输波特率。 参数整型变量CLK_FRE赋值为50_000_000,代表主时钟的频率。 ) ( //系统接口 input sys_clk , //系统时钟 系统复位信号,以低电平为有效状态。 //用户接口 UART发送的数据为uart_tx_data,此数据仅在uart_tx_en处于高电平状态时才有效。 输入信号uart_tx_en,表示数据发送有效;当此信号处于高电平状态时,即表明当前待发送的数据是有效的。 //UART发送 注册输出信号 uart_tx_done,表示在成功传输1字节数据后,该信号将维持一个周期的高电平状态。 定义输出端口为reg,名为uart_txd,代表UART的发送数据线tx。 ); 一旦接收到启动发送的信号,应确保暂存待发送的数据,以防止其后续发生变动或遗失。 每当系统时钟上升沿或系统复位低电平沿到来时,便执行以下操作:, 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) && (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 endmodule
3.4、Testbench
Testbench的设计如下:
将波特率设定为230400(此举旨在便于观察发送使能信号uart_tx_en,同时节省时间)。
在3000纳秒之后,提升uart_tx_en的发送使能信号至一个周期,并同步产生一组8位的随机信息,将其赋值给uart_tx_data,作为即将传输的数据内容。
观察UART上TX线的时序是否满足要求
// ******************************************************************************************************* // ** 作者 : 孤独的单刀 邮箱地址为:zachary_wu93@163.com。 博客地址为:https://blog.csdn.net/wuzhikaidetb,请访问。 // ** 日期 : 2022/07/29 **用途**:1、针对基于FPGA的串行通信发送模块进行测试的测试平台 向系统发送一组由8位随机数字组成的信号,然后观察该信号的波形是否与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 ; 参数整型BPS设定为'd230400',表示波特率; 参数整型CLK_FRE定义为50,000,000;即系统运行频率为50兆赫。 定义局部参数BIT_TIME为一个整数,其值为1000_000_000除以BPS,以此计算传输每个比特所需的具体时间。 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、仿真结果分析
仿真结果如下图(注释很详细):
图中可见,发送模块传输了单一数据8'h24,随后传输过程告一段落,然而,我们无法直接对发送线TX上的时序进行观察。

整体仿真时序
图中可观察到,发送模块成功发送了数据1个,具体为8'h24,随后发送过程结束。同时,我们能够看到发送线TX正按照UART时序规范发送数据,具体表现为00100100(低位在前,高位在后),这同样对应着8'h24。

单次发送时序
可以看到仿真结果是符合预期设计要求的。
3.6、上板实测
至此,发送模块的仿真测试已圆满结束;紧接着,我们将在一块Altera Cyclone IV E开发板上进行实际的板级测试。
构建一个测试用的发送模块,此模块负责调用串口通信的发送功能,并按照既定的时间间隔(默认为1秒钟)逐步提升发送启用标志,同时生成并递增发送数据,数据起始值为0x01,逐次加1,直至达到0xFF,若超过此值则回绕至0x00。
在电脑端运行串口调试程序,以接收并传输数据。通过分析串口调试程序所获取的数据,评估串口发送模块是否能够正常运作。
发送模块验证模块代码如下:
// ******************************************************************************************************* // ** 作者 : 孤独的单刀 // ** 邮箱 : zachary_wu93@163.com // ** 博客 : https://blog.csdn.net/wuzhikaidetb // ** 日期 : 2022/07/29 该模块负责对基于FPGA的串口发送驱动进行测试,旨在确保其功能的有效性和稳定性。 // 2、每个1s发送1个递增1的数据到上位机。 // ******************************************************************************************************* module uart_tx_test ( //系统接口 input sys_clk , input sys_rst_n , //UART发送线 UART发送端口标识为uart_txd。 ); 参数整型BPS定义为'd230400',代表波特率设置。 定义整型参数CLK_FRE值为'50,000,000',代表系统运行频率为50兆赫兹。 reg [31:0] cnt_time; REG UART_TX_ENABLE; // 发送功能激活,若此位处于高电平状态,即表明当前正处于数据传输需求阶段。 在寄存器reg的[7:0]位中存储的uart_tx_data,仅在uart_tx_en处于高电平状态时,其内容才通过UART进行发送。 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等依次被成功接收,这表明我们的发送功能运作良好。

4、串口接收模块
4.1、接口定义与整体设计
接收模块整体框图、输入输出信号如下所示:

其中信号描述如下:

需要特别指出的是,uart_rx_data代表的是接收到的单个字节信息,而uart_rx_done则是用来标识接收动作是否完成的标志,一旦该标志被置高,即表明此刻接收到的串口数据uart_rx_data已经具备了有效性。
4.2、设计思路
本模块能够接收各种波特率的数据(理论上适用),然而在应用本模块时,必须通过设定参数来具体化其配置,具体包括数据位为8位,起始位和停止位均为1位,并且不采用奇偶校验。
串口数据的传输过程始于起始位,该起始位通过将数据线降至低电平来标识,因此我们必须侦测数据线的下降变化,并对接收到的数据线进行三次敲击,以便准确捕捉其下降的瞬间。
一旦检测到数据接收线的下降边缘,便提升接收状态指示信号,此时标志模块启动接收流程;完成10比特数据的接收后,降低接收状态指示信号,标志着接收阶段的终止。
若波特率为9600,那么传输一个比特所需的时间是1秒除以9600,而一个数据包的传输包括10个比特(其中数据位8个,起始位和停止位各1个),因此总共需要1秒除以960。
若系统时钟频率设定为50MHz,相应地,其周期长度为20纳秒,据此计算,传输一个比特所需占用系统周期数量约为(1秒除以960)再除以20纳秒,结果大约为5208个。
在接收环节,我们采用计数器进行计数,其计数范围设定为从0到5208减1,这样的计数区间共有10个,因为一个字节的信息需要通过10个比特位来传输。
另外,还需要一个专门的计数装置来记录接收到的比特数,每当该装置的计数达到5207时,即表明已经成功接收了一个比特;同时,该计数装置的计数范围应设定在0至9之间。
在接收数据的过程中,需依据计数器的数值(即接收到的比特数计数器)kaiyun.ccm,在比特计数器的中点进行数据接收,并将数据传递至移位寄存器(因为电平中点处的数据最为稳定)。
若接收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的串口接收驱动模块; 可以对波特率进行重新配置,设定为BPS;同时,也可以调整主时钟的频率,即CLK_FRE。 起始位为1位比特,数据位为8位比特,停止位为1位比特,且不采用奇偶校验机制。 严禁对专业内容进行擅自修改,确保信息的准确性和权威性,维护知识的完整性。 module uart_rx #( 参数整型BPS值设定为9600,表示传输波特率。 参数整型变量CLK_FRE设定为50,000,000,代表输入时钟的频率值。 ) ( //系统接口 输入信号为系统时钟,即sys_clk,其频率为50MHz。 系统复位信号,sys_rst_n,不得更改。 //UART接收线 接收数据接口为uart_rxd,。 //用户接口 输出寄存器为uart_rx_done,表示数据接收已结束,该标志位为高电平时,即表明接收到的数据是有效的。 定义寄存器output reg [7:0] uart_rx_data,表示该寄存器存储的是接收到的数据,并且仅在uart_rx_done信号处于高电平状态时,该数据才有效。 ); 将neg_uart_rxd赋值为uart_rx_d3与uart_rx_d2非值的与运算结果,此操作旨在检测数据线的下降沿,以此作为数据传输开始的标志。 对数据线进行三次敲击,目的之一是确保不同时钟域的信号保持同步,以此避免亚稳态现象的发生;目的之二是为了捕捉信号的下降沿。 每当sys_clk的上升沿或sys_rst_n的下降沿到来时,立即执行。 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时,执行。 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 endmodule
4.4、Testbench
仿真模块的Testbench设计如下:
设定波特率为230400,这一设置旨在便于我们更清晰地观察发送使能信号uart_tx_en。
设定一个名为task的任务,该任务旨在接收以230400波特率逐比特输出的输入,并模拟上位机向FPGA发送数据的过程。
3000ns后,发送第1个随机数据
完成第一个随机数据的发送后,紧接着发送第二个随机数据,如此循环,总计需发送四个随机数据。
// *******************************************************************************************************
// ** 作者 : 孤独的单刀
// ** 邮箱 : zachary_wu93@163.com
// ** 博客 : https://blog.csdn.net/wuzhikaidetb
// ** 日期 : 2022/07/29
本功能旨在对基于FPGA的串口接收驱动模块进行测试,构建testbench以评估其性能。
构建一个模拟上位机时序发送数据的任务,用以测试串口接收驱动是否能够有效接收这些数据。
// 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 ;
局部参数整型BPS定义为'230400',代表波特率值。
定义局部参数整型变量CLK_FRE为50,000,000赫兹,代表系统时钟频率。
localparam integer CNT等于1000_000_000除以BPS的结果;该计算旨在得出传输每一个比特所需的时间,其计量单位为纳秒。
//初始时刻定义
initial begin
使用该函数设置时间显示格式为负九小时零分,单位为纳秒,格式长度为十。
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、仿真结果分析
仿真结果如下图(注释很详细):
图中展示了四个数据kaiyun全站网页版登录,分别是8'h24、8'h81、8'h09和8'h63;接收模块亦分别获取了这四个数据,即8'h24、8'h81、8'h09和8'h63。发送与接收的数据完全相同。

接收总体时序
该图展示了首次接收到的数据(二进制表示为8'h24,即00100100)的时序情况。

单个字节接收时序
4.6、上板测试
到目前为止,接收模块的仿真测试已经圆满结束,紧接着,我们将在Altera Cyclone IV E开发板上进行实际的板级测试。
首先,我们需要构建一个名为ISSP(In-System Sources and Probes)的IP核。该IP核具备在线输出的功能,相当于一个基础信号发生器,即Source。同时,它还能提供探针Probes,用于实时监控信号的输出情况。
在本设计过程中,我们借助Probes工具对串口接收到的数据进行监测。具体调用方式为:ISSP执行以下操作:

构建一个验证模块,该模块负责调用接收模块,即ISSP IP核。同时,在电脑上运用串口调试工具发送数据,依据接收到的数据信息,评估串口接收模块是否能够正常运行。
接收模块验证模块uart_rx_test代码如下:
// ******************************************************************************************************* // ** 作者 : 孤独的单刀 // ** 邮箱 : zachary_wu93@163.com // ** 博客 : https://blog.csdn.net/wuzhikaidetb // ** 日期 : 2022/07/29 该模块负责对基于FPGA的串口接收驱动进行测试,旨在确保其功能的正常运行。 // 2、例化串口接收驱动与ISSP IP核; 利用上位机向FPGA传输随机数据,并通过ISSP监测工具观察接收到的驱动数据,以此进行测试验证。 // ******************************************************************************************************* module uart_rx_test ( //系统接口 input sys_clk , input sys_rst_n , //UART接收线 input uart_rxd //接收数据线 ); 参数整型BPS等于'd230400',表示波特率为230400。 定义一个整型参数CLK_FRE,其值为50_000_000,代表系统时钟频率为50兆赫兹。 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 实例 ( 进行数据接收的检测,针对UART接收的数据,进行探查。 .source ( ) ); endmodule
安装完软件后,需在Quartus II软件中启动In-System Sources and Probes编辑器,随后来用串口调试工具传输数据序列0x55、0xaa、0x88(三者随机选取),接着留意In-System Sources and Probes编辑器内寄存器的数值,具体数据如下:,



5、总结
串口作为一种常用的通信协议与调试手段,请一定要熟练掌握!
利用FPGA技术实现串口操作相对简单,只需确保计数器设计符合波特率要求,通过这一计数器的操控,便能够完成数据的传输与接收任务。
来源:CSDN博主「孤独的单刀」的原创文章

