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 配置)

为了让磁力计正常工作,需要按顺序完成以下几步:

  1. 开启 ICM20948 内部的 I2C Master,并关闭 Bypass 模式
  2. 降低 I2C Master 对磁力计的轮询速度,给磁力计喘息的时间
  3. 配置 SLV0 连续循环读取磁力计数据
  4. 使用 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)即可

希望这篇文章能帮你节省几个通宵的调试时间!如果对你有帮助,欢迎点赞收藏 ⭐

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐