通信协议篇三:USART 串口通信原理详解从帧结构示波器逻辑分析仪波形分析到代码原理

异步串口是嵌入式里最常见的通信方式之一。它硬件简单、成本低、调试方便,既能做日志输出,也能连接上位机、蓝牙、WiFi、GPS、4G 等外设。本文把 USART / UART 的核心概念、帧结构、采样机制、调试方法和实战代码压缩到一篇里,尽量做到好查、好记、好用。

ℹ️ 先记住一句话:日常开发里说的“串口通信”,大多数场景其实就是 USART 的异步模式,常见连线只有 TXRXGND

1. USART 是什么

USART(Universal Synchronous/Asynchronous Receiver/Transmitter)是 MCU 常见的串行通信外设,用来完成 并行数据与串行比特流之间的转换,并负责设备之间按位发送与接收数据。

1.1 USART 和 UART 的区别

项目 UART USART
支持模式 只支持异步 支持同步 + 异步
日常开发常见性 很常见 很常见
实际常用场景 串口调试、模块通信 串口调试、模块通信

实际项目里如果只用了 TX / RX 两根信号线,通常就是在使用 异步串口

1.2 基本连线规则

  • TX:发送线
  • RX:接收线
  • GND:共地

连接原则:TX 接对方 RX,RX 接对方 TX,共地

💡 关键结论
异步串口通常是 全双工,发送和接收可以同时进行。

1.3 常见物理层接口

类型 特点 常见场景
TTL 串口 MCU 常用逻辑电平,常见 3.3V / 5V 单片机、开发板、模块直连
RS232 电平可能有正负摆动,不能直接接 MCU 老式电脑串口、工业设备
RS485 差分传输,抗干扰强,适合远距离和多节点 工业现场、Modbus

常见转换芯片:

  • USB ↔ 串口:CH340CP2102
  • TTL ↔ RS232:MAX3232SP3232
⚠️ 注意:单片机的 TX/RX 一般是 TTL 电平。电脑通常是 USB,老式串口通常是 RS232,因此电脑不能直接和 MCU 的 TTL 串口硬连,通常需要 USB 转 TTLRS232 电平转换芯片

1.4 为什么嵌入式里大量使用串口

  • 硬件简单,成本低
  • 外设支持广,几乎所有 MCU 都内置
  • 适合调试输出和模块通信
  • 使用门槛低,排查问题方便

常见用途:

  • 调试日志输出
  • 上位机通信
  • 外设连接(蓝牙 / WiFi / GPS / 4G)
  • BootLoader 下载
  • 模块间通信

2. 异步串口的核心时序

2.1 一帧结构

异步串口的核心是:双方提前约定波特率,然后按固定节拍发送与采样。

一帧通常包含:

  • 空闲位:高电平
  • 起始位:1 bit,低电平
  • 数据位:5~9 bit,常用 8 bit
  • 校验位:可选
  • 停止位:1 / 1.5 / 2 bit,高电平
空闲(1) → 起始位(0) → 数据位 → 校验位(可选) → 停止位(1)

2.2 为什么空闲是高电平

线路空闲时保持高电平,这样一旦出现 下降沿,接收端就能立即把它识别为起始位。

下降沿 = 一帧的起点

后续所有采样时刻,都是基于这个起点推导出来的。

2.3 校验位的作用

校验位本质上是 低成本检错手段,常见有:

  • 奇校验
  • 偶校验

例如偶校验要求:数据位中 1 的个数,加上校验位后必须是偶数。

⚠️ 注意:校验位只能发现部分错误,不能纠错。很多场景为了简单,直接使用 N(无校验)。

2.4 停止位的作用

停止位有两个作用:

  1. 告诉接收端这一帧结束了
  2. 让线路回到高电平,为下一帧做缓冲

常见配置:

  • 1 位停止位:默认、效率高
  • 2 位停止位:更稳,但更慢

可以把停止位理解为 帧与帧之间的缓冲区

2.5 最常见格式:8N1

8N1 是最常见的串口参数:

  • 8 位数据位
  • N:无校验
  • 1 位停止位
空闲(1) → 起始位(0) → D0 → D1 → D2 → D3 → D4 → D5 → D6 → D7 → 停止位(1)

2.6 三个关键结论

  • 数据位通常按 LSB 先发
  • 接收采样点通常放在 位中心
  • 两端波特率必须匹配,否则采样误差会逐位累积,最终出现乱码

常见波特率:

9600192003840057600115200

在普通 UART/USART 二进制电平通信里,通常可近似认为:

波特率 ≈ 比特率

2.7 时间直觉

115200 波特率为例:

  • 1 bit 时间约为:$1 / 115200 \approx 8.68\,\mu s$
  • 1 帧(8N1,共 10 bit)时间约为:$86.8\,\mu s$
💡 乱码先查这 5 项
baud ratedata bitsparitystop bits电平标准 是否一致。

3. STM32 中使用 USART

3.1 三个最关键的状态标志

标志位 含义 作用
TXE 发送数据寄存器空 可以继续写下一个字节
TC 发送完成 当前帧连同停止位都发完了
RXNE 接收数据寄存器非空 已收到新数据,需要及时读取

使用逻辑:

  • 发送流程:优先看 TXE,最后确认 TC
  • 接收流程:轮询或中断检测 RXNE,收到后及时读取
ℹ️ 区分一下TXE 只表示“现在可以再写数据”,并不代表这一帧已经真正发到线上;要确认完全发完,应看 TC

3.2 常见工作模式

模式 特点 适用场景
轮询 HAL_UART_Transmit/Receive,阻塞等待 简单调试
中断 HAL_UART_Transmit/Receive_IT,非阻塞 一般应用
DMA HAL_UART_Transmit/Receive_DMA,CPU 占用更低 大数据量 / 高效率

3.3 HAL 库轮询收发示例

MX_USART1_UART_Init();
uint8_t receive;

while (1) {
    if (HAL_OK == HAL_UART_Receive(&huart1, &receive, 1, HAL_MAX_DELAY)) {
        receive = receive + 1;
        HAL_UART_Transmit(&huart1, &receive, 1, 100);
    }
}

这段代码的逻辑很简单:

  1. 一直等待接收 1 个字符
  2. 收到后把字符值加 1
  3. 再发回去

适合用来验证:

  • 串口初始化是否正确
  • 收发链路是否正常
  • 上位机与单片机是否真正连通

4. MicroPython:ESP32-C3 使用硬件 UART

from machine import UART

uart = UART(1, baudrate=115200, tx=4, rx=5)

while True:
    if uart.any():
        recv = uart.read(1).decode()
        num = ord(recv)
        num += 1
        uart.write(chr(num))
        print(f"接收到{recv},发送回{chr(num)}")

代码逻辑:

  • UART(1) 初始化串口
  • 若串口缓冲区有数据,就读取 1 字节
  • 将字符转成 ASCII 数值后加 1
  • 再把新字符发回去

micropython串口实验运行截图


5. 串口波形与协议分析

5.1 逻辑分析仪适合看什么

逻辑分析仪更适合看 数字协议和字节解码

空闲(1) | 起始位(0) | 数据位(D0-D7) | 校验位(可选) | 停止位(1)

逻辑分析仪

观察重点:

  • 空闲时是否保持高电平
  • 是否存在明确的起始位下降沿
  • 每一位宽度是否基本一致
  • 协议解码参数是否正确

建议重点核对:

  • Baud rate = 115200
  • Data bits = 8
  • Parity = none
  • Stop bits = 1
  • 是否反相
  • 是否按 LSB first 发送

5.2 示波器适合看什么

示波器更适合分析 波形质量与时序细节

示波器图1

示波器图2

观察重点:

  • 位宽是否准确
  • 起始位是否正确拉低
  • 停止位是否回到高电平
  • 边沿是否干净
  • 是否有毛刺、过冲、振铃、抖动

115200 波特率为例,常用观察思路:

  • 先用 10 us/div 左右的时基
  • 使用 下降沿触发 抓起始位
  • 再用游标测 1 bit 的宽度,反推实际波特率

5.3 逻辑分析仪 vs 示波器

工具 更擅长
逻辑分析仪 看协议、看字节、看解码结果
示波器 看电平、看边沿、看抖动和噪声

6. GPIO 模拟串口(Bit-Bang)

⚠️ 使用建议:GPIO 模拟串口只适合 学习和低速实验。波特率稍高时,推荐直接使用 硬件 UART

6.1 MicroPython:GPIO 模拟发送

from machine import Pin
import utime

TX = Pin(2, Pin.OUT, value=1)
BAUD = 9600
BIT_US = int(1_000_000 / BAUD)

def uart_send_byte(data):
    TX.value(0)
    utime.sleep_us(BIT_US)

    for i in range(8):
        TX.value((data >> i) & 0x01)
        utime.sleep_us(BIT_US)

    TX.value(1)
    utime.sleep_us(BIT_US)

def uart_send_string(s):
    for ch in s:
        uart_send_byte(ord(ch))

uart_send_string("Hello\r\n")

发送逻辑:

  • 先输出 1 bit 低电平作为起始位
  • 再按 LSB first 发送 8 位数据
  • 最后输出高电平作为停止位

6.2 MicroPython:GPIO 模拟接收

from machine import Pin
import utime

RX = Pin(3, Pin.IN, Pin.PULL_UP)
BAUD = 9600
BIT_US = int(1_000_000 / BAUD)

def uart_recv_byte():
    while RX.value() == 1:
        pass

    utime.sleep_us(BIT_US + BIT_US // 2)

    value = 0
    for i in range(8):
        bit = RX.value()
        value |= (bit << i)
        utime.sleep_us(BIT_US)

    stop_bit = RX.value()
    return value, stop_bit

while True:
    data, stop = uart_recv_byte()
    print("recv:", data, chr(data), "stop:", stop)

接收逻辑:

  • 一直等 RX 从高变低,识别起始位
  • 延时到第 1 位数据的中心位置
  • 每隔 1 bit 周期采样一次
  • 最后可顺带读一下停止位

6.3 软件模拟串口的本质

软件模拟串口本质上就是:

  • 手动检测起始位
  • 手动延时对齐到位中心
  • 再按固定节拍逐位采样

因此它对以下问题非常敏感:

  • 延时误差
  • 中断打断
  • CPU 调度抖动
  • 波特率偏高导致时序来不及

如果误差过大,就会出现:

  • 丢位
  • 错位
  • 乱码

7. 串口采样机制与误差来源

7.1 为什么要在位中心采样

异步串口没有共享时钟,接收端只能在检测到起始位后,用自己的时钟估算后续每一位的采样时刻。之所以总强调“位中心采样”,是因为位中心离前后边沿最远,最不容易受到 边沿延迟、噪声抖动和双方时钟误差 的影响。

7.2 起始位后的采样流程

接收过程可以理解为:先对齐,再按节奏取样

  • 线路空闲时保持高电平
  • RX 从高变低时,接收端认为可能出现了起始位
  • 先延时约半个 bit 到一个 bit,把采样点对齐到位中心附近
  • 之后每隔 1 个 bit 周期采样一次
检测到下降沿 → 对齐起始位 → 每隔 1 bit 在中心采样一次

7.3 过采样

很多硬件 UART 并不是只在理论采样点“看一眼”,而是使用 过采样,常见有:

  • 8 倍过采样
  • 16 倍过采样

这样做的好处:

  • 更容易定位位中心
  • 抗短时噪声能力更强
  • 接收稳定性更高

可以粗略理解为:

  • 16 倍过采样更稳
  • 8 倍过采样更容易支持高波特率

7.4 为什么波特率误差会导致乱码

最麻烦的地方不是“偶尔采错一次”,而是 误差会持续累积

如果发送端和接收端的实际 bit 宽度略有偏差,那么:

  • 第 1 位时采样点可能只偏一点
  • 第 2 位时继续偏
  • 第 3 位再偏
  • 到后面几位时,采样点可能已经接近位边界

这时只要再叠加一点噪声或抖动,就可能采到相邻位,于是出现乱码、错位或校验错误。

7.5 常见误差来源

类别 典型问题
时钟误差 晶振精度有限、RC 震荡器误差大、温漂
边沿抖动 上升沿/下降沿过慢、毛刺、驱动不足
线缆与干扰 线太长、未共地、电磁干扰强、阻抗不合适
电平标准不匹配 3.3V/5V 混接、TTL 误接 RS232、信号反相

这些问题最终都会反映到波形质量和采样稳定性上,比如:

  • 位宽不稳
  • 边沿发虚
  • 噪声变大
  • 解码失败

7.6 实际排查建议

推荐按下面顺序排查:

  1. 先确认两端参数是否一致:baud rate / data bits / parity / stop bits
  2. 再确认连线是否正确:TX 接 RX、RX 接 TX、GND 共地
  3. 再确认电平标准是否匹配:TTL、RS232 还是 RS485
  4. 若仍有问题,先降低波特率再试
  5. 用逻辑分析仪看协议解码结果
  6. 必要时用示波器看位宽、边沿、毛刺、噪声和过冲
  7. 若怀疑时钟问题,再检查晶振、时钟源或校准配置
📋 速记

  • 串口本质:约定波特率 + 下降沿起帧 + 位中心采样
  • 最常见格式:8N1
  • 最常见问题:参数不匹配、未共地、电平不匹配、波形质量差
  • 调协议先看 逻辑分析仪,查波形质量再上 示波器
ℹ️ 一句话理解串口接收:接收端其实一直在“猜对方的节奏”。只要双方节奏差得不大,且采样点落在位中心附近,就能稳定收对;一旦偏差累积到位边界附近,就很容易出现乱码。

延伸阅读

1.嵌入式通信协议UART、I2C 、SPI、CAN、Modbus总线协议区别