Skip to content

UART 通信原理与实践

UART(Universal Asynchronous Receiver/Transmitter,通用异步收发器)是最简单、最广泛使用的串行通信协议。从调试串口到遥控器接收,UART 在嵌入式系统中无处不在。

上一篇:舵机与电机控制


1. 概念——异步串行通信

1.1 同步 vs 异步

通信协议的第一层分类:是否需要独立的时钟线。

同步通信(如 SPI):
  SCLK ──┐┌──┐┌──┐┌──┐┌──    ← 独立的时钟线,收发双方用同一时钟
  MOSI ──┘└──┘└──┘└──┘└──    ← 数据线
        上升沿采样

异步通信(如 UART):
  TX ──┐     ┌───┐   ┌─      ← 只有数据线,没有时钟线
       │     │   │   │
       └─────┘   └───┘
       收发双方各自独立计时,必须约定相同的波特率

1.2 UART 解决的问题

UART 将并行的字节数据转换为串行的比特流,通过一根 TX 线和一根 RX 线传输。异步意味着不用共享时钟——双方约定好传输速率(波特率)即可。

发送端:                            接收端:
   ┌───┐                            ┌───┐
   │ H │ 并行数据                    │ H │
   │ e │ ────┐               ┌────  │ e │
   │ l │     │   串行比特流   │      │ l │
   │ l │     └───────────────┘      │ l │
   │ o │  TX ───────────────── RX   │ o │
   └───┘                            └───┘

2. UART 的物理层

2.1 数据帧格式

一个 UART 数据帧包含以下部分:

空闲    起始位    数据位 (5~9 bit)    [校验位]  停止位   空闲
────┐   ┌───┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌───┐ ┌───┐
    │   │   │ │ │ │ │ │ │ │ │ │ │ │   │ │   │
    └───┘   └─┘ └─┘ └─┘ └─┘ └─┘ └───┘ └───┘
        └ D0  D1  D2  D3  D4  D5  D6  D7 ┘
        先发 LSB(最低位),最后发 MSB(最高位)
组成部分 长度 说明
空闲 任意 空闲时保持高电平
起始位 1 bit 低电平,告知接收方"数据来了"
数据位 5~9 bit(通常 8) 实际数据,LSB 先发
校验位 0~1 bit(通常无) 奇偶校验,可选
停止位 1~2 bit(通常 1) 高电平,表示一帧结束

为什么需要起始位? 接收方在没有数据传输时面对的是随机噪声电平。起始位的下降沿提供了明确的"数据开始"信号——接收方检测到下降沿后,开始在精确的时间点采样后续数据位。这个下降沿本质上是收发双方的同步信号:接收方以它为起点,按照约定的波特率推算每个数据位的最佳采样时刻。

为什么需要停止位? 停止位保证了两帧之间有一个最小间隔。如果没有停止位,两帧连续发送时,前一帧的最后一位和后一帧的起始位将无缝衔接,接收方无法区分帧边界。同时,停止位提供了上升沿让接收方验证自身时钟是否仍然同步——如果接收方在停止位时刻采到低电平,说明收发双方的波特率偏差过大。

为什么先发 LSB(低位)? 这源于早期 UART 芯片的硬件实现:移位寄存器自然向右移位,LSB 最先被移出。从通信角度看,对于低速调制解调器,先发送变化最快的低位比特也有利于信号同步。这一惯例被沿袭至今,成为所有 UART 设备的标准。

2.2 波特率(Baud Rate)

波特率即每秒传输的比特数(包括起始/停止/校验位)。收发双方必须约定相同的波特率。

常用波特率: 9600, 19200, 38400, 57600, 115200, 921600

以 115200 bps 为例:
每 bit 时间 = 1 / 115200 ≈ 8.68 μs
传输 1 字节(8N1 = 起始 1 + 数据 8 + 停止 1 = 10 bit)
耗时 = 10 × 8.68 μs ≈ 86.8 μs
最大吞吐 = 115200 / 10 = 11520 字节/秒 ≈ 11.5 KB/s

2.3 电平标准

标准 逻辑 1 (高) 逻辑 0 (低) 用途
TTL/CMOS (MCU 直连) 3.3V / 5V 0V 板级短距离通信
RS-232 -3V ~ -15V +3V ~ +15V PC 串口(DB9 接口)
RS-485 差分信号 (A > B) 差分信号 (A < B) 工业长距离(千米级)

注意:STM32 引脚输出的是 TTL 电平,与 PC 的 RS-232 电平不兼容!需要使用 MAX232 等电平转换芯片,或直接使用 USB-TTL 转换器(如 CP2102、CH340)。


3. UART 的外设硬件

3.1 STM32 的 USART 外设

STM32 的 USART(Universal Synchronous/Asynchronous Receiver/Transmitter)不仅支持异步通信,还支持同步模式(带时钟线)。在 GSRL 中我们只使用异步模式(UART)。

┌─────────────────────────────────────────────┐
│               USART 外设内部                  │
│                                              │
│  TX 引脚 ←── 发送移位寄存器 ←── 发送数据寄存器  │
│              (并→串转换)       (TDR)          │
│                                              │
│  RX 引脚 ──→ 接收移位寄存器 ──→ 接收数据寄存器  │
│              (串→并转换)       (RDR)          │
│                                              │
│          波特率发生器 (BRR)                    │
│          ┌─────────────────┐                 │
│          │ 总线时钟 ÷ BRR  │ → 采样时钟       │
│          └─────────────────┘                 │
│                                              │
│          过采样 (16× 或 8×)                   │
│          每个 bit 采样 16 次,取中间 3 次判多数  │
└─────────────────────────────────────────────┘

3.2 过采样机制

UART 接收端并不知道发送端的精确时钟相位,因此需要过采样来找到每个比特的最佳采样点:

发送信号:  ──┐     ┌───────────┐     ┌──
            │     │           │     │
            └─────┘           └─────┘
           起始位             数据位

16× 采样:  0123456789ABCDEF  0123456789...
                 ↑                ↑
           检测到下降沿      在第 8,9,10 次
           之后第 8,9,10     采样中取多数
           次采样确认起始位

4. HAL 库的 UART 操作

4.1 三种传输模式

模式 API 特点
阻塞 (Blocking) HAL_UART_Transmit() / HAL_UART_Receive() 等传输完才返回,卡住 CPU
中断 (Interrupt) HAL_UART_Transmit_IT() / HAL_UART_Receive_IT() 每字节产生中断
DMA HAL_UART_Transmit_DMA() / HAL_UART_Receive_DMA() 硬件自动搬运,CPU 零开销

在 GSRL 中,UART 接收采用DMA + 空闲中断的组合(最优方案)。

4.2 DMA + 空闲中断:GSRL 的方案

传统方式每接收一个字节都要产生中断,大量浪费 CPU 时间。

为什么不用逐字节中断而用 DMA + IDLE? 以 115200 bps 为例,每字节 10 bit(起始 1 + 数据 8 + 停止 1),每秒产生 11520 次中断。每次中断需要保存/恢复寄存器、执行回调,实际开销远不止 1μs,通常要 5~8μs。这意味着 CPU 仅处理 UART 接收就要消耗 5%~8% 的算力——在 1kHz 控制循环的机器人系统中,这是不可接受的。DMA(直接存储器访问)则完全不同:传输期间 CPU 零开销,硬件自动将数据从 UART 寄存器搬运到内存。配合空闲中断(IDLE),一整帧数据只产生一次中断,而非每字节一次。

GSRL 的做法:

① 启动 DMA 接收:
   HAL_UARTEx_ReceiveToIdle_DMA(&huart, buffer, maxLen);
   配置 DMA 将收到的数据从 UART 数据寄存器搬运到 buffer
   直到收到 maxLen 个字节或 UART 空闲中断发生

② 一帧数据结束 → UART 空闲中断触发:
   当 RX 线上超过 1 个字节时间没有新数据 → IDLE 中断

③ 回调处理数据:
   HAL_UARTEx_RxEventCallback(huart, actualLen)
   → 用户回调处理 buffer 中的有效数据

④ 重启 DMA 接收:
   HAL_UARTEx_ReceiveToIdle_DMA(...)
   准备接收下一帧
时间轴:
  RX:   [字节1][字节2][字节3] ...... [字节N]  ────空闲 > 1字节时间────
                                                    │
                                              空闲中断触发
                                             Size = N, 调用回调

4.3 GSRL Driver 实现

drv_uart.c 中的完整实现:

void UART_Init(UART_HandleTypeDef *huart, UART_Call_Back rxCallbackFunction, 
               uint16_t rxDataLimit)
{
    UART_Manage_Object_t *uart_obj = UART_Get_Object(huart);
    uart_obj->rxCallbackFunction = rxCallbackFunction;
    uart_obj->rxDataLimit = rxDataLimit;
    // 启动 DMA + 空闲中断接收
    HAL_UARTEx_ReceiveToIdle_DMA(huart, uart_obj->rxBuffer, rxDataLimit);
}

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    UART_Manage_Object_t *uart_obj = UART_Get_Object(huart);
    if (uart_obj->rxCallbackFunction != NULL) {
        uart_obj->rxCallbackFunction(uart_obj->rxBuffer, Size);
    }
    // 重启接收
    HAL_UARTEx_ReceiveToIdle_DMA(huart, uart_obj->rxBuffer, uart_obj->rxDataLimit);
}

void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
    // 错误恢复:重启 DMA 接收
    UART_Manage_Object_t *uart_obj = UART_Get_Object(huart);
    HAL_UARTEx_ReceiveToIdle_DMA(huart, uart_obj->rxBuffer, uart_obj->rxDataLimit);
}

5. UART 在 GSRL 中的实际应用

5.1 遥控器数据接收

DR16 遥控接收机通过 UART 输出 18 字节的遥控数据帧(100Hz)。GSRL 中:

// 用户任务中初始化
UART_Init(&huart3, dr16ITCallback, 36);

// ISR 中回调
void dr16ITCallback(uint8_t *Buffer, uint16_t Length) {
    dr16.receiveRxDataFromISR(Buffer);
}

5.2 裁判系统通信

RoboMaster 裁判系统通过 UART 下发比赛状态数据,GSRL 中的 dvc_referee 设备对此进行封装。

5.3 调试串口

printf 重定向到 UART,用于调试输出(通过 USB 虚拟串口或物理 UART 引脚)。


6. 常见问题与对策

问题 原因 对策
收到乱码 波特率不匹配 检查收发双方波特率设置
数据粘包/断帧 空闲中断配置不当 使用 IDLE 中断自动分帧
DMA 接收中断停止 错误中断后未重启 ErrorCallback 中重启
收发同时进行时丢数据 全双工但驱动未处理并发 使用独立 TX/RX DMA 通道
长距离通信不稳定 TTL 电平抗干扰差 改用 RS-485(差分信号)

7. 总结

要点 说明
异步通信 无时钟线,双方约定波特率
数据帧 起始位 + 数据位(5~9)+ 可选校验位 + 停止位
波特率 bits/s,常用 115200
过采样 16× 采样,每个 bit 取多次判多数
DMA + IDLE 最优接收方案,零 CPU 开销 + 自动分帧
电平标准 TTL (MCU) ≠ RS-232 (PC),需转换

与 GSRL 的关系

GSRL 中 drv_uart.c 封装了 DMA + 空闲中断的 UART 接收模式,dvc_remotecontroldvc_referee 等设备类依赖此驱动接收数据。遥控器、裁判系统的数据均通过 UART 进入系统。

下一篇:SPI 通信原理与实践

作者: Qing | 修改日期: 2026-07-18