简介:围绕SC7A20三轴微机械数字加速度计,这份资料整合了说明书与可直接移植的驱动C源码,面向嵌入式开发者、硬件工程师及传感器应用初学者,旨在帮助用户快速解决芯片初始化、寄存器读写与加速度数据解析等实际开发问题,也可作为低功耗运动检测方案的参考设计。压缩包大小约1.53MB,内含PDF格式的芯片参考资料以及.c/.h格式的驱动源码;源码中G_Sensor_SC7A20_Init函数详实展示了芯片ID校验、输出速率配置、高通滤波启用等关键操作,同时附带I2C底层读取接口,方便对照物理时序验证代码。已有566人学习浏览,说明该资料在同类传感器模块中具备较好的参考价值。通过学习此包,读者可完整理解SC7A20从上电配置到稳态数据输出的流程,并可直接将驱动框架移植至STM32等主流平台,缩短项目调试周期,也可作为教学演示或毕业设计的功能模块参考。
1. SC7A20 加速度计的选型边界与 I2C 总线识别
SC7A20 是一颗国产三轴微机械数字加速度计,2x2mm LGA 封装,寄存器映射与 ST 的 LIS3DH 对齐,I2C 从机地址 0x18/0x19 由 SDO 引脚电平决定,读 0x0F 寄存器固定返回 0x11。选它替代 LIS3DH 主要是供货和价格,但真正决定能不能跑起来的不是量程和灵敏度,而是中断路径和滤波器这两组寄存器的配置。本文从这份驱动 C 源码出发,把 CHIPID 校验、CTRL_REG1~6 的位级配置、12 位数据拼接、运动中断唤醒以及零偏校准串起来讲,适合正在做可穿戴、姿态检测、倾角监控的嵌入式开发者。原工程在 STM32F1 上验证过,寄存器地址沿用 LIS3DH 序列,可以平移到任何带 I2C 外设的 MCU 上。
2. 寄存器地图与 CTRL_REG1/2 的位级配置
2.1 关键寄存器速查表
SC7A20 的寄存器地址从 0x0F 到 0x3D,大多数在初始化阶段只需要碰 0x20~0x27 这一组控制寄存器。调试时我习惯先把下面这张表贴在代码文件头,写驱动时不用反复翻手册:
| 地址 | 寄存器名 | 作用 |
|---|---|---|
| 0x0F | WHO_AM_I | 芯片 ID,读回 0x11 |
| 0x20 | CTRL_REG1 | ODR 选择、低功耗模式、X/Y/Z 轴使能 |
| 0x21 | CTRL_REG2 | 高通滤波器截止频率、FDS、滤波模式 |
| 0x22 | CTRL_REG3 | INT1 引脚中断映射 |
| 0x23 | CTRL_REG4 | BDU、大端/小端、量程 FS、高分辨率 |
| 0x24 | CTRL_REG5 | FIFO 使能、中断锁存 LIR、4D/6D 检测 |
| 0x25 | CTRL_REG6 | INT2 引脚中断映射、中断极性 |
| 0x27 | STATUS_REG | ZYXDA 数据就绪、ZYXOR 数据覆盖 |
| 0x28~0x2D | OUT_X_L~OUT_Z_H | 三轴输出,低字节在前 |
| 0x30~0x33 | INT1_CFG/INT1_SRC/INT1_THS/INT1_DURATION | 运动检测阈值与持续时间 |
2.2 CTRL_REG1 的 ODR 位段与轴使能
源码里第一处写配置是Sensor_Write_Byte(0x20, &SC7A20_REG[0]),注释写的是 ODR 10Hz。0x20 寄存器的高四位决定输出数据速率,第三位 LPen 决定普通模式还是低功耗模式,低三位分别是 X、Y、Z 轴使能。10Hz 对应 ODR 编码 0010,所以向 0x20 写入 0x27(二进制 0010 0111)就是正常模式、三轴全开、10Hz 输出。
ODR 在初始化里是最不该随便抄的参数。10Hz 适合静态倾角测量,但跑步行或车辆振动检测就得提到 50Hz~200Hz,否则高频分量被内部抗混叠滤波器压掉,时域波形会变成一条平线。常用对应关系是:
| ODR 编码 | 输出速率 | 典型场景 |
|---|---|---|
| 0001 | 1 Hz | 静置电池供电监控 |
| 0010 | 10 Hz | 倾角、姿态静态测量 |
| 0011 | 25 Hz | 手柄翻转检测 |
| 0100 | 50 Hz | 步态检测低速段 |
| 0101 | 100 Hz | 一般运动检测 |
| 0110 | 200 Hz | 振动监测 |
| 0111 | 400 Hz | 高速冲击捕捉 |
2.3 CTRL_REG2 的高通滤波器 FDS 与 HPCF
源码里第二处写 0x21,注释是fds -->开启高通滤波器(滤掉,句子断了,但结合寄存器功能可以确认滤掉的是重力加速度带来的直流分量。CTRL_REG2 里 FDS 位置 1 表示高通滤波同时作用于输出数据和中断路径;FDS 为 0 时滤波只作用于中断路径,数据输出仍是完整信号。
HPCF[2:0] 三个位控制截止频率系数,注意它不是一个绝对频率值,必须和当前 ODR 联合查数据手册的公式表。同一组 HPCF 在 10Hz ODR 和 100Hz ODR 下的截止频率差一个数量级。我一般先选中间档位跑一组静止数据,看 Z 轴输出是否还能稳定在接近 1g 的位置,再决定往高还是往低调。做线性加速度剥离时把 FDS 打开,做纯倾角输出时保持关闭,避免相位偏移影响角度换算。
提示:高通滤波一旦打开,静止时 Z 轴输出会缓慢回落到 0,这是正常现象,不要当成传感器故障。
2.4 CTRL_REG4 的量程、BDU 与分辨率
CTRL_REG4 的 FS[1:0] 位决定测量范围,00 为 ±2g,01 为 ±4g,10 为 ±8g,11 为 ±16g。改变量程不会改变 ADC 位数,只是重新分配了每个 LSB 对应的毫 g 数,所以量程越大,分辨率越粗。静态倾角和水平校准用 ±2g,电机启停和跌落检测用 ±8g 以上。
这个寄存器里还有 BDU(块数据更新)位,置 1 后,在读低字节和高字节期间数据寄存器不会刷新,避免拼出高低字节来自不同采样点的跳跃值。源码初始化里虽然没有显式写 BDU,但在工程实践中建议在 CTRL_REG4 写入时直接|0x80一并打开。
3. 初始化驱动 C 代码与寄存器写入顺序
3.1 I2C 地址宏与底层读写封装
SC7A20 的七位从机地址由 SDO 引脚决定,SDO 接地为 0x18,接 VDD 为 0x19。I2C 读写地址需要在七位地址基础上左移一位。下面是我在工程里常用的封装:
#define SC7A20_ADDR 0x18 /* SDO = GND */ #define SC7A20_WR ((SC7A20_ADDR << 1) | 0x00) #define SC7A20_RD ((SC7A20_ADDR << 1) | 0x01) static void sensor_write_reg(u8 reg, u8 val) { I2C_Start(); I2C_SendByte(SC7A20_WR); I2C_WaitAck(); I2C_SendByte(reg); I2C_WaitAck(); I2C_SendByte(val); I2C_WaitAck(); I2C_Stop(); }这段代码的关键是把地址左移和读写位拼在一起,SC7A20_WR用于写寄存器地址,SC7A20_RD用于后续读数据。reg参数传寄存器地址,val传要写入的值。如果底层 I2C 已经有I2C_WriteBytes这类批量接口,也可以在上面再包一层,只是要保证每次写操作执行完整的 Start、地址、寄存器、数据、Stop 时序。
3.2 初始化函数的完整实现
原工程里的G_Sensor_SC7A20_Init在读到 ID 不对时只打印 Error,然后继续往下走,这在批量生产中是隐患。我在实际使用中改成直接 return,让上层知道传感器没就位:
void SC7A20_Init(u8 int_ths) { u8 id = 0; sensor_read_reg(0x0F, &id); if (id != 0x11) { printf("SC7A20 Chip_ID error: 0x%02X\r\n", id); return; } sensor_write_reg(0x20, 0x27); /* 10Hz,普通模式,XYZ 全开 */ sensor_write_reg(0x21, 0x00); /* 关闭高通滤波,原始数据直出 */ sensor_write_reg(0x23, 0x80); /* BDU=1,±2g,小端模式 */ sensor_write_reg(0x22, 0x04); /* 将 AOI1 中断映射到 INT1 引脚 */ sensor_write_reg(0x24, 0x08); /* LIR_INT1=1,中断锁存 */ sensor_write_reg(0x32, int_ths);/* 运动检测阈值 */ sensor_write_reg(0x33, 0x05); /* 持续时间,按 ODR 换算 */ }寄存器写入顺序有讲究:先配 0x20 确定 ODR,再配 0x21 滤波,最后才配中断相关的 0x22 和 0x24。因为中断持续时间单位依赖 ODR,如果先写中断参数再改 ODR,之前的时间窗会被重置。int_ths是外部传入的阈值,直接落到 INT1_THS 寄存器,每 LSB 对应 16mg,在 ±2g 量程下这个换算关系不变。
3.3 原工程配置表的指针传参问题
源码里Sensor_Write_Byte(0x20, &SC7A20_REG[0])第二个参数带取地址符,说明SC7A20_REG是一个数组,底层函数可能接收指针来遍历配置表。这种写法在单个芯片初始化时没问题,但换 MCU 后容易踩两个坑:一是数组元素顺序和寄存器编号必须完全一致,中间插入一行就会全部错位;二是如果底层形参是普通 u8 类型,这里会有编译警告,说明原工程是针对特定编译器写的。
我一般会把配置表改成显式的寄存器-数值对,结构体数组加一个结束标志,遍历写入时按地址逐个解析,可读性和可维护性都更好,也方便在调试时只改某一行:
typedef struct { u8 reg; u8 val; } sc7a20_cfg_t; static const sc7a20_cfg_t init_table[] = { {0x20, 0x27}, {0x21, 0x00}, {0x23, 0x80}, {0x22, 0x04}, {0x24, 0x08}, };4. 状态寄存器轮询与三轴数据的拼接换算
4.1 先看 STATUS_REG 再读数据
SC7A20 的数据寄存器是独立的,不需要每次都先读状态位。但为了避免读到上一次的旧数据,我习惯先读 0x27 检查 ZYXDA 位,再连续读出 6 个字节。ZYXDA 为 1 表示 X/Y/Z 三轴都已更新到新采样点。连续读 6 字节时使用 I2C 的寄存器连续读功能,地址只需要发一次 0x28:
void SC7A20_ReadAccel(int16_t *x, int16_t *y, int16_t *z) { u8 buf[6]; u8 status = 0; sensor_read_reg(0x27, &status); if (!(status & 0x08)) { return; /* ZYXDA 未置位,数据未更新 */ } sensor_read_regs(0x28, buf, 6); *x = (int16_t)((buf[1] << 8) | buf[0]) >> 4; *y = (int16_t)((buf[3] << 8) | buf[2]) >> 4; *z = (int16_t)((buf[5] << 8) | buf[4]) >> 4; }buf[0]是 X 轴低字节,buf[1]是高字节,拼接后的 16 位值要右移 4 位。SC7A20 的 12 位有效数据左对齐存放在高 12 位,低 4 位固定为 0,直接不舍弃低位的话,原始值会被放大 16 倍。
4.2 右移 4 位的精度解释
右移操作同时处理了符号扩展。(int16_t)((buf[1] << 8) | buf[0])先拼出完整 16 位有符号数,再算术右移 4 位,负数的高位补 1,正数补 0,这样得到的 12 位结果仍然是有符号补码。如果把 buf 直接转成无符号数再右移,负半轴的数值会全部错乱。这个问题在只测正 g 的场景不暴露,一旦翻转设备,Y 轴或 X 轴出现负值,换算出来的角度全是错的。
4.3 从原始值到重力加速度的换算
不同量程下的灵敏度不同,换算时通过 FS 选择对应的毫 g 系数:
| 量程 | 灵敏度(mg/LSB,参考) |
|---|---|
| ±2g | 1 |
| ±4g | 2 |
| ±8g | 4 |
| ±16g | 12 |
写出换算代码后,静止水平放置时 Z 轴应接近 1000mg 即 1g,X/Y 轴应趋近 0。把这个作为上板后的第一项自检,通常比任何寄存器诊断都直观:
static const float sc7a20_sensitivity[4] = { 0.001f, 0.002f, 0.004f, 0.012f }; float x_g = raw_x * sc7a20_sensitivity[fs_idx];注意 ±16g 档的 12mg/LSB 是按 LIS3DH 兼容定义给出的参考值,实际板卡上电后可以用翻转法验证:把一个轴对准重力方向,读取两次差应接近 2g,如果偏差大于 5%,优先查量程位是否真的写进去了。
4.4 用三轴合成姿态角校验数据合理性
读到三轴 g 值后,最简单的校验是计算俯仰角和横滚角:
float pitch = atan2f(-x_g, sqrtf(y_g * y_g + z_g * z_g)) * 57.3f; float roll = atan2f(y_g, z_g) * 57.3f;静止状态下角度应稳定在 0° 附近。如果角度随时间缓慢漂移超过 ±1°,要么是数据没经过零偏校准,要么是 ODR 太低导致噪声没有被平均掉。这个公式不适用于动态运动,动态场景下要融合陀螺仪数据,加速度计单独算出的角度在振动环境下会剧烈抖动。
5. 运动检测中断配置与唤醒路径
5.1 阈值寄存器 INT1_THS 与持续时间 INT1_DURATION
运动检测不需要 MCU 持续轮询,配置好 INT1 中断后,加速度超过阈值且持续够久,INT1 引脚会拉高,MCU 可以从低功耗模式唤醒。INT1_THS 每一 LSB 对应 16mg,和量程无关。假设要检测 200mg 以上的运动,写入值就是 200/16 ≈ 13,即 0x0D。
INT1_DURATION 的单位是采样周期数,不是毫秒。比如 ODR 设为 10Hz,一个采样周期是 100ms,写 0x05 表示需要连续 5 个周期超过阈值才触发中断,即 500ms。参考实现如下:
void SC7A20_EnableMotionInt(u8 ths_mg, u8 duration_samples) { u8 ths = ths_mg / 16; sensor_write_reg(0x30, 0x2A); /* 配置 AOI 模式与检测轴,见 5.2 */ sensor_write_reg(0x32, ths); /* 阈值,单位 16mg/LSB */ sensor_write_reg(0x33, duration_samples); sensor_write_reg(0x22, 0x04); /* AOI1 中断 -> INT1 引脚 */ sensor_write_reg(0x24, 0x08); /* LIR_INT1 锁存中断 */ }ths_mg是期望的毫 g 阈值,duration_samples是按当前 ODR 换算好的采样周期数。0x24的 LIR_INT1 置位后,触发条件消失中断电平也会保持,直到软件读一次 STATUS_REG 或 INT1_SRC 寄存器清除,这样 MCU 不会因为运动时断时续被反复唤醒。
5.2 INT1_CFG 的位置选择和 AOI 模式
0x30 是 INT1_CFG,负责选择哪几个轴参与检测、检测正方向还是负方向,以及多个条件之间是 AND 还是 OR。不同厂家对位定义的命名略有差异,配置前要对照 SC7A20 手册的寄存器表逐位填写。常见的使用方向:
- 单方向检测,比如只关心 Z 轴负方向的跌落,配置为 Z 轴负方向使能,其余轴禁用。
- 任意方向运动唤醒,配置为三轴正负方向全使能,AOI 设置为 OR 模式,这样任何方向超过阈值都会触发。
- 6D 方向检测用于判断设备当前朝向,需要配合 CTRL_REG5 的 D4D_INT1 位使用,此时 INT1_CFG 的 6D 位置 1。
我一般把0x30的值单独定义成宏,每个 bit 写成带名字的注释,方便不同项目复用:
#define SC7A20_INT1_CFG_ZHIE (1 << 5) /* Z 高阈值使能 */ #define SC7A20_INT1_CFG_ZUPE (1 << 4) /* Z 正方向检测 */ #define SC7A20_INT1_CFG_ZD (1 << 3) /* Z 负方向检测 */ #define SC7A20_INT1_CFG_YHIE (1 << 2) #define SC7A20_INT1_CFG_YUPE (1 << 1) #define SC7A20_INT1_CFG_YD (1 << 0)5.3 运动中断与数据就绪中断的分工
SC7A20 支持把不同中断源映射到 INT1 或 INT2 引脚。数据就绪中断每个 ODR 周期触发一次,适合用 DMA 批量搬运,不适合做唤醒源;运动中断只在事件发生时触发,MCU 可以一直睡在停机模式下。两者可以同时使能,比如用 DRDY 中断驱动数据采集,用运动检测中断做休眠唤醒,分别映射到 INT1 和 INT2,互不干扰。
唤醒后的第一件事是读一次 STATUS_REG 清中断,再读三轴数据。如果不清锁存,下一次运动事件来临时 INT1 引脚不会产生新的边沿,MCU 可能错过唤醒。这个坑在低功耗设备调试中非常常见,现象是第一次唤醒后就再也不触发。
提示:中断锁存模式下测试时不要用万用表看引脚电平,要用示波器抓边沿,锁存电平会误导判断。
6. 排查经验:读不到 ID、数据跳变与零偏校准
6.1 读 ID 失败的三类原因排查表
初始化第一步就读 WHO_AM_I,读不到 0x11 就没有继续往下写的必要。实际项目里遇到问题先对照这张表:
| 现象 | 可能原因 | 处理方法 |
|---|---|---|
| 读到 0xFF | 器件没上电或 SDA 无上拉 | 确认 VDD 供电,检查 I2C 上拉电阻接 2.2k~4.7k |
| 读到其他值,但不是 0x11 | SDO 引脚电平不对 | 检查 SDO 是否接地,地址宏是否匹配 |
| 偶尔读到 0x11,偶尔读不到 | I2C 速率过高或时序紧张 | 把 I2C 时钟降到 100kHz 再试 |
| 系统中有多个设备应答 | 地址冲突 | 换 SDO 电平切换地址,或改硬件分时访问 |
STLink、CP2102 这类串口调试工具如果输出日志不正常,先查 USB 转串口驱动是否装齐,不要一上来就怀疑传感器代码。硬件能通的情况下,0x11 这个 ID 是稳定的,不需要做重试机制。
6.2 数据跳变的修复手段
读出的加速度值偶尔跳变一大截,最常见的原因是高低字节来自两次不同的采样更新。解决方法就是在 CTRL_REG4 里打开 BDU 位,同时用连续读一次取走 6 字节,两种手段组合基本能消除拼数跳变。还有一种情况是 ODR 高于实际需要的采样率,数据噪声本身变大,表现为每个点都有几个 LSB 的抖动,这时降低 ODR 或开启 CTRL_REG4 的高分辨率模式比滤波更有效。
6.3 静止零偏的采集与校准
SC7A20 即使没有外部撞击,静止输出也会有几十毫 g 的零偏,来自封装应力和 ADC 失调。最直接的校准方式是水平放置采集 N 个点取均值:
#define OFFSET_SAMPLES 200 void SC7A20_CalibrateOffset(int16_t *off_x, int16_t *off_y, int16_t *off_z) { int32_t sx = 0, sy = 0, sz = 0; int16_t x, y, z; for (int i = 0; i < OFFSET_SAMPLES; i++) { SC7A20_ReadAccel(&x, &y, &z); sx += x; sy += y; sz += z; HAL_Delay(10); /* 10ms 间隔采样,总时长 2 秒 */ } *off_x = sx / OFFSET_SAMPLES; *off_y = sy / OFFSET_SAMPLES; *off_z = sz / OFFSET_SAMPLES; }采集时设备要水平静置,Z 轴最好朝向正上方,这样 Z 轴零偏平均值里混入的是 1g 分量,校准后需要用水平台面上的基准值再次验证。把计算出的 offset 放到全局变量里,每次读取后先减 offset 再乘灵敏度。做完零偏校准后,滚动和俯仰角度误差一般能控制在 0.5° 以内。标定完建议把设备放在标准水平台上,将 Z 轴读数再复核一次,偏差控制在 20mg 以内算达标。这个 offset 在后续设定 INT1_THS 阈值时也要扣除,否则实际运动检测的触发门限会比预期偏移。
本文还有配套的精品资源,点击获取