PocketOracle 的核心交互是「摇一摇出签」——摇晃不直接唤醒设备,但在亮屏的 换签窗口里,摇一下就能换一张签。检测摇晃的是一颗 SC7A20 三轴加速度计 (国产芯片,LIS2DH 兼容寄存器),挂 I2C 总线。这篇文章记录从上电探测到运动 中断稳定的全过程,重点是那个差点把人逼疯的坑:不接高通滤波器,重力会让 中断永远锁死。
上电:先确认芯片在
SC7A20 有两个可选 I2C 地址(0x19,SDO 接高;0x18,SDO 接低)。这颗板子的 SDO 悬空,电平不确定,所以驱动把两个地址各试一遍,用 WHO_AM_I(寄存器 0x0F,固定 返回 0x11)确认身份:
const uint16_t addrs[] = { 0x19, 0x18 }; // SDO 悬空,两个地址都试
for (int i = 0; i < 2; i++) {
// 挂设备 → 读 WHO_AM_I → 等于 0x11 就找到了,否则摘掉换下一个
}比「芯片不在」更隐蔽的是「总线上不去」。bringup 时设备怎么都扫不到,最后发现 外部 4.7k 上拉电阻根本没起作用——只有打开 ESP32 的内部上拉之后,SC7A20(0x19) 和同一条总线上的 RX8025T 时钟芯片(0x32)才有 ACK:
i2c_master_bus_config_t bus = {
// ...
.flags.enable_internal_pullup = true, // 外部上拉缺失,内部上拉顶上
};内部上拉驱动 100 kHz 的 I2C 实测稳定;想上 400 kHz 之前,还是先把外部上拉补好。
运动检测的寄存器链路
SC7A20 的运动中断是一套 AOI(方向/事件检测)逻辑,从采样到 INT 引脚要过七道 配置。最终生效的是 INT1 一路,负责亮屏窗口内的「摇一摇换签」;旧板曾把同样 的配置复用到 INT2 做深度睡眠唤醒,新板改版后这条路已随摇晃唤醒一起移除:
| 寄存器 | 值 | 作用 |
|---|---|---|
| CTRL1 (0x20) | 0x2F | ODR=10 Hz 低功耗模式,XYZ 三轴使能 |
| CTRL2 (0x21) | 0x01 | 高通滤波器(HPF)接入 AOI 通路——本文主角 |
| INTx_THS (0x32/0x36) | 0x30 | 触发阈值,约 770 mg(2 g 量程) |
| INTx_DUR (0x33/0x37) | 0x05 | 持续 5 个采样 @ 10 Hz = 500 ms 才算数 |
| INTx_CFG (0x30/0x34) | 0x2A | XYZ 高事件,OR 组合(任一轴超标即算) |
| CTRL5 (0x24) | bit3/bit1 | LIR:事件锁存,读到 SRC 才释放 |
| CTRL3 / CTRL6 | 0x40 / 0x20 | 把 AOI1/AOI2 路由到 INT1/INT2 引脚 |
阈值和时长的取舍是这套配置的灵魂:
- THS = 0x30 ≈ 770 mg:放桌上敲键盘、隔壁关门这类低于 500 mg 的振动全部 过滤,用手腕摇一下(轻松超过 1 g 的变化)才触发;
- DUR = 5 @ 10 Hz:单次尖峰——比如设备掉在桌上的瞬间——不触发,持续 500 ms 的运动才被认定为「摇晃」。
坑:重力是最大的干扰源
第一版配置没开 HPF(CTRL2 保持 0),现象很诡异:设备一进深度睡眠,INT 立刻变高; 醒来清掉锁存,下一轮又锁,永远消停不下来。
翻寄存器手册才想明白:AOI 检测的是「加速度是否超过阈值」,而静止平放时 Z 轴永远 扛着 +1 g 的重力,770 mg 的阈值根本罩不住它——事件条件恒成立,锁存清一次 立一次。
解法就一行:把高通滤波器接进 AOI 通路(CTRL2 = 0x01)。HPF 把静态重力分量滤掉, 只剩运动产生的动态变化,事件条件就只对真实的加速度变化成立了。代价是重写配置后 HPF 需要几百毫秒稳定,进睡前要留出这段时间,否则第一下是误触发。
这个坑的通用教训:加速度计的「静止」在原始数据里不是零,是 1 g。任何基于 绝对阈值的判断,都要先想清楚重力去哪儿了。
锁存与释放:LIR 机制
CTRL5 的 LIR 位决定事件的处理模型:不锁存时 INT 是内部定时的脉冲;开启 LIR 后, INT 电平保持到软件读 SRC 寄存器为止。睡眠唤醒场景必须开 LIR——芯片要在主控睡觉 期间把事件「攒住」,等主控醒来确认:
esp_err_t sc7a20_clear_irq_int1(void)
{
uint8_t src;
return rd(REG_INT1_SRC, &src); // 读 SRC 即释放锁存
}释放时机也有讲究:换签窗口里检测到 INT1 变高之后,要先清锁存再继续等下一 次,否则同一个事件会被反复读到。项目里的做法是「边沿触发 + 触发后等 INT1 回低,期间不停清锁存」,保证一次摇晃只换一张签。
睡眠时让传感器活着
深度睡眠时 ESP32-C3 的 GPIO 会掉配置,但 SC7A20 必须继续采样攒事件。做法是把
I2C 两个脚用 gpio_hold 钉在当前电平,再打开深度睡眠期间的全局 hold:
gpio_hold_en(PIN_I2C_SDA);
gpio_hold_en(PIN_I2C_SCL);
gpio_deep_sleep_hold_en();对应的坑在唤醒侧:hold 的状态会活着穿过唤醒,重新初始化 I2C 之前必须先把两个脚
释放掉(gpio_hold_dis),否则总线被 hold 卡死,第一次唤醒之后的每次 I2C 访问
都「not responding」。这个 bug 的表象是「设备第二次摇晃就失灵」,非常误导。
小结
到这里整条链路稳定运行:摇晃 → 传感器锁存事件 → 主控在换签窗口里读到 INT1 电平,读 SRC 释放锁存 → 换一张签;设备放桌上,HPF 滤掉重力、阈值滤掉环境 振动,可以安安静静睡大觉。加速度计运动检测的配置项看着多,真正要调的只有 三个数:阈值、时长、HPF 开关——剩下的都是把它们正确地写进正确的寄存器。