SC7A21三轴加速度计:从fF电容检测到中断计步的工程解析
简介:这份SC7A21中文数据手册面向嵌入式硬件、驱动开发与运动传感应用人员,聚焦士兰微三轴数字加速度计的选型、寄存器配置与系统集成,也可为手机平板、室内导航、图像旋转、智能穿戴等场景提供器件依据。文档围绕±2G/±4G/±8G/±16G四档量程、12bit数据输出、I²C/SPI接口及1.71V—3.6V宽压低功耗设计展开,并给出6D/4D方向、自由落体、单击双击、睡眠唤醒等中断检测,以及计步、抬手识别、FIFO、自测试和倾斜校准的实现说明。压缩包仅含1个PDF文件,约2.07MB,正文共39页,除功能概述外还收录管脚定义、内部框图、极限参数、机械与电气参数、I²C/SPI时序和寄存器描述,便于对照原理图与驱动代码逐项核对地址、中断映射及初始化流程。已有1150人学习,适合需要快速掌握芯片配置、低功耗策略与运动事件处理的工程师查阅。
1. 一颗加速度计到底在测什么:从 pF 到 fF 的运动学翻译
把手机横过来,屏幕自动旋转;手环抬腕亮屏——这些动作的起点都是一次运动学测量:器件必须先知道重力向量在自己坐标系里指向哪。SC7A21 这类三轴微机械加速度计看起来只是“输出三个数”,真正反直觉的是它的输入:静止时敏感结构的电容是 pF 量级,1g 加速度拽出来的变化只有 fF 量级,也就是千分之一。整颗芯片的设计难点,全压在这千分之一怎么被低噪声地放大、量化,再翻译成 12 位补码。
值得搞懂它,是因为现场 90% 的“数据不对”根本不是算法问题,而是量程档位、ODR、中断阈值、总线时序这些底层参数没对齐。把这份手册里的几条主线理顺——电容链路怎么走、地址自增怎么省时钟、中断状态机怎么配阈值和时间——你会发现那些看起来玄学的漂移和抖动,其实都有明确的物理出处和寄存器出处。
2. 传感链条:fF 级电容失衡怎么变成 12 位补码
2.1 质量块、差分电容与那一级电荷积分
机械部分的结构很朴素:一块悬空的质量块通过锚点挂在硅框架上,可以在三维空间里自由移动,外面做盖帽保护,防止封装注塑时把机械结构压坏。质量块和固定电极之间构成差分电容,稳定状态下这个电容是 pF 级;一旦有加速度,质量块相对固定端产生位移,差分电容失衡,最大改变量只有 fF 级。
这里有两个设计动机值得停下来想。第一是为什么用差分而不是单端:封装应力、温度漂移、注塑带来的介电环境变化,在差分结构里是共模量,两边一起动、相减就没了;而加速度引起的位移是差模量,一正一负,相减翻倍。这一手同时解决了灵敏度低和一个大得多的麻烦——漂移。第二是为什么后面必须跟低噪声电荷放大器:fF 级变化对应的电荷量极小,直接进 ADC 会被噪声淹没,所以先用电荷积分电路把电容变化积成电压,再由 ADC 量化。手册里那句“对加在差分电容上的电压脉冲进行积分,以积分结果衡量差分电容大小”,说的就是这个电荷放大过程。
链路定型之后,带宽也就被钉死了:系统带宽等于 ODR/2。采样率 100Hz 时有效带宽 50Hz,这不是随便写的数字,而是采样定理给的上限——再高就会混叠。做手势识别、敲击检测这类需要保留高频分量的场景,ODR 直接决定你能看到多快的动作。
2.2 量程与灵敏度:换档等于换一把尺子
四个量程档不是简单的“放大倍数切换”,它同时改变了三件事:满量程、每 LSB 代表多少 mg、以及你能分辨的最小加速度。
| FS[1:0] | 满量程 | 灵敏度(HR 模式) | 1g 对应 LSB | 等效分辨率 |
|---|---|---|---|---|
| 00 | ±2g | 1 mg/digit | 1000 | 1 mg |
| 01 | ±4g | 2 mg/digit | 500 | 2 mg |
| 10 | ±8g | 4 mg/digit | 250 | 4 mg |
| 11 | ±16g | 8 mg/digit | 125 | 8 mg |
这张表最容易被误读的地方是“量程越大越不灵敏”。物理上它是同一套电容结构,靠改变积分增益和 ADC 参考来适配不同量程,代价就是每 LSB 代表的加速度变粗。所以做倾角测量、静态姿态判断,永远选 ±2g;做跌落检测、冲击记录,才往 ±8g、±16g 走。手册里给出的零漂指标是在 ±2g 下 ±40mg,温漂 ±0.5mg/°C,灵敏度温漂 ±0.01%/°C——注意零漂这个量级:40mg 相当于 2.3° 的静态倾角误差,这就是为什么纯靠加速度计做绝对倾角,不校准是撑不住精度的。
把原始值换成物理量的代码短得几乎不像核心逻辑,但每行都对应上面一个参数:
/* 原始数据在 0x28~0x2D,低字节在前;FS=0 时 1 LSB = 1 mg */ static const uint8_t sens_mg[4] = {1, 2, 4, 8}; /* 与 FS[1:0] 一一对应 */ static int16_t le16(const uint8_t *p) { /* BLE=0:小端序,低字节在前;置位 BLE 则要反过来拼 */ return (int16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8)); } void acc_to_mg(const uint8_t *buf, uint8_t fs, int32_t out[3]) { out[0] = le16(&buf[0]) * sens_mg[fs]; /* X:0x28 / 0x29 */ out[1] = le16(&buf[2]) * sens_mg[fs]; /* Y:0x2A / 0x2B */ out[2] = le16(&buf[4]) * sens_mg[fs]; /* Z:0x2C / 0x2D */ }三个参数决定这段代码对不对:sens_mg必须和当前 FS 档位一致,改档忘了改表是最常见的“数值整体偏大/偏小”来源;le16里的字节序受 BLE 位控制,寄存器名上那个(BLE=0)就是提示;用int16_t强转是为了让补码自动变成负数,别自己写符号位判断。还有一点:12 位有效数据放在 16 位寄存器里,低位那几位不是精度,只是噪声和填充,把它当 16 位分辨率去算,算出来的有效位数会虚高。
2.3 功耗账本:为什么 400Hz 和 1.5Hz 差着两个数量级
在 ODR=50Hz 的同一条件下,高性能模式 290µA、正常模式 14µA、低功耗模式 8µA,掉电 0.2µA。三档差着二十倍,不是靠“降电压”实现的,而是靠改变模拟前端的占空比和平均次数:采样间隔里把积分器、ADC 关掉,只在采样窗口打开,平均次数越多噪声越低、电流越大。高性能模式在 ±2g 下能拿到 12 位有效位,就是拿电流换来的信噪比。
这解释了一个常见误配:把 ODR 拉到 400Hz 却用低功耗模式,指望既省电又灵敏,结果噪声大到计步乱跳。反过来,做静态的抬腕检测,用 290µA 的高性能模式也是浪费——抬手识别内部用的是另一套定点标度(1g = 64 LSB,约 6 位精度),它压根不需要 12 位数据。参数之间是有耦合的,选档位的时候得先问一句“我这个场景真正需要多少有效位”。
3. 数字接口:一根 CS 线切两种总线,一位地址换一次 burst
3.1 CS 决定协议,SDO 决定地址
同一组引脚要同时支持 I²C 和 SPI,靠的是第 10 脚 CS 的电平:高电平走 I²C,低电平使能 SPI。这个设计意味着硬件上没有“自动识别”,CS 接错电平,后面所有时序都是白忙。I²C 模式下 SCL/SDA 复用第 12、2 脚,SPI 四线模式下变成 SPC/SDI,SDO 单独一根;三线模式下 SDO 让出来,SDI 变双向。
更容易翻车的是地址。7 位从机地址是 0011xxxb,低三位里有一位由 SDO 引脚决定:SDO 悬空或接高,地址是 0x19(写 0x32、读 0x33);SDO 接逻辑低,地址是 0x18(写 0x30、读 0x31)。手册里还专门提示,若要把 SDO 外接低电平,需要通过 I²C 总线通讯关闭 SDO 的内部上拉电阻——因为这三根脚默认内置 50k~60k 上拉,接到 VDDIO。上拉阻值这么大,走线一长、速率一上 400kHz,上升沿就爬不起来,示波器上看是圆角,逻辑分析仪上看是偶发 NACK。这是典型的“电路上没画错、时序上过不去”。
3.2 子地址最高位:一个 bit 换掉整段握手
I²C 写完从机地址之后发的那个字节叫子地址,它的低 7 位是寄存器地址,最高位是地址自增开关。最高位为 1,后续访问地址自动加一,可以连续读写;为 0,每次都打同一个寄存器。手册给的例子很干脆:读 0x28~0x2D 六个字节,子地址发0xA8(就是0x28 | 0x80)。
/* 0x28 起连读 6 字节:子地址最高位置 1,打开地址自增 */ static int acc_read_xyz(i2c_bus_t *bus, uint8_t addr7, uint8_t out[6]) { uint8_t sub = 0x28 | 0x80; /* 0xA8,burst 读的关键 */ if (i2c_write(bus, addr7, &sub, 1)) /* 只发子地址,不发数据 */ return -1; return i2c_read(bus, addr7, out, 6); /* 一次读完 X/Y/Z */ }为什么强调“一次读完”?因为分三次单字节读,三次读之间可能夹着新的采样点,X 来自第 n 次采样、Z 来自第 n+1 次,合成出来的向量长度就不是 1g 了,做姿态解算时会看到莫名其妙的“伪运动”。这个坑在低 ODR 下不明显,一旦 ODR 上到 200Hz 就开始露头。同理,SPI 侧的地址域也是 7 位地址加自增位,多字节读写靠 M-S 位控制,M-S 为 0 时每次访问同一地址,为 1 时地址递增。
3.3 SPI 三线模式的先有鸡还是先有蛋
三线模式靠 SIM 位置 1 打开,可问题是:配置这个位本身得先能通信。手册给的解法是利用“四线写模式只用三根线”这个事实——写操作本来就不需要 SDO,所以先用四线模式的写时序把 SIM 位置 1,之后 SDO 让出来,SDI 变双向,再按三线时序访问。
SPI 的时序细节也有几个数字要记住:时钟最高 10MHz,单字节读写 16 个时钟,bit0 是读写控制位(0 写 1 读),bit1~7 是地址,bit8~15 是数据,MSB 先发;数据在 SPC 下降沿驱动、上升沿采样,最后一个 SPC 上升沿必须在 CS 上升沿之前完成。还有一条明确的功能开关:多设备复用 SPC/MOSI/MISO 时,要向 0x0E 寄存器写 0x04 关闭 I²C 通讯功能。不关的话,SDA 脚上的内部上拉和外部总线状态会互相干扰,表现是 SPI 偶尔读回全 0xFF。
4. 中断与内置状态机:为什么这些判断要放在硅片里
4.1 中断是事件通道,数据寄存器是数据通道
INT1 和 INT2 两根脚不是数据出口,它们是事件出口。芯片内部有一堆检测模块,命中条件就置状态位、拉中断,MCU 只需要在中断里读一下状态寄存器,判断是哪类事件,绝大多数时间可以睡。这个划分直接决定了系统功耗结构:如果靠 MCU 轮询 0x27 状态寄存器判断有没有新数据,主控就永远睡不深;把判断下推到传感器,主控才能长时间待在低功耗态。
中断源是可以映射的,比如抬手功能可以在 0x08 里分别配置 INT1、INT2 映射成“抬手状态值”“抬手动作”“放下动作”三种语义,计步在 0x10 里可以映射“溢出”“达到差值”“达到设定阈值”。这意味着同一根引脚在不同产品里可能代表完全不同的含义,调试时先看映射配置,再看现象。
4.2 所有检测功能其实是同一套骨架:阈值 + 持续时间 + 与或关系
把手册里那些功能摊开看,会发现它们都是同一个模板的实例:一个或多个轴的输出与阈值比较,比较结果按 AND/OR 组合,再要求持续若干个采样周期,最后置位。差别只在参数名字。
| 中断源 | 判据寄存器 | 时间控制 | 典型场景 |
|---|---|---|---|
| 6D/4D 方向 | AOI1/AOI2_THS、AOI_SRC | AOI_DURATION | 横竖屏、翻盖检测 |
| 自由落体 | AOI_THS(低阈值 + 多轴 AND) | AOI_DURATION | 跌落保护 |
| 单击/双击 | CLICK_THS | TIME_LIMIT / LATENCY / WINDOW | 敲击唤醒 |
| 睡眠/唤醒 | ACT_THS | ACT_DURATION | 低功耗驻留 |
| 计步 | S_WTM、S_DELTA | STEP_MODE 约束 ODR | 手环计步 |
| 抬手 | T_Angle、Angle_Scale | TT_TH / FT_TH,2^n × 0.1s | 抬腕亮屏 |
单击双击那组参数最能说明这套骨架的意图:THS 管“多大力算一次敲”,TIME_LIMIT 管“按下最长多久”,LATENCY 管“两次敲之间至少隔多久”,WINDOW 管“第二次敲必须在多久内到达才算双击”。四个参数分别对应机械按键抖动、误触和双击判定窗口三个现实问题,本质上是把一段软件滤波逻辑固化成了比较器加计数器。抬手识别也一样,TT_TH 是“进入抬手状态要持续多久”(2 的设定值次方乘 0.1 秒),FT_TH 是“离开抬手状态要持续多久”,前者防误触发,后者防闪烁。
还有一个细节暴露了设计取舍:抬手检测内部用的是 1g = 64 LSB 的标度,目标角度值用 7 位补码表示再乘 2,静止时加速度值最大 64 约对应 90°。也就是说这条支路只有 6 位左右的精度,而主数据通道是 12 位。为什么不复用高精度数据?因为抬手判断只需要粗角度,独立支路可以做得更小更省电,还不占用主数据通道的采样节奏。
4.3 计步器:把算法放进硅片的三笔账
内置计步的价值不在算法多先进,而在于三笔账算得过来。第一笔是功耗:主控跑计步算法要定期唤醒、读数据、算峰值,累计功耗远大于传感器内部一个状态机;第二笔是内存:算法要缓冲区、要滤波状态,低端 MCU 的 RAM 紧得很;第三笔是实时性:片内逻辑每个采样周期都在看,不会因为主控忙别的就漏掉一步。
计步值本身是个 24 位寄存器,从 0x15 起三个字节,低位在前:
/* 24 位计步值:0x15 起连读 3 字节,低位在前 */ uint8_t step[3]; uint32_t steps; acc_burst_read(0x15 | 0x80, step, 3); steps = ((uint32_t)step[2] << 16) | ((uint32_t)step[1] << 8) | step[0];24 位意味着到 1600 多万步才溢出,正常佩戴基本不会碰到,但 0x14 里仍然给了OVER_STEP标志位,说明设计上考虑了长时间不读的情况。STEP_CLR位是自清零的,写 1 清完自己回 0,不需要软件再写回 0;STEP_MODE是个硬约束——置 0 要求 ODR ≥ 25Hz,置 1 要求 ODR ≥ 50Hz,本质是内部算法需要足够的采样密度才能分辨出步态周期。ODR 配低了,STEP_EN打开了也不走数,这种问题查寄存器是查不出来的,只能回去看 ODR 档位。
5. 避坑与排查:五个现场反复出现的翻车点
5.1 数据类:读到了,但读的是错的
现象:三轴数据能读出来,静止放着 Z 轴约 1000、XY 接近 0,看着没问题,但一动起来数值明显偏小,做倾角算出来的角度也不对。原因:量程档位和换算表不匹配。寄存器里配的是 ±8g,代码里还按 1mg/LSB 在算,所有值都小了三倍多。解决:把 FS 字段和灵敏度表做成同一个枚举,改档必须一起改,别让两处配置有机会分家。
现象:三轴数据偶尔跳出一个明显不合理的点,向量模值突然变成 1.4g 又回来。原因:分三次单字节读,X/Y/Z 来自不同采样时刻;也可能是读 6 字节的过程中被新采样打断,高字节和低字节不是同一次转换的结果。解决:一律 burst 读,并且先确认数据就绪再读;对时间敏感的场景打开块数据更新保护,避免读的过程中寄存器被刷新。
5.2 总线类:扫不到设备、偶发 NACK
现象:I²C 上扫不到任何设备,或者能扫到但地址是 0x19 而不是预期的 0x18。原因:SDO 引脚悬空,内部上拉把它拉到高,地址就落在 0x19;想用 0x18 必须把 SDO 明确接低,并且通过总线关掉它的内部上拉。解决:地址先用 0x19 试通,再决定硬件上怎么接;不要在没确认地址的情况下怀疑芯片坏了。
现象:白天好好的,晚上或者温度一变化就开始偶发 NACK,示波器上看 SCL/SDA 上升沿是圆弧。原因:内部上拉只有 50k~60k,总线电容稍大时间常数就上去了,400kHz 快速模式下上升沿超标。解决:外部加 2.2k~4.7k 上拉,或者降到 100kHz 标准模式先用通;总线挂多个从机时更要注意容性负载。
现象:SPI 共享总线时,别的设备一通信,加速度计就读回全 0xFF。原因:没有关掉 I²C 功能,SDA 脚上的内部上拉参与进来,把总线状态搅乱。解决:初始化时向 0x0E 写 0x04 关闭 I²C 通讯功能,再开始用 SPI。
5.3 配置类:中断不来、计步不走
现象:自由落体中断怎么都不触发,阈值调大调小都没反应。原因:这类检测要多个轴同时满足条件才置位,只使能了某一个轴,或者持续时间设得太长,实际跌落过程还没走完就落地了。解决:三个轴都参与比较,持续时间按 ODR 折算成毫秒去核对,别只看设定值。
现象:计步功能使能了,走了一万步还是 0。原因:STEP_MODE要求的 ODR 门槛没满足;或者STEP_EN没置位;或者中途误写了STEP_CLR。解决:按“ODR 档位 → STEP_MODE → STEP_EN → 读 0x14 状态”的顺序逐级确认,状态寄存器里的DELTA_STEP、WTM_STEP能告诉你算法到底在不在工作。
现象:初始化时写 0x0F 读回不是 0x13,后面所有配置都不生效。原因:上电后芯片还在引导加载阶段,NVM 里的修调值正在往寄存器里搬,这段时间写寄存器会被覆盖;也可能是复位没做完就发命令。解决:上电先延时等稳定,再读 WHO_AM_I 确认通信正常,最后才配置;软复位之后同样要走一遍这个流程,别复位完立刻写参数。
6. 校准、自测试与那条 276 LSB 的判据
出厂校准这块的设计取舍值得单独说。芯片内部有灵敏度和零漂的修调值,存在 NVM 里,上电后自动加载到寄存器,用户不做任何校准就能用。这么设计的好处很直接:省掉了产线上一台转台和一道工序,也避免了“用户校得比出厂还差”的风险。代价是它只能补偿芯片自己的一致性,补偿不了板级应力——零漂在手册里的定义就写得很清楚,它是机械部分应力的结果,焊到 PCB 上或者处在外部压力环境下会变。所以内置了产品倾斜校准,对贴片和板卡安装导致的倾斜做补偿,参数存在片内、不占系统资源,固件升级也不影响——这一手是在“出厂校准覆盖不到的地方”补的一刀。
零漂 ±40mg、温漂 ±0.5mg/°C 这两个数字,决定了要不要做二次校准。40mg 对应 2.3° 静态倾角误差,做屏幕旋转、翻盖检测完全够用,做高精度水平仪就不够。判断方法很朴素:六面法。把板子六个面依次朝下静置,每个面记录三轴输出,X/Y/Z 各自的正反向读数相减除以 2 得灵敏度,相加除以 2 得零偏。这套流程跑一遍,你就能分清误差是来自芯片本身的零漂,还是来自焊接应力——两者的区别是后者会随板子弯曲而变。
自测试则是另一类工具,它解决的是“不想上转台也想验证机械链路”。使能自测试位之后,一个静电驱动力被加到质量块上,模拟出确定的加速度输入;此时输出是外部加速度和静电驱动力的代数和。手册给的判据是 FS=0 时 X/Y 轴 276 LSB、Z 轴 984 LSB。Z 轴的数字比 X/Y 大了三倍多,不是笔误,而是 Z 轴的电极面积和弹簧结构跟面内两轴不同,同样的静电力产生的等效加速度不一样。用这条判据时有个必须注意的前提:得把重力分量扣掉,或者干脆水平放置让 Z 轴正对重力,否则你测到的是自测力加重力的合力,怎么算都对不上。
早年我在一个模拟项目上吃过这个亏:自测读出来 Z 轴差了将近 1000 LSB,我一口咬定是芯片坏了,换了两片都一样,最后才发现板子是竖着插在治具上的,重力全叠在 X 轴上。从那以后我每次跑自测试都强制走一遍“先水平静置读基线、再开自测读差值、最后和手册范围比对”的三步,一步都不省。这份手册的价值也在这里——它把每个数字背后的前提条件都写清楚了,276 和 984 不是随便给的两个阈值,而是告诉你这条链路在什么条件下算正常。希望帮到你。
本文还有配套的精品资源,点击获取