三大低速总线之UART详解

本科阶段在搞硬件设计的时候就经常碰到三大低速总线(UART、SPI、IIC),奈何好记忆不如烂笔头,趁着现在基于github搭建了个人博客,就把这三大低速总线的原理和应用做一个详细的总结吧,以便日后查阅。

1、UART简介

UART 是一种采用异步串行通信方式的通用异步收发传输器(universal asynchronous receiver-transmitter), 它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。

UART 串口通信需要两根信号线来实现,一根用于串口发送,另外一根负责串口接收,如图 1 所示。对于 PC 来说它的 TX 要和对于 FPGA 来说的 RX 连接,同样 PC 的 RX 要和 FPGA 的 TX 连接,如果是两个 TX 或者两个 RX 连接那数据就不能正常被发送出去或者接收到。

图 1 串口通信连接图

UART 在发送或接收过程中的一帧数据由 4 部分组成,起始位、数据位、奇偶校验位和停止位,如图 2 所示。

图 2 异步串行通信数据格式

起始位:当不传输数据时,UART 数据传输线通常保持高电压电平。若要开始数据传输,发送 UART会将传输线从高电平拉到低电平并保持 1 个波特率周期。当接收 UART 检测到高到低电压跃迁时,便开始以波特率对应的频率读取数据帧中的位。

数据帧:其包含所传输的实际数据。如果使用奇偶校验位,数据帧长度可以是 5 位到 8 位。如果不使用奇偶校验位,数据帧长度可以是 9 位。在大多数情况下,数据以最低有效位优先方式发送。

奇偶校验:奇偶性描述数字是偶数还是奇数。通过奇偶校验位,接收 UART 判断传输期间是否有数据发生改变。电磁辐射、不一致的波特率或长距离数据传输都可能改变数据位。接收 UART 读取数据帧后,将计数值为 1 的位,检查总数是偶数还是奇数。如果奇偶校验位为 0(偶数奇偶校验),则数据帧中的 1 或逻辑高位总计应为偶数。如果奇偶校验位为 1(奇数奇偶校验),则数据帧中的 1 或逻辑高位总计应为奇数。当奇偶校验位与数据匹配时,UART 认为传输未出错。但是,如果奇偶校验位为 0,而总和为奇数,或者奇偶校验位为 1,而总和为偶数,则 UART 认为数据帧中的位已改变。

停止位:为了表示数据包结束,发送 UART 将数据传输线从低电压驱动到高电压并保持 1 到 2 位时间。

UART 通信过程中的数据格式及传输速率是可设置的,为了正确的通信,收发双方应约定并遵循同样的设置。数据位可选择为 5、6、7、8 位,其中 8 位数据位是最常用的,在实际应用中一般都选择 8 位数据 位;校验位可选择奇校验、偶校验或者无校验位;停止位可选择 1 位(默认),1.5 或 2 位。串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,单位是 bps(位/秒),常用的波特率有 9600、 19200、38400、57600 以及 115200 等。

波特率:每秒通过信号传输的码元数称为码元的传输速率,常用符号“Baud”表示,其单位为“波特每秒”(Bps)。在信息传输通道中,携带数据信息的信号单元叫作码元(因为串口是 1bit 进行传输的,所以其码元就代表一个二进制数),串口常见的波特率有 4800、9600、19200、115200 等,其实意思就是每秒传输这么多个比特位数(bit)。

通信信道每秒传输的信息量称为位传输速率,简称“比特率”,其单位为“每秒比特数”(bps)。比特率可由波特率计算得出,公式为比特率=波特率×单个调制状态对应的二进制位数。

如果使用的是 115200 的波特率,其串口的比特率为 115200Bps×1bit = 115200bps,由计算得串口发送或者接收 1bit 数据的时间为一个波特,即 1/115200s。

目前在各种设备中,都是利用CH340、CP2102等 USB 转串口芯片来实现串口通信,这些芯片内部集成了 USB 转串口的功能,只需要通过 USB 接口连接到 PC 上,就可以实现串口通信。

图 3 USB 串口通信连接示意图

2、UART接收模块(uart_rx.v)设计

该模块实现了一个基于AXI4-Stream接口的UART接收器,支持可配置的数据位宽(DATA_WIDTH)和波特率(通过prescale参数)。主要功能包括:

  • 串行数据接收:通过rxd引脚接收数据。

  • AXI4-Stream接口输出:通过m_axis_tdatam_axis_tvalid输出数据。

  • 错误检测:支持帧错误(frame_error)和溢出错误(overrun_error)。

  • 状态指示:通过busy信号指示接收状态。

2.1、关键信号与寄存器

2.1.1、输入/输出接口

类型 名称 位宽 方向 描述
parameter DATA_WIDTH - - 数据宽度,默认值为8
parameter CLK_FREQ - - 输入时钟频率,默认值为200MHz
parameter BAUD_RATE - - 目标波特率,默认值为115200bps
localparam prescale - - 分频系数 = CLK_FREQ/(BAUD_RATE*8)
wire clk 1 input 系统时钟
wire rst 1 input 系统复位,高电平有效
wire m_axis_tdata DATA_WIDTH output AXI Stream数据输出
wire m_axis_tvalid 1 output AXI Stream数据有效信号
wire m_axis_tready 1 input AXI Stream就绪信号
wire rxd 1 input UART接收数据线
wire busy 1 output 接收器忙状态指示
wire overrun_error 1 output 数据溢出错误指示
wire frame_error 1 output 帧格式错误指示

2.2.2、内部寄存器

寄存器 功能说明
rxd_reg 同步后的rxd输入,避免亚稳态
prescale_reg 分频计数器,控制采样时机
bit_cnt 比特计数器,跟踪当前接收的比特位
data_reg 临时存储接收到的数据位
busy_reg 接收状态标志(忙/空闲)
overrun_error_reg 溢出错误标志
frame_error_reg 帧错误标志
m_axis_tdata_reg 输出数据寄存器
m_axis_tvalid_reg 输出数据有效标志

2.2、实现流程详解

2.2.1、复位初始化

  • 寄存器清零:在复位信号(rst)为高电平时,所有寄存器和状态标志被清零,rxd_reg初始化为高电平(UART空闲状态)。
always @(posedge clk) begin
    if (rst) begin
        m_axis_tdata_reg <= 0;
        m_axis_tvalid_reg <= 0;
        rxd_reg <= 1;          // UART空闲状态为高电平
        prescale_reg <= 0;
        bit_cnt <= 0;
        busy_reg <= 0;
        overrun_error_reg <= 0;
        frame_error_reg <= 0;
    end 
    else begin
        // 正常操作逻辑
    end
end

2.2.2、同步与状态更新

  • 同步输入rxd_reg在每个时钟上升沿采样rxd,避免亚稳态。

  • 错误标志清零overrun_error_regframe_error_reg在每个周期开始时清零,仅在有错误时置位。

2.2.3、接收逻辑

空闲状态(bit_cnt = 0)

  • 检测起始位:当rxd_reg从高电平跳变为低电平时,启动接收流程。
if (!rxd_reg) begin
    prescale_reg <= (prescale << 2) - 2;  // 起始位采样点
    bit_cnt <= DATA_WIDTH + 2;            // 总比特数:起始位 + 数据位 + 停止位
    data_reg <= 0;                        // 清空数据寄存器
    busy_reg <= 1;                        // 进入忙状态
end

接收数据位(bit_cnt > DATA_WIDTH + 1)

  • 起始位确认:确保起始位有效。
if (bit_cnt > DATA_WIDTH + 1) begin
    if (!rxd_reg) begin                   // 起始位有效
        bit_cnt <= bit_cnt - 1;
        prescale_reg <= (prescale << 3) - 1; // 数据位采样点:1倍波特率周期
    end else begin                        // 起始位无效,中止接收
        bit_cnt <= 0;
        prescale_reg <= 0;
    end
end

数据位采集(bit_cnt > 1)

  • 移位接收:每个数据位在中间点采样,并右移存入data_reg
else if (bit_cnt > 1) begin
    bit_cnt <= bit_cnt - 1;
    prescale_reg <= (prescale << 3) - 1;  // 更新分频计数器
    data_reg <= {rxd_reg, data_reg[DATA_WIDTH-1:1]}; // 右移并插入新比特
end

停止位确认(bit_cnt = 1)

  • 停止位检测:停止位应为高电平,否则触发帧错误。
else if (bit_cnt == 1) begin
    bit_cnt <= 0;
    if (rxd_reg) begin                    // 停止位有效
        m_axis_tdata_reg <= data_reg;     // 输出数据
        m_axis_tvalid_reg <= 1;           // 置位有效信号
        overrun_error_reg <= m_axis_tvalid_reg; // 若前次数据未读,触发溢出错误
    end else begin
        frame_error_reg <= 1;             // 停止位无效,触发帧错误
    end
end

2.2.4、错误处理

  • 溢出错误(Overrun Error):当新数据已接收(m_axis_tvalid_reg == 1),但前次数据未被读取(m_axis_tready == 0)时触发。

  • 帧错误(Frame Error):停止位无效(停止位为低电平),触发帧错误。

2.2.5、波特率控制

  • 分频计数器prescale_reg控制采样时机,递减至0时触发下一次采样。

3、UART发送模块(uart_tx.v)设计

该模块实现了一个基于AXI4-Stream接口的UART发送器,将并行数据转换为串行数据输出,支持可配置的数据位宽(DATA_WIDTH)和波特率(通过prescale输入)。主要功能包括:

  • 串行数据发送:通过txd引脚发送数据。

  • AXI4-Stream输入s_axis_tdata(数据)、s_axis_tvalid(数据有效)、s_axis_tready(模块就绪)。

  • 波特率控制prescale(波特率分频系数)。

  • 状态指示:通过busy信号指示发送状态。

3.1、关键寄存器与信号

3.1.1、输入/输出接口

类型 名称 位宽 方向 描述
parameter DATA_WIDTH - - 数据宽度,默认值为8
parameter CLK_FREQ - - 输入时钟频率,默认值为200MHz
parameter BAUD_RATE - - 目标波特率,默认值为115200bps
localparam prescale - - 分频系数 = CLK_FREQ/(BAUD_RATE*8)
wire clk 1 input 系统时钟
wire rst 1 input 系统复位,高电平有效
wire s_axis_tdata DATA_WIDTH input AXI Stream数据输入
wire s_axis_tvalid 1 input AXI Stream数据有效信号
wire s_axis_tready 1 output AXI Stream就绪信号
wire txd 1 output UART发送数据线
wire busy 1 output 发送器忙状态指示

3.1.2、内部寄存器

寄存器/信号 描述
s_axis_tready_reg 控制AXI tready信号,表示模块是否可接收新数据。
txd_reg 存储当前发送的串行数据位(最终输出到txd)。
busy_reg 表示模块是否正在发送数据(高电平有效)。
data_reg 存储待发送的数据帧(包含数据位)。
prescale_reg 波特率分频计数器,用于控制每个位的持续时间。
bit_cnt 位计数器,跟踪当前发送的位位置。

3.2、发送流程详解

3.2.1、初始状态(空闲)

  • txd_reg保持高电平(空闲状态)。

  • s_axis_tready_reg为高,允许接收新数据。

  • busy_reg为低,表示模块空闲。

3.2.2、数据接收与启动发送(s_axis_tvalid有效且模块空闲s_axis_tready_reg为高)

  • 锁存数据data_reg <= s_axis_tdata

  • 设置起始位txd_reg <= 0

  • 初始化计数器prescale_reg <= (prescale << 3) - 1bit_cnt <= DATA_WIDTH + 1

  • 状态切换busy_reg置高,s_axis_tready_reg置低。

if (s_axis_tvalid) begin
    s_axis_tready_reg <= !s_axis_tready_reg;
    prescale_reg      <= (prescale << 3) - 1;
    bit_cnt           <= DATA_WIDTH + 1;
    data_reg          <= s_axis_tdata;
    txd_reg           <= 0;
    busy_reg          <= 1;
end

3.2.3、数据位发送(bit_cnt > 1)

  • 分频计数prescale_reg递减至0。

  • 移位发送{data_reg, txd_reg} <= {1'b0, data_reg}txd_regdata_reg的最低位(LSB优先发送)。

  • 更新计数器bit_cnt减1,重置prescale_reg

if (bit_cnt > 1) begin
    bit_cnt             <= bit_cnt - 1;
    prescale_reg        <= (prescale << 3) - 1;
    {data_reg, txd_reg} <= {1'b0, data_reg};
end 

3.2.4、停止位发送(bit_cnt == 1)

  • 设置停止位txd_reg <= 1

  • 分频计数prescale_reg <= prescale << 3(停止位持续时间)。

  • 状态恢复bit_cnt减至0,busy_reg置低,s_axis_tready_reg置高。

else if (bit_cnt == 1) begin
    prescale_reg <= (prescale << 3);
    bit_cnt      <= bit_cnt - 1;
    txd_reg      <= 1;
end
else if (bit_cnt == 0) begin
    s_axis_tready_reg <= 1;
    busy_reg          <= 0;
    //其他逻辑
end

3.3、状态转换示意

     +-------------+
     |   空闲状态   |
     | (busy=0)    |
     +-----+-------+
           | s_axis_tvalid & s_axis_tready
           v
     +-------------+   bit_cnt > 1  +-----------------+
     | 发送起始位   |+-------------->| 发送数据位(循环) |
     | (txd=0)     |                | (LSB优先)       |
     +-----+-------+                +--------+--------+
           |                                  |
           | bit_cnt == 1                     |
           v                                  |
     +-------------+                          |
     | 发送停止位   | <------------------------+
     | (txd=1)     |
     +-------------+

4、UART回环测试

比较简单,只需要将发送模块和接收模块互相连接,然后通过串口助手发送数据,观察接收端是否能够正常接收到数据即可。

4.1、输入/输出接口

类型 名称 位宽 方向 描述
parameter DATA_WIDTH - - 数据宽度,默认值为8
parameter CLK_FREQ - - 输入时钟频率,默认值为200MHz
parameter BAUD_RATE - - 目标波特率,默认值为115200bps
wire sys_clk_p 1 input 系统差分时钟正端
wire sys_clk_n 1 input 系统差分时钟负端
wire sys_rst_n 1 input 系统复位信号,低电平有效
wire uart_rxd 1 input UART接收数据线
wire uart_txd 1 output UART发送数据线

4.2、模块实例化

uart_tx #(
    .DATA_WIDTH(DATA_WIDTH),
    .CLK_FREQ(CLK_FREQ),
    .BAUD_RATE(BAUD_RATE)
)
uart_tx_inst (
    .clk(sys_clk),
    .rst(~sys_rst_n),
    // axi input
    .s_axis_tdata(m_axis_tdata),
    .s_axis_tvalid(m_axis_tvalid),
    .s_axis_tready(m_axis_tready),
    // output
    .txd(uart_txd),
    // status output
    .busy(tx_busy)
);

uart_rx #(
    .DATA_WIDTH(DATA_WIDTH),
    .CLK_FREQ(CLK_FREQ),
    .BAUD_RATE(BAUD_RATE)
)
uart_rx_inst (
    .clk(sys_clk),
    .rst(~sys_rst_n),
    // axi output
    .m_axis_tdata(m_axis_tdata),
    .m_axis_tvalid(m_axis_tvalid),
    .m_axis_tready(m_axis_tready),
    // input
    .rxd(uart_rxd),
    // status output
    .busy(rx_busy),
    .overrun_error(rx_overrun_error),
    .frame_error(rx_frame_error)
);

4.3、测试结果

  • 波特率:115200bps

  • 数据位:8位

  • 校验位:无

  • 停止位:1位

图 4 USB 串口回环测试结果图

三大低速总线之UART详解
https://yao-jiangyu.github.io/uart-introduction/
作者
小姚
发布于
2025年2月7日
许可协议