打工人日报#20251102
打工人日报#20251102
串口通信介绍
串口一帧数据介绍:起始位检测、数据位接收、校验位处理、停止位检测、接收标志与控制
1.起始位检测
- 作用:起始位用于通知接收方一帧数据即将开始传输,是数据传输的起始标志。
- 信号特征:在空闲状态下,串口线保持高电平。当发送方准备发送数据时,会将串口线的电平拉低,这个持续时间为一个波特周期的低电平信号就是起始位。例如,若波特率为 115200bps,那么起始位的持续时间约为 1152001s。
- 检测方式:接收方通常通过对串口接收引脚信号进行采样和逻辑判断来检测起始位。为了避免亚稳态等问题,一般会对接收信号进行同步处理,例如通过多级寄存器打拍。假设使用三个寄存器 uart_rxd_d0、uart_rxd_d1 和 uart_rxd_d2,在每个时钟上升沿,uart_rxd_d0 采样 uart_rxd 的值,uart_rxd_d1 采样 uart_rxd_d0 的值,uart_rxd_d2 采样 uart_rxd_d1 的值。然后通过逻辑表达式 uart_rxd_d2 & (~uart_rxd_d1) 来检测下降沿,当该表达式为高电平时,说明检测到了起始位。检测到起始位后,接收方会启动后续的数据接收流程,包括初始化波特率计数器和数据位计数器等。
2.数据位接收
- 作用:数据位承载了实际要传输的信息,其位数可以根据通信协议进行设置,常见的有 5 位、6 位、7 位或 8 位。
- 接收过程:在检测到起始位后,接收方按照预先设定的波特率,在每个波特周期的中间位置对信号进行采样,以获取数据位的值。这是因为在波特周期中间,信号经过传输延迟和噪声干扰后相对稳定,能最大程度保证采样的准确性。例如,若系统时钟为 50MHz,波特率为 115200bps,为了达到准确的采样时机,会计算出每个波特周期对应的系统时钟周期数(约 11520050000000≈434 个系统时钟周期),然后在第 217 个左右的系统时钟周期进行采样。
- 数据存储:采样得到的数据位按照从低位到高位的顺序依次存储到一个数据寄存器中。例如,如果是 8 位数据,先采样到的最低有效位(LSB)会存储在寄存器的最低位,后续依次存储其他位,直到最高有效位(MSB)存储完成,这样就完成了一帧数据位的接收。
3.校验位处理
- 作用:校验位用于检测数据在传输过程中是否发生错误,提高数据传输的可靠性。
- 校验方式:
- 奇校验:发送方会调整校验位的值,使得数据位和校验位中 1 的总数为奇数。接收方接收到数据后,同样统计数据位和校验位中 1 的个数,如果 1 的总数不是奇数,则说明传输过程中可能发生了错误。
- 偶校验:与奇校验相反,发送方保证数据位和校验位中 1 的总数为偶数。接收方通过检查 1 的总数是否为偶数来判断数据传输是否正确。
- 无校验:即不使用校验位,这种方式数据传输速度相对较快,但无法检测传输错误,适用于对数据准确性要求不高或传输环境较为可靠的场景。
- 校验处理:当配置了校验位时,接收方在接收到数据位后,按照选定的校验方式对数据位进行校验计算,然后将计算结果与接收到的校验位进行比较。如果两者一致,则认为数据在传输过程中没有发生错误;如果不一致,则表明数据传输可能出现错误,接收方可能会采取重传请求或其他错误处理措施,例如丢弃这帧数据并记录错误信息。
4.停止位检测
- 作用:停止位用于标识一帧数据的结束,告知接收方该帧数据已完整传输。
- 信号特征:停止位是一个高电平信号,其持续时间通常为 1 位、1.5 位或 2 位波特周期。例如,若波特率为 115200bps,1 位停止位的持续时间约为 1152001s。
- 检测过程:接收方在接收完数据位和校验位(如果有)后,继续按照波特率的节奏对信号进行采样。当检测到连续的高电平信号,且高电平持续的时间符合设定的停止位长度时,就确认检测到了停止位。例如,若设定为 1 位停止位,在采样到数据位或校验位后的下一个波特周期中间位置,连续采样到高电平,就表示检测到了停止位。检测到停止位后,接收方知道一帧数据完整接收完毕,可以对接收的数据进行后续处理,如将数据从接收缓冲区转移到应用程序处理的内存区域等。
5.接收标志与控制
- 接收标志:在串口接收过程中,常使用一个接收标志(如 rx_flag)来表示当前的接收状态。它是一个状态信号,用于向其他模块或逻辑单元表明串口接收的工作状态。
- 控制逻辑:
- 起始控制:当检测到起始位时,接收标志置为高电平,表示进入接收过程。这个标志可以触发接收模块内的各种操作,如启动数据位计数器、初始化波特率计数器等,确保数据能够正确接收。
- 过程控制:在接收过程中,接收标志保持高电平,用于控制数据存储的时机和顺序。例如,只有当接收标志为高电平时,采样到的数据位才会被正确存储到数据寄存器中。
- 结束控制:当检测到停止位时,接收标志置为低电平,表示接收结束。此时,接收标志可以触发其他操作,如通知数据处理模块有新的数据可用,或者将接收缓冲区清空,准备接收下一帧数据。同时,接收标志也可以作为状态信息提供给系统的其他部分,以便系统了解串口接收的工作情况,例如在系统状态监控界面中显示串口接收是否正常工作。
串口接收模块端口与功能描述

通过串口调试助手发送数据给开发版,UART 串口接收数据并将接收到的数据发送给上位机
串口的比特率为 115200Bps 是什么意思?
意味着该串口每秒钟能够传输 115200 比特(bit)的数据
可得串口发送或者接收 1bit 数据的时间为一个波特,即 1/115200s
如果用 50MHz(周期为 20ns)的系统时钟来计数
要计数的个数为 cnt = (1s×10^9)ns/115200bit)ns/20ns ≈ 434 个系统时钟周期
1.单个比特传输与系统时钟计数关系:
- 已知串口波特率为 115200bps,意味着每秒钟传输 115200 比特数据,那么传输 1 比特数据的时间(1 个波特)为 Tbit=1152001s。将其换算成纳秒,Tbit=1152001s×109ns/s≈8680.56ns。
- 系统时钟频率为 50MHz,周期 Tsys=20ns。所以传输 1 比特数据需要的系统时钟周期数 cnt=TsysTbit=20ns8680.56ns≈434 个系统时钟周期。这表明每传输 1 比特数据,系统时钟需要计数 434 次。
2.一秒内系统时钟计数总数: - 因为 1 秒钟要传输 115200 比特数据,而每传输 1 比特需要 434 个系统时钟周期,所以 1 秒钟内系统时钟需要计数的总数为 434×115200 个时钟周期。
从另一个角度验证,系统时钟频率为 50MHz,即 1 秒钟有 50×106 个时钟周期。计算传输 115200 比特数据所需的时钟周期数 434×115200=50096800,与 50MHz 系统时钟 1 秒钟的时钟周期数在数量级上相符(由于前面计算 cnt 时存在近似,会有一定误差)。
这种计算在 UART 设计中非常重要,用于确定波特率发生器的参数,以产生准确的波特率,保证数据的正确传输与接收。
即每位数据之间的间隔要在50MHz 的时钟频率下计数 434 次,这样就是至少需要9 位的波特率计数器来计数,保留冗余设置为16bit
串口接收数据打拍,是为了消除亚稳态,什么是亚稳态?
亚稳态是由于违背了触发器的建立和保持时间而产生的。寄存器采样需要满足一定的建立时间(setup)和
保持时间(holdup),而异步电路没有办法保证建立时间(setup)和保持时间(holdup),所以会出现亚稳
态。
uart_rx.v
module uart_rx(input clk , //系统时钟input rst_n , //系统复位,低有效input uart_rxd , //UART 接收端口output reg uart_rx_done, //UART 接收完成信号output reg [7:0] uart_rx_data //UART 接收到的数据);//parameter defineparameter CLK_FREQ = 50000000; //系统时钟频率 50MHzparameter UART_BPS = 115200 ; //串口波特率localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数 BPS_CNT 次;50000000 / 115200 ≈ 434;为了达到 115200 的波特率,每传输 1 比特数据,需要在系统时钟下计数大约 434 次//reg definereg uart_rxd_d0;reg uart_rxd_d1;reg uart_rxd_d2;reg rx_flag ; //接收过程标志信号reg [3:0 ] rx_cnt ; //接收数据计数器reg [15:0] baud_cnt ; //波特率计数器reg [7:0 ] rx_data_t ; //接收数据寄存器//wire define//定义了一个名为 start_en 的线网类型信号,它用于捕获接收端口下降沿(起始位),并生成一个时钟周期的脉冲信号。这个信号通常用于启动串口接收数据的相关操作wire start_en;//*****************************************************//** main code//*****************************************************//捕获接收端口下降沿(起始位),得到一个时钟周期的脉冲信号assign start_en = uart_rxd_d2 & (~uart_rxd_d1) & (~rx_flag);//uart_rxd_d1 和 uart_rxd_d2 是对串口接收数据信号 uart_rxd 进行打拍处理后的信号。打拍操作通常是为了同步信号,使其与系统时钟同步,便于在时序逻辑中进行处理。一般来说,uart_rxd_d1 是 uart_rxd 在一个时钟周期前的值,uart_rxd_d2 是 uart_rxd 在两个时钟周期前的值。
//uart_rxd_d2 & (~uart_rxd_d1):这部分逻辑用于检测 uart_rxd 信号的下降沿。当 uart_rxd 信号从高电平变为低电平时,uart_rxd_d2 为高电平(因为它是两个时钟周期前的值,此时还是高电平),uart_rxd_d1 为低电平(因为它是一个时钟周期前的值,此时已经变为低电平),那么 uart_rxd_d2 & (~uart_rxd_d1) 的结果为高电平,即检测到了下降沿。
//& (~rx_flag):rx_flag 可能是一个表示当前是否正在进行接收操作的标志信号。~rx_flag 表示当 rx_flag 为低电平时,即当前没有正在进行的接收操作时,这部分逻辑才为真。这是为了确保只有在没有正在接收数据(即接收操作空闲)时,检测到的起始位下降沿才有效,避免在接收过程中误检测到下降沿而产生错误的启动信号。//针对异步信号的同步处理//always @(posedge clk or negedge rst_n):这是一个 always 块,其敏感列表包含两个信号,posedge clk 表示时钟信号 clk 的上升沿,negedge rst_n 表示复位信号 rst_n 的下降沿。这意味着每当 clk 上升沿到来或者 rst_n 下降沿到来时,always 块内的逻辑就会被执行。always @(posedge clk or negedge rst_n) beginif(!rst_n) begin//当复位信号 rst_n 为低电平(有效复位)时,执行以下逻辑/*uart_rxd_d0 <= 1'b0;:将信号 uart_rxd_d0 赋值为低电平。uart_rxd_d0 是对 uart_rxd 进行同步处理过程中的第一个寄存器。
uart_rxd_d1 <= 1'b0;:将信号 uart_rxd_d1 赋值为低电平。uart_rxd_d1 是同步过程中的第二个寄存器,它通常用于存储 uart_rxd_d0 前一个时钟周期的值。
uart_rxd_d2 <= 1'b0;:将信号 uart_rxd_d2 赋值为低电平。uart_rxd_d2 是同步过程中的第三个寄存器,它用于存储 uart_rxd_d1 前一个时钟周期的值。在检测信号变化(如下降沿)时,uart_rxd_d2 和 uart_rxd_d1 常被用于判断。*/uart_rxd_d0 <= 1'b0;uart_rxd_d1 <= 1'b0;uart_rxd_d2 <= 1'b0;endelse begin//当复位信号 rst_n 为高电平(即不处于复位状态)时,执行以下逻辑/*uart_rxd_d0 <= uart_rxd;:在每个时钟上升沿,将异步信号 uart_rxd 的值赋给 uart_rxd_d0。这一步将异步信号 uart_rxd 同步到了时钟域 clk 下,uart_rxd_d0 的值会在每个时钟上升沿根据 uart_rxd 的值更新。
uart_rxd_d1 <= uart_rxd_d0;:将 uart_rxd_d0 的值赋给 uart_rxd_d1。这样 uart_rxd_d1 就保存了 uart_rxd_d0 前一个时钟周期的值。
uart_rxd_d2 <= uart_rxd_d1;:将 uart_rxd_d1 的值赋给 uart_rxd_d2。uart_rxd_d2 保存了 uart_rxd_d1 前一个时钟周期的值。*/uart_rxd_d0 <= uart_rxd;uart_rxd_d1 <= uart_rxd_d0;uart_rxd_d2 <= uart_rxd_d1;endend/*uart_rxd_d0、
uart_rxd_d1 和
uart_rxd_d2 形成了一个三级寄存器链,对异步信号
uart_rxd 进行同步处理。这种同步处理有助于避免亚稳态问题,并且为后续对
uart_rxd 信号的时序分析和逻辑判断提供了稳定的同步信号。例如,前面提到的通过
uart_rxd_d2 & (~uart_rxd_d1) 检测
uart_rxd 的下降沿,就是基于这种同步后的信号进行的。*///主要用于控制串口接收标志 rx_flag,通过检测串口接收数据的起始位和停止位来确定接收过程的开始和结束。//给接收标志赋值//lways @(posedge clk or negedge rst_n):这是一个 always 块,其敏感列表包含时钟信号 clk 的上升沿和复位信号 rst_n 的下降沿。意味着每当 clk 上升沿到来或者 rst_n 下降沿到来时,always 块内的逻辑就会被执行。always @(posedge clk or negedge rst_n) beginif(!rst_n)//当复位信号 rst_n 为低电平(有效复位)时,执行以下操作rx_flag <= 1'b0;//将接收标志 rx_flag 赋值为低电平,表示当前没有处于接收状态else if(start_en) //检测到起始位 //当检测到 start_en 信号为高电平时,即检测到串口接收数据的起始位rx_flag <= 1'b1; //接收过程中,标志信号 rx_flag 拉高 ;将接收标志 rx_flag 赋值为高电平,表示开始进入接收过程//在停止位一半的时候,即接收过程结束,标志信号 rx_flag 拉低else if((rx_cnt == 4'd9) && (baud_cnt == BAUD_CNT_MAX/2 - 1'b1))/*rx_cnt == 4'd9:rx_cnt 可能是一个用于计数接收数据位的计数器,当它计数到 9 时,表示已经接收到了包括起始位、数据位和停止位在内的所有位(假设起始位 1 位,数据位 8 位,停止位 1 位)。
baud_cnt == BAUD_CNT_MAX/2 - 1'b1:baud_cnt 是波特率计数器,用于按照波特率的节奏采样数据。BAUD_CNT_MAX 是为了得到指定波特率,对系统时钟计数的最大值。当 baud_cnt 计数到 BAUD_CNT_MAX/2 - 1 时,表示停止位已经传输了一半。两个条件同时满足时,意味着接收过程即将结束。*/rx_flag <= 1'b0; //将接收标志 rx_flag 赋值为低电平,表示接收过程结束else //如果既不满足复位条件,也不满足起始位检测条件和停止位检测条件,rx_flag <= rx_flag; //保持 rx_flag 的当前值不变。这在接收过程中,当既不是起始位也不是停止位的中间阶段,维持 rx_flag 为高电平,以表示接收正在进行。end //波特率的计数器赋值/*always @(posedge clk or negedge rst_n):这是一个 always 块,敏感列表包含时钟信号 clk 的上升沿和复位信号 rst_n 的下降沿。意味着只要 clk 上升沿到来或者 rst_n 下降沿到来,always 块内的逻辑就会被执行。*/always @(posedge clk or negedge rst_n) beginif(!rst_n) //当复位信号 rst_n 为低电平(有效复位)时baud_cnt <= 16'd0;//将波特率计数器 baud_cnt 赋值为 16 位的 0;确保在系统复位时,计数器被清零,为新的计数过程做好准备else if(rx_flag) begin //处于接收过程时,波特率计数器(baud_cnt)进行循环计数//当接收标志 rx_flag 为高电平时,表示处于串口数据接收过程if(baud_cnt < BAUD_CNT_MAX - 1'b1)/*检查波特率计数器 baud_cnt 的当前值是否小于 BAUD_CNT_MAX - 1。
BAUD_CNT_MAX 是为了得到指定波特率,对系统时钟计数的最大值(例如,系统时钟频率为 50MHz,波特率为 115200bps 时,
BAUD_CNT_MAX = 50000000 / 115200 ≈ 434)。这里减 1 是因为计数器从 0 开始计数。
*/
/*
如果 baud_cnt 小于 BAUD_CNT_MAX - 1,则将 baud_cnt 的值增加 1。这意味着在每个时钟上升沿,只要计数未达到
BAUD_CNT_MAX,计数器就会递增,用于按照波特率的节奏进行计数。
*/baud_cnt <= baud_cnt + 16'b1;else//当 baud_cnt 达到 BAUD_CNT_MAX - 1 时baud_cnt <= 16'd0; //计数达到一个波特率周期后清零end else/*将
baud_cnt 重新赋值为 0,实现循环计数。即完成一个波特率周期的计数后,计数器清零,准备下一个波特率周期的计数。这确保了在整个接收过程中,波特率计数器能按照设定的波特率周期持续循环计数。*/baud_cnt <= 16'd0; //接收过程结束时计数器清零end//对接收数据计数器(rx_cnt)进行赋值/*这是一个 always 块,其敏感信号包括时钟信号 clk 的上升沿和复位信号 rst_n 的下降沿。这意味着每当 clk 上升沿到来或者 rst_n 下降沿到来时,always 块内的代码就会被执行*/always @(posedge clk or negedge rst_n) beginif(!rst_n)//当复位信号 rst_n 为低电平(即有效复位状态)时rx_cnt <= 4'd0;//将接收数据计数器 rx_cnt 赋值为 4 位的 0。这一步确保在系统复位时,计数器回到初始状态,准备开始新的接收计数过程。else if(rx_flag) begin //处于接收过程时 rx_cnt 才进行计数;当接收标志 rx_flag 为高电平时,表示串口处于数据接收过程,if(baud_cnt == BAUD_CNT_MAX - 1'b1) //当波特率计数器计数到一个波特率周期时//检查波特率计数器 baud_cnt 是否达到 BAUD_CNT_MAX - 1;表示一个波特率周期结束。rx_cnt <= rx_cnt + 1'b1; //接收数据计数器加 1;如果一个波特率周期结束,将接收数据计数器 rx_cnt 的值增加 1。这意味着每经过一个完整的波特率周期,rx_cnt 就会增加 1,用于统计接收的数据位数。else//如果 baud_cnt 还未达到 BAUD_CNT_MAX - 1,rx_cnt <= rx_cnt;//保持 rx_cnt 的当前值不变。这确保在一个波特率周期内,rx_cnt 不会重复增加,只有在每个波特率周期结束时才增加。endelse//当 rx_flag 为低电平(即接收过程结束)时rx_cnt <= 4'd0; //接收过程结束时计数器清零;将接收数据计数器 rx_cnt 重新赋值为 4 位的 0。这使得计数器在一次接收过程结束后回到初始状态,为下一次接收数据做好准备。end //根据 rx_cnt 来寄存 rxd 端口的数据always @(posedge clk or negedge rst_n) beginif(!rst_n)rx_data_t <= 8'b0;//将存储接收数据的寄存器rx_data_t全部清0,初始化为 8 位全 0。这确保在系统复位时,接收数据的存储区域被清空,为新的接收操作做准备。else if(rx_flag) begin //系统处于接收过程时,当接收标志 rx_flag 为高电平,表示系统处于串口数据接收过程if(baud_cnt == BAUD_CNT_MAX/2 - 1'b1) begin //判断 baud_cnt 是否计数到数据位的中间;检查波特率计数器 baud_cnt 是否计数到 BAUD_CNT_MAX/2 - 1。//在串口通信中,通常在每个数据位的中间位置对信号进行采样,以获取最稳定、准确的数据值。这里 BAUD_CNT_MAX 是为达到指定波特率,对系统时钟计数的最大值,BAUD_CNT_MAX/2 - 1 即对应数据位中间位置的计数值。case(rx_cnt)//根据接收数据计数器 rx_cnt 的值,决定将采样到的数据位寄存到 rx_data_t 的相应位置。4'd1 : rx_data_t[0] <= uart_rxd_d2; //寄存数据的最低位4'd2 : rx_data_t[1] <= uart_rxd_d2;4'd3 : rx_data_t[2] <= uart_rxd_d2;4'd4 : rx_data_t[3] <= uart_rxd_d2;4'd5 : rx_data_t[4] <= uart_rxd_d2;4'd6 : rx_data_t[5] <= uart_rxd_d2;4'd7 : rx_data_t[6] <= uart_rxd_d2;4'd8 : rx_data_t[7] <= uart_rxd_d2; //寄存数据的高低位default : ;endcase endelse//如果 baud_cnt 未计数到数据位的中间位置rx_data_t <= rx_data_t;//保持 rx_data_t 的当前值不变,等待合适的采样时机。endelse//当 rx_flag 为低电平(接收过程结束)时rx_data_t <= 8'b0;//再次将 rx_data_t 清零,准备下一次接收数据。end //给接收完成信号和接收到的数据赋值always @(posedge clk or negedge rst_n) beginif(!rst_n) begin //当复位信号rst_n为低电平(即有效复位)时/*将接收完成信号uart_rx_done赋值为低电平。这表明系统当前尚未完成串口数据的接收操作,此信号通常用于向其他模块表明接收状态,低电平意味着接收还在进行中或者尚未开始。*/uart_rx_done <= 1'b0;//将接收完成信号;uart_rx_done置为低电平,表示尚未完成接收操作/*将存储接收到数据的uart_rx_data寄存器赋值为 8 位全零。这是为了在复位时清除之前可能残留的数据,确保在新的接收过程开始前,数据存储处于初始状态。*/uart_rx_data <= 8'b0;//end//当接收数据计数器计数到停止位,且 baud_cnt 计数到停止位的中间时/*当接收数据计数器rx_cnt的值达到 4'd9(表示已经接收到了起始位、8 位数据位和停止位,总共 9 个位),并且波特率计数器
baud_cnt的值达到BAUD_CNT_MAX/2 - 1(表示此时处于停止位的中间位置,数据已经稳定)时*/else if(rx_cnt == 4'd9 && baud_cnt == BAUD_CNT_MAX/2 - 1'b1) begin/*将接收完成信号uart_rx_done拉高为高电平。这向其他模块表明一帧串口数据已经完整接收,可以进行后续的数据处理操作。*/uart_rx_done <= 1'b1 ; //拉高接收完成信号/*将临时存储接收数据的rx_data_t赋值给uart_rx_data。rx_data_t在之前的接收过程中逐位存储了接收到的数据,此时将其内容传递给uart_rx_data,完成数据的最终存储,以便其他模块使用。*/uart_rx_data <= rx_data_t; //并对 UART 接收到的数据进行赋值end /*当既不满足复位条件(rst_n为高电平),也不满足接收完成条件(rx_cnt不等于 4'd9 或者baud_cnt不等于BAUD_CNT_MAX/2 - 1)时,*/else beginuart_rx_done <= 1'b0;//当既不满足复位条件(rst_n为高电平),也不满足接收完成条件(rx_cnt不等于 4'd9 或者baud_cnt不等于BAUD_CNT_MAX/2 - 1)时,uart_rx_data <= uart_rx_data;//保持uart_rx_data寄存器的值不变,因为还未到接收完成并更新数据的时候。endendendmodule
uart_tx.v
module uart_tx(input clk, // 系统时钟,为模块提供时序基准input rst_n, // 系统复位信号,低电平有效,用于将模块状态重置为初始状态input uart_tx_en, // UART的发送使能信号,高电平时允许模块开始发送数据input [7:0] uart_tx_data, // UART要发送的数据,8位宽output reg uart_txd, // UART发送引脚,用于输出串行数据output reg uart_tx_busy // 发送忙信号,高电平时表示模块正在发送数据
);// parameter defineparameter CLK_FREQ = 50000000; // 系统时钟频率,单位为Hz,这里设置为50MHzparameter UART_BPS = 115200; // 串口波特率,即每秒传输的比特数localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS; // 为了得到指定波特率,对系统时钟计数的最大值。// 它表示在一个波特周期内系统时钟的周期数// reg definereg [7:0] tx_data_t; // 发送数据寄存器,用于暂存要发送的数据reg [3:0] tx_cnt; // 发送数据计数器,用于记录已发送的数据位数reg [15:0] baud_cnt; // 波特率计数器,用于按照波特率的节奏进行计数//*****************************************************//** main code//*****************************************************// 当uart_tx_en为高时,暂存输入的并串数据,且拉高BUSY信号always @(posedge clk or negedge rst_n) beginif(!rst_n) begintx_data_t <= 8'b0; // 复位时,清空发送数据寄存器uart_tx_busy <= 1'b0; // 复位时,将发送忙信号置为低,表示不忙end// 发送使能时,暂存要发送的数据,并拉高BUSY信号else if(uart_tx_en) begintx_data_t <= uart_tx_data; // 将输入的要发送的数据存入发送数据寄存器uart_tx_busy <= 1'b1; // 拉高发送忙信号,表示开始发送数据end// 当计数到停止位结束时,清空发送过程else if(tx_cnt == 4'd9 && baud_cnt == BAUD_CNT_MAX - BAUD_CNT_MAX/16) begintx_data_t <= 8'b0; // 清空发送数据寄存器uart_tx_busy <= 1'b0; // 拉低发送忙信号,表示发送结束endelse begintx_data_t <= tx_data_t; // 保持发送数据寄存器的值不变uart_tx_busy <= uart_tx_busy; // 保持发送忙信号的当前状态endend// 波特率的计数器赋值always @(posedge clk or negedge rst_n) beginif(!rst_n)baud_cnt <= 16'd0; // 复位时,将波特率计数器清零// 当处于发送过程时,波特率计数器(baud_cnt)进行循环计数else if(uart_tx_busy) beginif(baud_cnt < BAUD_CNT_MAX - 1'b1)baud_cnt <= baud_cnt + 16'b1; // 波特率计数器递增elsebaud_cnt <= 16'd0; // 计数达到一个波特率周期后清零,准备下一个周期计数end elsebaud_cnt <= 16'd0; // 发送过程结束时计数器清零end// tx_cnt进行递增always @(posedge clk or negedge rst_n) beginif(!rst_n)tx_cnt <= 4'd0; // 复位时,将发送数据计数器清零else if(uart_tx_busy) begin // 处于发送过程时tx_cnt才进行计数if(baud_cnt == BAUD_CNT_MAX - 1'b1) // 当波特率计数器计数到一个波特率周期时tx_cnt <= tx_cnt + 1'b1; // 发送数据计数器加1,记录已发送的数据位数elsetx_cnt <= tx_cnt; // 否则保持发送数据计数器的值不变endelsetx_cnt <= 4'd0; // 发送过程结束时计数器清零end// 根据tx_cnt来给uart发送引脚赋值always @(posedge clk or negedge rst_n) beginif(!rst_n)uart_txd <= 1'b1; // 复位时,将UART发送引脚置为高电平(空闲状态)else if(uart_tx_busy) begincase(tx_cnt)4'd0 : uart_txd <= 1'b0 ; // 起始位,拉低电平4'd1 : uart_txd <= tx_data_t[0]; // 数据位最低位4'd2 : uart_txd <= tx_data_t[1];4'd3 : uart_txd <= tx_data_t[2];4'd4 : uart_txd <= tx_data_t[3];4'd5 : uart_txd <= tx_data_t[4];4'd6 : uart_txd <= tx_data_t[5];4'd7 : uart_txd <= tx_data_t[6];4'd8 : uart_txd <= tx_data_t[7]; // 数据位最高位4'd9 : uart_txd <= 1'b1 ; // 停止位,拉高电平default : uart_txd <= 1'b1; // 其他情况,保持高电平endcaseendelseuart_txd <= 1'b1; // 空闲时发送引脚为高电平endendmodule
顶层模块
module uart_loopback (input sys_clk, // 外部 50MHz 时钟,为整个模块提供时序基准input sys_rst_n, // 外部复位信号,低电平有效,用于复位模块内各组件// UART 端口 input uart_rxd, // UART 接收端口,用于接收外部串口数据output uart_txd // UART 发送端口,用于向外部发送串口数据
);// parameter defineparameter CLK_FREQ = 50000000; // 定义系统时钟频率为 50MHzparameter UART_BPS = 115200; // 定义串口波特率为 115200bps// wire definewire uart_rx_done; // UART 接收完成信号,当一帧数据接收完毕时置高wire [7:0] uart_rx_data; // UART 接收数据,存储接收到的 8 位数据//*****************************************************//** main code//*****************************************************// 串口接收模块// 通过 # 语法传递参数 CLK_FREQ 和 UART_BPS 给 uart_rx 模块uart_rx #(.CLK_FREQ(CLK_FREQ), .UART_BPS(UART_BPS)) u_uart_rx (.clk(sys_clk), // 连接系统时钟到串口接收模块的 clk 端口.rst_n(sys_rst_n), // 连接复位信号到串口接收模块的 rst_n 端口.uart_rxd(uart_rxd), // 连接 UART 接收端口到串口接收模块的 uart_rxd 端口.uart_rx_done(uart_rx_done), // 接收完成信号输出连接到当前模块的 uart_rx_done 信号.uart_rx_data(uart_rx_data) // 接收数据输出连接到当前模块的 uart_rx_data 信号);// 串口发送模块// 通过 # 语法传递参数 CLK_FREQ 和 UART_BPS 给 uart_tx 模块uart_tx #(.CLK_FREQ(CLK_FREQ), .UART_BPS(UART_BPS)) u_uart_tx (.clk(sys_clk), // 连接系统时钟到串口发送模块的 clk 端口.rst_n(sys_rst_n), // 连接复位信号到串口发送模块的 rst_n 端口.uart_tx_en(uart_rx_done), // 发送使能信号由接收完成信号驱动.uart_tx_data(uart_rx_data), // 要发送的数据来自接收模块接收到的数据.uart_txd(uart_txd), // 串口发送端口连接到当前模块的 uart_txd 端口.uart_tx_busy() // 这里忽略了 uart_tx_busy 信号,可能在本模块中不需要使用);endmodule
仿真
`timescale 1ns / 1ns
// `timescale 指令用于定义时间单位和时间精度。这里表示时间单位为1ns,时间精度也为1ns。
// 这意味着在本模块中,所有的时间延迟和定时相关的数值都以1ns为基本单位,并且仿真器在处理时间相关操作时,精度达到1ns。module tb_uart_loopback();// parameter defineparameter CLK_PERIOD = 20; // 定义一个参数 CLK_PERIOD,用于表示时钟周期,这里设置为20ns。// reg definereg sys_clk; // 定义一个名为sys_clk的寄存器,用于表示系统时钟信号reg sys_rst_n; // 定义一个名为sys_rst_n的寄存器,用于表示系统复位信号,低电平有效reg uart_rxd; // 定义一个名为uart_rxd的寄存器,用于表示UART接收引脚信号// wire definewire uart_txd; // 定义一个名为uart_txd的线网,用于连接到UART发送引脚信号//*****************************************************//** main code//*****************************************************// 发送 8'h55 8'b0101_0101initial beginsys_clk <= 1'b0; // 初始时,将系统时钟信号sys_clk设置为低电平sys_rst_n <= 1'b0; // 初始时,将系统复位信号sys_rst_n设置为低电平,使系统处于复位状态uart_rxd <= 1'b1; // 初始时,将UART接收引脚信号uart_rxd设置为高电平,代表空闲状态#200; // 延迟200nssys_rst_n <= 1'b1; // 经过200ns后,将复位信号拉高,释放复位,系统开始正常工作#1000; // 再延迟1000nsuart_rxd <= 1'b0; // 将uart_rxd拉低,发送起始位#8680; // 延迟8680ns,这个时间长度大约对应一个波特周期(根据设定的波特率计算得出)uart_rxd <= 1'b1; // 发送数据位D0#8680; // 再延迟8680nsuart_rxd <= 1'b0; // 发送数据位D1#8680; // 依次类推,按照波特率的节奏发送后续数据位uart_rxd <= 1'b1; // 发送数据位D2#8680; uart_rxd <= 1'b0; // 发送数据位D3#8680; uart_rxd <= 1'b1; // 发送数据位D4#8680; uart_rxd <= 1'b0; // 发送数据位D5#8680; uart_rxd <= 1'b1; // 发送数据位D6#8680; uart_rxd <= 1'b0; // 发送数据位D7 #8680; uart_rxd <= 1'b1; // 发送停止位#8680; uart_rxd <= 1'b1; // 发送完毕后,将uart_rxd保持为高电平,回到空闲状态end// 50Mhz 的时钟,周期则为 1/50Mhz = 20ns,所以每 10ns,电平取反一次always #(CLK_PERIOD / 2) sys_clk = ~sys_clk;// 这是一个always块,每经过CLK_PERIOD / 2(即10ns),就将sys_clk信号取反,从而生成一个周期为20ns(频率为50MHz)的时钟信号。// 实例化待测模块uart_loopback u_uart_loopback(.sys_clk(sys_clk), // 将系统时钟信号连接到待测模块的sys_clk端口.sys_rst_n(sys_rst_n), // 将系统复位信号连接到待测模块的sys_rst_n端口.uart_rxd(uart_rxd), // 将UART接收引脚信号连接到待测模块的uart_rxd端口.uart_txd(uart_txd) // 将UART发送引脚信号连接到待测模块的uart_txd端口);endmodule
阅读
《杀死一只知更鸟》完结

