4、UART波形反推:从起始位、数据位、校验位、停止位的波形,反推波特率、数据格式以及发送/接收函数

说实话,UART 波形反推这件事,是我当年刚入行时被面试官考过的一道题。那时候我盯着示波器上的波形,脑子一片空白。后来做嵌入式调试多了,才发现这其实是基本功——你手里没有源码,只有一根逻辑分析仪的线,照样能把对方的通信协议扒个底朝天。

这一章,我就带你从波形入手,一步步反推出波特率、数据格式,甚至能猜出发送和接收函数是怎么写的。

4.1 先认识 UART 波形的“骨架”

UART 是异步串行通信,没有时钟线。全靠双方约定好波特率,才能正确采样。波形长什么样?我直接说结论:空闲时,TX/RX 线都是高电平。一旦开始传输,先拉低一个位的时间,这就是起始位。然后依次送出数据位(LSB 或 MSB 在前),接着是可选的校验位,最后拉高至少一个位的时间,作为停止位。

你想想看,如果示波器上抓到一段波形,怎么下手?

核心步骤:

  1. 找到起始位(从高电平跳变到低电平的那个下降沿)
  2. 测量一个数据位的宽度(即波特率的倒数)
  3. 数一数数据位有多少个(通常是 8 位,但也有 7 位或 9 位)
  4. 看有没有校验位,以及停止位是 1 位、1.5 位还是 2 位

我在项目中遇到过一种情况:对方用了 7 位数据 + 偶校验 + 2 位停止位。当时我按 8N1 去解析,结果全是乱码。后来仔细量了停止位宽度,才发现多了一倍。嗯,这就是波形反推的价值。

4.2 反推波特率:用示波器量一个位的时间

波特率怎么算?说白了,就是 1 秒内能传多少个位。你只需要量出一个数据位的时间宽度,然后取倒数就行。

举个例子:假设你在示波器上看到起始位之后的第一个数据位,宽度是 104 微秒(μs)。那么:

波特率 = 1 / (104 × 10⁻⁶) ≈ 9615 bps

这接近 9600 bps,是标准波特率。如果量出来是 8.68 μs,那就是 115200 bps。我个人的习惯是:多量几个位取平均值,因为示波器触发可能有抖动。

小技巧: 如果你用逻辑分析仪,直接让软件自动测量位宽,更准。但用手持示波器时,记得把时基调到能看清一个位的细节,别用太粗的时基。

4.3 反推数据格式:数据位、校验位、停止位

拿到波特率之后,下一步就是看数据格式。我一般按这个顺序来:

4.3.1 数数据位个数

从起始位之后的第一个跳变开始,数到停止位之前。常见的是 8 位,但有些老设备用 7 位。怎么判断?看波形上有没有明显的“长高电平”或“长低电平”。如果数据位之后紧跟着一个校验位,那数据位可能只有 7 位。

4.3.2 判断校验位类型

校验位分三种:无校验(N)、奇校验(O)、偶校验(E)。怎么区分?

  • 无校验:数据位之后直接就是停止位,没有额外的位。
  • 偶校验:校验位使得数据位 + 校验位中“1”的总数为偶数。
  • 奇校验:校验位使得“1”的总数为奇数。

我曾经调试过一个工业传感器,它用了奇校验。我一开始按无校验去读,每帧都错。后来用逻辑分析仪抓了波形,手动算了一下“1”的个数,才发现是奇校验。避坑指南:如果你发现数据偶尔对、偶尔错,先怀疑校验位配置

4.3.3 确定停止位长度

停止位是拉高的时间。量一下从最后一个数据位(或校验位)结束到下一帧起始位下降沿之间的高电平宽度。如果是 1 位宽,那就是 1 位停止位;如果是 1.5 位或 2 位宽,那就要相应调整。

注意: 有些芯片在发送完停止位后,会立即拉低开始下一帧。这时候停止位宽度可能看起来很短。别慌,多抓几帧对比一下。

4.4 反推发送/接收函数:从波形看代码逻辑

波形不仅能告诉你参数,还能透露代码是怎么写的。我总结了几种常见模式:

波形特征 可能的代码实现
连续发送,帧间间隔固定 使用 DMA 或中断方式,没有软件延时
帧间间隔很长且不固定 可能是轮询发送,CPU 被其他任务打断
起始位之前有短暂的低电平毛刺 GPIO 初始化时没有先拉高,或者上拉电阻没配
停止位之后立即拉低 可能是连续发送模式,或者发送缓冲区没清空

举个例子:有一次我抓到一个设备的 TX 波形,每发完一帧,停止位之后会有一个 2ms 的高电平,然后才发下一帧。我推测它的发送函数里有一个 delay_ms(2)。后来反编译固件,果然找到了一个 HAL_Delay(2)。你看,波形会说话。

4.5 实战:手把手反推一个真实波形

假设你抓到下面这段波形(用文字描述):

空闲高电平 → 下降沿 → 低电平持续 104μs → 高低变化 8 次 → 高电平持续 104μs → 空闲高电平

我们来一步步反推:

  1. 起始位:下降沿之后的 104μs 低电平,就是起始位。
  2. 波特率:1 / 104μs ≈ 9600 bps。
  3. 数据位:8 次高低变化,说明是 8 位数据。假设 LSB 在前,那么第一个数据位就是最低位。
  4. 校验位:数据位之后直接是高电平,没有额外的位,所以是无校验(N)。
  5. 停止位:高电平持续 104μs,正好是一个位宽,所以是 1 位停止位。

结论:这是一个 9600 8N1 的 UART 帧。发送函数大概率是标准的 HAL_UART_Transmit() 或类似的库函数。

我的建议: 平时调试时,养成用逻辑分析仪抓 UART 波形的习惯。别只盯着串口助手看十六进制数。波形能告诉你硬件层面的问题,比如电平不匹配、波特率漂移、帧格式错误。这些是软件层面看不到的。

4.6 总结一下

UART 波形反推,说白了就是三步:量位宽、数位数、看校验。只要掌握了起始位、数据位、校验位、停止位在波形上的特征,你就能像读摩斯电码一样读懂 UART。我见过不少工程师,遇到通信问题第一反应是改代码,其实先看看波形,往往能省下半天时间。

下一章,我会讲 SPI 波形的反推。SPI 比 UART 多一根时钟线,但反推起来反而更直观。到时候见。