Loading

UART 串口通信

UART 串口通信


1. UART 到底是什么?

UART,全称 Universal Asynchronous Receiver/Transmitter,中文通常叫“通用异步收发器”。

它不是一种复杂总线,而是一种很常见的点对点串行通信方式。在嵌入式开发里,UART 经常用于:

  • MCU 和调试串口模块通信
  • STM32 打印日志
  • MCU 与蓝牙模块、WiFi 模块、4G 模块通信
  • LED 控制器、传感器、仪表设备的数据交互
  • 上位机与单片机之间收发指令

UART 的特点是:

  • 不需要时钟线
  • 发送端和接收端提前约定波特率
  • 数据按 bit 一个一个发出去
  • 结构简单,非常适合调试和低速通信

一句话理解:

UART 就是双方约好节拍,然后一位一位地把数据发出去。


2. UART 常见应用方向

UART 的价值不在于“速度有多快”,而在于它简单、稳定、成本低、调试方便。在嵌入式项目里,它经常承担下面几类工作。

应用方向 典型场景 UART 的作用
调试日志 STM32/ESP32/51 单片机开发 打印程序状态、变量值、错误信息
上位机通信 PC 软件、串口助手、产测工具 收发命令、读取设备状态、配置参数
模块通信 蓝牙、WiFi、4G、GPS、语音模块 MCU 通过 AT 指令或私有协议控制模块
工业/照明设备 LED 控制器、电源控制板、仪表设备 参数设置、运行状态上报、故障诊断
Bootloader 升级 串口 ISP、固件升级工具 通过串口下载程序或更新固件
协议桥接 UART 转 RS-485、UART 转 USB 作为底层收发通道,外面再套 Modbus 等协议

比如在照明电源或 LED 控制器里,UART 可以用来做:

  • 读取输入电压、输出电流、温度、故障码
  • 修改亮度、电流档位、调光参数
  • 做产线烧录、校准和老化测试
  • 和上位机工具通信,方便现场调试
  • 通过 RS-485 收发器扩展成远距离总线通信

所以 UART 不只是“打印 printf 的口”,它也可以是产品调试、参数配置、模块控制和售后诊断的重要接口。


3. 三根线:TX、RX、GND

UART 最容易接错的地方是:TX/RX 不是同名直连,而是交叉连接。

典型连接方式如下:

设备 A                  设备 B
TX   ---------------->   RX
RX   <----------------   TX
GND  -----------------   GND

也就是:

引脚 作用 连接方式
TX Transmit,发送数据 接对方 RX
RX Receive,接收数据 接对方 TX
GND 参考地 双方共地

为什么 TX 要接 RX?

因为 TX 是“我发出去”的线,RX 是“我接收”的线。A 设备发出的数据,应该进入 B 设备的接收端;B 设备发出的数据,也应该进入 A 设备的接收端。

所以不要这样接:

TX ---- TX   ❌
RX ---- RX   ❌

正确记忆:

TX 接对方 RX,RX 接对方 TX,GND 必须共地。

这里还要注意一个工程细节:UART 是逻辑通信方式,不等于 RS-232 或 RS-485。STM32、51、ESP32 这类 MCU 的串口通常是 TTL/CMOS 电平,如果要接传统 RS-232 接口,需要 MAX3232 之类的电平转换芯片;如果要走 RS-485,则需要 RS-485 收发器。


4. 一帧 UART 数据长什么样?

UART 是异步通信,没有单独的时钟线。那接收端怎么知道什么时候开始接收?答案是:靠一帧数据里的 起始位

常见 UART 帧结构如下:

Idle → Start → Data bits → Parity(optional) → Stop
 1       0       D0...D7        可选             1

可以把一帧 UART 数据理解成下面这个过程:

  1. 空闲状态是高电平:Idle = 1
  2. 起始位拉低:Start = 0
  3. 发送 8 个数据位
  4. 可选发送校验位
  5. 停止位回到高电平:Stop = 1

最常见配置是 8N1

名称 含义
8 8 个数据位
N No parity,无奇偶校验
1 1 个停止位

所以 8N1 一帧实际占用:

1 起始位 + 8 数据位 + 1 停止位 = 10 bit

这点很重要。很多人看到 9600 bps,会误以为每秒能发 9600 字节。其实不是。

如果是 9600 bps、8N1:

9600 bit/s ÷ 10 bit/byte ≈ 960 byte/s

也就是说,有效字节速率大约是 960 字节每秒。


5. UART 为什么是 LSB first?

理解 UART 波形时,还要注意一个点:

LSB first:D0 → D7

UART 通常是低位先发。也就是说,一个字节不是从最高位 bit7 开始发,而是从最低位 bit0 开始。

例如发送一个字节:

0x42 = 0b0100_0010

内存里我们习惯写成 bit7 到 bit0:

bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
 0    1    0    0    0    0    1    0

但 UART 线上发送顺序是:

bit0 → bit1 → bit2 → bit3 → bit4 → bit5 → bit6 → bit7
 0      1      0      0      0      0      1      0

所以如果你用逻辑分析仪看 UART 波形,不要直接按肉眼从左到右当作二进制高位到低位读,否则容易读反。


6. 波特率:双方约好的节拍

UART 没有时钟线,所以发送端和接收端必须提前约定一个速度,这个速度就是波特率

波特率可以简单理解为:

每秒传输多少个 bit 时间单位。

公式是:

$$
T_{bit}=\frac{1}{baud}
$$

例如:

$$
T_{bit}=\frac{1}{9600}\approx104.17\mu s
$$

也就是说,9600 bps 时,每个 bit 持续约 104 微秒。

再看常见的 115200 bps:

$$
T_{bit}=\frac{1}{115200}\approx8.68\mu s
$$

波特率越高,每个 bit 的时间越短,对时钟误差、线缆质量、干扰和采样时机的要求也越高。

常见波特率如下:

波特率 每 bit 时间 常见用途
9600 约 104.17 μs 低速设备、稳定调试
38400 约 26.04 μs 中低速通信
115200 约 8.68 μs STM32 日志、调试串口常用
1 Mbps 1 μs 较高速短距离通信

7. 接收端为什么要在 bit 中心采样?

UART 接收端检测到起始位下降沿后,会根据波特率推算后续每个 bit 的采样位置。

理想情况下,它不会在边沿采样,而是尽量在 bit 的中间采样。

原因很简单:边沿附近最容易受这些因素影响:

  • 信号上升沿/下降沿不是无限陡峭
  • 发送端和接收端时钟存在误差
  • 线路有干扰和抖动
  • 电平转换器可能带来延迟

所以中心采样更稳:

bit 时间窗口: |-----------|
采样点:       ^ 中心附近

很多 MCU 的 UART 外设内部会做 8 倍或 16 倍过采样,本质上也是为了更可靠地判断当前 bit 是 0 还是 1。

所以实际通信时要记住这件事:

双方波特率一致,接收端在每个 bit 中心附近采样。


8. STM32 HAL 里 UART 初始化到底在配什么?

在 STM32 HAL 里,UART 初始化通常可以拆成 7 步:

  1. 选择 USART1 + GPIO
  2. 设置 BaudRate = 115200
  3. 设置 WordLength = 8B
  4. 设置 StopBits = 1
  5. 设置 Parity = NONE
  6. 设置 Mode = TX_RX
  7. 调用 HAL_UART_Init(&huart1) 写入外设寄存器

典型代码如下:

UART_HandleTypeDef huart1;

huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;

HAL_UART_Init(&huart1);

这些参数组合起来,其实就是我们常说的:

115200 8N1

含义是:

参数 含义
115200 波特率
8 8 个数据位
N 无奇偶校验
1 1 个停止位

初始化不是“形式代码”,它最终会写入 USART 的 BRR、CR1、CR2、CR3 等寄存器,让硬件真正按这个格式发送和接收。


9. 中断接收:收到数据后要快速放进 Buffer

STM32 HAL 中断接收的常见流程是:

收到数据 → 进入中断 → 写入 Buffer → 重新开启接收

典型代码:

uint8_t rx_byte;

void uart_start_receive(void)
{
    HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1)
    {
        ring_push(rx_byte);
        HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
    }
}

这里有两个重点。

9.1 回调里不要做复杂解析

中断回调应该快进快出,不建议在里面做:

  • 长时间 printf
  • 阻塞式发送
  • 大量字符串解析
  • 复杂协议处理
  • 延时等待

更推荐的方式是:

中断里:只收字节,放入 ring buffer
主循环/任务里:再慢慢解析协议

这样做可以减少丢字节风险,也能让程序结构更清楚。

9.2 HAL_UART_Receive_IT 通常要重新开启

HAL 的中断接收常见用法是一次接收指定长度,比如这里是 1 字节:

HAL_UART_Receive_IT(&huart1, &rx_byte, 1);

当这 1 字节接收完成后,会进入:

HAL_UART_RxCpltCallback(...)

如果你在回调里不重新调用 HAL_UART_Receive_IT,那通常就只能收到第一次数据,后面的数据不会继续进入这个回调。

所以这里要特别强调:

回调末尾要重新打开下一次接收。


10. UART 通信常见问题排查

如果串口不通,可以按这个顺序查:

问题 现象 排查方法
TX/RX 接反 完全收不到 交换 TX/RX 试试
没有共地 数据乱码、不稳定 确认 GND 相连
波特率不一致 乱码 双方都设成 115200 或 9600
帧格式不一致 乱码或偶发错误 确认都是 8N1
电平不匹配 收不到甚至损坏 TTL 不能直接接 RS-232
回调没重开接收 只收到第一个字节 回调里再次调用 HAL_UART_Receive_IT
中断里处理太慢 丢数据 改成 ring buffer 缓存

最实用的排查口诀:

先查线,再查地;
再查波特率和 8N1;
最后查代码有没有重新开启接收。

11. 总结

UART 的核心,其实可以压缩成四句话:

重点 一句话记忆
接线 TX 接对方 RX,GND 要共地
帧格式 Idle=1,Start=0,8N1 最常用
波特率 双方节拍一致,接收端中心采样
接收代码 中断里快进快出,先放 buffer,再重开接收

UART 看起来简单,但工程上很多问题都出在细节:线接错、地没接、波特率不一致、忘记重新开启中断接收。只要把这几个点抓住,绝大多数串口问题都能快速定位。

如果你正在学 STM32 或嵌入式通信,建议先把 UART 搞透。它是调试日志、模块通信、协议解析的基础,后面学 I2C、SPI、CAN、RS-485 都会更顺。

posted @ 2026-05-30 23:12  cc_record  阅读(22)  评论(0)    收藏  举报