ICM20948 九轴驱动避坑指南:磁力计 AK09916 读取全解析
1. 前言与问题背景
在开发惯性导航、姿态解算(AHRS)时,ICM20948 凭借其出色的功耗表现和集成的 AK09916 磁力计,成为了非常受欢迎的 9 轴传感器。
但在实际驱动开发中,很多人(包括我)都会遇到一个非常头疼的问题:
加速度计和陀螺仪的数据读取都很正常,但磁力计数据要么全是 0,要么读出来一个死数据一直不更新,或者是一堆乱码!
本文将带你一步步梳理 ICM20948 读取九轴数据的正确方式,重点解决磁力计 AK09916 无法正常读取的底层时序逻辑问题。
2. 为什么磁力计这么难读?
ICM20948 和 AK09916 其实是封装在同一个芯片里的两个独立 Die。主控 MCU(如 STM32/GD32)通过 SPI/I2C 和 ICM20948 通信,而 ICM20948 内部自带了一个 I2C Master(主控制器),专门负责去和内部的 AK09916 磁力计通信。
读取磁力计失败,通常有以下三大天坑:
| # | 问题 | 描述 |
|---|---|---|
| 1 | 指令没有下发成功 | 没有正确地将 AK09916 配置为"连续测量模式" |
| 2 | I2C 仲裁与时序冲突 | 内部 I2C Master 读得太快,一直霸占总线,导致 AK09916 的 ADC 转换被干扰,永远无法准备好数据 |
| 3 | 被 DRDY 标志卡死 | 这是最容易踩的坑,下面会重点讲解 |
3. 正确的初始化流程(I2C Master 配置)
为了让磁力计正常工作,需要按顺序完成以下几步:
- 开启 ICM20948 内部的 I2C Master,并关闭 Bypass 模式
- 降低 I2C Master 对磁力计的轮询速度,给磁力计喘息的时间
- 配置 SLV0 连续循环读取磁力计数据
- 使用 SLV1 对磁力计进行单次配置(软复位 → 设置为 100Hz 连续测量模式)
核心初始化代码如下:
// -------------------- BANK0: 启用 I2C Master,并关闭 Bypass --------------------
ICM20948_SelectBank(ICM20948_BANK_0_E);
// 打开 FIFO_EN(bit6) + I2C_MST_EN(bit5)
// 注意:不要开启 I2C_MST_P_NSR,确保每次读取有 Stop 位,避免时序冲突
GDSPI0WriteReg(ICM20948_USER_CTRL_E, 0x40 | 0x20);
delay_1ms(10);
// 关闭 Bypass(0x0F 寄存器),确保内部 Master 接管 I2C 总线
GDSPI0WriteReg(0x0F, 0x00);
delay_1ms(10);
// -------------------- BANK3: 配置 I2C Master 和 SLV 接口 --------------------
ICM20948_SelectBank(ICM20948_BANK_3_E);
GDSPI0WriteReg(I2C_MST_CTRL_E, 0x07); // I2C Master 时钟 345.6kHz
// 【关键降频】配置 SLV0 读取延迟,让 I2C Master 慢点轮询
GDSPI0WriteReg(0x01, 0x01); // I2C_MST_DELAY_CTRL,启用 SLV0_DELAY
GDSPI0WriteReg(0x15, 0x09); // I2C_SLV4_CTRL,设置采样分频
// 【关键分组】配置 SLV0 连续读取 9 字节(ST1 到 ST2)
GDSPI0WriteReg(I2C_SLV0_ADDR_E, 0x80 | 0x0C); // 0x0C 为 AK09916 地址,0x80 表示读
GDSPI0WriteReg(I2C_SLV0_REG_E, 0x10); // 从 ST1(0x10)开始读
// Enable(bit7) + GRP(bit4) + Len=9
// GRP=1 确保这 9 字节作为一个原子事务读取!
GDSPI0WriteReg(I2C_SLV0_CTRL_E, 0x80 | 0x10 | 9);
// -------------------- 使用 SLV1 下发单次配置:软复位 --------------------
GDSPI0WriteReg(I2C_SLV1_ADDR_E, 0x0C); // AK09916 写模式
GDSPI0WriteReg(I2C_SLV1_REG_E, 0x32); // CNTL3 寄存器
GDSPI0WriteReg(I2C_SLV1_DO_E, 0x01); // 触发软复位
GDSPI0WriteReg(I2C_SLV1_CTRL_E, 0x80 | 1); // Enable SLV1,len=1
delay_1ms(20);
GDSPI0WriteReg(I2C_SLV1_CTRL_E, 0x00); // 用完必须关闭 SLV1!
delay_1ms(10);
// -------------------- 使用 SLV1 下发单次配置:100Hz 连续测量 --------------------
GDSPI0WriteReg(I2C_SLV1_REG_E, 0x31); // CNTL2 寄存器
GDSPI0WriteReg(I2C_SLV1_DO_E, 0x08); // 0x08 = 100Hz 连续测量模式
GDSPI0WriteReg(I2C_SLV1_CTRL_E, 0x80 | 1); // Enable SLV1,len=1
delay_1ms(20);
GDSPI0WriteReg(I2C_SLV1_CTRL_E, 0x00); // 用完关闭
4. 磁力计读取:千万别被 DRDY 卡死!
❌ 错误写法(新手常见踩坑)
很多人的代码是这样写的:读取 ST1 寄存器,判断 if (st1 & 0x01)(即 DRDY 置位)才去解析磁力计 X/Y/Z 数据。
这在 ICM20948 里是绝对的深坑!
🔍 原理解析
由于底层 I2C Master 在后台以极高速度(比如 1ms 一次)自动轮询 AK09916,而主控 MCU 可能以 5Hz(200ms)的慢速去读取 ICM20948 的缓存(EXT_SLV_SENS_DATA)。
这意味着:在你读取的空隙,底层已经刷新了几十次数据! 这就导致 ST1 的 DRDY(0x01)瞬间被清零,取而代之的通常是 DOR(0x02)数据覆盖位。如果你死死卡住 st1 & 0x01,就会觉得"数据永远没准备好"。
✅ 正确做法:不卡 DRDY,只看溢出位
// 切换到 BANK0,获取外部传感器数据缓存
ICM20948_SelectBank(ICM20948_BANK_0_E);
// 直接读取 I2C Master 搬运过来的 9 个字节
uint8_t st1 = GDSPI0ReadReg(ICM20948_EXT_SLV_SENS_DATA_00_E); // ST1(状态,可不判断)
uint8_t hxl = GDSPI0ReadReg(ICM20948_EXT_SLV_SENS_DATA_01_E); // HXL
uint8_t hxh = GDSPI0ReadReg(ICM20948_EXT_SLV_SENS_DATA_02_E); // HXH
uint8_t hyl = GDSPI0ReadReg(ICM20948_EXT_SLV_SENS_DATA_03_E); // HYL
uint8_t hyh = GDSPI0ReadReg(ICM20948_EXT_SLV_SENS_DATA_04_E); // HYH
uint8_t hzl = GDSPI0ReadReg(ICM20948_EXT_SLV_SENS_DATA_05_E); // HZL
uint8_t hzh = GDSPI0ReadReg(ICM20948_EXT_SLV_SENS_DATA_06_E); // HZH
(void)GDSPI0ReadReg(ICM20948_EXT_SLV_SENS_DATA_07_E); // TMPS(忽略)
uint8_t st2 = GDSPI0ReadReg(ICM20948_EXT_SLV_SENS_DATA_08_E); // ST2
// ST2 的 bit3(HOFL)为 0,说明磁场未溢出,数据有效!
if ((st2 & 0x08) == 0) {
int16_t mx = (int16_t)((hxh << 8) | hxl);
int16_t my = (int16_t)((hyh << 8) | hyl);
int16_t mz = (int16_t)((hzh << 8) | hzl);
// AK09916 灵敏度固定为 0.15 uT/LSB
float MagX_uT = mx * 0.15f;
float MagY_uT = my * 0.15f;
float MagZ_uT = mz * 0.15f;
}
5. 如何验证磁力计数据是否正确?
读出数据后,可以按照以下标准进行验证:
① 静止时微弱跳动
地磁场十分微弱,加上传感器底噪,放在桌面上静止时,数据应该在 ±1 ~ ±3 μT 之间微小波动,绝对不会像死机一样纹丝不动。
② 模长合理
通常环境下地磁场总强度:
X2+Y2+Z2≈30∼60 μTX2+Y2+Z2≈30∼60 μT
若桌面附近有铁制品,可能飙到 100+ μT,属正常现象。
③ 水平旋转测试(Yaw 轴)
将板子平放于桌面,缓慢旋转 360°。你会观察到:
- Z 轴数据基本不变
- X 轴和 Y 轴会像正弦波一样交替变化
出现以上现象,说明磁力计彻底调通了 ✅
6. 总结
ICM20948 是一款强大的九轴芯片,但它的 I2C Master 机制容易让人迷惑。
避坑三连 ⚠️
✅ 别开
I2C_MST_P_NSR,必须保留 Stop 位,否则时序混乱✅ 配置
SLV0_CTRL时,必须加上GRP标志位(0x10),保证 9 字节原子读取✅ 读取数据时,别用
if (st1 & 0x01)卡脖子,直接看 ST2 的溢出位(HOFL)即可
希望这篇文章能帮你节省几个通宵的调试时间!如果对你有帮助,欢迎点赞收藏 ⭐
更多推荐

所有评论(0)