3. 串行通信协议基础:UART协议帧结构、波特率与采样、起始位/数据位/校验位/停止位详解

说到嵌入式通信,UART 绝对是最常用的接口之一。我刚开始做嵌入式那会儿,调试的第一块板子就是靠 UART 打印日志。说白了,UART 就是两个设备之间,用一根线发、一根线收,再加上一根共地线,就能聊起来了。

但别看它简单,里面门道不少。很多新手上来就栽在波特率不匹配、校验位搞错这些坑里。今天咱们就把 UART 的帧结构、波特率、采样这些核心概念彻底捋一遍。

3.1 UART 帧结构:一帧数据长什么样?

UART 是异步通信,没有时钟线。那接收方怎么知道什么时候开始读数据?靠的就是帧结构。每一帧数据,由四个部分组成:起始位、数据位、校验位(可选)、停止位。

我习惯把一帧数据想象成一列火车:起始位是火车头,告诉站台“我要进站了”;数据位是车厢,装着你要传的信息;校验位是检票员,检查车厢有没有出问题;停止位是车尾,告诉站台“这趟车结束了”。

标准 UART 帧结构(8N1 格式):

空闲(高电平)→ 起始位(低电平)→ 数据位(LSB 先发)→ 校验位(可选)→ 停止位(高电平)→ 空闲(高电平)

下面这张图,把一帧数据的完整结构画出来了。你可以对照着看,每个部分在时间轴上的位置和电平状态。

UART 帧结构图 UART 帧结构(8N1 格式) 空闲 高电平 起始位 低电平 D0 D1 D2 D3 D4 D5 D6 D7 停止位 高电平 数据位从 LSB(最低有效位)开始发送,校验位可选,停止位可以是 1、1.5 或 2 位

3.2 起始位与停止位:通信的“握手信号”

起始位:空闲时总线是高电平。发送方把电平拉低,持续一个位时间。接收方检测到这个下降沿,就知道“数据要来了”。

停止位:数据发完后,发送方把电平拉高,持续至少一个位时间。这给接收方一个缓冲,准备接收下一帧。

停止位可以是 1 位、1.5 位或 2 位。我一般用 1 位停止位,除非通信环境特别差。曾经在一个电机控制项目里,干扰太大,换成 2 位停止位后,误码率明显下降。

3.3 数据位:你要传的真正内容

数据位通常是 5 到 9 位。最常见的是 8 位,刚好一个字节。发送顺序是 LSB(最低有效位)先发,MSB 最后发。

举个例子,你要发送 0x55(二进制 01010101),在线上看到的顺序是:1→0→1→0→1→0→1→0。你想想看,如果接收方按 MSB 先读,那数据就全乱了。

我的习惯: 数据位固定用 8 位。除非你传的是 7 位 ASCII 码,或者特殊传感器数据,否则 8 位最通用。很多库和工具默认就是 8 位,省心。

3.4 校验位:数据对不对?

校验位用来检测传输错误。有三种模式:

  • 无校验(N):不发送校验位。帧里只有起始位、数据位、停止位。比如 8N1 就是 8 数据位、无校验、1 停止位。
  • 奇校验(O):数据位里 1 的个数加上校验位,总共有奇数个 1。
  • 偶校验(E):数据位里 1 的个数加上校验位,总共有偶数个 1。

举个例子,数据位是 0b01010101,里面有 4 个 1(偶数)。

  • 奇校验:校验位设为 1,让总 1 数变成 5(奇数)。
  • 偶校验:校验位设为 0,总 1 数保持 4(偶数)。

接收方收到后,自己算一遍 1 的个数。如果奇偶不对,就说明数据出错了。

注意: 校验位只能检测奇数个位翻转错误。如果两个位同时翻转(比如 00→11),奇偶性不变,校验就查不出来。所以校验位不是万能的,只是基础防护。

3.5 波特率与采样:双方怎么对上节奏?

UART 没有时钟线,双方得约定好一个“节奏”。这个节奏就是 波特率,单位是 bps(bits per second),表示每秒传输多少个位。

常见的波特率有:9600、19200、38400、115200。我调试时最爱用 115200,速度快,大部分 MCU 都支持。

接收方怎么知道每个位什么时候采样?靠的是 过采样。接收方用比波特率高得多的时钟去采样,通常是波特率的 16 倍。

具体流程是这样的:

  1. 接收方检测到起始位的下降沿。
  2. 等待 8 个采样时钟(半个位时间),确认确实是低电平,避免毛刺误触发。
  3. 之后每 16 个采样时钟采样一次,正好落在每个位的中间位置。

为什么要采中间?因为信号在跳变沿附近不稳定,中间最可靠。我见过有人把采样点设偏了,结果在数据边缘采样,误码率飙升。

波特率误差容忍度:

理论上,收发双方的波特率误差不能超过 ±2%。实际上,我用 16 倍过采样时,误差容忍度能到 ±3% 左右。超过这个范围,采样点就会偏移到数据边缘,导致误码。

3.6 常见 UART 格式速查表

下面这个表,列出了最常用的几种 UART 格式。你写代码配置串口时,直接对照着设就行。

格式名称 数据位 校验位 停止位 典型应用
8N1 8 无 1 最通用,调试、传感器、GPS 模块
8E1 8 偶校验 1 工业 Modbus RTU
8O1 8 奇校验 1 某些老式终端设备
7E1 7 偶校验 1 ASCII 字符传输(老协议)
8N2 8 无 2 干扰大的环境,增加容错

3.7 避坑指南:我踩过的 UART 坑

做嵌入式这些年,UART 的坑我踩了不少。分享几个典型的:

  • 波特率不匹配:收发双方设成不同波特率,出来的全是乱码。我建议调试时先发 0x55(01010101),用示波器看波形,数一下每个位的时间对不对。
  • 校验位搞反:有一次我设了偶校验,对方设了奇校验。数据能通,但偶尔丢包。查了半天才发现是校验模式不一致。
  • 停止位太短:在高速率下(比如 921600 bps),1 位停止位可能不够。接收方处理不过来,下一帧就丢了。换成 2 位停止位就好了。
  • 共地问题:两个设备的地电位差太大,信号会漂移。我遇到过两个板子用长线连接,地线太细,结果通信时好时坏。加粗地线就解决了。

调试小技巧: 用逻辑分析仪抓 UART 波形,比用示波器方便。设好波特率,它能自动解析出数据内容。我每次调 UART 都会先抓波形,确认帧结构对不对,再去看软件逻辑。

3.8 代码示例:STM32 UART 初始化配置

下面是一个 STM32 的 UART 初始化代码,配置成 115200 波特率、8N1 格式。你可以参考这个结构,移植到其他 MCU 上。

// STM32 UART 初始化示例(8N1, 115200 bps)
void UART_Init(void)
{
    // 1. 使能 GPIO 和 USART 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    // 2. 配置 TX (PA9) 为推挽复用输出,RX (PA10) 为浮空输入
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    // 3. 配置 USART 参数:115200, 8数据位, 无校验, 1停止位
    USART_InitTypeDef USART_InitStruct;
    USART_InitStruct.USART_BaudRate = 115200;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    USART_InitStruct.USART_StopBits = USART_StopBits_1;
    USART_InitStruct.USART_Parity = USART_Parity_No;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
    USART_Init(USART1, &USART_InitStruct);

    // 4. 使能 USART
    USART_Cmd(USART1, ENABLE);
}

这段代码里,波特率、数据位、校验位、停止位都设得明明白白。你改一下波特率或者校验模式,就能适配不同设备。

3.9 小结

UART 看起来简单,但每个细节都影响通信质量。起始位和停止位是握手信号,数据位是你要传的内容,校验位是基础检错,波特率决定了双方的对齐节奏。过采样让接收方能在每个位的中间采样,提高抗干扰能力。

下次你调 UART 时,如果遇到乱码或者丢包,先检查波特率对不对,再看校验位和停止位是否匹配。用逻辑分析仪抓一下波形,问题基本就能定位。