三大低速总线之UART详解
本科阶段在搞硬件设计的时候就经常碰到三大低速总线(UART、SPI、IIC),奈何好记忆不如烂笔头,趁着现在基于github搭建了个人博客,就把这三大低速总线的原理和应用做一个详细的总结吧,以便日后查阅。
1、UART简介
UART 是一种采用异步串行通信方式的通用异步收发传输器(universal asynchronous receiver-transmitter), 它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。
UART 串口通信需要两根信号线来实现,一根用于串口发送,另外一根负责串口接收,如图 1 所示。对于 PC 来说它的 TX 要和对于 FPGA 来说的 RX 连接,同样 PC 的 RX 要和 FPGA 的 TX 连接,如果是两个 TX 或者两个 RX 连接那数据就不能正常被发送出去或者接收到。

UART 在发送或接收过程中的一帧数据由 4 部分组成,起始位、数据位、奇偶校验位和停止位,如图 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 上,就可以实现串口通信。

2、UART接收模块(uart_rx.v
)设计
该模块实现了一个基于AXI4-Stream接口的UART接收器,支持可配置的数据位宽(DATA_WIDTH
)和波特率(通过prescale
参数)。主要功能包括:
串行数据接收:通过
rxd
引脚接收数据。AXI4-Stream接口输出:通过
m_axis_tdata
和m_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_reg
和frame_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) - 1
,bit_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_reg
取data_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位
