FPGA-结合协议时序实现UART收发器(四):串口驱动模块uart_drive、例化uart_rx、uart_tx

FPGA-结合协议时序实现UART收发器(四):串口驱动模块uart_drive、例化uart_rx、uart_tx

串口驱动模块uart_drive、例化uart_rx、uart_tx,功能实现


文章目录

  • FPGA-结合协议时序实现UART收发器(四):串口驱动模块uart_drive、例化uart_rx、uart_tx
  • 一、功能实现
  • 二、uart_drive代码
  • 总结


一、功能实现

对照代码,串口发送模块uart_drive实现功能包括:

  • CLK_DIV时钟分频,将系统输入时钟分频成串口所需波特率时钟
  • rst_gen_module复位产生模块,分频后的波特率时钟复位不稳定,自己产生波特率时钟复位
  • r_rx_overvalue 过采样,动态时钟纠正方法,识别获取起始位位置
  • r_rx_overlock 过采样锁,控制过采样开关
  • r_user_rx_valid 用户接受有效,利用valid来判断valid上升沿从而判断数据接收完毕,用于控制过采样锁r_rx_overlock
  • r_uart_rx_clk_rst,处理纠正后的准确时钟复位r_uart_rx_clk_rst,利用准确的复位+系统时钟产生所需要的准确的串口接收时钟
  • 统一到同一时钟域中,即波特率时钟域,慢两拍
    -r_user_rx_data_1 <= w_user_rx_data;
    r_user_rx_data_2 <= r_user_rx_data_1;
    r_user_rx_valid_1 <= w_user_rx_valid;
    r_user_rx_valid_2 <= r_user_rx_valid_1;

二、uart_drive代码

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2023/09/09 13:14:22
// Design Name: 
// Module Name: uart_drive
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description:     串口数据的回环驱动模块 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module uart_drive#(
    //串口可调参数
    parameter    P_SYSTEM_CLK        = 50_000_000,    
    parameter    P_UART_BUADRATE    = 9600,
    parameter    P_UART_DATA_WIDTH  = 8,
    parameter    P_UART_STOP_WIDTH  = 1,
    parameter    P_UART_CHECK       = 0
)( 
    //串口驱动输入输出
    input                               i_clk            ,
    input                               i_rst            ,
    
    input                               i_uart_rx       ,
    output                              o_uart_tx       ,

    input   [P_UART_DATA_WIDTH - 1 : 0] i_user_tx_data  ,//用户输出数据,作为驱动的输入,即先输入到驱动处理再输出
    input                               i_user_tx_valid ,//握手
    output                              o_user_tx_ready ,

    output  [P_UART_DATA_WIDTH - 1 : 0] o_user_rx_data  ,//用户输入数据,作为驱动的输出,即先经过驱动输出再输入到用户
    output                              o_user_rx_valid ,

    output                              o_user_clk,
    output                              o_user_rst


   
);

localparam  P_CLK_DIV_NUMBER = P_SYSTEM_CLK / P_UART_BUADRATE;//分频数

wire                                    w_uart_buadclk                  ;//串口波特率时钟线
wire                                    w_uart_buadclk_rst              ;
wire                                    w_uart_rx_clk                   ;//串口接收时钟线
wire                                    w_uart_rx_rst                   ;  
reg                                     r_uart_rx_clk_rst               ;     
reg     [2:0]                           r_rx_overvalue                  ;       
reg     [2:0]                           r_rx_overvalue_1d               ;//延迟1拍信号     
reg                                     r_rx_overlock                   ;//过采样锁,控制过采样
reg                                     r_user_rx_valid                 ;//用户接收有效,用于判断可进行再次接收,即再次过采样判断起始位
wire    [P_UART_DATA_WIDTH - 1 : 0]     w_user_rx_data                  ;
wire                                    w_user_rx_valid                 ;

reg     [P_UART_DATA_WIDTH - 1 : 0]     r_user_rx_data_1                ;
reg     [P_UART_DATA_WIDTH - 1 : 0]     r_user_rx_data_2                ;
reg                                     r_user_rx_valid_1               ;
reg                                     r_user_rx_valid_2               ;


//统一到同一时钟域中,即波特率时钟域
assign  o_user_clk      = w_uart_buadclk;
assign  o_user_rst      = w_uart_buadclk_rst;
assign  o_user_rx_data  = r_user_rx_data_2;
assign  o_user_rx_valid = r_user_rx_valid_2;


//分频模块,将时钟频率分频成串口波特率时钟
CLK_DIV_module#(
    .P_CLK_DIV_CNT           (P_CLK_DIV_NUMBER)    //最大为65535
)
CLK_DIV_module_u0(
    .i_clk                   (i_clk)             ,//输入时钟
    .i_rst                   (i_rst)             ,//high value,分配时钟的复位不准确,所以需要重新生成复位rst_gen_module
    .o_clk_div               (w_uart_buadclk)     //分频后的时钟
);


//复位产生模块,因为分频时钟后复位不准确,所以自己来产生复位
rst_gen_module#(
    .P_RST_CYCLE    (10)     //复位的周期
)
rst_gen_module_u0
(
   .i_clk           (w_uart_buadclk)             ,
   .o_rst           (w_uart_buadclk_rst)

);

//分频模块,用于过采样计数判断起始位下降沿,用于串口接收使用
//产生能够准确获取中间数据的始终
CLK_DIV_module#(
    .P_CLK_DIV_CNT           (P_CLK_DIV_NUMBER)    //最大为65535
)
CLK_DIV_module_u1(
    .i_clk                   (i_clk)             ,//输入时钟
    .i_rst                   (r_uart_rx_clk_rst) ,//初始状态高电平
    .o_clk_div               (w_uart_rx_clk)     //分频后的时钟
);




uart_rx#(
    //串口可调参数
      .P_SYSTEM_CLK                     (P_SYSTEM_CLK      ),    
      .P_UART_BUADRATE                  (P_UART_BUADRATE   ),
      .P_UART_DATA_WIDTH                (P_UART_DATA_WIDTH ),
      .P_UART_STOP_WIDTH                (P_UART_STOP_WIDTH ),
      .P_UART_CHECK                     (P_UART_CHECK      )
)
uart_rx_u0( 
    //串口驱动输入输出
   .i_clk                               (w_uart_rx_clk)     ,
   .i_rst                               (w_uart_buadclk_rst)     ,
   
   .i_uart_rx                           (i_uart_rx)     ,   

   .o_user_rx_data                      (w_user_rx_data)     ,//用户输入数据,作为驱动的输出,即先经过驱动输出再输入到用户
   .o_user_rx_valid                     (w_user_rx_valid)

   
);

uart_tx#(
    //串口可调参数
    .P_SYSTEM_CLK                   (P_SYSTEM_CLK        ),
    .P_UART_BUADRATE                (P_UART_BUADRATE     ),
    .P_UART_DATA_WIDTH              (P_UART_DATA_WIDTH   ),
    .P_UART_STOP_WIDTH              (P_UART_STOP_WIDTH   ),
    .P_UART_CHECK                   (P_UART_CHECK        )
)
uart_tx_u0( 
    //串口驱动输入输出
   .i_clk                           (w_uart_buadclk)             ,
   .i_rst                           (w_uart_buadclk_rst)         ,
   .o_uart_tx                       (o_uart_tx)                  ,

   .i_user_tx_data                  (i_user_tx_data)             ,//用户输出数据,作为驱动的输入,即先输入到驱动处理再输出
   .i_user_tx_valid                 (i_user_tx_valid)             ,//握手
   .o_user_tx_ready                 (o_user_tx_ready)

);





/*
串口接收会存在亚稳态状态,虽然可以使用打两拍的方法解决,但不够稳妥,一样出现较大累计误差
故采用时钟来动态纠正,使用过采样方法来检测起始位的下降沿,产生新的分频后的时钟,使用该时钟可以保证每次读取数据时都在数据上升沿的中间位置
虽然使用时钟动态纠正方法一样会存在累计误差,但因为每次串口传输8bit数据,并且每次都进行了时钟纠正,所以足以稳定满足使用效果
*/

//处理过采样r_rx_overvalue,不断获取传输值
always @(posedge i_clk or posedge i_rst) 
begin
    if(i_rst)   //初始状态
        r_rx_overvalue <= 'd0;
    else if(!r_rx_overlock)   //变化状态
        r_rx_overvalue <= {r_rx_overvalue[1:0] , i_uart_rx};//低位获取i_uart_rx再左移,此处先发低位还是先发高位影响不大,主要是进行高低电平判断起始位用
    else        //保持状态
        r_rx_overvalue <= 3'b111;
end


//处理过采样锁r_rx_overlock,控制过采样,因为只需要过采样起始位的位置
//通过读取三位数据来判断当前是否为起始位的位置,当前3拍全为低电平0,前3拍全为高电平1,则判断为起始位下降沿位置
always @(posedge i_clk or posedge i_rst)
begin
    if(i_rst)
        r_rx_overlock <= 'd0;
    else if(!w_user_rx_valid && r_user_rx_valid)   //本次传输完成,可以再次进行过采样了,使用vaild判断,同样采用慢一拍来判断上升沿,当前信号r_user_rx_valid为1,前一拍信号w_user_rx_valid为0,即上升沿,表示传输完成
        r_rx_overlock <= 'd0;
    else if(r_rx_overvalue == 3'b000 && r_rx_overvalue_1d == 3'b111)   //识别到起始位的位置,此时不需要再次过采样了
        r_rx_overlock <= 'd1;
    else
        r_rx_overlock <= r_rx_overlock;
end

//处理用户接收有效信号r_user_rx_valid,用于表示本次接收数据完毕,可再次进行接收数据即再次过采样来判断起始位位置
//同样也是采用慢一拍的信号来结合判断vaild的上升沿
always @(posedge i_clk or posedge i_rst)
begin
    if(i_rst)
        r_user_rx_valid <= 'd0;
    else
        r_user_rx_valid <= w_user_rx_valid; //r_user_rx_valid比w_user_rx_valid慢一拍,即同一时刻,w_user_rx_valid为当前值,r_user_rx_valid为前一拍值
end


//处理纠正后的准确时钟复位r_uart_rx_clk_rst,利用准确的复位+系统时钟产生所需要的准确的串口接收时钟
//利用准确的时钟复位作为分频模块的复位输入,输出准确的串口接收时钟,即每次都在数据位中间上升沿
always @(posedge i_clk or posedge i_rst)
begin
    if(i_rst)
        r_uart_rx_clk_rst <= 'd1;
    else if(r_rx_overvalue == 3'b000 && r_rx_overvalue_1d == 3'b111)//起始位时
        r_uart_rx_clk_rst <= 'd0;
    else
        r_uart_rx_clk_rst <= r_uart_rx_clk_rst;
end


/*
//uart的rx的output与tx的输入不是同一时钟域的,需要统一到同一时钟域中
//即o_user_rx_data、o_user_rx_vaild统一到w_uart_buadclk波特率时钟域中
//因为他们的时钟频率比较相近,要求不严格,所以打两拍就算统一到波特率时钟域了!!!!!!!我目前也不太理解!!!
*/
//处理统一始终域问题,打两拍
always @(posedge w_uart_buadclk or posedge w_uart_buadclk_rst)
begin
    if(w_uart_buadclk_rst)
    begin
        r_user_rx_data_1  <= 'd0;
        r_user_rx_data_2  <= 'd0;
        r_user_rx_valid_1 <= 'd0;
        r_user_rx_valid_2 <= 'd0;
    end
    else
    begin
        r_user_rx_data_1  <= w_user_rx_data;
        r_user_rx_data_2  <= r_user_rx_data_1;
        r_user_rx_valid_1 <= w_user_rx_valid;
        r_user_rx_valid_2 <= r_user_rx_valid_1;
    end
end





endmodule


总结

串口驱动模块uart_drive实现,功能包含实现,同时例化uart_rx、uart_tx

你可能感兴趣的:(FPGA开发,fpga开发,嵌入式硬件)