运动实现记录

apps/iot 运动判定路径的实现判断与被否方案。AI 产出,未经人类审阅,见上级说明。

每条判断标注依据:[测试] 有对应单元测试,[实测] 有硬件测量并附复现方式,[据称] 未复现、仅留痕。


QMI8658A 的 tap engine 不可用,敲击判定改在 core 侧

[实测] 该器件的轻点引擎在使能瞬间锁住一个不释放的 tap 位、并冻结 TAP_NUM:上电约 0.4 s 起 STATUS1.TAP 恒为 0x02/0x42,TAP_STATUS 冻在 0xB0(TAP_NUM = 0、极性/轴位反复翻转),真实敲击再也进不了寄存器;驱动只在 0→1 上升沿发布事件,锁死位永不回落,发布路径因此永不触发。

排查顺序与被推翻的假设:

  1. ODR 太慢?§10.4 建议 tap 检测 ODR > 200 Hz,旧固件停在 224.2 Hz,一次冲量只落 2–5 个样本。三档窗长(5/9/18)× 两档 UDMThr(0x0190/0x0300)在 224.2 Hz 下行为纹丝不变。
  2. 抬到 896.8 Hz(6DOF 首个高于 500 Hz 的档位),参数回到手册示例 UDMThr = 0x0190、窗长 80 ms,与唯一可工作的公共参照 WaveShare TapDetectionExample 对齐。冲量落面足够宽,行为仍纹丝不变。
  3. 信号与阈值是否成立?在驱动内镜像引擎的逐轴 Alpha 均值与 Gamma 能量,把 sq(平方残差)与 pk(能量)打到台架:真实敲击落在 2.4–4.9 g²,越过 0.8 g² 的 PeakMagThr,能量随后回落到 0.4 g² 以下。信号、阈值、时序全部成立。

卡点在任何参数闸之外,而在状态机本身。同一套数学在 core 的 20 ms 流上成立,于是敲击契约改由 core/src/drivers/motion/recognizer.rs 承担;QMI8658_CAPABILITIES 因此不声明 TAP——把 core 侧算出来的东西记到硬件头上,是能力集唯一的用途(面板要能分辨"报不出来"与"阈值没到")被破坏的方式。

ODR 留在 896.8 Hz 不再是为了敲击,而是给 No-Motion 引擎的样本计窗与陀螺 yaw 诊断。

台架校验清单(前三条针对芯片引擎本身,驱动已不走这条路径,仅供回头验证芯片行为;后三条是仍在用的路径):

  • 敲一下看 TAP 计 1、2T/3T 不动,TR 该拍跳到约 250–4800 mG(峰阈平方根 250 起步,较用力敲击数千到约 4850 剪顶)
  • 快速敲两下 2T 计 1,三下 3T 计 1
  • 拿起-停顿-放回 TAP 不报
  • 落定后 STL 只计 1(不再帧率级自增)
  • 普通拿起/放下(不必甩)LFT/PLC 各计 1
  • 静置 LR 应在几十 mG

敲击检测用原始加速度幅值,不用减掉的重力

[测试] recognizer.rs 覆盖敲击、转身与一次敲击计数。

最初的做法是减掉一个滑动重力估计再比阈值。被否:估计本身的误差会跟着整段运行,而桌面/手的过渡恰好就是被测信号;raw 幅值只在传感器噪声底附近漂移(实测静止摆动 8-22 mG),所以 150 mG 的门限带能同时躲开误差与真实过渡。

同理,SDK 的平滑基线在敲击期间必须冻结。敲击是重力无关的瞬态,让基线去吸收它会留下一个越过安静门限的过冲,把下一次敲击的峰值窗口饿死。


峰值窗口的语义照抄 datasheet 10.1

[测试] recognizer.rs 的 TapPhase 状态机。

窗口不自己发明,按 datasheet 10.1 的 walk-through 衰减语义实现:在窗口内没有跌回安静门限以下的峰值判为按压或转身而不是敲击。这是"排一次敲击"和"长按一个键"在同一次持续里唯一的分界。

SHAKE_ON_MG 的 2.0 g 同理:比较的是矢量残差并数过零次数而非在门限之上的时间——一次敲击(单脉冲)与一次摇晃(每半周一个脉冲)在这里的差别是次数,不是幅度。W3C 参考实现的 2.5 g / 60 Hz 单轴幅值不适用:识别器每 20 ms 才拿到一个样本,峰值被采样漏掉;3 g 摇晃在 5 Hz 处只剩约 2.5 g 残差。

SHAKE_ON_MG 与静止门限 [据称] 未在硬件上复测,数值取自 SR。


加速度低通滤波器实测无关,于是关掉

[实测] 同组手势、aLPF 开/关两次通过。

静止噪声与敲击读数两种配置下完全一致:衰减来自结构与耦合,不来自这个滤波器。

于是 CTRL5 把 aLPF_EN 清掉,保留 896.8 Hz 全带宽——敲击是宽带瞬态,滤波器只会在它到达之前削掉峰。识别器的门限(峰值 250 mG / 安静 150 mG)是在这个未滤波流上标定的,所以改回打开滤波器必须同时重标门限。陀螺滤波器同样关着,因为没有语义读陀螺路径的峰值。


No-Motion 是电平,不是事件

[测试] a_still_bit_held_across_polls_reports_the_transition_once(bsp-esp/src/components/qmi8658.rs)。

引擎在设备静止的每一次轮询上都把 STATUS1.bit6 置起。直接把该位当事件发布,会在一台静止的设备上按帧率持续报 Still。

因此只发布 0→1 的上升沿:位跨轮询保持高就不再报,之后的另一次上升才算新的。

STATUS1 的原始值仍然随帧上行,让面板的 ST 行能区分"锁存的事件位"与"引擎确实在重新抬起"——寄存器一旦在事件解出后被丢弃,这个区分就没了。