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

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

连这文件是什么总线都不知道

在 TT Lab 中继续学习

目标

亲手解码四份 I2C 采集和三份 SPI 采集,最后拿到三份没有标注名称的采集,判断出是哪种总线,并为故障附上数字证据后汇报。

为什么重要

驱动给出的“设备无响应”这一行里,混杂着四种不同的原因:地址错误、从设备在拖延时间、上拉较弱导致上升用尽了整个位时间、总线整个被锁死。这四种在波形上截然不同,修复的人也各不相同——固件、驱动的恢复流程、硬件。如果能从波形上区分,报告中的一行就能点名一位负责人。而 SPI 这边正相反,时钟是一起送来的,所以没有速度问题,取而代之的是,在哪个边沿读取以及选择线何时拉低,会造成悄无声息的故障。

步骤

  1. 读取两线采集 — 测量采样率、上升时间和 SCL 低电平区间的分布。
  2. 找出 START 和 STOP — 只把 SCL 为高期间的 SDA 变化当作条件来统计。
  3. 读出地址和 ACK — 以 9 位为一组还原字节和 ACK。
  4. 无人应答的地址 — 确认 NACK 实际上是什么的缺失。
  5. 不是主设备造成的区间 — 通过长度分布找出时钟拉伸。
  6. 上升用尽了位时间 — 用上升时间和两个阈值证明弱上拉。
  7. 四种组合中是哪一种 — 根据空闲电平和数据变化的边沿确定模式。
  8. 选择线延迟时个数先开口 — 观察 8 的倍数被打破。
  9. 判断三份无名采集 — 连同证据一起汇报总线和故障。

参考

读取两线采集

用 mkdir -p /root/bus-triage 创建工作文件夹,读取 /opt/fixtures/serialbus/i2c/read-ok.csv,把 sample_rate_hz、sample_count、duration_us、channels(头部的通道名称列表)、scl_rise_time_samples、sda_rise_time_samples、scl_low_runs、scl_low_median_us 保存到 /root/bus-triage/levels.json。上升时间是采集文件中最后一次发生的完整上升(从 0.33 V 以下到 2.97 V 以上)所用的采样点数,SCL 低电平区间是从下降的采样点到再次上升的采样点。

0.33 V 和 2.97 V 是 3.30 V 的 10% 和 90%。低电平区间位于下降沿与随后的上升沿之间,直到最后仍保持低电平而结束的区间,长度未知,所以不计入。

找出 START 和 STOP

在 /root/bus-triage/i2c.py 中实现 conditions(scl_v, sda_v, threshold_v=1.65)。在 SCL 连续两个采样点为高期间,SDA 下降则记为 ("start", i),上升则记为 ("stop", i),按时间顺序放入列表后返回。SCL 为低时 SDA 的变化不是条件。把它应用到 read-ok.csv,并把 start_count、stop_count、starts、stops(采样点序号列表)、scl_high_windows(已确认升起后又再次落下的 SCL 高电平区间的个数——直到最后仍保持高电平的区间,长度未知,所以不计入)保存到 /root/bus-triage/i2c-frames.json。

条件判定只在 scl[i] 和 scl[i-1] 都为高时进行。必须排除与边沿重叠的瞬间,才不会把重复 START 误认为数据。评分器会先用简短的测试表来确认。

读出地址和 ACK

把 read-ok.csv 完整解码。在 SCL 高电平区间的正中央读取 SDA 来收集位,每 9 位为一组,前 8 位从高位开始组合成字节,第九位为 0 则视为 ACK。把 bytes(每项包含 value 和 ack)、address(第一个字节右移 1 位)、first_rw、register、second_rw、data(第三个字节之后的值)、repeated_starts(采样点序号列表)、last_byte_acked 保存到 /root/bus-triage/i2c-read.json。first_rw 和 second_rw 是 "read" 或 "write"。

重复 START 不会中断传输,所以要重置数字节的流程,但传输要继续下去。最后一个字节的 NACK 不是故障,而是主设备告知不要再发送的正常流程。

无人应答的地址

把 /opt/fixtures/serialbus/i2c/nack.csv 解码,把 start_n、stop_n、byte_count、address、rw、acked(第九位是否为 0)、data_bytes_after_address(全部字节数减 1)保存到 /root/bus-triage/i2c-nack.json。

在 ACK 位置,主设备会松开 SDA。保持高电平意味着没有人把它拉低,地址错误、没有供电、设备故障看起来都是同样的样子。还请确认主设备随即就发出了 STOP。

不是主设备造成的区间

测出 /opt/fixtures/serialbus/i2c/stretch.csv 中全部 SCL 低电平区间的长度,把 scl_low_runs、median_low_us、max_low_us、stretch_us(最大值减去中位数)、stretch_start_n(最长区间开始的采样点序号)、bytes(解码出的字节列表)、stop_n 保存到 /root/bus-triage/i2c-stretch.json。

使用中位数,是因为平均值会被一个很长的区间拉偏。如果用采样点序号指出变长的区间在第几个字节之后,就能看到从设备是什么时候拖延时间的。

上升用尽了位时间

测量 /opt/fixtures/serialbus/i2c/weak-pullup.csv。把 sda_rise_time_us、scl_rise_time_us(二者都是从 10% 到 90%)、scl_high_windows(与第 2 步的定义相同)、sda_peak_v(每个 SCL 高电平区间内 SDA 最大值的最大值)、ones_at_1v65 和 ones_at_vih(该区间的最大值分别不低于 1.65 V 和 VIH 的区间个数)、vih_v(0.7 乘以 3.30,四舍五入到小数点后两位)、stops_at_1v65 和 stops_at_vih(只改变 SDA 判定阈值时检出的 STOP 条件个数)保存到 /root/bus-triage/i2c-pullup.json。

比较 SDA 和 SCL 的上升时间,就能立即看出哪根线的上拉较弱。如果 STOP 的个数随阈值而变,说明缓慢的上升越过到了 SCL 高电平区间内,看起来像是条件。

四种组合中是哪一种

在 /root/bus-triage/spi.py 中实现 decode(cs_v, sclk_v, mosi_v, miso_v, cpol, cpha, threshold_v=1.65)。CS 为低期间,cpha 为 0 时在走向 (1 减 cpol) 的边沿采样,为 1 时在走向 cpol 的边沿采样。返回 {"edges": 采样的边沿数, "mosi": 字节列表, "miso": 字节列表, "leftover_bits": 边沿数除以 8 的余数},位从高位开始组合。分别判定 /opt/fixtures/serialbus/spi/mode0.csv 和 mode3.csv,把 sclk_idle、cpol、cpha、mode、sampling_edges、mosi_changes_on、mosi、miso 以 mode0 和 mode3 两个块保存到 /root/bus-triage/spi-mode.json。

cpol 是第一个采样点的时钟电平。cpha 由 MOSI 变化的时刻之前紧邻的(8 个采样点以内)时钟边沿的方向决定——那个边沿不是读取的一侧。如果这个方向与空闲电平相同,则 cpha 为 0,不同则为 1。mode 是 cpol 乘以 2 再加 cpha。

选择线延迟时个数先开口

把 /opt/fixtures/serialbus/spi/cs-late.csv 按模式 0 解码,把 cs_low_start、cs_low_end(CS 落下的采样点和再次升起的采样点)、sclk_edges_total(整个采集的时钟边沿数)、sampling_edges_total(它的一半)、sampling_edges_in_cs(CS 为低期间采样的边沿数)、leftover_bits、bytes(MOSI 字节列表)保存到 /root/bus-triage/spi-cs.json。

最先出现异常的是个数,而不是值。如果有边沿落到了 CS 之外,8 的倍数就被打破,出现多余的位。以这种状态组合出的字节,全都错开了一格。

判断三份无名采集

判定 /opt/fixtures/serialbus/unknown/ 下的 capture-1.csv、capture-2.csv、capture-3.csv,把 capture_1、capture_2、capture_3 三个块保存到 /root/bus-triage/triage.json。三个块都包含 channels(通道数)、bus("uart"、"i2c"、"spi" 之一)、fault("polarity-inverted"、"sda-stuck-low"、"miso-idle"、"baud-mismatch"、"address-nack"、"clock-stretch"、"weak-pullup"、"cs-timing"、"mode-mismatch" 之一)。此外,capture_1 还包含 idle_v、baud、text,capture_2 包含 start_count、stop_count、longest_sda_low_us,capture_3 包含 miso_edges_in_cs、miso_bytes、mosi_bytes。

仅凭通道数和空闲电平,总线就几乎可以确定。如果是一条线却空闲为低,请把所有位翻转后再读。如果是两条线却没有 STOP,请看看是什么阻止了 STOP。在四条线中,整个传输期间一次也没有变化的线,本身就是证据。