Bootstrap

FPGA学习记录(1)_UART串口通信_正点原子领航者开发板

前言

        今天学习正点原子领航者开发板UART串口通信模块,以下是记录的笔记和重写的代码。


1:UART串口通信

1.1 基础知识

        串行通信:优点:占用的引脚IO少,成本低           缺点:传输速率低

        并行通信:优点:传输速率快                                 缺点:占用的引脚IO较多,成本高

        单工通信:数据只能沿一个方向传输

        半双工通信:数据可以沿两个方向传输,但需要分时进行

        全双工通信:数据可以同时进行双向传输

        同步通信:带时钟端口的数据传输

        异步通信:没有时钟端口,发送方和接收方使用各自的时钟控制数据的收发过程

1.2 串行通信接口

1.3 UART 串口通信

1.3.1物理层

        接口类型:串行通信接口有RS232/RS485/RS422等

        RS232电平:逻辑1:-15V ~ -3V       逻辑0:+3V ~ +15V

1.3.2协议层

        注:BAUD_CNT_MAX = CLK_FREQ/UART_BPS

        即:计数器最大值 = 晶振周期 / 比特率

        相关位置协议如下图:

1.4 亚稳态

        首先,亚稳态是当正常采集数据时候,由于触发器信号会有延时的问题,很难正常采集其是0还是1。所以,这里采用打三拍的方式,如下代码,打三拍减少了tmet的时间,每打一拍,其时间降低为原来的1/2。

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        uart_rxd_d0 <= 1'b0;
        uart_rxd_d1 <= 1'b0;
        uart_rxd_d2 <= 1'b0;
    end
    else begin
        uart_rxd_d0 <= uart_rxd;
        uart_rxd_d1 <= uart_rxd_d0;
        uart_rxd_d2 <= uart_rxd_d1;
    end
end

2:波形图——写代码

2.1 系统框图

2.2 uart_rx波形图

        注: 停止位标志位必须在时段周期一半的时候进行停止,卡沿的话,如果停止位后立刻进行开始标志位,那么开始标志位会被直接错过(忽视),所以取一半的计数(0~216)。

        代码分析:

        根据波形从上往下逐一信号有逻辑的书写和分析        

        1:start_en(由uart_rxd_d1,uart_rxd_d2,rx_flag波形推出):他是捕获接收端口下降沿(起始位),得到一个时钟周期的脉冲信号,简单理解为既获得了接收信号标志位rx_flag为0,且获得打三拍后的数据uart_rxd_d2为1和打两拍的数据uart_rxd_d1为0的信号(捕获下降沿)。

         2:uart_rxd_d2(由uart_rxd_d0,uart_rxd_d1,uart_rxd_d2波形推出):写打三拍的信号。

        3:接收标志rx_flag赋值(由start_en波形推出):检测到起始位start_en为1后,标志信号rx_flag拉高为1(为baud_cnt计数做准备)。在停止位一半的时候(baud_cnt计数的一半且rx_cnt为9),即接收过程结束,标志信号rx_flag拉低为0。

        4:波特率的计数器baud_cnt赋值(由标志位rx_flag推出):标志位rx_flag为1,开始计数,标志位rx_flag为0,清零。计数到(BAUD_CNT_MAX-1)时,清零。

        5:对接收数据计数器(rx_cnt)进行赋值(由波特率的计数器baud_cnt和标志位rx_flag推出):标志位rx_flag为1,开始计数,标志位rx_flag为0,清零。baud_cnt到(BAUD_CNT_MAX-1)时,rx_cnt就+1。

        6:根据rx_cnt来寄存rxd端口的数据rx_data_t(由接收数据计数器rx_cnt,波特率的计数器baud_cnt和标志位rx_flag推出):标志位rx_flag为1,开始寄存数据,标志位rx_flag为0,寄存数据rx_data_t清零。baud_cnt计数到数据位的中间时,rx_data_t开始寄存uart_rxd_d2的数据。

        7:给接收完成信号uart_rx_done和接收到的数据uart_rx_data赋值(由接收数据计数器rx_cnt和波特率的计数器baud_cnt推出):当接收数据计数器rx_cnt到停止位的一半(rx_cnt为9即且波特率的计数器baud_cnt一半的时候),将rx_data_t的值赋给uart_rx_data,且uart_rx_done赋1,否则的话rx_data_t不变,uart_rx_done赋0。

module uart_rx(
    input                sys_clk     ,
    input                sys_rst_n   ,
    input                uart_rxd    ,   
    output  reg          uart_rx_done,
    output  reg [7:0]    uart_rx_data
);
parameter       CLK_FREQ = 50000000         ;
parameter       UART_BPS = 115200           ;
localparam      BAUD_CNT_MAX = CLK_FREQ/UART_BPS;

wire            start_en    ;
reg             uart_rxd_d0 ;
reg             uart_rxd_d1 ;
reg             uart_rxd_d2 ;
reg             rx_flag     ;
reg   [15:0]    baud_cnt    ;
reg   [3:0]     rx_cnt      ;
reg   [7:0]     rx_data_t   ;

// 1:start_en(由uart_rxd_d1,uart_rxd_d2,rx_flag波形推出)
assign start_en = (~rx_flag) & uart_rxd_d2 & (~uart_rxd_d1);

//  2:uart_rxd_d2(由uart_rxd_d0,uart_rxd_d1,uart_rxd_d2波形推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        uart_rxd_d0 <= 1'b0;
        uart_rxd_d1 <= 1'b0;
        uart_rxd_d2 <= 1'b0;
    end
    else begin
        uart_rxd_d0 <= uart_rxd     ;
        uart_rxd_d1 <= uart_rxd_d0  ;
        uart_rxd_d2 <= uart_rxd_d1  ;
    end
end

//3:接收标志rx_flag赋值(由start_en波形推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        rx_flag <= 1'b0;
    else if(start_en)
        rx_flag <= 1'b1;
    else if(baud_cnt == (BAUD_CNT_MAX/2 - 1'b1) && rx_cnt == 4'd9)
        rx_flag <= 1'b0;
    else 
        rx_flag <= rx_flag;
end

// 4:波特率的计数器baud_cnt赋值(由标志位rx_flag推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        baud_cnt <= 16'd0;
    else if(baud_cnt < (BAUD_CNT_MAX - 1'b1) && rx_flag)
            baud_cnt <= baud_cnt + 1'b1;
    else
            baud_cnt <= 16'd0;
end

// 5:对接收数据计数器(rx_cnt)进行赋值
//(由波特率的计数器baud_cnt和标志位rx_flag推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        rx_cnt <= 1'b0;
    else if(rx_flag) begin
        if(baud_cnt == (BAUD_CNT_MAX - 1'b1))
            rx_cnt <= rx_cnt + 1'b1;
        else
            rx_cnt <= rx_cnt;
        end
    else
        rx_cnt <= 1'b0;
end 

//6:根据rx_cnt来寄存rxd端口的数据rx_data_t
//(由接收数据计数器rx_cnt,波特率的计数器baud_cnt和标志位rx_flag推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        rx_data_t <= 8'd0;
    else if(rx_flag) begin
        if(baud_cnt == (BAUD_CNT_MAX/2 - 1'b1)) begin
            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:rx_data_t <= rx_data_t;
            endcase
        end
            else rx_data_t <= rx_data_t;
    end
    else    
        rx_data_t <= 8'd0;
end
 //7:给接收完成信号uart_rx_done和接收到的数据uart_rx_data赋值
 //(由接收数据计数器rx_cnt和波特率的计数器baud_cnt推出)
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(rx_cnt == 8'd9 && baud_cnt == (BAUD_CNT_MAX/2 - 1'b1)) begin 
        uart_rx_done <= 1'b1        ;
        uart_rx_data <= rx_data_t   ;
    end 
    else begin
        uart_rx_done <= 1'b0        ;
        uart_rx_data <= uart_rx_data;
    end
end
endmodule

2.3 uart_tx波形图

       注: 如图,根据工程经验,其停止位是在整个周期的15/16处进行停止较为合适,不易发生数据堵塞。

        代码分析:

        根据波形从上往下逐一信号有逻辑的书写和分析        

        1:当使能信号uart_tx_en为高时,寄存输入的并行数据tx_data_t,并拉高uart_tx_busy信号(由uart_tx_en,tx_cnt,baud_cnt推出):当uart_tx_en为1时,并行数据tx_data_t寄存来自uart_tx_data的数据且拉高uart_tx_busy信号为1。当计数到停止位结束时,停止发送过程,即清空发送数据寄存器,并拉低BUSY信号。其他情况下,信号保持不变。

        2:波特率的计数器baud_cnt赋值(由使能信号uart_tx_en和忙信号uart_tx_busy推出):uart_tx_busy为1,开始计数,uart_tx_busy为0,清零。计数到(BAUD_CNT_MAX-1)时,清零。

        3:发送数据计数器tx_cnt进行赋值(由波特率的计数器baud_cnt和使能信号uart_tx_en推出):使能信号uart_tx_en为1,开始计数,tx_cnt清零。uart_tx_busy为1,且baud_cnt到(BAUD_CNT_MAX-1)时,tx_cnt就+1。uart_tx_busy为0,tx_cnt清零。

        4:根据tx_cnt来给发送端口uart_txd赋值(由忙信号uart_tx_busy和发送数据计数器tx_cnt推出):当忙信号uart_tx_busy为1,根据协议对信号进行发送。当tx_cnt为0时,起始位uart_txd为0。当tx_cnt为1~8时,分别给uart_txd发送数据。当tx_cnt为1时,停止位uart_txd为1。其他时间段,全是停止位。

        注:uart_txd空闲的时候,其发送端口是高电平1。

module uart_tx(
    input             sys_clk     ,
    input             sys_rst_n   ,  
    input             uart_tx_en  ,
    input       [7:0] uart_tx_data,
    output reg        uart_txd    ,
    output reg        uart_tx_busy
    );
parameter       CLK_FREQ = 50000000         ;
parameter       UART_BPS = 115200           ;
localparam      BAUD_CNT_MAX = CLK_FREQ/UART_BPS;

reg  [7:0]  tx_data_t   ;
reg  [15:0] baud_cnt    ;
reg  [3:0]  tx_cnt      ;

//1:当使能信号uart_tx_en为高时,寄存输入的并行数据tx_data_t,并拉高uart_tx_busy信号
//(由uart_tx_en,tx_cnt,baud_cnt推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        tx_data_t <= 8'd0   ;
        uart_tx_busy <= 1'b0;
    end    
    else if(uart_tx_en) begin
        uart_tx_busy <= 1'b1        ;
        tx_data_t <= uart_tx_data   ;
    end
    else if(tx_cnt == 4'd9 && baud_cnt == (BAUD_CNT_MAX*15/16 - 1'b1)) begin
        uart_tx_busy <= 1'b0    ;
        tx_data_t <= 8'd0       ;
    end 
    else begin
        uart_tx_busy <= uart_tx_busy;
        tx_data_t <= tx_data_t      ;
    end  
end

//2:波特率的计数器baud_cnt赋值(由使能信号uart_tx_en和忙信号uart_tx_busy推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        baud_cnt <= 16'd0;
    else if(baud_cnt < (BAUD_CNT_MAX - 1'b1) && uart_tx_busy)
            baud_cnt <= baud_cnt + 1'b1;
    else
            baud_cnt <= 16'd0;
end

//3:发送数据计数器tx_cnt进行赋值(由波特率的计数器baud_cnt和使能信号uart_tx_en推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        tx_cnt <= 4'b0;  
    else if(uart_tx_busy) begin
        if(baud_cnt == (BAUD_CNT_MAX - 1'b1))
            tx_cnt <= tx_cnt + 1'b1;
        else
            tx_cnt <= tx_cnt;
        end
    else
        tx_cnt <= 4'b0;
end 

//4:根据tx_cnt来给发送端口uart_txd赋值(由忙信号uart_tx_busy和发送数据计数器tx_cnt推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_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] ;   
            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         ;
        endcase
    end
    else
        uart_txd    <= 1'b1;
end         
            
endmodule

3:DEBUG解决丢包数据

3.1 Vivado调试

        采样深度:1,000,000,000/115200*10*3/20=13020

        1s除以比特率为115200,要观察十位(协议上0~9),所以乘以10,观察三个字节(传_收_传),乘以3,晶振是50Mhz,所以再除以周期20ns,这就是应设置采样深度13020。

        这里uart_rx_done两次时间间隔实际为4322,使得在uart_tx_busy降为0之前就发生了uart_rx_done,导致上一个数据还没发完,就让发下一个数据了,只有当下个uart_rx_done的信号来临时,才会发送,所以完全错过了上一个uart_rx_done的信号。

        理论值:1,000,000,000/115200*10/20 = 4340,即1s除以比特率,乘以10个位,除以周期20ns,与实际值差了18个周期。

3.2 解决方法

        思路:无论怎么改变接收位的代码,这里他都会使得uart_rx_done的间隔时间变短,数据更加丢包。所以,这里必须改变发送位的代码,来调整uart_rx_done的间隔时间,具体解决方案就是当发送为接收到了uart_rx_done的代码,就立刻去发送下一段通信,剩下没发完的直接省略。

        代码:当接收到uart_tx_en的信号,就对立刻对baud_cnt和tx_cnt都清零,并且发送完整停止位,这样就可以重新计数,发送下一次发送的数据。

module uart_tx(
    input             sys_clk     ,
    input             sys_rst_n   ,  
    input             uart_tx_en  ,
    input       [7:0] uart_tx_data,
    output reg        uart_txd    ,
    output reg        uart_tx_busy
    );
parameter       CLK_FREQ = 50000000         ;
parameter       UART_BPS = 115200           ;
localparam      BAUD_CNT_MAX = CLK_FREQ/UART_BPS;

reg  [7:0]  tx_data_t   ;
reg  [15:0] baud_cnt    ;
reg  [3:0]  tx_cnt      ;

//1:当使能信号uart_tx_en为高时,寄存输入的并行数据tx_data_t,并拉高uart_tx_busy信号
//(由uart_tx_en,tx_cnt,baud_cnt推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        tx_data_t <= 8'd0   ;
        uart_tx_busy <= 1'b0;
    end    
    else if(uart_tx_en) begin
        uart_tx_busy <= 1'b1        ;
        tx_data_t <= uart_tx_data   ;
    end
    else if(tx_cnt == 4'd9 && baud_cnt == (BAUD_CNT_MAX - 1'b1)) begin
        uart_tx_busy <= 1'b0    ;
        tx_data_t <= 8'd0       ;
    end 
    else begin
        uart_tx_busy <= uart_tx_busy;
        tx_data_t <= tx_data_t      ;
    end  
end

//2:波特率的计数器baud_cnt赋值(由使能信号uart_tx_en和忙信号uart_tx_busy推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        baud_cnt <= 16'd0;
    else if(uart_tx_en)
        baud_cnt <= 16'd0;
    else if(baud_cnt < (BAUD_CNT_MAX - 1'b1) && uart_tx_busy)
            baud_cnt <= baud_cnt + 1'b1;
    else
            baud_cnt <= 16'd0;
end

//3:发送数据计数器tx_cnt进行赋值(由波特率的计数器baud_cnt和使能信号uart_tx_en推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        tx_cnt <= 4'b0;
    else if(uart_tx_en)  
        tx_cnt <= 16'd0;      
    else if(uart_tx_busy) begin
        if(baud_cnt == (BAUD_CNT_MAX - 1'b1))
            tx_cnt <= tx_cnt + 1'b1;
        else
            tx_cnt <= tx_cnt;
        end
    else
        tx_cnt <= 4'b0;
end 

//4:根据tx_cnt来给发送端口uart_txd赋值(由忙信号uart_tx_busy和发送数据计数器tx_cnt推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_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] ;   
            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         ;
        endcase
    end
    else
        uart_txd    <= 1'b1;
end         
            
endmodule

参考内容

;