打工人日报#202510012
打工人日报#20251012
知识点
UART
通过串口调试助手发送数据给开发版,UART 串口接收数据并将接收到的数据发送给上位机
uart_loopback.v
// UART环回测试模块
// 功能:将UART接收端收到的数据直接通过发送端回传,实现数据环回功能
module uart_loopback(input sys_clk, // 外部50MHz时钟信号input sys_rst_n, // 外部复位信号,低电平有效// UART接口信号input uart_rxd, // UART接收端口output uart_txd // UART发送端口
);// 参数定义
parameter CLK_FREQ = 50000000; // 系统时钟频率,单位:Hz
parameter UART_BPS = 115200; // UART通信波特率// 内部连线定义
wire uart_rx_done; // UART接收完成标志(高电平有效)
wire [7:0] uart_rx_data; // UART接收的数据(8位)// *****************************************************
// ** 主逻辑
// *****************************************************// 例化UART接收模块
// 功能:将串行的UART接收信号(uart_rxd)转换为并行数据(uart_rx_data)
// 接收完成后输出接收完成标志(uart_rx_done)
uart_rx #(.CLK_FREQ (CLK_FREQ), // 传递系统时钟频率参数.UART_BPS (UART_BPS) // 传递UART波特率参数
)
u_uart_rx(.clk (sys_clk), // 时钟输入.rst_n (sys_rst_n), // 复位输入(低电平有效).uart_rxd (uart_rxd), // UART接收信号输入.uart_rx_done(uart_rx_done),// 接收完成标志输出.uart_rx_data(uart_rx_data) // 接收数据输出(8位并行)
);// 例化UART发送模块
// 功能:将并行数据(uart_rx_data)转换为串行的UART发送信号(uart_txd)
// 当接收完成标志(uart_rx_done)有效时启动发送
uart_tx #(.CLK_FREQ (CLK_FREQ), // 传递系统时钟频率参数.UART_BPS (UART_BPS) // 传递UART波特率参数
)
u_uart_tx(.clk (sys_clk), // 时钟输入.rst_n (sys_rst_n), // 复位输入(低电平有效).uart_tx_en (uart_rx_done),// 发送使能(使用接收完成标志作为触发信号).uart_tx_data(uart_rx_data),// 待发送数据(来自接收模块的输出).uart_txd (uart_txd), // UART发送信号输出.uart_tx_busy( ) // 发送忙标志(未使用,悬空)
);endmodule
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; //系统时钟频率parameter UART_BPS = 115200 ; //串口波特率localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数 BPS_CNT 次//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 definewire start_en;//*****************************************************//** main code//*****************************************************//捕获接收端口下降沿(起始位),得到一个时钟周期的脉冲信号assign start_en = uart_rxd_d2 & (~uart_rxd_d1) & (~rx_flag);//针对异步信号的同步处理always @(posedge clk or negedge rst_n) beginif(!rst_n) beginuart_rxd_d0 <= 1'b0;uart_rxd_d1 <= 1'b0;uart_rxd_d2 <= 1'b0;endelse beginuart_rxd_d0 <= uart_rxd;uart_rxd_d1 <= uart_rxd_d0;uart_rxd_d2 <= uart_rxd_d1;endend//给接收标志赋值always @(posedge clk or negedge rst_n) beginif(!rst_n)rx_flag <= 1'b0;else if(start_en) //检测到起始位rx_flag <= 1'b1; //接收过程中,标志信号 rx_flag 拉高//在停止位一半的时候,即接收过程结束,标志信号 rx_flag 拉低else if((rx_cnt == 4'd9) && (baud_cnt == BAUD_CNT_MAX/2 - 1'b1))rx_flag <= 1'b0;elserx_flag <= rx_flag;end //波特率的计数器赋值always @(posedge clk or negedge rst_n) beginif(!rst_n)baud_cnt <= 16'd0;else if(rx_flag) begin //处于接收过程时,波特率计数器(baud_cnt)进行循环计数if(baud_cnt < BAUD_CNT_MAX - 1'b1)baud_cnt <= baud_cnt + 16'b1;elsebaud_cnt <= 16'd0; //计数达到一个波特率周期后清零end elsebaud_cnt <= 16'd0; //接收过程结束时计数器清零end//对接收数据计数器(rx_cnt)进行赋值always @(posedge clk or negedge rst_n) beginif(!rst_n)rx_cnt <= 4'd0;else if(rx_flag) begin //处于接收过程时 rx_cnt 才进行计数if(baud_cnt == BAUD_CNT_MAX - 1'b1) //当波特率计数器计数到一个波特率周期时rx_cnt <= rx_cnt + 1'b1; //接收数据计数器加 1elserx_cnt <= rx_cnt;endelserx_cnt <= 4'd0; //接收过程结束时计数器清零end //根据 rx_cnt 来寄存 rxd 端口的数据always @(posedge clk or negedge rst_n) beginif(!rst_n)rx_data_t <= 8'b0;else if(rx_flag) begin //系统处于接收过程时if(baud_cnt == BAUD_CNT_MAX/2 - 1'b1) begin //判断 baud_cnt 是否计数到数据位的中间case(rx_cnt)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 endelserx_data_t <= rx_data_t;endelserx_data_t <= 8'b0;end //给接收完成信号和接收到的数据赋值always @(posedge clk or negedge rst_n) beginif(!rst_n) beginuart_rx_done <= 1'b0;uart_rx_data <= 8'b0;end//当接收数据计数器计数到停止位,且 baud_cnt 计数到停止位的中间时else if(rx_cnt == 4'd9 && baud_cnt == BAUD_CNT_MAX/2 - 1'b1) beginuart_rx_done <= 1'b1 ; //拉高接收完成信号uart_rx_data <= rx_data_t; //并对 UART 接收到的数据进行赋值end else beginuart_rx_done <= 1'b0;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, // 待发送的8位数据output reg uart_txd , // UART发送端口output reg uart_tx_busy // 发送忙状态信号(高电平表示正在发送)
);// 参数定义
parameter CLK_FREQ = 50000000; // 系统时钟频率(50MHz)
parameter UART_BPS = 115200 ; // 串口波特率(115200bps)
localparam BAUD_CNT_MAX = CLK_FREQ / UART_BPS; // 波特率计数最大值,用于将系统时钟分频为波特率时钟// 寄存器定义
reg [7:0] tx_data_t; // 发送数据缓存寄存器,用于暂存待发送的8位数据
reg [3:0] tx_cnt ; // 发送位计数器,记录当前发送的位序号(0~9,对应起始位到停止位)
reg [15:0] baud_cnt ; // 波特率计数器,用于生成波特率时钟(每计数到BAUD_CNT_MAX完成一次波特率周期)// *****************************************************
// ** 主逻辑
// *****************************************************// 数据缓存与发送忙信号控制
always @(posedge clk or negedge rst_n) beginif(!rst_n) begin // 复位状态tx_data_t <= 8'b0; // 清空数据缓存uart_tx_busy <= 1'b0; // 复位时不处于发送状态end// 发送使能有效时,锁存输入数据并置高忙信号else if(uart_tx_en) begintx_data_t <= uart_tx_data; // 锁存待发送的数据uart_tx_busy <= 1'b1; // 标记为发送忙end// 发送完成条件:当发送到停止位(tx_cnt=9),且波特率计数接近结束(预留一小段时间确保信号稳定)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; // 标记为发送完成,清除忙信号end// 其他情况保持当前状态else begintx_data_t <= tx_data_t;uart_tx_busy <= uart_tx_busy;end
end// 波特率时钟生成(分频逻辑)
always @(posedge clk or negedge rst_n) beginif(!rst_n)baud_cnt <= 16'd0; // 复位时计数器清零// 发送忙时,波特率计数器递增,生成波特率时钟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// 发送位计数控制(按位发送:起始位→8位数据位→停止位)
always @(posedge clk or negedge rst_n) beginif(!rst_n)tx_cnt <= 4'd0; // 复位时计数清零else if(uart_tx_busy) begin // 发送忙时,才进行位计数// 每完成一个波特率周期(baud_cnt计数到最大值),切换到下一位if(baud_cnt == BAUD_CNT_MAX - 1'b1)tx_cnt <= tx_cnt + 1'b1;elsetx_cnt <= tx_cnt; // 未完成当前波特率周期时,保持当前位endelsetx_cnt <= 4'd0; // 发送空闲时,计数清零
end// 发送数据输出(按UART协议生成信号)
always @(posedge clk or negedge rst_n) beginif(!rst_n)uart_txd <= 1'b1; // 复位时,发送端口处于空闲状态(高电平)else if(uart_tx_busy) begin // 发送忙时,按位输出信号case(tx_cnt)4'd0 : uart_txd <= 1'b0 ; // 起始位(低电平)4'd1 : uart_txd <= tx_data_t[0]; // 数据位0(最低位)4'd2 : uart_txd <= tx_data_t[1]; // 数据位14'd3 : uart_txd <= tx_data_t[2]; // 数据位24'd4 : uart_txd <= tx_data_t[3]; // 数据位34'd5 : uart_txd <= tx_data_t[4]; // 数据位44'd6 : uart_txd <= tx_data_t[5]; // 数据位54'd7 : uart_txd <= tx_data_t[6]; // 数据位64'd8 : uart_txd <= tx_data_t[7]; // 数据位7(最高位)4'd9 : uart_txd <= 1'b1 ; // 停止位(高电平)default : uart_txd <= 1'b1;endcaseendelseuart_txd <= 1'b1; // 发送空闲时,端口处于空闲状态(高电平)
endendmodule
tb_uart_loopback.v
`timescale 1ns/1ns //浠跨湡鐨勫崟浣�/浠跨湡鐨勭簿搴�module tb_uart_loopback();//parameter define
parameter CLK_PERIOD = 20;//鏃堕挓鍛ㄦ湡涓� 20ns//reg define
reg sys_clk ; //鏃堕挓淇″彿reg sys_rst_n; //澶嶄綅淇″彿reg uart_rxd ; //UART 鎺ユ敹绔彛//wire definewire uart_txd ; //UART 鍙戦�佺鍙�//*****************************************************//** main code//*****************************************************//鍙戦�� 8'h55 8'b0101_0101initial beginsys_clk <= 1'b0;sys_rst_n <= 1'b0;uart_rxd <= 1'b1;#200sys_rst_n <= 1'b1; #1000uart_rxd <= 1'b0; //璧峰浣�#8680uart_rxd <= 1'b1; //D0#8680uart_rxd <= 1'b0; //D1#8680uart_rxd <= 1'b1; //D2#8680uart_rxd <= 1'b0; //D3#8680uart_rxd <= 1'b1; //D4#8680uart_rxd <= 1'b0; //D5#8680uart_rxd <= 1'b1; //D6#8680uart_rxd <= 1'b0; //D7 #8680uart_rxd <= 1'b1; //鍋滄浣�#8680uart_rxd <= 1'b1; //绌洪棽鐘舵�� end//50Mhz 鐨勬椂閽燂紝鍛ㄦ湡鍒欎负 1/50Mhz=20ns,鎵�浠ユ瘡 10ns锛岀數骞冲彇鍙嶄竴娆�always #(CLK_PERIOD/2) sys_clk = ~sys_clk;//渚嬪寲椤跺眰妯″潡uart_loopback u_uart_loopback(.sys_clk (sys_clk ),.sys_rst_n (sys_rst_n),.uart_rxd (uart_rxd ),.uart_txd (uart_txd ));endmodule
阅读
《杀死一只知更鸟知》
第十七章