TT Lab
开始
学习 学习路径 课程

传感器没坏,是板子听不懂

从乱码里把波特率找回来

在 TT Lab 中继续学习

目标

亲手从采集到的采样序列中解码 UART 帧,并在发送时钟错位的采集中重新测出真实的位时间,让可读的句子重新出现。在这个过程中,用数字统计奇偶校验和帧错误各自抓住了什么、漏掉了什么。

为什么重要

UART 没有时钟线。双方必须信任同一个位时间,而验证这种信任的手段,只有每个帧一次的起始位。因此时钟一旦错位,帧就会从后半部分开始崩溃,而症状只会以“字符乱码”的形式上报。本实验就是练习把这类报告变成错位了百分之几这样的数字。关键在于,修正的不是值,而是时间轴,而这条时间轴的依据,已经包含在采集文件里了。

步骤

  1. 为采样序列建立时间轴 — 用头部的采样率把采样点序号换算成时间,并留下概览。
  2. 提高阈值,边沿就会移动 — 实现 rising_edges,并测量两个阈值下检出时间的差。
  3. 编写帧解码器 — 实现 decode,读取距起始沿 (k + 0.5) 个位时间处的采样点。
  4. 读取正常采集 — 在帧错误为 0 的情况下得到句子。
  5. 抽稀采样点,找出判读崩溃的位置 — 以 1、4、10、25、50 的间隔重新解码。
  6. 用标称波特率读取时的症状 — 测量错位采集的帧错误和最短脉冲。
  7. 重新测出位时间并还原句子 — 从粗略值开始,用较长的基线逐步收窄。
  8. 统计奇偶校验漏掉的部分 — 数出悄悄出错的帧有多少个。

参考

为采样序列建立时间轴

用 mkdir -p /root/bus-uart 创建工作文件夹,读取 /opt/fixtures/serialbus/uart/hello-9600.csv,把 sample_rate_hz(从头部读到的值)、sample_count(采样行数)、duration_us(采样点数 x 1,000,000 / 采样率)、min_v、max_v、idle_level(第一个采样点不低于 1.65 V 为 "high",否则为 "low")保存到 /root/bus-uart/survey.json。

头部是以 # 开头的行,sample_rate_hz 就在其中。下一行是列名,再下面是采样点。时间不在文件里,要用采样点序号和采样率计算。

提高阈值,边沿就会移动

在 /root/bus-uart/edges.py 中实现 rising_edges(volts, threshold_v)。返回满足 volts[i-1] 低于阈值且 volts[i] 不低于阈值的所有 i,按升序排列成列表(i 从 1 开始)。然后对 hello-9600.csv 分别应用 1.65 V 和 2.90 V 两个阈值,把 count_1v65、count_2v90、first_1v65、first_2v90、shift_samples(first_2v90 减去 first_1v65)、shift_us 保存到 /root/bus-uart/threshold.json。

如果边沿的斜率是有限的,较高的阈值会更晚才被越过。请确认两个阈值得到的个数相同,只是时间不同。评分器也会用与采集文件无关的简短表格来测试这个函数。

编写帧解码器

在 /root/bus-uart/uart.py 中实现 decode(volts, sample_rate_hz, baud, threshold_v=1.65, parity=None)。找出从空闲(高)落到起始位的边沿 i,把距 i 有 (k + 0.5) 个位时间的采样点用 int() 取整,读作第 k 位。数据为 8 位,从低位开始。把每个帧装入 {"start": i, "value": 字节, "framing_ok": 停止位是否为 1, "parity_ok": 奇偶校验是否匹配} 后以列表返回,并从读取停止位的采样点的下一个位置开始寻找下一个边沿。parity 是 None 或 "even"。

每个位的采样点数是 sample_rate_hz / baud,它不是整数。parity 为 None 时,parity_ok 始终为真。评分器会先用手工构造的测试表(正常帧、停止位为 0 的帧、只翻转了奇偶位的帧)来确认。

读取正常采集

把 hello-9600.csv 按 9600 Bd 解码,把 frame_count、framing_errors、bytes(整数列表)、text(把字节原样连成字符的字符串)保存到 /root/bus-uart/message.json。

帧错误为 0 才算正常。如果不是 0,请重新检查每个位的采样点数或寻找起始沿的条件。

抽稀采样点,找出判读崩溃的位置

把 hello-9600.csv 的采样序列按 1、4、10、25、50 的间隔抽稀(volts[::k]),把各自的采样率设为 rate // k,再按 9600 Bd 重新解码。在 /root/bus-uart/sampling.json 中按此顺序放入 5 个 cases,每项包含 decimate、sample_rate_hz、samples_per_bit、text_ok(字符串是否与未抽稀的结果相同)。并把 text_ok 第一次变为假的间隔写入 first_failing_decimate。

当每个位的采样点数接近 1 时,解码器不会报错,而是给出另一个字符。text_ok 必须是布尔值,而不是字符串 "true"。

用标称波特率读取时的症状

把 /opt/fixtures/serialbus/uart/drift-8n1.csv 按 9600 Bd 解码,把 frame_count、framing_errors、bytes 保存到 /root/bus-uart/drift.json。此外,收集电平发生变化的采样点序号,求出相邻间隔的最小值,写作 shortest_pulse_us,把 1,000,000 / 9600 写作 nominal_bit_time_us,二者之比写作 ratio。

最短的间隔是一个位。这个值与标称位时间相差百分之几,就是这份采集的诊断结果。请确认帧数与正常采集相近,而只有帧错误增加了。

重新测出位时间并还原句子

从 drift-8n1.csv 重新测出位时间。先取边沿间隔的最小值作为粗略值,再逐步拉长基线来收窄——在从第一个边沿起(当前估计值 x 16)个采样点之内,选出最远的边沿,把这个距离除以估计值并四舍五入得到整数,再用这个整数去除,然后把倍数每次扩大 4 倍,重复到最后一个边沿。把结果保存到 /root/bus-uart/measured.json,包含 bit_time_us、baud(1,000,000 除以 bit_time_us)、用该波特率重新解码的 text,以及 framing_errors。

不能一次就按整个长度取整——如果粗略值的误差乘以位数超过 0.5 位,就会选出错误的整数。仅凭粗略值也能读出字符,但评分要求误差在 0.2% 以内,所以不能跳过收窄的步骤。

统计奇偶校验漏掉的部分

把 /opt/fixtures/serialbus/uart/drift-8e1.csv 按偶校验解码两次——一次用重新测出的位时间(这一次是真值),一次用 9600 Bd。把 frames、parity_errors、framing_errors、flagged(两者之一被抓到的帧数)、wrong_bytes(值与真值不同的帧数)、silently_wrong(值不同,但奇偶校验和帧检查都通过的帧数)、text_at_measured 写入 /root/bus-uart/parity.json。

奇偶校验只看 1 的个数的奇偶性。翻转偶数个时会照样通过,所以 flagged 可能小于 wrong_bytes。请把两次解码的结果按帧的顺序配对比较。