基于边沿采样的 RS485 帧格式自识别解码方法

概述

本方法用于将 RS485 总线电平翻转边沿的时间序列还原为原始字节流,在RS485多主多从总线仲裁的基础上实现波特率自适应、帧格式自识别的无侵入总线数据监测。

效果演示:

硬件基础

边沿捕获

解码的数据来源是定时器捕获的连续边沿时间戳。当 RS485 总线上发生电平翻转(上升沿/下降沿)时,触发定时器中断,记录当前计数值存入环形缓冲区。

边沿跳变: ‾‾\__/‾‾\__/‾‾\__/‾‾‾‾‾‾‾‾‾‾‾\__/‾‾
↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
时间序列: t0 t1 t2 t3 t4 t5 t6 t7

每个字节至少产生一个起始位下降沿,加上数据位内部的翻转,一个字节通常产生多个边沿。

最小位宽

采样过程中会对边沿间隔做最小簇均值滤波

  1. 记录每个边沿与上一个边沿的时间差,记作脉宽 Wₑ
  2. 首个脉宽误差较大,直接丢弃。
  3. Wₑ < Wᵦ * (0.5 + 0.1):发现更小的边沿间隔,重置位宽加权平均值 Wᵦ
  4. Wₑ < Wᵦ * (1.0 + 0.1):视作等宽的边沿间隔,纳入位宽加权平均值 Wᵦ

这样得到一个零样本的先验位宽均值 Wᵦ,单位为定时器时钟周期数。

若前 X 个字节是以 81O 发送的连续 0xF0 或者以 81E 发送的连续 0xE0 或其他类似的情况,在位宽推测期间得到的最小脉宽与实际位宽不符,则无法正确解码。

8/1/O
0 0 0 0 0 1 1 1 1 1 1 F0
0 0 0 0 0 1 1 1 1 1 1 F0
0 0 0 0 0 1 1 1 1 1 1 F0
0 0 0 0 0 1 1 1 1 1 1 F0
0 0 0 0 0 1 1 1 1 1 1 F0 会被错误解码为 22222/8/1/N 的 0x55 (0 1 0 1 0 1 0 1 0 1)

8/1/E
0 0 0 0 0 0 1 1 1 1 1 E0
0 0 0 0 0 0 1 1 1 1 1 E0
0 0 0 0 0 0 1 1 1 1 1 E0
0 0 0 0 0 0 1 1 1 1 1 E0
0 0 0 0 0 0 1 1 1 1 1 E0 会被错误解码为 22222/8/1/N 的 0x55 (0 1 0 1 0 1 0 1 0 1)

解码流程

完整解码经过以下几个阶段:

波特率匹配

建立一张常用波特率表:

波特率 理论位宽
300 3333
600 1667
1200 833
2400 417
4800 208
9600 104
19200 52
38400 26
57600 17
115200 9

理论位宽 = Round(定时器频率(1MHz) / 波特率)

将实测的位宽以 ±1% 容错率与上表匹配,匹配成功则用理论位宽替换实测值,失败则用实测值继续。这样既能适应非标准波特率,又能规整常见波特率的位宽,提升后续解码准确率。

时间戳对齐

输入:原始边沿时间序列
输出:规整边沿时间序列

目的:将受硬件噪声、布线延迟等影响的原始时间戳”对齐”到理想位宽的整数倍位置上。

为什么要对齐?

下图中橙色折线是采样边沿,蓝色折线是理论边沿。看似误差不大,但随着时间积累,误差线性递增,会导致后续的位宽步进扫过时无法正确判断边沿位置,最终导致比特流重建失败。

虽然边沿误差会越来越大,但是脉宽误差却是比较稳定的,通过规整脉宽将采样边沿对齐到最近的位宽整数倍位置,可以大幅提高解码准确率。

WE=Round(We/Wb)×Wb        We=tntn1

比特流重建

输入:理想边沿时间序列
输出:二进制比特流数组

目的:按理想位宽步进扫过时间轴,在每个理想位宽位置判断是否有真实边沿落在容差范围内。有边沿则翻转比特值,无边沿则保持。目的是补全缺失的边沿位置(如连续多个相同电平位),确保时间轴是均匀的位宽步进。

0 0 1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 1 0 1 1
0 1 1 0 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0
0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 1 1 0 1 1 0 1 0 0 0 1 0 0 1 1 1 0 1 1 1 0 0 0 0 0 1 1 0 1
1 0 0 1 1 0 0 0 1 0 0 0 1 0 1 1 0 0 1 1 0 0 0 1 0 1 1 0 0 1 1 0 1 0 1 0 1 1 0 0 0 1 0 1 1 0 0 1 1 0 0 0
1 0 0 0 1 0 0 0 1 0 0 1 0 1 0 1 0 1 1 0 0 0 1 0 1 0 1 0 0 1 1 1 1 1 0 0 1 1 0 1 0 0 0 1 1 # # # # # # #

帧格式识别

从重建的比特流中自动推断 UART 帧格式(数据位数、是否有校验位、校验位类型),为后续比特流还原成字节提供依据。

[起始位(0)] [数据位(5/6/7/8/9)] [校验位(N/O/E)] [停止位(1/2/1.5)]

候选格式

由于数据位和停止位自动识别的成功率不高且应用较少,目前只考虑 8 数据位和 1 停止位的情况。

候选 数据位 校验位 帧长度 帧布局
81N 8 0 10 S(0) | D0‥D7 | S(1)
81P 8 1 11 S(0) | D0‥D7 | P | S(1)

评分体系

第一轮评分不区分奇/偶,只判断校验位是否存在。第二轮再用奇/偶校验公式推测校验类型。

分值 = 起始位匹配帧数 × 10  +  停止位匹配帧数 × 2  +  校验位规律帧数 × (6 or 1 or 0)
项目 权重 检查条件
起始位=0帧数 ×2 每帧必查
停止位=1帧数 ×10 每帧必查
校验位有规律 +6 有校验位且停止位有效
校验位无规律 +1 有校验位且停止位有效

取样帧数:平衡样本量与计算量,最多检查前 8 帧。

真实格式 81N 得分 81P 得分
81N = N × 12 ≈ N × 05
81P ≈ N × 05 = N × 18

只要 N ≥ 1,错误候选的分差远大于正确候选,格式识别非常可靠。

字节流提取

按照上面识别出来的帧格式,逐帧校验:

  • 起始位必须为 0
  • 停止位必须为 1

通过校验后,提取数据位,组装为字节值:

M1: (115200/8/1/E) (9±1) 20:
0 0 1 1 1 1 1 1 1 1 1 FE
0 0 1 1 1 1 1 1 1 1 1 FE
0 0 1 1 1 1 1 1 1 1 1 FE
0 0 1 1 1 1 1 1 1 1 1 FE
0 0 0 0 1 0 1 1 0 1 1 68
0 1 0 0 0 0 0 0 0 1 1 01
0 0 0 0 0 0 0 0 0 0 1 00
0 0 0 0 0 0 0 0 0 0 1 00
0 0 0 0 0 0 0 0 0 0 1 00
0 0 0 0 0 0 0 0 0 0 1 00
0 0 0 0 0 0 0 0 0 0 1 00
0 0 0 0 1 0 1 1 0 1 1 68
0 1 0 0 0 1 0 0 0 0 1 11
0 0 0 1 0 0 0 0 0 1 1 04
0 1 1 0 0 1 1 0 0 0 1 33
0 1 0 1 0 1 1 0 0 0 1 35
0 0 0 1 0 1 1 0 0 1 1 34
0 1 0 1 0 1 1 0 0 0 1 35
0 1 1 1 0 1 1 0 1 0 1 B7
0 0 1 1 0 1 0 0 0 1 1 16

SL: (115200/8/1/E) (9±1) 23:
0 0 1 1 1 1 1 1 1 1 1 FE
0 0 1 1 1 1 1 1 1 1 1 FE
0 0 1 1 1 1 1 1 1 1 1 FE
0 0 1 1 1 1 1 1 1 1 1 FE
0 0 0 0 1 0 1 1 0 1 1 68
0 1 0 0 0 0 0 0 0 1 1 01
0 0 0 0 0 0 0 0 0 0 1 00
0 0 0 0 0 0 0 0 0 0 1 00
0 0 0 0 0 0 0 0 0 0 1 00
0 0 0 0 0 0 0 0 0 0 1 00
0 0 0 0 0 0 0 0 0 0 1 00
0 0 0 0 1 0 1 1 0 1 1 68
0 1 0 0 0 1 0 0 1 1 1 91
0 1 1 1 0 0 0 0 0 1 1 07
0 1 1 0 0 1 1 0 0 0 1 33
0 0 0 1 0 1 1 0 0 1 1 34
0 0 0 1 0 1 1 0 0 1 1 34
0 1 0 1 0 1 1 0 0 0 1 35
0 1 1 0 0 1 1 0 0 0 1 33
0 0 0 1 0 0 0 1 0 0 1 44
0 1 0 1 0 1 1 0 0 0 1 35
0 1 0 1 0 0 1 1 1 1 1 E5
0 0 1 1 0 1 0 0 0 1 1 16