基于FPGA的UART实现(学习笔记)

文章介绍了UART和USART两种串口通信方式,强调了时钟同步对UART通信稳定性的重要性,提到UART最高波特率限制以及如何通过PLL/MMCM确保稳定时钟。还详细描述了UART发送端的AXI4协议握手过程,包括数据传输、计数器使用以及错误检查机制。RX模块则通过超采样降低亚稳态影响。实验结果显示,使用优化后的UART在不同波特率下具有低误码率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

异步串口通信(UART):通信中发送端与接收端的时钟不一致。

同步串口通信(USART):通信中发送端与接收端的时钟一致。

UART:速率最高2Mbps(基本出现到达极限),2Mbps已经不稳定,1Mbps接收1w个byte误码率为0。

从板上出来的时钟不建议直接使用,建议使用PLL/MMCM来输出稳定时钟。

首先,UART的目标是实现一个稳定的波特率,同时尽可能的降低误码率。uart时序网上很多,这边不进行描述。其中分频,复位等时序就不占用空间了。

tx部分:

tx发送时采用AXI4协议中握手信号,以自身输出的ready信号与上级输入的vaild信号进行握手进行数据传输。此时会产生一个tx时钟的握手信号,进行开始握手。那么接下来可以考虑ready的输出。在握手后,立刻拉低ready,进行数据输出。数据输出完后,拉高ready,ready分有校验位拉高和无校验位拉高。

对于发送数据,采用计数器开始计数。一般情况下以ready拉低后进行计数,结束条件与上述ready类似。

在ready拉低期间,不断右移数据,将最低位数据不断通过tx发送出去。

module uart_tx#(
    parameter                   P_SYSTEM_CLK      = 50_000_000      ,   //输入时钟
    parameter                   P_UART_BUADRATE   = 9600            ,   //波特率
    parameter                   P_UART_DATA_WIDTH = 8               ,   //数据宽度
    parameter                   P_UART_STOP_WIDTH = 1               ,   //1 or 2
    parameter                   P_UART_CHECK      = 0                   //None = 0 ,Event = 1, Odd = 2    
)
(
    input   wire                                i_clk               ,
    input   wire                                i_rst               ,

    output  wire                                o_uart_tx           ,

    input   wire    [P_UART_DATA_WIDTH - 1 : 0] i_user_tx_data      ,     //用户发送的数据
    input   wire                                i_user_tx_data_vaild,
    output  wire                                o_user_tx_data_ready
);

/***************function**************/
    
/***************parameter*************/
    
/***************port******************/
    
/***************mechine***************/
    
/***************reg*******************/
reg                             ro_uart_tx              ; 
reg                             ro_user_tx_data_ready   ; 
reg [15:0]                      r_cnt                   ;   //计数器位宽高于16 bits时,组合逻辑过高,谨慎使用
reg [P_UART_DATA_WIDTH - 1 : 0] r_tx_data               ;
reg                             r_tx_check              ;


/***************wire******************/
wire                            w_tx_active             ;
/***************component*************/
    
/***************assign****************/
assign o_uart_tx            = ro_uart_tx                                    ; 
assign o_user_tx_data_ready = ro_user_tx_data_ready                         ;

assign w_tx_active          = i_user_tx_data_vaild & o_user_tx_data_ready   ;

/***************always****************/
always @(posedge i_clk or negedge i_rst) begin
    if (i_rst) begin
        ro_user_tx_data_ready <= 'd1; 
    end else if (w_tx_active) begin
        ro_user_tx_data_ready <= 'd0; 
    end else if (r_cnt == 2 + P_UART_DATA_WIDTH + P_UART_STOP_WIDTH - 3 && P_UART_CHECK == 0 ) begin
        ro_user_tx_data_ready <= 'd1;    
    end else if (r_cnt == 2 + P_UART_DATA_WIDTH + P_UART_STOP_WIDTH - 2 && P_UART_CHECK > 0 ) begin
        ro_user_tx_data_ready <= 'd1;    
    end else begin
        ro_user_tx_data_ready <= ro_user_tx_data_ready;
    end
end    

always @(posedge i_clk or negedge i_rst) begin
    if (i_rst) begin
        r_cnt <= 'd0;
    end else if (r_cnt == 2 + P_UART_DATA_WIDTH + P_UART_STOP_WIDTH - 3 && P_UART_CHECK == 0) begin //2 表示 开始位  校验位
        r_cnt <= 'd0;
    end else if (r_cnt == 2 + P_UART_DATA_WIDTH + P_UART_STOP_WIDTH - 2 && P_UART_CHECK > 0) begin 
        r_cnt <= 'd0;
    end else if (!ro_user_tx_data_ready) begin
        r_cnt <= r_cnt + 1'd1;
    end else begin
        r_cnt <= r_cnt;
    end
end

always @(posedge i_clk or negedge i_rst) begin
    if (i_rst) begin
        r_tx_data <= 'd0;
    end else if (w_tx_active) begin
        r_tx_data <= i_user_tx_data;
    end else if (!ro_user_tx_data_ready) begin
        r_tx_data <= r_tx_data >> 1;
    end else begin
        r_tx_data <= r_tx_data;
    end
end

always @(posedge i_clk or negedge i_rst) begin
    if (i_rst) begin
        ro_uart_tx <= 'd1;
    end else if (w_tx_active) begin
        ro_uart_tx <= 'd0;
    end else if ((r_cnt == 3 + P_UART_DATA_WIDTH - 3) && P_UART_CHECK > 0) begin    //开启校验位
        ro_uart_tx <= P_UART_CHECK == 1 ? ~r_tx_check : r_tx_check;                 //判断是奇还是偶
    end else if ((r_cnt == 3 + P_UART_DATA_WIDTH - 3) && P_UART_CHECK == 0) begin   //未开启校验,直接发送停止
        ro_uart_tx <= 'd1;
    end else if ((r_cnt >= 3 + P_UART_DATA_WIDTH - 2) && P_UART_CHECK > 0) begin    //开启了校验,发送完校验,直接发送停止
        ro_uart_tx <= 'd1;
    end else if (!ro_user_tx_data_ready) begin
        ro_uart_tx <= r_tx_data[0];
    end else begin
        ro_uart_tx <= 'd1;
    end
end

always @(posedge i_clk or negedge i_rst) begin
    if (i_rst)begin
        r_tx_check <= 'd0;
    end else if (r_cnt == 3 + P_UART_DATA_WIDTH - 3) begin
        r_tx_check <= 'd0;
    end else begin
        r_tx_check <= r_tx_check ^ r_tx_data[0];
    end
end

endmodule

rx模块:

 输入数据没有采用打两拍,是在顶层以50M时钟进行超采样。降低了亚稳态

module uart_rx#(
    parameter                   P_SYSTEM_CLK      = 50_000_000      ,   //输入时钟
    parameter                   P_UART_BUADRATE   = 9600            ,   //波特率
    parameter                   P_UART_DATA_WIDTH = 8               ,   //数据宽度
    parameter                   P_UART_STOP_WIDTH = 1               ,   //1 or 2
    parameter                   P_UART_CHECK      = 0                   //None = 0 ,Odd = 1, Event = 2
)
(
    input       wire                                i_clk               ,
    input       wire                                i_rst               ,

    input       wire                                i_uart_rx           ,

    output      wire    [P_UART_DATA_WIDTH - 1 : 0] o_user_rx_data      ,
    output      wire                                o_user_rx_data_vaild
);

/***************function**************/
    
/***************parameter*************/
    
/***************port******************/
    
/***************mechine***************/
    
/***************reg*******************/
reg [P_UART_DATA_WIDTH - 1 : 0]                 ro_user_rx_data                 ;
reg                                             ro_user_rx_data_vaild           ;
reg [ 1:0]                                      ri_uart_rx                      ;
reg [15:0]                                      r_cnt                           ;
reg                                             r_rx_check                      ;

/***************wire******************/
    
/***************component*************/
    
/***************assign****************/
assign o_user_rx_data       = ro_user_rx_data       ;
assign o_user_rx_data_vaild = ro_user_rx_data_vaild ;
/***************always****************/
always @(posedge i_clk or negedge i_rst) begin
    if (i_rst)begin
        ri_uart_rx <= 2'b11;
    end else begin
        ri_uart_rx <= {ri_uart_rx[0],i_uart_rx};
    end
end

always @(posedge i_clk or negedge i_rst) begin
    if (i_rst)begin
        r_cnt <= 'd0;
    end else if (r_cnt == 2 + P_UART_DATA_WIDTH + P_UART_STOP_WIDTH - 2 && P_UART_CHECK == 0) begin
        r_cnt <= 'd0;
    end else if (r_cnt == 2 + P_UART_DATA_WIDTH + P_UART_STOP_WIDTH - 1 && P_UART_CHECK > 0) begin
        r_cnt <= 'd0;
    end else if (i_uart_rx == 1'b0 || r_cnt > 0) begin
        r_cnt <= r_cnt + 'd1;
    end else begin
        r_cnt <= r_cnt;
    end
end

always @(posedge i_clk or negedge i_rst) begin
    if (i_rst)begin
        ro_user_rx_data <= 'd0;
    end else if (r_cnt >= 1 && r_cnt <= P_UART_DATA_WIDTH) begin
        ro_user_rx_data <= {i_uart_rx,ro_user_rx_data[P_UART_DATA_WIDTH - 1 :1]};
    end else begin
        ro_user_rx_data <= ro_user_rx_data;
    end
end

always @(posedge i_clk or negedge i_rst) begin
    if (i_rst)begin
        ro_user_rx_data_vaild <= 'd0;
    end else if (r_cnt == P_UART_DATA_WIDTH + 0 && P_UART_CHECK == 0) begin
        ro_user_rx_data_vaild <= 'd1;
    end else if (r_cnt == P_UART_DATA_WIDTH + 1 && P_UART_CHECK == 1 && i_uart_rx == !r_rx_check) begin
        ro_user_rx_data_vaild <= 'd1;
    end else if (r_cnt == P_UART_DATA_WIDTH + 1 && P_UART_CHECK == 2 && i_uart_rx == r_rx_check) begin
        ro_user_rx_data_vaild <= 'd1;
    end else begin
        ro_user_rx_data_vaild <= 'd0;
    end
end

always @(posedge i_clk or negedge i_rst) begin
    if (i_rst)begin
        r_rx_check <= 'd0;
    end else if (r_cnt >= 1 && r_cnt <= P_UART_DATA_WIDTH) begin
        r_rx_check <= r_rx_check ^ i_uart_rx;
    end else begin
        r_rx_check <= 'd0;
    end
end

最终采用回环输出。采用fifo进行在中间缓存,以115200波特率发送10000bytes误码率为0.以1Mbps发送误码率为0。黑金Spartan 7 开发板跑的结果如下:

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值