基于verilog的UART串行总线协议模块设计(含原理、源码、AXI封装、C驱动文件)

本文介绍UART通信原理及特点,包括异步通信、串行数据传输和全双工操作。详细解析UART数据帧格式、波特率计算及其对传输速率的影响。提供UART发送与接收模块的Verilog实现,以及FIFO缓冲器和跨时钟域处理方案。最后,展示AXI接口封装方法,便于与CPU交互。


本文涉及的所有代码仅用于学习交流,不得用于其他用途

一、UART简介

  UART即通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),两个UART设备在进行通信时,发送数据的设备将并行的数据转化为一位一位的串行数据,并按照给定的时钟频率将串行数据依次打到TX通信线上,接收方检测RX上的电平,按照给定的时钟频率读取RX线上电平,并将采样到的串行数据还原成并行数据;
在这里插入图片描述

二、UART通信的特点

  ·异步:设备之间没有统一的时钟线连接
  ·串行:数据以固定的速率一位一位进行发送
  ·全双工:UART设备能够同时进行数据接收和发送
  ·逻辑电平:RS232(3V~25V为高电平 -3V或-25V为低电平)
   TTL(5V或3.3V为高电平 0V为低电平)
  首先UART是一个异步通信,即通信设备之间是不需要时钟线的,这不同于IIC与SPI等串行通信,UART设备通信双方均在自己的时钟系统下工作;
  全双工也很好理解,这里发送线与接收线是独立的分开的,且在每个UART设备的UART模块中,都会设计串口接收模块与发送模块,两者可以同时工作;
  UART通信使用的逻辑电平有很多,在单片机系统中长用TTL电平进行UART传输,除此之外还有RS232,RS485,但这些都不是一种协议,而是一种电平逻辑规范,我们从高电平的识别上来看,TTL允许电压波动的范围较小,因此它也是非常易受到干扰的;相比之下RS232的电压范围相对宽些;RS485使用的是差分传输,因此抗干扰能力更强;
综上,三种逻辑电平规范在传输距离上:TTL<RS232<RS485
  (本次设计将基于TTL电平)


三、UART传输速率

  波特率:描述UART设备传输数据快慢的单位,以每秒位数(bps)表示。
  *UART对波特率的要求比较严格,因为其是异步通信,要求通信双方的波特率相差不能超过10%。
  UART常用波特率:9600、115200、192000、230400等
  假如选择的波特率为9600b/s,则其1s内能够传输9600bit,即9600/8 = 1200字节每秒;
波特率是否越大越好?
  在平时的设备通信中,往往会觉得串口传输速率较慢,因此会把波特率提高来加快传输速率,但波特率也不是越大越好,而是越适合越好;
  举个简单的例子,在单片机中UART模块后面往往会跟一个FIFO来做数据缓冲,因为单片机读取FIFO的速度有限,如果读FIFO速率小于UART向FIFO的写速率,且FIFO深度有限的情况下,则很容易造成FIFO满,满状态的FIFO不允许数据写入,此时就会造成丢包;
  此时有以下几种方法解决,1.增加FIFO深度(参考FIFO文章)2.降低波特率使得读速率大于写速率。3.如果是突发传输,则要根据UART的接收频率、单片机的读出速率来合理计算FIFO深度,使得数据不会溢出(参考FIFO文章);


四、UART数据帧格式

空闲:UART数据线保持高电平。
起始位:传输线TXD由高电平转换为低电平。(必须有)
数据位:5-8位长度数据位,通常从最低位开始发送。(通常为8位)(必须有)
奇偶校验位:判断数据传输过程中是否有数据位发生错误变化。(可以无)
停止位:在数据包发送结束后,发送数据线保持1~2位高电平。(必须有)

在这里插入图片描述
  UART在空闲时,会保持TX与RX线为高电平;
  当一个数据帧进行发送时,首先发送端(TX)会将高电平拉低(发送一个0bit)示意通信的开始;另一接收端在检测到TX线被拉低后,则开始接收数据;
  接下来发送端会将8位数据按照从低位到高位的顺序依次将其打到TX线上,接收方则按照对应波特率读取RX线电平;
  如果通信双方设置了需要校验,则在8位数据发送结束后,会附加1bit的奇偶校验位;(数据量少的通信中不需要)
  在通信需要结束时,发送端会将TX线拉高,接收方检测到这个高电平则结束这次通信;(停止位可以是1,1.5,2个,一般来说是1个)
例:通过UART发送数据0x33(不带校验位,且1个停止位)
在这里插入图片描述对第三节中的字节传输速率做出修正:
  如果是不带校验位,且只有一个停止位,则传输一个字节数据需要10bit,据此得到传输字节的真正速率:
  9600/10 = 960字节每秒


五、UART模块设计

  设计UART模块实际上是设计UART 发送模块与UART 接收模块;
  在设计UART模块过程中需重点关注:
    波特率如何产生?
    串并转换、并串转换如何进行?
    起始位、停止位如何检测?
    rx信号如何采样?
  波特率的产生:
  波特率实际上就是一个分频器,其分频系数为fsys_clk/Baunds;
  fsys_clk为UART模块所接的时钟源,Baunds则是所要得到的波特率;
例: fsys_clk = 50_000_000 Hz, Baunds = 115200,则分频系数Div0 = fsys_clk/Baunds = 50_000_000 / 115200 = 434
串并转换、并串转换:
  并串转换应用于发送模块,其本质是一个移位寄存器,驱动时钟为波特率分频器输出的时钟,输入信号为并行8位数据,输出为串行数据;首先对并行的8位数据进行重建,得到数据帧;
  如0x33则重建后的十位数据为:{1‘b1,0x33,1’b0},最低位的1’b0为起始位,最高位的1’b1为停止位;
  串并转换应用于接收模块,其本质上也是一个移位寄存器,只不过其输出为8位并行数据,输入为串行数据;
起始位、停止位检测:
  起始位的检测可以通过检测高电平到低电平的下降沿来实现;
  停止位可以在接收满8位数据后,在第9位数据上进行检测;
在这里插入图片描述rx信号采样:
  接收信号首先需要进行同步处理,因为rx信号来自另一个时钟域;
  其次同步后的信号需要采样其中间位置,因为此时的电平是最稳定的;
在这里插入图片描述


5.1 串行发送模块

综上,发送模块功能可划分为:
在这里插入图片描述

并串转换器在检测到有效的Valid信号后将tx_data包装成一帧数据{1‘b1,tx_data,1’b0}
同时发送状态机检测到valid信号后,进入到发送状态,此时波特率分频器开始计数,控制比特计数器增,并串转换模块则将对应比特位的数据串行发送出去。

源码:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: Goodwayhouse
// Engineer: Ge Wen Jie
// 
// Create Date: 2022/09/11 11:23:52
// Design Name: 
// Module Name: uart_dtx
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////
module uart_dtx#(
        parameter integer   UART_TX_REF_CLK_FRE = 50_000_000,
        parameter integer   UART_TX_BAUNDS_RATE = 115200

    )(
        input  wire               uart_ref_clk,//uart tx module reference clock
        input  wire               uart_tx_nrst,//uart tx module reset signal
        input  wire  [31:0]    uart_baunds_div,//baunds rate division
        input  wire  [7:0]        uart_tx_data,//uart tx 8bits data input    
        input  wire          uart_tx_data_qvld,//send data valid signal
        output wire             uart_tx_finish,//uart send data finish flag
        output wire               uart_tx_busy,//uart tx module busy flag
        output wire               uart_tx_dout //serial data output
    );

    //uart tx module baunds rater counter max, start data out, end data out definitions
    localparam    integer        UART_BAUNDS_COUNTER_MAX = UART_TX_REF_CLK_FRE/UART_TX_BAUNDS_RATE;
    localparam                   UART_TX_START_DOUT =  1'b0;
    localparam                   UART_TX_END_DOUT = 1'b1;

    //uart tx send state definition
    localparam    UART_IDLE0  = 3'b000;
    // localparam    UART_IDLE1  = 3'b001;
    localparam    UART_TXDATE = 3'b010;
    // localparam    UART_TXFINISH = 3'b100;

    //inner signals definitions
    reg                                          tx_out;
    reg                                          tx_finish;
    reg                                          tx_busy;
    reg [3:0]                                    tx_bit_cnt;
    reg [31:0]                                   tx_baunds_cnt;
    reg [2:0]                                    uart_tx_state;
    reg [2:0]                                    uart_tx_next_state;
    reg [9:0]                                    uart_tx_buffer;
    reg                                          uart_wr_en;
    //inner signal connect
    assign uart_tx_finish = tx_finish;
    assign uart_tx_busy = tx_busy;
    assign uart_tx_dout = tx_out;

    always@(posedge uart_ref_clk)
    begin
        if(~uart_tx_nrst)
        begin
            uart_tx_state <= UART_IDLE0;
        end
        else begin
            uart_tx_state <= uart_tx_next_state;
        end
    end

    always@(*)
    begin
        case(uart_tx_state)
            UART_IDLE0://waiting a valid data input
            begin 
                if(uart_tx_data_qvld) 
                begin
                    uart_tx_next_state <= UART_TXDATE;
                end
                else
                begin
                    uart_tx_next_state <= UART_IDLE0;
                end
            end
            UART_TXDATE://waiting tx module send data finish
            begin
                if((tx_finish == 1'b1) && (tx_busy == 1'b0))
                begin
                    uart_tx_next_state <= UART_IDLE0;
                end
                else begin
                    uart_tx_next_state <= UART_TXDATE;
                end
            end
            default:uart_tx_next_state <= UART_IDLE0;
        endcase
    end

    always@(posedge uart_ref_clk)
    begin
        if(~uart_tx_nrst)
        begin
            tx_busy <= 'b0;
            tx_finish <= 'b1;
            tx_out <= 1'b1;
        end
        else begin
            if((tx_finish == 1'b1)&&(tx_busy == 1'b0)&&(uart_tx_data_qvld))//sample date when tx finish and tx is not busy
            begin
                uart_tx_buffer <= {
   
   UART_TX_END_DOUT,uart_tx_data,UART_TX_START_DOUT};
                tx_finish <= 1'b0;
                tx_busy <= 1'b1;
                tx_out <= 1'b1;
            end
            else if((tx_finish == 1'b0)&&(tx_busy == 1'b1)&&(uart_tx_state == UART_TXDATE))
            begin
                if(uart_wr_en==1'b1)
                begin
                    if(tx_bit_cnt != 4'd10)begin tx_out <= uart_tx_buffer[0];uart_tx_buffer <= uart_tx_buffer >> 1;  end
                    else begin tx_out <= UART_TX_END_DOUT;tx_finish <= 1'b1;tx_busy <= 1'b0; end
                end
                else begin tx_busy <= tx_busy;tx_finish <= tx_finish;tx_out <= tx_out; end
            end
        end
    end

    always@(posedge uart_ref_clk)
    begin
        if(~uart_tx_nrst)
        begin
           tx_baunds_cnt <= 'b0;
           tx_bit_cnt <= 'b0;
           uart_wr_en <= 1'b0;
        end
        else begin
            if(uart_tx_state == UART_IDLE0)
            begin
                 tx_baunds_cnt <= 'b0;
                 tx_bit_cnt <= 'b0;
                 uart_wr_en <= 1'b0;
            end
            else if(uart_tx_state == UART_TXDATE)
            begin
                if(tx_baunds_cnt != uart_baunds_div - 1)
                begin
                        uart_wr_en <= 1'b0;
                        tx_bit_cnt <= tx_bit_cnt;
                        tx_baunds_cnt <= tx_baunds_cnt + 1;
                end
                else begin
                    uart_wr_en <= 1'b1;
                    tx_baunds_cnt <= 'b0;
                    tx_bit_cnt <= tx_bit_cnt + 1; 
                end
            end
        end
    end
endmodule

5.2 串行接收模块

串行模块的功能可划分为下图所示;
在这里插入图片描述
  首先边沿检测电路对rx进行检测,如果检测到下降沿,则开始一次Uart接收,接收状态机检测到边沿检测电路发出的有效信号,则进入接收状态;
  在接收状态下,波特率分频器开始工作,这里需要注意,为了能够在rx每比特的中间时刻采用,在第0bit时进行一次f(clk) / Baunds Rate / 2分频,并忽略第一次接收的数据;
比特计数器在检测到接收满8bit后,控制并串模块进行一次高电平停止位的检测,若成功则将并行数据输出,接收状态机回到空闲状态;
  源码:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: Goodwayhouse
// Engineer: Ge Wen Jie
// 
// Create Date: 2022/09/15 16:52:19
// Design Name: 
// Module Name: uart_drx
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////
module uart_drx#(
        parameter integer   UART_RX_REF_CLK_FRE = 50_000_000,
        parameter integer   UART_RX_BAUNDS_RATE = 115200

    )(
        input  wire               uart_ref_clk,//uart rx module reference clock
        input  wire               uart_rx_nrst,//uart rx module reset signal
        input  wire  [31:0]    uart_baunds_div,//baunds rate division
        input  wire                uart_rx_din, //serial data input          
        output wire          uart_rx_data_qvld,//receive data valid signal
        output wire             uart_rx_finish,//uart send data finish flag
        output wire               uart_rx_busy,//uart rx module busy flag
        output wire  [7:0]        uart_rx_data //uart rx 8bits data input  
    );

    localparam    IDLE0 = 2'b00;
    localparam    UART_RECV = 2'b01;

    localparam    integer        UART_BAUNDS_COUNTER_MAX = UART_RX_REF_CLK_FRE/UART_RX_BAUNDS_RATE;
    localparam                   UART_TX_START_DOUT =  1'b0;
    localparam                   UART_TX_END_DOUT = 1'b1;

    //inner signals definitions
    reg             rx_finish;
    reg             rx_busy;
    reg  [7:0]      rx_data;
    reg             rx_data_qvld;
    reg  [7:0]      rx_data_buffer;//receive data buffer
    reg  [3:0]      bit_cnt;//receive bit counter
    reg  [1:0]      uart_recv_state;
    reg  [1:0]      uart_recv_next_state;
    reg [31:0]      rx_baunds_cnt;
    reg             uart_rd_en;

    //inner signals connect
    assign uart_rx_busy = rx_busy;
    assign uart_rx_data = rx_data;
    assign uart_rx_finish = rx_finish;
    assign  uart_rx_data_qvld = rx_data_qvld;

    //uart send start check
    wire  uart_send_start;
    reg uart_rx_din_dff;
    assign uart_send_start = uart_rx_din_dff & ~uart_rx_din;
    always @(posedge uart_ref_clk) begin
        if (~uart_rx_nrst) begin
            // reset
            uart_rx_din_dff <= 1'b0;
        end
        else
        begin
            uart_rx_din_dff <= uart_rx_din;
        end
    end

    always@(posedge uart_ref_clk)
    begin
        if (~uart_rx_nrst) begin
            // reset
            uart_recv_state <= IDLE0;
        end
        else
        begin
                uart_recv_state <= uart_recv_next_state;
        end
    end

    always@(*)
    begin
        case(uart_recv_state)
            IDLE0:begin//check if UART send start
                if(uart_send_start == 1'b1) begin
                    uart_recv_next_state <= UART_RECV;
                end
                else begin
                    uart_recv_next_state <= IDLE0;
                end
            end
            UART_RECV:begin //receiver data from master
                if((rx_busy == 1'b0) && (rx_finish == 1'b1))begin
                    uart_recv_next_state <= IDLE0;
                end
                else begin
                    uart_recv_next_state <= UART_RECV;
                end
            end
            default:uart_recv_next_state <= IDLE0;
        endcase
    end

    always@(posedge uart_ref_clk)
    begin
        if(~uart_rx_nrst)begin
            rx_data <= 'b0;
            rx_data_buffer <= 'b0;
            rx_busy <= 1'b0;
            rx_finish <= 1'b1;
            rx_data_qvld <= 1'b0;
        end
        else begin
            if((rx_busy == 1'b0) && (rx_finish == 1'b1))
            begin
                rx_data_qvld <= 1'b0;
                if(uart_send_start == 1'b1)
                begin
                    rx_data_buffer <= 'b0;rx_busy <= 1'b1; rx_finish <= 1'b0;
                end
            end
            else if((rx_busy == 1'b1) && (rx_finish == 1'b0) && (uart_recv_state == UART_RECV) && (uart_rd_en == 1'b1))
            begin
                if((bit_cnt != 4'd0)&&(bit_cnt != 4'd10))
                begin
                    rx_data_buffer <= {
   
   uart_rx_din,rx_data_buffer[7:1]};
                end
                // rx_data_buffer <= {uart_rx_din,rx_data_buffer[7:1]};
                else if ((bit_cnt == 4'd10)&&(uart_rx_din == 1'b1)) begin
                    rx_busy <= 1'b0;
                    rx_finish <= 1'b1;
                    rx_data_qvld <= 1'b1;
                    rx_data <= rx_data_buffer;
                    rx_data_buffer <= 'b0;
                end
            end
        end
    end

    always@(posedge uart_ref_clk)
    begin
        if(~uart_rx_nrst)
        begin
           rx_baunds_cnt <= 'b0;
           bit_cnt <= 'b0;
           uart_rd_en <= 1'b0;
        end
        else begin
            if(uart_recv_state 
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PPRAM

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值