☰
ICM-20948九轴传感器航向角融合实战:从陀螺仪漂移到磁力计校准
2026/10/6 11:26:58 网站建设 项目流程

无人机在悬停或者低速巡航时,航向角漂移是让很多人头疼的问题。飞着飞着一看地面站,机头方向已经偏了十几度,返航时对不准起降点,甚至原地打转。这个现象背后,多半是姿态解算里航向角的融合没做好。ICM-20948这颗九轴传感器集成了三轴陀螺仪、三轴加速度计和三轴磁力计,价格不高、资料够用,是中小型飞控和自研飞控板里很常见的选择。但很多人拿到它之后,只用了陀螺仪和加速度计做姿态,磁力计要么没接、要么接了不会用,结果航向角只能靠陀螺仪积分,时间一长必然漂。这篇内容就围绕ICM-20948,把陀螺仪和磁力计怎么配合、航向角怎么算、代码怎么写讲清楚,适合正在做飞控、云台、机器人定位的开发者参考,也适合刚接触九轴传感器、想把航向角做稳的人。

1. 航向角为什么会漂,ICM-20948能解决什么

1.1 陀螺仪积分漂移的物理本质

先把这个问题的根说透。陀螺仪输出的是角速度,单位是度每秒或者弧度每秒。要得到角度,必须对角速度做时间积分。积分这件事本身没问题,问题出在陀螺仪有零偏。所谓零偏,就是传感器在完全静止时,输出并不是严格的零,而是有一个很小的偏置值,比如0.5度每秒。这个偏置在积分过程中会被不断累加,一分钟就是30度,十分钟就是300度,航向角直接废掉。

零偏还分两种。一种是固定零偏,上电后基本不变,可以通过静止校准减掉。另一种是温漂零偏,随着温度变化而缓慢变化,芯片发热、环境温度改变都会让它动。固定零偏好处理,温漂零偏就麻烦了,你不可能每隔几分钟就停下来重新校准一次。所以单靠陀螺仪,航向角在几十秒到几分钟的尺度上就会明显漂移,这是物理层面的限制,不是代码写得不好。

加速度计能帮忙修正俯仰和横滚,因为重力方向是天然的参考。但加速度计对航向角完全无能为力,因为重力矢量是垂直的,绕垂直轴旋转不改变重力在三个轴上的投影。这就是为什么只用六轴(陀螺仪+加速度计)的方案,航向角必然漂。要修正航向,必须引入一个能感知水平方向绝对参考的传感器,磁力计就是干这个的。

1.2 磁力计提供的绝对航向参考

地球磁场在水平方向有一个稳定的分量,指向磁北。磁力计测量三个轴的磁场强度,通过计算水平面内磁场矢量的方向,就能得到相对于磁北的航向角。这个参考是绝对的,不会随时间漂移,只要周围没有强磁干扰,它就能长期稳定地给出航向。

但磁力计也有它的问题。第一,它测的是总磁场,包括地球磁场和周围所有铁磁物质、电流产生的干扰磁场。无人机上的电机、电调、电池线,通电后都会产生磁场,直接污染磁力计读数。第二,磁力计本身有零偏和标度因数误差,三个轴的灵敏度不完全一致,不校准的话算出来的航向角会有周期性误差。第三,磁力计动态响应慢,电机转速变化时干扰剧烈,不能单独用它做航向,必须和陀螺仪融合。

所以正确的思路是:陀螺仪提供短期高动态的航向变化,磁力计提供长期绝对参考,两者通过互补滤波或者卡尔曼滤波融合。陀螺仪负责快,磁力计负责稳,各取所长。ICM-20948把这三样东西集成在一颗芯片里,通过I2C或SPI读取,省去了外接磁力计的麻烦,而且三者出厂时已经做了基本的轴对齐,用起来比分立方案省心。

1.3 ICM-20948相比MPU6050的关键差异

很多人是从MPU6050转过来的,这里必须说清楚两者的区别。MPU6050是六轴,只有陀螺仪和加速度计,没有磁力计。用MPU6050做航向角,只能靠陀螺仪积分,漂移是必然的,除非你外接一个HMC5883或者QMC5883之类的磁力计,但外接又涉及轴对齐和安装位置问题。

ICM-20948是九轴,内部集成了AK09916磁力计。注意,AK09916是作为ICM-20948的从设备挂在内部I2C总线上的,主控要读磁力计数据,需要通过ICM-20948的I2C主机接口去访问,而不是直接读ICM-20948的寄存器。这一点是新手最容易卡住的地方,很多人以为磁力计数据和其他数据一样直接读就行,结果读出来全是零或者固定值。

另外ICM-20948支持DMP(数字运动处理器),可以把姿态解算放到芯片内部做,减轻主控负担。但DMP的配置比较复杂,而且官方DMP库对磁力计的支持在不同版本里表现不一致。对于想搞清楚原理、方便调试的开发者,我更建议先用原始数据自己做融合,把航向角算明白,再考虑要不要上DMP。

2. 硬件连接与寄存器配置的实操细节

2.1 供电、总线和中断引脚的接法

ICM-20948的供电范围是1.71V到3.6V,VDD和VDDIO要分别接。VDD是核心供电,VDDIO是IO口供电,两者可以都接3.3V,但如果主控是1.8V逻辑,VDDIO就要接1.8V,否则I2C电平不匹配。这一点在画板子的时候就要注意,不要想当然全接3.3V。

总线方面,ICM-20948支持I2C和SPI。I2C的话,AD0引脚决定地址,接地是0x68,接VDDIO是0x69。SPI的话,CS片选拉低使能,时钟最高可以到7MHz。对于飞控这种对实时性有要求的场景,SPI比I2C快,而且不受总线仲裁影响,建议优先用SPI。但SPI占引脚多,如果主控引脚紧张,I2C在400kHz下也够用,只是读取频率上不去,融合时要注意采样率。

中断引脚INT1和INT2可以配置成数据就绪中断,这样主控不用轮询,数据准备好了中断通知,能降低CPU占用,也能保证采样时刻的确定性。做航向角融合时,采样时刻的确定性很重要,因为积分对时间间隔敏感,如果读取时间抖动大,积分误差也会变大。建议把数据就绪中断接上,在中断里置标志位,主循环里读取和计算。

2.2 唤醒、时钟源与量程设置

ICM-20948上电后默认处于睡眠模式,必须先唤醒。流程是:先读WHO_AM_I寄存器确认通信正常,值应该是0xEA。然后写PWR_MGMT_1寄存器,清除SLEEP位,同时选择时钟源。时钟源建议选自动选择或者内部RC,不要用外部时钟除非你确实接了晶振。选自动的话,芯片会优先用陀螺仪时钟,稳定性比内部RC好。

量程设置要根据应用场景来。陀螺仪量程有±250、±500、±1000、±2000度每秒几档。无人机做航向角,角速度一般不会超过±500度每秒,选±500的话分辨率是65.5 LSB每度每秒,比±2000的16.4 LSB精细四倍。量程越小分辨率越高,但饱和风险越大。如果你做的是穿越机这种剧烈机动的,选±1000或±2000保险一些。加速度计量程有±2g到±16g,航向角融合里加速度计主要用来算俯仰横滚,±4g或±8g够用。

磁力计AK09916的量程固定是±4900微特斯拉,分辨率0.15微特斯拉,这个不用配。但磁力计的测量模式要设置,有单次测量、连续测量10Hz、20Hz、50Hz、100Hz几档。航向角融合建议用连续测量50Hz或100Hz,保证数据更新率跟得上陀螺仪。

2.3 通过I2C主机接口读取磁力计数据

这是整个配置里最关键也最容易出错的一步。AK09916挂在ICM-20948内部的I2C总线上,主控要读它,得通过ICM-20948的I2C主机接口。具体流程是:先配置I2C_MST_CTRL寄存器设置内部I2C时钟,一般400kHz。然后配置I2C_SLV0_ADDR、I2C_SLV0_REG、I2C_SLV0_CTRL,让ICM-20948自动去读AK09916的某个寄存器,把数据放到EXT_SLV_SENS_DATA寄存器里。主控读EXT_SLV_SENS_DATA就能拿到磁力计数据。

AK09916的WHO_AM_I寄存器地址是0x01,值应该是0x09。读之前先确认这个值,确认磁力计在线。磁力计数据寄存器从0x11开始,连续6个字节是X、Y、Z的低高字节,小端格式。注意AK09916的数据是16位有符号数,但实际有效位是14位,最高两位是符号扩展,读出来直接当int16用就行。

还有一个坑:AK09916有数据就绪标志,在ST1寄存器里。如果不等就绪就读,可能读到旧数据或者数据不一致。稳妥的做法是配置ICM-20948的I2C主机去读ST1,判断就绪后再读数据。但这样会增加总线开销,简单做法是保证读取频率低于磁力计输出频率,比如磁力计设100Hz,主控50Hz读一次,基本不会读到重复数据。

3. 从原始数据到航向角的完整计算链路

3.1 陀螺仪零偏校准与温度补偿

上电后第一件事是陀螺仪零偏校准。把无人机放在水平静止的台面上,采集几百到一千个陀螺仪样本,求平均值,这个平均值就是零偏。之后每次读取陀螺仪数据,减去这个零偏再积分。采集样本时要注意,台面要稳,不能有振动,电机不能通电,否则采到的零偏不准。

零偏校准的样本数怎么定?理论上越多越准,但太多浪费时间。我一般采1000个,在1kHz采样率下就是1秒,够用了。采完之后可以算一下标准差,如果标准差很大,说明有振动或者干扰,要重新采。标准差应该在零点几LSB的量级,如果到了几十LSB,肯定有问题。

温度补偿这块,ICM-20948内部有温度传感器,可以读出来。严格的做法是建立零偏随温度变化的曲线,在不同温度下分别校准,然后运行时根据当前温度插值补偿。但这对大多数应用来说过于复杂。实际做法是:如果工作环境温度变化不大,比如室内或者常温户外,固定零偏校准就够了。如果温度变化大,可以在陀螺仪静止时检测零偏变化,做缓慢的自适应修正,但这需要判断静止状态,逻辑复杂一些。

3.2 磁力计的椭球校准与硬磁软磁修正

磁力计不校准,航向角误差可以到几十度,校准之后能压到一两度以内。校准分两步:硬磁校准和软磁校准。

硬磁干扰是固定的磁场偏置,比如无人机上的螺丝、电机磁钢,它们产生一个恒定的附加磁场,让磁力计的测量球心偏离原点。修正方法就是找到这个球心,把测量值减去球心。实际操作是把无人机拿在手里,缓慢旋转各个方向,采集大量磁力计数据,然后求三个轴的最大值和最小值,球心就是(最大值+最小值)/2。这个方法简单,但要求旋转时覆盖所有方向,采集点要均匀。

软磁干扰是铁磁材料对磁场的扭曲,让测量球变成椭球。修正需要一个3x3的变换矩阵,把椭球变回球。这个矩阵可以通过椭球拟合算法求出来,比如最小二乘拟合。代码实现上,采集数据后解一个线性方程组,得到椭球参数,再转换成变换矩阵。这个过程计算量不大,可以在上位机做,把矩阵存到飞控里用。

校准质量怎么判断?校准后把磁力计数据归一化,模长应该接近1。如果模长在0.9到1.1之间波动,说明校准还行。如果波动超过20%,说明校准没做好,或者周围有动态干扰。注意校准要在实际安装状态下做,磁力计装上无人机之后再校准,因为安装位置的铁磁物质会影响磁场分布。

3.3 用互补滤波融合陀螺仪与磁力计

融合的思路很直接:陀螺仪积分得到航向角变化,磁力计算出绝对航向角,两者做加权平均。互补滤波的公式是:

航向角 = α * (航向角 + 陀螺仪角速度 * dt) + (1 - α) * 磁力计航向角

α是权重,接近1,比如0.98。这样陀螺仪的短期变化被保留,磁力计的长期参考慢慢把漂移拉回来。α越大,对磁力计依赖越小,动态响应好但漂移修正慢;α越小,磁力计影响越大,长期稳但磁干扰时航向角会跳。

这里有个细节:磁力计算出的航向角是绝对角度,范围0到360度,而陀螺仪积分出来的是连续角度,可能超过360或者小于0。融合时要先把两者统一到同一个参考系,处理角度环绕问题。比如磁力计算出350度,陀螺仪积分算出-5度,它们其实差5度,不能直接相减。做法是把角度差归一化到-180到180度之间,再做加权。

磁力计航向角的计算要用到俯仰和横滚角做倾斜补偿。因为磁力计是固定在机体上的,机体倾斜时,磁力计测到的水平分量不是真正的水平分量。补偿公式是:

Xh = Xm * cos(pitch) + Zm * sin(pitch) Yh = Xm * sin(roll) * sin(pitch) + Ym * cos(roll) - Zm * sin(roll) * cos(pitch) 航向角 = atan2(-Yh, Xh)

其中Xm、Ym、Zm是磁力计原始数据,pitch和roll来自加速度计解算。不做倾斜补偿的话,机体倾斜时航向角误差会很大,倾斜30度可能带来十几度误差。

4. C代码实现与调试中的常见问题

4.1 传感器驱动层的读写封装

先写底层读写。以SPI为例,ICM-20948的SPI读操作需要把寄存器地址的最高位置1,写操作最高位清0。读的时候先发地址,再发一个空字节收数据。代码大概这样:

uint8_t icm20948_read_reg(uint8_t reg) { uint8_t tx[2] = {reg | 0x80, 0x00}; uint8_t rx[2]; spi_transfer(tx, rx, 2); return rx[1]; } void icm20948_write_reg(uint8_t reg, uint8_t val) { uint8_t tx[2] = {reg & 0x7F, val}; spi_transfer(tx, NULL, 2); }

批量读用连续地址读,效率高。读磁力计数据要通过EXT_SLV_SENS_DATA寄存器,一次读6个字节。注意读之前要确保I2C主机已经配置好并且正在工作,否则读出来是旧数据。

初始化顺序很重要:先复位,延时,读WHO_AM_I确认,然后唤醒,配置时钟,配置量程和采样率,配置中断,配置I2C主机读磁力计,最后使能。每一步之间要加适当延时,芯片内部状态切换需要时间,太快了配置不生效。

4.2 姿态解算主循环的代码结构

主循环的结构建议是:中断置标志,主循环检测标志后读取数据、计算、输出。读取时一次性把加速度计、陀螺仪、磁力计、温度都读出来,保证时间戳一致。计算顺序是:先用加速度计算俯仰横滚,再用俯仰横滚和磁力计算航向,最后用陀螺仪和磁力计航向做互补滤波。

void attitude_update(float dt) { // 读取原始数据 read_imu_raw(&acc, &gyro, &mag); // 加速度计解算俯仰横滚 float pitch = atan2f(-acc.x, sqrtf(acc.y*acc.y + acc.z*acc.z)); float roll = atan2f(acc.y, acc.z); // 磁力计倾斜补偿后算航向 float mx = mag.x * cosf(pitch) + mag.z * sinf(pitch); float my = mag.x * sinf(roll)*sinf(pitch) + mag.y*cosf(roll) - mag.z*sinf(roll)*cosf(pitch); float mag_yaw = atan2f(-my, mx) * RAD_TO_DEG; // 陀螺仪积分 gyro_yaw += (gyro.z - gyro_bias_z) * dt; // 角度差归一化 float diff = mag_yaw - gyro_yaw; while (diff > 180.0f) diff -= 360.0f; while (diff < -180.0f) diff += 360.0f; // 互补滤波 gyro_yaw += (1.0f - ALPHA) * diff; // 归一化到0-360 while (gyro_yaw >= 360.0f) gyro_yaw -= 360.0f; while (gyro_yaw < 0.0f) gyro_yaw += 360.0f; }

这段代码里dt的准确性很关键。如果用固定定时器中断触发,dt就是固定的。如果用主循环计时,dt会抖动,积分误差大。建议用定时器以固定频率触发姿态更新,比如500Hz或1kHz。

4.3 磁干扰导致航向跳变的处理

实际飞行中,电机一转,磁力计数据就乱。这时候如果还硬融合,航向角会跟着跳,飞控输出会抖。处理办法是检测磁干扰强度,干扰大时降低磁力计权重甚至暂时不融合,只靠陀螺仪短时维持。

怎么检测?磁力计校准后模长应该接近1,如果模长偏离1超过阈值,比如0.7到1.3之外,说明有干扰。另一个指标是磁力计航向角和陀螺仪积分航向角的差值,如果差值突然变大,也可能是干扰。检测到干扰后,把互补滤波的α临时调到接近1,让磁力计几乎不起作用,等干扰过去再慢慢恢复。

还有一种情况是磁力计数据本身跳变,比如I2C读取出错,读到了0或者最大值。这种要在驱动层做数据有效性检查,连续几个周期数据不变或者超出合理范围就标记为无效,融合时跳过。

4.4 调试工具与验证方法

调试航向角,光看代码不行,得有工具。最简单的办法是把数据通过串口打到上位机,用波形显示。看三条曲线:陀螺仪积分航向、磁力计航向、融合后航向。静止时融合航向应该稳定,缓慢转动时应该跟得上,快速转动时应该平滑不跳。

验证精度的话,可以用一个已知的参考,比如把无人机对准正北,看航向角是不是接近0度。或者用一个高精度的转台,转固定角度对比。没有转台的话,可以在地面画一个圆,每隔45度做一个标记,手动转动对比。

还有一个实用技巧:把磁力计校准后的数据画成三维散点图,应该是一个球面。如果明显是椭球或者有偏移,说明校准没做好。这个在上位机用Python的matplotlib几行代码就能画,比盲调高效得多。

5. 几个容易踩的坑和我的实际经验

5.1 磁力计轴对齐与安装方向

ICM-20948内部磁力计的轴和陀螺仪加速度计的轴理论上是对齐的,但实际安装到板子上,板子又装到无人机上,坐标系会变。如果磁力计的X轴和陀螺仪的X轴方向不一致,算出来的航向角会差一个固定角度。这个角度可以在校准的时候一并测出来,作为偏置补偿掉。

安装位置也很讲究。磁力计要尽量远离电机、电调和电源线。如果板子只能装在中间,那至少让磁力计那一侧远离大电流走线。我见过有人把飞控装在电池正上方,磁力计读数被电池磁场污染得没法用,最后只能加长支架把飞控挪开。

5.2 采样率与滤波的匹配

陀螺仪采样率、磁力计采样率、融合更新率,这三个要匹配好。融合更新率应该等于或高于陀螺仪采样率,否则会丢数据。磁力计采样率可以低于融合率,因为磁力计本身带宽低,更新太快也没用,反而增加总线负担。

滤波方面,陀螺仪数据建议做一点低通滤波,截止频率根据应用定,无人机一般50到100Hz。但滤波会引入相位延迟,延迟大了融合会振荡。所以滤波不能太狠,要权衡。磁力计数据本身噪声大,但更新率低,可以做滑动平均,窗口不要太大,3到5个点就行。

5.3 上电顺序与初始化失败的排查

有时候上电后读WHO_AM_I不对,或者磁力计读不到。排查顺序是:先确认供电和电平,用示波器看VDD和VDDIO是否正常。然后确认SPI或I2C波形,看时钟和数据线有没有信号。如果通信正常但WHO_AM_I不对,可能是芯片没复位干净,加长复位后的延时。如果磁力计读不到,检查I2C主机配置,确认AK09916的WHO_AM_I能读到0x09。

还有一个隐蔽的问题:ICM-20948的I2C主机和主控的I2C是两套独立的总线,配置时不要搞混寄存器。主控访问ICM-20948用一套地址,ICM-20948访问AK09916用另一套,两者通过芯片内部桥接。理解这个结构,调试起来就清晰了。

5.4 长时间运行后的漂移复现与验证

做完融合后,要做长时间静止测试,至少跑半小时,看航向角漂多少。好的融合应该在一两度以内。如果漂了几十度,说明磁力计没起作用或者校准有问题。然后做动态测试,手动转动无人机,看航向角跟随是否平滑,有没有跳变。

我自己的经验是,磁力计校准做得好,互补滤波参数调得合适,静止半小时航向漂移能控制在1度以内,动态跟随误差在2到3度。这个精度对于大多数无人机航向控制够用了。如果要求更高,就得上卡尔曼滤波,把陀螺仪零偏也作为状态量估计,能进一步压漂移,但计算量和调参复杂度都上去了。

最后分享一个调试时的小习惯:每次改完融合参数,先在地面静止看十分钟波形,确认没有慢漂和振荡,再上机测试。地面能发现的问题,不要带到空中去发现。航向角这个东西,地面看着稳,空中不一定稳,但地面都不稳,空中肯定不稳。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询