LSM6DS3 采样契约:104 Hz 与第一条 IRQ 的取舍

作者:

在

结论:LSM6DS3 采集链的风险不在「读不到数」,而在三处静默丢样本与一处未校准语义:一次 os.read() 可能带回多条 GPIO 事件却只处理第一条;启动有效性门从第一次有效性检查起计 0.5 秒,期间样本读取成功但不发布;source 在 ID 核对前是 velodyne 占位值。以下基于固定源码快照 c8fb906 的静态分析与假 SMBus/GPIO 探针,未打开 GPIO/I2C、未在真实芯片上执行自测。

实例、总线与启动顺序

sensord.main() 实例化加速度、陀螺仪、温度三个传感器对象,都指向 I2C bus 1,并以 0x6A 地址访问同一颗 LSM6DS3。每个对象通过 Sensor.__init__() 独立打开 SMBus,寄存器访问经 Linux ioctl,最多读 32 字节块。main() 先逐对象 reset、再逐对象 init;温度类不覆盖 reset,所以真正写复位的是加速度和陀螺仪两个对象。

init() 接受 WHO_AM_I 0x69/0x6A,据此把 source 设为 LSM6DS3 或 LSM6DS3TR-C;加速度设置 104 Hz、默认 ±2g、自动地址递增和 INT1 数据就绪,陀螺仪设置 104 Hz、默认 ±250 dps 和 INT1 数据就绪。三个对象共享芯片寄存器,后续适配不能把它们当作彼此独立的物理器件热插拔。

初始化异常只记录日志,不从 sensors_cfg 中移除该对象,IRQ 线程仍会尝试读取它。自测也不一致:加速度仅在 LSM_SELF_TEST == "1" 时运行,陀螺仪在变量存在时运行、即使值是 0 也会触发;自测中的 _wait_for_data_ready() 没有总超时,芯片不产生 data-ready 时初始化可长时间停在忙等。

中断读取:只处理第一条事件

gpiochip_get_ro_value_fd() 请求 GPIO chip 0、line 84 的双边沿事件;interrupt_loop() 轮询 100 ms,并尝试把 IRQ 336 或 335 绑定到 CPU 1。代码把 time.time_ns() - time.monotonic_ns() 视为内核 GPIO 事件时间到单调时间的偏移,每次中断重新估计,偏移跳变超过 10 ms 时跳过本次事件并更新基准——这个换算依赖内核事件时间戳的时钟域,不能在新内核/驱动上未经核对就复用。

关键取舍在 os.read():它一次请求最多 16 * sizeof(gpioevent_data) 字节,但随后只调用一次 from_buffer_copy(dat) 并处理第一条事件。若内核实际返回多条完整记录,其余事件已从 fd 读走却不进入逐传感器处理,可造成样本稀疏或时间间隔变大。IRQ 还是双边沿,代码没有按 gpioevent_data.id 区分上升/下降沿。

构造输入 观察到的原函数路径 未验证范围
一次读取串接两个 GPIO 事件,时间戳 9100/9200 ns,假时钟偏移 9000 ns 传给传感器的时间只有首条的 100 ns,发布一条消息;第二条未进入处理循环 真实 GPIO 驱动是否一次返回多条、积压率及时间戳时钟域
返回不足一条 gpioevent_data 的字节 from_buffer_copy() 抛 ValueError,异常发生在逐传感器 try 外 真实内核短读条件与线程监督/重启行为
温度传感器连续三次未过启动有效性门 三次读取均执行,但 Ratekeeper.keep_time() 调用数为零 假 Ratekeeper 不测 CPU 占用或 I2C 物理总线频率

样本时间与 0.5 秒有效性门

Sensor.is_data_valid() 从第一次调用有效性检查起计 0.5 秒,而不是从供电或 init() 开始;在此期间样本读取可能成功但不发布。温度由 polling_loop() 按 SERVICE_LIST 的 2 Hz 轮询,LSM6DS3_Temp.get_event() 在读取时以单调时钟自行打时间戳,并按芯片变体使用 16 或 256 LSB/°C。

启动 0.5 秒内温度循环的 continue 位于 Ratekeeper.keep_time() 之前,所以该短阶段可能密集轮询 I2C;不能把它的初期调用频率视为稳定 2 Hz。

数值换算与上下游健康门

每次合格 GPIO 事件用同一个 ts 依次查询加速度和陀螺仪:加速度先检查 XLDA,再读 6 字节三轴有符号 16 位值,按 9.81*2/32768 转 m/s²;陀螺仪检查 GDA 后按 8.75 mdps/LSB 转 rad/s。两者都输出 [y, -x, z],并把换算后的中断时间填入 SensorEventData.timestamp;DataNotReady 被静默跳过,其他 I2C/消息异常只记录。

构造输入 原类观察结果 边界
16 位原始值 0xFFFF、0x7FFF、0x8000 分别解析为 -1、32767、-32768 只验证主机有符号转换,不验证芯片实际量程
首次有效性检查从 t=10.0 开始 恰好 t=10.5 仍为假,超过 0.5 秒才为真 假单调时钟,不证明 IRQ 与初始化时序
WHO_AM_I 正确、错误与空块 正确 ID 返回;错误 ID 触发断言;空块在 [0] 抛 IndexError 若 Python 以 -O 运行,assert 保护可被移除
加速度状态未就绪与三轴原始字节 未就绪抛 DataNotReady;就绪后按 [y,-x,z] 输出,示例 [-9.81,-9.81,0] m/s² 假总线提供完整六字节,短块与真实安装方向未验证
陀螺仪 +1000,-1000,0 与温度原始 16 轴序为 [y,-x,z];两芯片来源分别得 26.0 与 25.0625 °C 假消息只确认计算路径,不验证温度标定

下游 locationd.handle_log() 对加速度/陀螺仪先比较事件内时间与消息 logMonoTime,相差超过 100 ms 时忽略,再核对来源与数值一致性;sensor_all_checks() 要求两路消息有效且最近 0.1 秒有接收。source 在 ID 核对前默认是 velodyne 占位值,而 locationd._validate_sensor_source() 仅排除 bmx055,因此初始化失败后的来源健康不能仅凭消息 valid=True 或该来源门判定。

几处探针细节补充了这一层的取舍:不足一窗时仍会发送原始音频,两个半窗合成一次分析,一次超大回调只留下尾部。Sensor.is_data_valid() 的 0.5 秒是时间门而非数值门,而主机 SMBus 在回包长度为零时会回退请求长度、传感器层不会独立检查数据是否来自新的物理样本——因此传输成功、数据就绪、样本新鲜度与数值有效性应分成不同状态。

常见问题

为什么一次中断可能丢掉样本?

因为 os.read() 一次最多请求 16 条事件结构,但代码随后只处理第一条;其余完整记录已从 fd 读走却不进入逐传感器处理,表现为样本稀疏或时间间隔变大。

启动后前 0.5 秒为什么没有样本发布?

Sensor.is_data_valid() 从第一次有效性检查起计 0.5 秒,而不是从供电或 init() 开始;在此期间样本读取可能成功,但不会发布。

加速度就读绪了,为什么还可能是无效来源?

source 在 ID 核对前默认是 velodyne 占位值,而 _validate_sensor_source() 只排除 bmx055;初始化失败后的来源健康不能只靠消息 valid=True 或该来源门判断。

自测失败会自动恢复芯片寄存器吗?

不一定。探针确认:差值低于阈值且两轮读取正常时,实现会先写寄存器关闭自测/采样再抛异常;但若中途 I2C 读取异常,关闭写入没有执行,且实际芯片是否保留测试状态尚未验证。

延伸阅读

参考资料

  • 项目 02 —— openpilot-research/IMU_IRQ_I2C_SAMPLE_CONTRACT_DEEP_DIVE.md(LSM6DS3 中断、I2C 样本与定位健康契约,含源码行号索引)
  • 项目 02 —— openpilot-research/probes/test_sensord_loop_contract.py(中断/温度循环三项假依赖探针)
  • 项目 02 —— openpilot-research/probes/test_imu_sensor_conversion_contract.py(数值换算与异常五项隔离探针)

说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

更新记录

首次发布 2026-10-11 最近核对:2026-10-11

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注