UART通信故障排查(上):波特率不匹配、时钟配置错误导致波特率偏差、数据位/停止位/校验位配置错误
UART 通信,说白了就是两个设备之间用一根线串着说话。看起来简单,但实际项目中出问题最多的往往就是它。我调试过不下几十个 UART 故障案例,十有八九都出在三个地方:波特率对不上、时钟算错了、或者数据格式没统一。
这一章咱们就专门聊这三个坑。我会把每个问题的现象、根因、排查手段都掰开揉碎了讲。嗯,你跟着走一遍,以后遇到 UART 哑火或者乱码,心里就有底了。
1. 波特率不匹配——最经典的“哑巴”故障
波特率不匹配,是 UART 通信里最常见的故障。现象很典型:发送端一直在发,接收端却收不到任何数据,或者收到一堆乱码。
为什么会这样?
UART 是异步通信,没有时钟线。接收端必须按照约定的速率去采样数据。如果两边波特率不一致,采样点就会偏移,最终采到错误的数据位。
核心结论:波特率误差超过 ±2% 时,通信基本不可靠。误差超过 ±5% 时,几乎必然丢帧或乱码。
1.1 常见波特率值对比
| 标准波特率 (bps) | 常用场景 | 典型误差容忍度 |
|---|---|---|
| 9600 | 调试串口、低速传感器 | ±2.5% |
| 115200 | 标准通信、蓝牙模块 | ±2.0% |
| 460800 | 高速数据传输 | ±1.5% |
| 921600 | 批量固件升级 | ±1.0% |
我的习惯:调试阶段先用 9600 或 115200,这两个波特率对时钟误差最宽容。等通信稳定了再往上提。
1.2 排查步骤
- 用示波器测 TX 引脚波形——看一个 bit 的宽度。比如 115200 bps,一个 bit 应该是 8.68 µs。如果差太多,就是波特率设置有问题。
- 检查两边的波特率寄存器值——STM32 的 USART_BRR 寄存器决定了分频系数。算一下实际值是否等于目标值。
- 用逻辑分析仪抓一帧数据——看起始位、数据位、停止位的采样点是否在 bit 中间。
我曾经踩过的坑:有一次用 STM32F103 和 GPS 模块通信,波特率设成 115200,但 GPS 模块实际用的是 115200 的“近似值”。示波器一看,两边差了 3.2%。最后我把 STM32 的时钟微调了一下才搞定。
2. 时钟配置错误导致波特率偏差
波特率不匹配,很多时候不是代码写错了,而是时钟树配错了。STM32 的 UART 时钟源可以是 APB 总线时钟(PCLK1 或 PCLK2),也可以是外部时钟。如果 APB 时钟频率和你算的不一样,波特率自然就偏了。
我见过一个案例:工程师把 APB1 时钟设成了 36 MHz,但代码里按 72 MHz 去算 USART_BRR。结果实际波特率只有目标值的一半。通信当然不行。
2.1 时钟树与波特率的关系
STM32 的波特率计算公式:
USART_BRR = UART_CLK / (16 * 目标波特率)
UART_CLK 取决于你用的是哪个 UART 外设:
- USART1、USART6:挂在 APB2 上,时钟源通常是 PCLK2
- USART2、USART3、UART4、UART5:挂在 APB1 上,时钟源是 PCLK1
关键点:APB1 和 APB2 的时钟频率可能不同。比如 STM32F4 系列,APB1 最高 42 MHz,APB2 最高 84 MHz。如果你把 USART2 的时钟当成 84 MHz 去算,波特率会偏一倍。
2.2 排查时钟配置的步骤
- 读 RCC 寄存器——确认 HCLK、PCLK1、PCLK2 的实际值。
- 核对 USART_BRR 的计算值——用实际时钟频率重新算一遍。
- 用示波器测 TX 引脚——验证实际波特率是否等于目标值。
我建议:在初始化代码里加一句打印,把 SystemCoreClock 和 USART_BRR 的值输出到调试串口。这样一眼就能看出时钟有没有配错。
2.3 常见时钟配置错误
| 错误类型 | 现象 | 根因 |
|---|---|---|
| APB 分频系数设错 | 波特率偏整数倍 | PCLK 实际值比预期高或低一倍 |
| HSE 或 HSI 频率不对 | 所有外设时钟都偏 | 外部晶振或内部振荡器频率偏差 |
| PLL 配置错误 | 系统时钟频率异常 | PLL 倍频/分频系数算错 |
| UART 时钟源选错 | 波特率完全不对 | 选了 LSE 或 HSI 但代码按 PCLK 算 |
我曾经遇到过一次:客户用 STM32L4 做低功耗产品,UART 时钟源选了 LSE(32.768 kHz),但代码里按 4 MHz 去算 USART_BRR。结果波特率差了 120 多倍。查了两天才发现是时钟源选择的问题。
3. 数据位/停止位/校验位配置错误
波特率对了,时钟也对了,但通信还是不行?那多半是数据格式没对上。UART 的数据帧结构包括起始位、数据位、校验位和停止位。两边必须完全一致才能正常通信。
3.1 数据帧结构
一个标准的 UART 数据帧:
起始位(1 bit) + 数据位(5~9 bit) + 校验位(0/1 bit) + 停止位(0.5/1/1.5/2 bit)
最常见的配置是:8 数据位 + 无校验 + 1 停止位(8N1)。但很多设备会用 7E1(7 数据位 + 偶校验 + 1 停止位)或 8E1。
3.2 配置不一致的典型现象
| 配置差异 | 现象 |
|---|---|
| 数据位长度不同 | 接收端每帧都少或多一个 bit,数据整体偏移 |
| 校验位不匹配 | 接收端校验失败,丢弃数据或触发错误中断 |
| 停止位长度不同 | 接收端采样点偏移,偶尔丢帧 |
排查方法:用逻辑分析仪抓一帧数据,数一下 bit 数。起始位是低电平,停止位是高电平。数清楚数据位有几个 bit,校验位有没有,停止位多长。然后跟代码里的配置对比。
3.3 STM32 的配置代码示例
// 8N1 配置
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
// 7E1 配置
huart1.Init.WordLength = UART_WORDLENGTH_7B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_EVEN;
// 8E2 配置
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_2;
huart1.Init.Parity = UART_PARITY_EVEN;
我的习惯:写代码之前先看对方设备的数据手册,确认它用的数据格式。不要想当然用 8N1。很多工业传感器和 PLC 用的是 7E1 或 8E1。
3.4 校验位带来的“隐藏坑”
校验位有一个容易忽略的地方:当使能校验位时,数据位长度会变化。比如 STM32 的 HAL 库,如果设置 WordLength = 8B 且使能校验位,实际传输的是 8 位数据 + 1 位校验。但有些芯片会把校验位算在数据位里,变成 9 位数据(含校验)。
嗯,这里要注意。我遇到过两次这种问题,都是因为两边对“数据位长度”的理解不一致。
避坑指南:如果对方设备手册写的是“8 bit data + even parity”,在 STM32 里要设 WordLength = 9B 且 Parity = Even。因为 STM32 把校验位算在 WordLength 里。这一点跟很多其他 MCU 不一样。
4. 知识体系总览
下面这张图把 UART 通信故障的三个核心维度串起来了。你可以把它当成排查时的路线图。
5. 总结一下
UART 通信故障排查,说白了就是三板斧:
- 波特率——用示波器测 bit 宽度,确认两边一致
- 时钟——核对 RCC 配置,确保 USART_BRR 计算正确
- 数据格式——用逻辑分析仪抓帧,数清楚数据位、校验位、停止位
这三步走完,90% 的 UART 问题都能定位。剩下的 10% 可能是硬件电气问题(比如电平不匹配、信号干扰),那个咱们下一章再聊。
最后说一句:调试 UART 的时候,别急着改代码。先拿示波器看一眼波形。波形不会骗人,代码可能会。