起因
一块新的 STM32 开发板,UART 通讯怎么都不通。串口助手收到的全是乱码。
我以为是波特率的问题,改了 9600、115200、460800,全是乱码。我以为是时钟配置的问题,检查了 RCC 寄存器,HSE 赫兹没错。
花了两天,最后发现原因让人哭笑不得。
第一层:物理层
先确认硬件没问题。用示波器量 TX 引脚:
示波器显示:方波,周期约 86.8μs
1 / 86.8μs ≈ 11520 Hz?不对,应该是 115200 Hz
等等,这个波形的频率是 115200 Hz 的 十分之一。
检查时钟树:PLL 输出 72MHz,APB1 总线时钟 = 72MHz / 2 = 36MHz,USART2 挂在 APB1 上。
但我在代码里写的是:
// 错误:使用了错误的时钟源
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
// 但初始化时用的是 SystemCoreClock(72MHz)来算波特率
第二层:波特率计算
STM32 的 UART 波特率是这样算的:
BaudRate = fCK / (16 * USARTDIV)
其中 fCK 是 USART 的输入时钟。如果 USART2 挂在 APB1(36MHz),但代码用 72MHz 去算,波特率就差了一倍。
// 修正
uint32_t apb1_clock = 36000000; // 36MHz
uint32_t usartdiv = apb1_clock / (16 * 115200);
改完之后……还是乱码。
第三层:接线
用万用表量了一下 TX 和 RX 的连通性:
TX (PA2) → 转接板 TX ✓
RX (PA3) → 转接板 RX ✓
GND → GND ✓
VCC → 3.3V ✓
看起来没问题。但我没注意到转接板上的 TX/RX 标签是 相对于 USB 端 的。
实际连接应该是:
MCU TX → 转接板 RX(不是 TX)
MCU RX → 转接板 TX(不是 RX)
换了一下,还是乱码。
第四层:电平
示波器显示的逻辑电平:
高电平:3.3V ✓
低电平:0.2V ✓
没问题。但转接板是 RS232 电平的吗?不,它是 USB-TTL 的,3.3V 逻辑。
第五层:数据格式
我突然想到,串口助手的配置是 8N1(8 数据位,无校验,1 停止位):
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
完全匹配。但等等——我的代码里还调用了另一个函数:
// 这行把数据位改成了 9 位!
USART_WordLengthConfig(USART2, USART_WordLength_9b);
这是之前调试时留下的代码,被我忘了。删掉它。
第六层:中断优先级
即使轮询模式能发数据,中断模式也未必能正常收发。检查 NVIC 配置:
// USART2 的中断优先级设置对吗?
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
配置没问题。但 SysTick 的优先级也是 0,可能存在优先级竞争。
第七层:真相
最终的最终,我用逻辑分析仪抓了完整的一帧数据:
起始位: 0
数据位: 0 1 0 0 1 0 0 0 ← 这是 'H' 的 ASCII(0x48)的 **反序**
停止位: 1
数据是对的!只是逻辑分析仪显示的是 LSB-first,而串口助手解析成了 MSB-first。
所以数据一直是正确的,串口助手的配置才是罪魁祸首。
串口助手默认的"数据位顺序"选项被设成了 MSB first,而 UART 协议是 LSB first。
最终修复
把串口助手的"LSB/MSB"选项改成 LSB first,一切正常。
整个过程中,硬件没问题、时钟没问题、波特率没问题、接线没问题、数据格式没问题——只有一个显示工具的配置选项错了。
教训
- 调试通讯问题时,先用逻辑分析仪确认物理层数据
- 不要相信任何工具的默认配置
- 在你怀疑代码之前,先怀疑你自己
- 保存好每一步的示波器截图,方便事后对比
- 最难的 bug 往往是最简单的疏忽