协议篇之UART协议

一、写在前面

  由于设计需要,需要入门学习一下UART协议。本文主要学习UART协议的数据帧结构,并根据其数据接收和数据发送的原理进行RTL级代码设计。

二、UART协议简介

  通用异步收发传输协议(Universal Asynchronous Receiver/Transmitter Protocol,UART)是一种在计算机、嵌入式系统和其他电子设备之间传输数据的通信协议。它使用串行全双工通信来传输数据,发送和接收设备之间的数据传输是异步的,也就是说,每个比特都是按照预定义的时间间隔传输的,而不是在固定的时钟周期内进行传输。
  UART协议通常使用RS-232或RS-485等物理层协议来连接发送和接收设备。由于UART协议是一种简单、易于实现的协议,因此它在很多应用中得到广泛应用,例如串口通信、控制台、模块通信等。在工业、汽车、医疗和家庭等领域,UART协议都有着广泛的应用。

三、UART协议数据帧结构

  UART协议中传输数据是串行传输的,其数据帧结构通常包含以下几个部分:

  • 起始位(Start Bit):一个低电平的比特,用于标识数据传输的开始;
  • 数据位(Data Bits):包含要传输的数据的比特数,通常是5、6、7或8个比特。数据可以是数字、字母、符号或二进制数据;
  • 奇偶校验位(Parity Bit):可选的比特,用于验证数据的正确性。奇偶校验位的值可以是奇校验或偶校验,它们根据数据位中1的个数确定;
  • 停止位(Stop Bit):一个时钟高电平的比特,用于标识数据传输的结束;

  值得注意的是,UART数据帧结构的长度和内容可以根据需要进行调整,但它们必须在发送和接收设备之间协商一致,以确保数据的正确传输。

  UART完整的数据帧结构如下图所示(这里不包含校验位)。
在这里插入图片描述

3.1 UART发送过程

  在UART数据发送的过程中,发送设备会先发送一个起始位START,然后发送数据位DATA(数据位的发送是先发送低位,后发送高位),然后发送可选的奇偶校验位PARITY(一般不设置奇偶校验位),最后发送一个停止位STOP。

3.2 UART接收过程

  UART数据接收的过程,与UART数据发送的过程是差不多的,接收设备会一直等待发送设备的一个低电平的起始位。当接收到起始位,接收设备将开始接收发送设备发送的数据位,并将多比特的数据存储在缓冲区中。然后如果使用了奇偶校验位,接收设备需要使用接收到的奇偶校验位来校验数据位,验证接收到的数据是否正确。然后等待停止位的到来,结束一帧数据的接收,并将接收到的数据从缓冲区中读出,等待下一帧数据的到来。

3.3 UART传输速率

  UART协议的数据传输速率有多种,其中我们常用的波特率有4种:9600bps、19200bps、38400bps和115200bps。

  • 9600bps:UART通信中最常见的传输速率之一,适用于大多数低速数据传输应用;
  • 19200bps:中等速度的传输速率,适用于一些需要快速传输数据的应用;
  • 38400bps:较快的传输速率,适用于需要更快数据传输速度的应用;
  • 115200bps:UART通信中更快的传输速率,适用于需要非常快速数据传输速度的应用;

  同样的,UART的传输速率必须保证发送设备与接收设备之间一致,否则发送设备无法传输正确的数据到接收设备。
  波特率表征串口传输的速率,比如串口波特率为9600bps,那么表示1秒可以传输9600bit,那么,每个比特传输的时间为:

1000000000 n s / 9600 = 104166 n s 1000000000ns/9600=104166ns 1000000000ns/9600=104166ns

  如果我们给系统约束的系统时钟频率为50MHz,则系统时钟周期为20ns,也就是说,每个比特传输的时钟周期个数为:

104166/20=5208

  也就是说,在系统时钟频率为50MHz的情况下,传输每个比特需要保持5208个时钟周期不变。

  在这里,需要补充一个重要概念:波特率Baud与比特率Bit Rate是两个不同的概念。波特率与比特率的关系也可换算成:

                          比特率 = 波特率 * 单个调制状态对应的二进制位数

  而在串口传输中,由于采用二进制传输,数值非0即1,所以在串口传输中,波特率与比特率的关系如下:

                                      比特率 = 波特率

四、UART收发模块设计

4.1 UART接收模块设计

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2023/04/29 16:17:06
// Design Name: 
// Module Name: uart_rx
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//

module uart_rx
#(
    parameter BAUD_RATE   = 9600       , //波特率
    parameter SYS_CLK_FRE = 100_000_000  //系统时钟频率
)
(
    input  sys_clk  , //系统时钟
    input  sys_rst_n, //复位信号
    input  uart_rx_i, //串行接收数据

    output [7:0] uart_rx_data_o, //接收到的8bit信号
    output       rx_end //接收结束
);

/**************************参数**************************/
localparam CLK_DIV = SYS_CLK_FRE/BAUD_RATE; //单比特数据传输耗费时钟数量
localparam IDLE    = 11'b0000_0000_001; //初始化
localparam START   = 11'b0000_0000_010; //起始位
localparam BIT_0   = 11'd0000_0000_100; //第0比特
localparam BIT_1   = 11'b0000_0001_000; //第1比特
localparam BIT_2   = 11'b0000_0010_000; //第2比特
localparam BIT_3   = 11'b0000_0100_000; //第3比特
localparam BIT_4   = 11'b0000_1000_000; //第4比特
localparam BIT_5   = 11'b0001_0000_000; //第5比特
localparam BIT_6   = 11'b0010_0000_000; //第6比特
localparam BIT_7   = 11'b0100_0000_000; //第7比特
localparam STOP    = 11'b1000_0000_000; //停止位

/**************************寄存器**************************/
reg                       r_uart_rx_i_dly1; //串行数据接收打一拍
reg                       r_uart_rx_i_dly2; //串行数据接收打两拍(消除亚稳态)
reg [$clog2(CLK_DIV)-1:0] clk_div_cnt; //时钟计数器
reg [7:0]                 rx_data    ; //数据寄存
reg [7:0]                 r_uart_rx_data_o; //接收到的8bit数据
reg                       rx_en    ; //接收使能(高电平期间,处于接收工作状态)
reg                       r_rx_end; //接收结束

reg [10:0] cuur_state;
reg [10:0] next_state;

/**************************网表型**************************/
wire negedge_check; //下降沿检测信号

/**************************组合逻辑**************************/
assign negedge_check = (~r_uart_rx_i_dly1) && r_uart_rx_i_dly2;
assign uart_rx_data_o = r_uart_rx_data_o;
assign rx_end = r_rx_end;

/**************************时序逻辑**************************/
always @(posedge sys_clk or negedge sys_rst_n)
    if(!sys_rst_n) begin
        r_uart_rx_i_dly1 <= 1'b1;
        r_uart_rx_i_dly2 <= 1'b1;
    end
    else begin
        r_uart_rx_i_dly1 <= uart_rx_i;
        r_uart_rx_i_dly2 <= r_uart_rx_i_dly1;
    end

always @(posedge sys_clk or negedge sys_rst_n)
    if(!sys_rst_n)
        rx_en <= 1'b0;
    else if(negedge_check)
        rx_en <= 1'b1;
    else if(rx_end)
        rx_en <= 1'b0;
    else
        rx_en <= rx_en;

always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        clk_div_cnt <= 'd0;
    else if(rx_en)
        if(clk_div_cnt < CLK_DIV)
            clk_div_cnt <= clk_div_cnt + 1'd1;
        else
            clk_div_cnt <= 'd0;
    else
        clk_div_cnt <= 'd0;
end

/**************************状态机**************************/
always @(posedge sys_clk or negedge sys_rst_n)
    if(!sys_rst_n) 
        cuur_state <= IDLE;
    else
        cuur_state <= next_state;

always @(*) begin
    case(cuur_state)
        IDLE : next_state = (negedge_check) ? START : IDLE;
        START: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_0 : START;
        BIT_0: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_1 : BIT_0;
        BIT_1: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_2 : BIT_1;
        BIT_2: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_3 : BIT_2;
        BIT_3: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_4 : BIT_3;
        BIT_4: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_5 : BIT_4;
        BIT_5: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_6 : BIT_5;
        BIT_6: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_7 : BIT_6;
        BIT_7: next_state = (clk_div_cnt == CLK_DIV-1) ? STOP  : BIT_7;
        STOP : next_state = (clk_div_cnt == CLK_DIV>>1) ? IDLE  : STOP;
        default: next_state = IDLE;
    endcase
end

always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        rx_data <= 8'd0;
    end
    else begin
        case(cuur_state)
            BIT_0: rx_data[0] <= (clk_div_cnt == CLK_DIV>>1) ? uart_rx_i : rx_data[0];
            BIT_1: rx_data[1] <= (clk_div_cnt == CLK_DIV>>1) ? uart_rx_i : rx_data[1];
            BIT_2: rx_data[2] <= (clk_div_cnt == CLK_DIV>>1) ? uart_rx_i : rx_data[2];
            BIT_3: rx_data[3] <= (clk_div_cnt == CLK_DIV>>1) ? uart_rx_i : rx_data[3];
            BIT_4: rx_data[4] <= (clk_div_cnt == CLK_DIV>>1) ? uart_rx_i : rx_data[4];
            BIT_5: rx_data[5] <= (clk_div_cnt == CLK_DIV>>1) ? uart_rx_i : rx_data[5];
            BIT_6: rx_data[6] <= (clk_div_cnt == CLK_DIV>>1) ? uart_rx_i : rx_data[6];
            BIT_7: rx_data[7] <= (clk_div_cnt == CLK_DIV>>1) ? uart_rx_i : rx_data[7];
            default: rx_data <= rx_data;
        endcase
    end
end

always @(negedge sys_clk or negedge sys_rst_n) 
    if(!sys_rst_n) begin
        r_uart_rx_data_o <= 'd0;
        r_rx_end <= 1'b0;
    end
    else if(cuur_state == STOP && clk_div_cnt == CLK_DIV>>1) begin
        r_uart_rx_data_o <= rx_data;
        r_rx_end <= 1'b1;
    end
    else begin
        r_uart_rx_data_o <= r_uart_rx_data_o;
        r_rx_end <= 1'b0;
    end

endmodule

4.2 UART发送模块设计

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2023/04/28 11:03:30
// Design Name: 
// Module Name: uart_tx
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//

module uart_tx
#(
    parameter BAUD_RATE   = 9600       , //波特率
    parameter SYS_CLK_FRE = 100_000_000  //系统时钟频率
)
(
    input       sys_clk       , //系统时钟
    input       sys_rst_n     , //复位信号
    input       tx_start      , //发送开始信号
    input [7:0] uart_tx_data_i, //需要发送的8bit数据

    output      uart_tx_o     , //串行发送数据
    output      tx_end          //发送结束
    );

/**************************参数**************************/
localparam CLK_DIV = SYS_CLK_FRE/BAUD_RATE; //单比特数据传输耗费时钟数量
localparam IDLE    = 11'b0000_0000_001; //初始化
localparam START   = 11'b0000_0000_010; //起始位
localparam BIT_0   = 11'd0000_0000_100; //第0比特
localparam BIT_1   = 11'b0000_0001_000; //第1比特
localparam BIT_2   = 11'b0000_0010_000; //第2比特
localparam BIT_3   = 11'b0000_0100_000; //第3比特
localparam BIT_4   = 11'b0000_1000_000; //第4比特
localparam BIT_5   = 11'b0001_0000_000; //第5比特
localparam BIT_6   = 11'b0010_0000_000; //第6比特
localparam BIT_7   = 11'b0100_0000_000; //第7比特
localparam STOP    = 11'b1000_0000_000; //停止位

/**************************寄存器**************************/
reg [$clog2(CLK_DIV)-1:0] clk_div_cnt     ; //时钟计数器
reg [7:0]                 r_uart_tx_data_i; //发送数据寄存
reg                       tx_en      ; //发送使能(该信号为高电平期间,进行数据的发送)
reg                       r_uart_tx_o; //串行发送数据
reg                       r_tx_end   ; //发送结束

reg [10:0] cuur_state; //现态
reg [10:0] next_state; //次态

/**************************网表型**************************/
assign uart_tx_o = r_uart_tx_o;
assign tx_end = r_tx_end;

/**************************组合逻辑**************************/
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        clk_div_cnt <= 'd0;
    else if(tx_en && clk_div_cnt < CLK_DIV)
        clk_div_cnt <= clk_div_cnt + 1'd1;
    else
        clk_div_cnt <= 'd0;
end

always @(posedge sys_clk or negedge sys_rst_n)
    if(!sys_rst_n)
        r_uart_tx_data_i <= 'd0;
    else if(tx_start)
        r_uart_tx_data_i <= uart_tx_data_i;
    else
        r_uart_tx_data_i <= r_uart_tx_data_i;

/**************************状态机**************************/
always @(posedge sys_clk or negedge sys_rst_n)
    if(!sys_rst_n) 
        cuur_state <= IDLE;
    else
        cuur_state <= next_state;

always @(*) begin
    case(cuur_state)
        IDLE : next_state = (tx_start) ? START : IDLE;
        START: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_0 : START;
        BIT_0: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_1 : BIT_0;
        BIT_1: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_2 : BIT_1;
        BIT_2: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_3 : BIT_2;
        BIT_3: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_4 : BIT_3;
        BIT_4: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_5 : BIT_4;
        BIT_5: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_6 : BIT_5;
        BIT_6: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_7 : BIT_6;
        BIT_7: next_state = (clk_div_cnt == CLK_DIV-1) ? STOP  : BIT_7;
        STOP : next_state = (clk_div_cnt == CLK_DIV>>1) ? IDLE  : STOP;
        default: next_state = IDLE;
    endcase
end

always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        tx_en <= 1'b0;
        r_uart_tx_o <= 1'b1;
        r_tx_end <= 1'b0;
    end
    else begin
        case(cuur_state)
            IDLE :
              begin
                r_uart_tx_o <= 1'b1;
                if(tx_start)
                  tx_en <= 1'b1;
                else
                  tx_en <= 1'b0;
              end

            START: r_uart_tx_o <= 1'b0;
            BIT_0: r_uart_tx_o <= r_uart_tx_data_i[0];
            BIT_1: r_uart_tx_o <= r_uart_tx_data_i[1];
            BIT_2: r_uart_tx_o <= r_uart_tx_data_i[2];
            BIT_3: r_uart_tx_o <= r_uart_tx_data_i[3];
            BIT_4: r_uart_tx_o <= r_uart_tx_data_i[4];
            BIT_5: r_uart_tx_o <= r_uart_tx_data_i[5];
            BIT_6: r_uart_tx_o <= r_uart_tx_data_i[6];
            BIT_7: r_uart_tx_o <= r_uart_tx_data_i[7];
            STOP :
              begin
                  r_uart_tx_o <= 1'b1;
                  if(clk_div_cnt == CLK_DIV>>1)
                      r_tx_end <= 1'b1;
                  else
                      r_tx_end <= 1'b0;
              end

            default:
              begin
                tx_en <= 1'b0;
                r_uart_tx_o <= 1'b1;
                r_tx_end <= 1'b0;
              end
        endcase
    end
end

endmodule

4.3 UART回环顶层模块

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2023/04/30 10:52:44
// Design Name: 
// Module Name: uart_loop
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//

module uart_loop
#(
    parameter BAUD_RATE   = 9600       , //波特率
    parameter SYS_CLK_FRE = 100_000_000  //系统时钟频率
)
(
    input   sys_clk     , //系统时钟
    input   sys_rst_n   , //复位信号
    input   uart_rx_i   , //串行接收数据
    output  uart_tx_o     //串行发送数据
);

/**************************网表型**************************/
wire            rx_end    ; //接收结束
wire            tx_start; //发送开始
wire    [7:0]   uart_rx_data_o; //接收到的8bit数据
wire    [7:0]   uart_tx_data_i; //发送的8bit1数据
wire            tx_end; //发送结束

/**************************组合逻辑**************************/
assign tx_start = rx_end;
assign uart_tx_data_i = uart_rx_data_o;

/**************************模块例化**************************/
//接收模块
uart_rx
#(
    .BAUD_RATE  (BAUD_RATE  ), 
    .SYS_CLK_FRE(SYS_CLK_FRE)  
)
uart_rx_inst
(
    .sys_clk       (sys_clk       ), 
    .sys_rst_n     (sys_rst_n     ), 
    .uart_rx_i     (uart_rx_i     ), 
    
    .uart_rx_data_o(uart_rx_data_o), 
    .rx_end        (rx_end        )
);

//发送模块
uart_tx
#(
    .BAUD_RATE  (BAUD_RATE  ), 
    .SYS_CLK_FRE(SYS_CLK_FRE)  
)
uart_tx_inst
(
    .sys_clk       (sys_clk       ), 
    .sys_rst_n     (sys_rst_n     ), 
    .tx_start      (tx_start      ), 
    .uart_tx_data_i(uart_tx_data_i), 

    .uart_tx_o     (uart_tx_o     ), 
    .tx_end        (tx_end        )  

    );

endmodule

4.4 UART回环上板验证

  将比特流文件写入FPGA开发板后,打开串口助手,依次发送11、22给FPGA,FPGA在接收到数据后又发送回PC端,可以看到在串口助手接收窗口中分别接收到了数据11和22。

在这里插入图片描述
在这里插入图片描述

  也可以连续发送多个字节数据,分别发送33、44、55、66、77、88、99、AA、BB、CC、DD、EE、FF给FPGA,在串口助手的接收窗口中看到PC端接收到了FPGA发送的数据33、44、55、66、77、88、99、AA、BB、CC、DD、EE、FF,上板验证通过。

在这里插入图片描述

五、写在最后

  在本文中,我们学习了UART数据帧结构以及UART协议中一些基本的概念,并使用Verilog实现串口通信的收发操作,并进行上板验证。欢迎评论区友好交流批评指正!!!

在这里插入图片描述

  • 7
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值