简介:基于STM32CubeMX的MPU9250驱动与磁力计椭球拟合工程,面向STM32F4系列嵌入式开发者及惯性导航入门者,帮助在CubeMX图形化配置基础上快速生成初始化代码,并完成加速度、陀螺仪和磁力计三路传感器数据的读取与解析。工程包含可直接运行的示例,以STM32F4CE11U6为主控,默认MPU9250 ID为0x71,若更换MPU9255需改为0x73;加速度输出单位为g,陀螺仪为度/s,磁力计为向量,便于直接对接姿态解算或滤波算法。资源共182个文件,以c/h源码、uvprojx/uvoptx工程文件、ioc图形配置、hex固件及map、axf、lst等编译输出为主,压缩包仅8.31MB,源码与构建产物分离,目录结构便于按模块查阅。磁力计部分已实现椭球拟合,但不含校正补偿,驱动代码在末尾预留修改接口,用户可根据实际硬件调整驱动方式。目前已有1731人学习下载,适合需要快速搭建九轴IMU数据采集与磁力计标定流程的开发者参考。
1. 先搞清楚:磁力计椭球拟合到底在拟合什么
MPU9250 这颗九轴传感器在工程里最常见的翻车点不是加速度计、也不是陀螺仪,而是那颗挂在内部 I2C 总线上的磁力计。很多人照着例程把原始数据读出来,发现航向角忽左忽右,以为是滤波没做好,调了几天卡尔曼参数毫无改善。真正的元凶是磁力计的误差根本没用数学方法校准掉。
这颗磁力计的实际型号是 AK8963,输出数据会受到硬磁偏移、软磁误差、非正交误差的共同影响。把三轴数据丢到坐标系里看,理想情况下所有采样点分布在以原点为圆心的球面上,实际上它们落在一个偏离原点、且形状被压扁拉伸的椭球上。椭球拟合要做的事情,就是从一堆杂乱样本中把这个椭球的中心、旋转、缩放参数估计出来,再反向修正原始数据。在本机上用 CubeMX 生成工程、写驱动、跑拟合,整个验证闭环一晚上就能走通。这篇文章写给刚拿到 MPU9250 想把它用进自己项目的开发者,也写给那些数据读出来了却不知道下一步该怎么校准的人。
2. CubeMX 配置与 MPU9250 驱动初始化:从时钟树到 I2C 总线细节
2.1 为什么先选 I2C 而不是 SPI
MPU9250 同时支持 I2C 和 SPI,磁力计 AK8963 偏偏挂在内部总线上,外部只能通过 MPU9250 的 I2C 主机接口或旁路模式访问。做椭球拟合需要频繁采集样本,I2C 在 400 kHz 模式下读一次三轴数据只要零点几个毫秒,做校准场景下性能完全够用。SPI 的优势在高频姿态更新,为了后续数据融合方便,我一般还是用 I2C,这样引脚少、CubeMX 配置简单,而且 AK8963 这个特殊结构本身就是 I2C 地址 0x0C 挂在 MPU9250 后面。
很多人在这颗芯片上第一次翻车就是被这个从机地址绕晕。MPU9250 本体地址是 0x68 或 0x69(取决于 AD0 引脚),AK8963 在旁路模式下对外表现成地址 0x0C。你不需要理解内部总线的完整仲裁机制,只需要记住一件事:先配置旁路模式,然后主控直接用 0x0C 就能访问磁力计。
2.2 CubeMX 里的具体配置步骤
常见做法是在 CubeMX 里先选 MCU 型号(比如开发板上那类 STM32F4 或 L4 系列),系统时钟配置完成后只需要打开一个 I2C 外设。
配置项我一般这样填:
| 配置项 | 推荐值 | 说明 |
|---|---|---|
| I2C Speed Mode | Fast Mode | 400 kHz,足够快且 AK8963 完全支持 |
| I2C Clock No Stretch Mode | Disable | 保持默认关闭,否则部分器件回应异常 |
| Addressing Mode | 7-bit | AK8963 是 7 位地址 0x0C |
| DMA / Interrupt | 都不开 | 校准采集用阻塞式读取最简单,开中断反而容易踩回调坑 |
I2C 引脚选择 PB8/PB9 或 PB6/PB7 取决于具体板子,CubeMX 会自动把 SCL/SDA 映射到 GPIO 复用功能上。生成代码后,HAL 库里的hi2c实例就已经初始化好了,不用手写引脚配置。
2.3 驱动初始化:先验证设备 ID,再进旁路模式
磁力计和 MPU9250 的初始化顺序有讲究。上电后 AK8963 默认处于掉电模式,必须先让 MPU9250 的 INT_PIN_CFG 寄存器打开 I2C 旁路,外部主机才能直接访问 0x0C。代码写在初始化函数里。
#include "i2c.h" #define MPU9250_ADDR 0x68 #define AK8963_ADDR 0x0C #define MPU_WHOAMI 0x75 #define MPU_INT_PIN_CFG 0x37 #define AK8963_WIA 0x00 #define AK8963_CNTL1 0x0A uint8_t mpu9250_init(void) { uint8_t id = 0; // 读 WHO_AM_I,MPU9250 正常返回 0x71 或 0x73,不同批次略有差异 HAL_I2C_Mem_Read(&hi2c1, MPU9250_ADDR << 1, MPU_WHOAMI, I2C_MEMADD_SIZE_8BIT, &id, 1, 100); if (id != 0x71 && id != 0x73) { return 1; // 通信异常 } // 开启 I2C 旁路,之后才能直接访问 AK8963 uint8_t cfg = 0x02; HAL_I2C_Mem_Write(&hi2c1, MPU9250_ADDR << 1, MPU_INT_PIN_CFG, I2C_MEMADD_SIZE_8BIT, &cfg, 1, 100); HAL_Delay(5); // 读 AK8963 的 WIA,正常应返回 0x48 HAL_I2C_Mem_Read(&hi2c1, AK8963_ADDR << 1, AK8963_WIA, I2C_MEMADD_SIZE_8BIT, &id, 1, 100); if (id != 0x48) { return 2; // 磁力计通信失败 } return 0; }这里有个容易忽略的细节:HAL 的I2C_Mem_Read第三个参数是设备地址左移一位后的值,因为 HAL 库内部会自己拼读/写位。有人直接把 0x0C 填进去,结果读写全失败,这是 I2C 地址处理方式的经典搞混点。旁路模式下 MPU9250 只是把外部请求转发给 AK8963,时序上不需要额外处理,读出来的数据就是磁力计原始值。
2.4 初始化顺序为什么不能反过来
有人习惯先配置 AK8963 再开旁路,结果是写进去的寄存器值全部丢失。原因很简单:上电时 AK8963 还挂在 MPU9250 内部总线上,外部请求根本到不了它。先把旁路打开,就等于把一个门打通了,后续所有寄存器操作才能真正到达磁力计。这条顺序约束和寄存器配置本身一样重要,调不通的时候先检查这一步。
3. 读取加速度计与磁力计原始数据:寄存器、量程与单位换算
3.1 AK8963 的寄存器结构
磁力计数据不是从 MPU9250 的 0x3B 之后读的,那样读出来的是加速度计和陀螺仪。AK8963 的数据寄存器从 0x03 到 0x08,每个轴 16 位,共 6 个字节。读取之前要往 CNTL1 寄存器写工作模式,决定是单次测量还是连续测量。校准场景下我推荐连续测量模式 2,频率约 100 Hz,数据稳定且不需要每次触发。
| 寄存器地址 | 名称 | 说明 |
|---|---|---|
| 0x00 | WIA | 设备 ID,固定 0x48 |
| 0x02 | ST1 | Bit0 为数据就绪标志 DRDY |
| 0x03~0x08 | HXL~HZH | 三轴磁力计原始数据,小端模式 |
| 0x09 | ST2 | Bit4 为数据溢出标志 HOFL |
| 0x0A | CNTL1 | 工作模式与输出位宽控制 |
| 0x0B | CNTL2 | 软复位寄存器,写 0x01 触发 |
| 0x10~0x12 | ASAX~ASAZ | 出厂敏感度校正值 |
16 位输出时 AK8963 的满量程固定为 ±4800 μT,灵敏度 0.15 μT/LSB,没有像加速度计那样可选的量程寄存器。这个特性让单位换算非常简单,但也让很多人误以为它能换量程,实际上改 CNTL1 的位宽只能得到 14 位数据,量程不变。
3.2 连续读取磁力计的完整代码
驱动读数据要处理两个标志位:ST1 的 DRDY 表示新数据准备好,ST2 的 HOFL 表示数据溢出。如果 HOFL 置位,这一帧直接丢弃,避免把溢出值送进拟合算法。
typedef struct { int16_t x; int16_t y; int16_t z; } mag_raw_t; #define AK8963_ST1 0x02 #define AK8963_HXL 0x03 #define AK8963_ST2 0x09 uint8_t ak8963_read_raw(mag_raw_t *out) { uint8_t st1 = 0, st2 = 0, buf[6]; // 检查数据就绪位 HAL_I2C_Mem_Read(&hi2c1, AK8963_ADDR << 1, AK8963_ST1, I2C_MEMADD_SIZE_8BIT, &st1, 1, 100); if ((st1 & 0x01) == 0) { return 1; } // 连续读出 6 字节,高字节在前,HAL 读出来后需要自行组合成 int16_t HAL_I2C_Mem_Read(&hi2c1, AK8963_ADDR << 1, AK8963_HXL, I2C_MEMADD_SIZE_8BIT, buf, 6, 100); // 读 ST2 同时清除数据就绪状态 HAL_I2C_Mem_Read(&hi2c1, AK8963_ADDR << 1, AK8963_ST2, I2C_MEMADD_SIZE_8BIT, &st2, 1, 100); if (st2 & 0x08) { return 2; // 数据溢出,丢弃本帧 } out->x = (int16_t)((buf[0] << 8) | buf[1]); out->y = (int16_t)((buf[2] << 8) | buf[3]); out->z = (int16_t)((buf[4] << 8) | buf[5]); return 0; }一个小经验:读取顺序一定是 ST1、数据、ST2。ST2 寄存器一旦被读,DRDY 标志就会自动清零,所以它在整个流程里扮演“确认收货”的角色。如果漏掉这一步,下一帧数据可能还带着旧的标志位,配合不严谨的等待逻辑容易读到重复数据。
3.3 出厂灵敏度校正为什么必须做
AK8963 出厂前在寄存器 0x10~0x12 里保存了三轴敏感度微调系数,范围 1~255,通常接近 128。不做修正时三个轴的灵敏度最大可以偏差 3% 左右,这个偏差放在椭球拟合里就是软磁误差的一部分,虽然拟合算法理论上能把非等比例缩放吸收掉,但做不做出厂修正会影响拟合收敛后的残差。工程上我习惯先修正再送拟合。
uint8_t asa[3]; // 读取自 0x10~0x12 float k[3]; for (int i = 0; i < 3; i++) { k[i] = ((float)asa[i] - 128.0f) / 256.0f + 1.0f; }修正后的值等于原始数据乘以对应系数。这里注意系数要转成 float 再算,直接整数除法会得到 1 或 0,数据被整没了一截。很多从网上搬运的代码在这里踩了隐式类型转换的坑,拟合出的椭球中心明显偏离真值。
3.4 加速度计量程这样选,辅助数据采集
校准磁力计时加速度计不是必需,但我习惯同时记录它的数据。原因很简单:采集数据时需要知道当前传感器姿态,否则无法判断采样点是否覆盖了完整球面。加速度计量程在 CubeMX 生成的代码里可以通过写寄存器 0x1C 修改,常见做法是选 ±4 g 或 ±8 g,然后在代码里用对应的灵敏度换算。校准场景对加速度计精度要求低,±4 g 默认设置就够用。
4. 磁力计椭球拟合从零实现:中心、主轴与缩放参数
4.1 先把误差模型的数学讲透
理想情况下,在地球上任意一个固定位置,地磁场矢量的大小是恒定的。传感器绕各种姿态旋转时,三轴磁力计输出在理想无误差条件下应该全部落在同一个球面上。实际情况是硬磁干扰给三个轴各叠加了一个固定偏移,软磁干扰则是各向异性缩放,把球面变成了椭球面。
观测模型可以写作 h_m = A·h_true + b,h_true 的模长恒为 R,A 是包含缩放和旋转的 3×3 矩阵,b 是三轴偏移。椭球拟合的目标就是估计 A 和 b,然后把校准后的值重新还原成规范球面上的点。工程实现上,我不直接去解这个非线性问题,而是分两步走:第一步估计椭球中心,第二步估计三个主轴方向的缩放系数。这两步都能用成熟的线性代数工具完成,代码量小、可调试性强。
4.2 样本数据的采集要求
椭球拟合的效果直接取决于样本质量。至少要采集 200 个点以上才稳定,数据必须覆盖椭球表面的各个方向。最直观的做法是让设备绕三个正交轴各画几个完整的 8 字轨迹,还要避免采样点集中在一个平面内。如果只在一个平面里转圈,数据在第三个轴上的方差趋近于零,协方差矩阵的某个特征值会非常小,拟合出的缩放系数直接爆掉。
这个采集过程我一般写成循环 500 次,每次间隔 10 ms,要求操作者在这 5 秒内不停翻转传感器。上位机直接通过串口把原始数据灌进 PC 端脚本,确认数据覆盖后再把数组作为静态样本送进拟合函数。
4.3 中心估计的两种方法
最粗暴的方法是拿三轴最大最小值的平均值作为中心估计。这种方法的优点是计算量小、容易理解,缺点是样本不均衡时偏心严重。我推荐用均值迭代法:先假设中心为零,计算所有样本到中心的距离并求出平均值,然后以平均距离为半径将样本点归一化,重新估计中心。实际工程里我常用一个更稳的组合——先取极值中点作为初值,再做一次均值校正。
void estimate_center(const int16_t samples[][3], int n, float center[3]) { float min[3] = {1e9f, 1e9f, 1e9f}; float max[3] = {-1e9f, -1e9f, -1e9f}; for (int i = 0; i < n; i++) { for (int j = 0; j < 3; j++) { float v = (float)samples[i][j]; if (v < min[j]) min[j] = v; if (v > max[j]) max[j] = v; } } for (int j = 0; j < 3; j++) { center[j] = (min[j] + max[j]) / 2.0f; } // 对每个样本点,如果它到中心的距离明显偏离平均值, // 在修正中心时降低它的权重,这一步能抑制离群点影响 float sum_dist = 0.0f; for (int i = 0; i < n; i++) { float dx = (float)samples[i][0] - center[0]; float dy = (float)samples[i][1] - center[1]; float dz = (float)samples[i][2] - center[2]; sum_dist += sqrtf(dx * dx + dy * dy + dz * dz); } float mean_r = sum_dist / (float)n; float corr[3] = {0.0f, 0.0f, 0.0f}; for (int i = 0; i < n; i++) { float dx = (float)samples[i][0] - center[0]; float dy = (float)samples[i][1] - center[1]; float dz = (float)samples[i][2] - center[2]; float r = sqrtf(dx * dx + dy * dy + dz * dz); if (r < 0.8f * mean_r || r > 1.2f * mean_r) { continue; // 丢弃明显离群的样本 } corr[0] += dx; corr[1] += dy; corr[2] += dz; } for (int j = 0; j < 3; j++) { center[j] += corr[j] / (float)n * 0.5f; } }这段代码把两个步骤压缩在一个函数里。先求极值中点作为初值,再统计平均距离、剔除离群点、把偏移残差的一半补偿回中心。系数 0.5 是一个经验阻尼值,防止离群点占多时中心被拉飞。这个函数不依赖矩阵库,在单片机上可以直接运行。
4.4 协方差特征分解:求椭球主轴与缩放系数
中心确定后,把每个样本减去中心,然后构造 3×3 协方差矩阵。协方差矩阵的特征向量就是椭球的主轴方向,特征值对应这个方向上样本分布的方差,也就是半轴长度的平方。求特征值分解我选用雅可比法,它实现简单、对 3×3 矩阵收敛快,MCU 上跑也毫无压力。
void jacobi_eigen(float a[3][3], float v[3][3], float d[3]) { float b[3] = {a[0][0], a[1][1], a[2][2]}; float z[3] = {0.0f, 0.0f, 0.0f}; float offdiag = 0.0f; for (int p = 0; p < 3; p++) { for (int q = 0; q < 3; q++) { offdiag += a[p][q] * a[p][q]; } } offdiag = sqrtf(offdiag); if (offdiag < 1e-9f) { // 已经是近似对角阵,特征值直接取对角元素 for (int i = 0; i < 3; i++) { v[i][0] = (i == 0) ? 1.0f : 0.0f; v[i][1] = (i == 1) ? 1.0f : 0.0f; v[i][2] = (i == 2) ? 1.0f : 0.0f; d[i] = b[i]; } return; } // 初始化单位矩阵 for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { v[i][j] = (i == j) ? 1.0f : 0.0f; } } for (int sweep = 0; sweep < 20; sweep++) { for (int p = 0; p < 2; p++) { for (int q = p + 1; q < 3; q++) { if (fabsf(a[p][q]) < 1e-6f) continue; float theta = (b[q] - b[p]) / (2.0f * a[p][q]); float t = (theta >= 0.0f) ? 1.0f / (theta + sqrtf(theta * theta + 1.0f)) : 1.0f / (theta - sqrtf(theta * theta + 1.0f)); float c = 1.0f / sqrtf(t * t + 1.0f); float s = t * c; // 更新矩阵 a 的上三角元素 for (int k = 0; k < 3; k++) { float akp = a[k][p]; a[k][p] = c * akp - s * a[k][q]; a[k][q] = s * akp + c * a[k][q]; } for (int k = 0; k < 3; k++) { float vkp = v[k][p]; v[k][p] = c * vkp - s * v[k][q]; v[k][q] = s * vkp + c * v[k][q]; } float bq = b[q]; b[q] = s * a[p][q] + c * bq; b[p] = c * b[p] - s * a[p][q]; a[p][q] = 0.0f; } } } for (int i = 0; i < 3; i++) { d[i] = b[i]; } }参数说明:输入a是 3×3 对称矩阵,输出v是特征向量矩阵,d是特征值数组。代码里的a[p][q]在每轮旋转后不断变稀疏,迭代 20 次对 3×3 矩阵来说已经足够。这套代码不依赖任何外部数学库,可以直接放在工程里。
特征值求出后,缩放系数按 1.0 / sqrtf(特征值) 计算。如果想让拟合后的模长恢复到目标场强 R,就再乘一个 R / 平均半径 的比例因子。特征向量方向上的缩放系数乘以原始样本减去中心之后的值,就完成了整个椭球到球面的校正。
4.5 把拟合参数应用回原始数据
拟合的最终产物是三个偏移量、三个主轴方向向量和三个缩放系数。应用时我先转旋转校正,再转缩放校正。
void apply_calibration(const float raw[3], const float center[3], const float axes[3][3], const float scale[3], float out[3]) { float tmp[3]; for (int i = 0; i < 3; i++) { tmp[i] = raw[i] - center[i]; } // 投影到主轴坐标系 float proj[3]; for (int i = 0; i < 3; i++) { proj[i] = tmp[0] * axes[0][i] + tmp[1] * axes[1][i] + tmp[2] * axes[2][i]; } // 在各主轴方向上缩放 for (int i = 0; i < 3; i++) { proj[i] *= scale[i]; } // 投影回原始坐标系 for (int i = 0; i < 3; i++) { out[i] = proj[0] * axes[i][0] + proj[1] * axes[i][1] + proj[2] * axes[i][2]; } }axes[i]是以主轴向量为列的旋转矩阵。校准后的数据会重新落回一个以原点为中心、半径接近 R 的球面上。到了这一步,航向角计算才真正有精度可言。
5. 避坑:磁力计校准的六个常见翻车点、原因与修复
5.1 读出来的数据全是 0xFF 或 0x00
现象:串口打印磁力计数据,发现三个轴要么全是 255,要么全为 0,和设备 ID 一起读时完全对不上。原因多数是 I2C 地址处理错误。HAL 库的HAL_I2C_Mem_Read期望收到的是左移一位后的地址,直接传 0x0C,库内部再左移一位就变成了 0x18,设备地址错得离谱。
解决:检查所有访问 AK8963 和 MPU9250 的函数,把地址参数统一写成MPU9250_ADDR << 1和AK8963_ADDR << 1。还有一类是旁路未开,初始化顺序反了,AK8963 的 WIA 读不到,先确认MPU_INT_PIN_CFG写入了 0x02,再检查从机地址。
5.2 WHO_AM_I 读出来不是手册上写的那个数
现象:读寄存器 0x75,有的板子返回 0x71,有的返回 0x73,有人按手册只认 0x71,结果程序一直报初始化失败。
原因:MPU9250 存在不同批次,ID 有差异,0x73 是常见变体。解决:读出来同时判断 0x71 和 0x73,或者干脆只读不判断,直接进入后续配置。做驱动时对这类 ID 校验建议做成可配置项,而不是写死,否则换一块芯片就翻一次车。
5.3 拟合出的中心偏移量巨大,且方向固定不变
现象:offset 算出来有几千甚至上万,数据看着不对。
原因:采集数据时传感器没有旋转,或者只在一个姿态下静态采集。样本点全挤在一个小区域里,拟合算法只能看到椭球的一小块表面,中心估计被严重拉偏。解决:采集阶段强制要求设备旋转,覆盖球面至少七八成区域。可以用加速度计数据辅助检查,也写一个上位机散点图脚本,直接从串口读原始数据画三维散点图。
5.4 协方差矩阵特征值出现接近零或负值
现象:特征值算出来有负数,或者某个值只有几百,缩放系数变成几十甚至上百,校准后的数据形状异常。
原因:样本点几乎落在一个平面内,某个方向的方差被抹平了。特征值接近零意味着那个主轴的椭球半径几乎为零,缩放时因为除以近似零直接爆炸。解决:检查样本是否覆盖三维空间,不要只做平面旋转。更细节的做法是,特征值小于最大特征值的 5% 时,强制把该轴缩放系数设为一个安全上限,不让数值溢出。
5.5 校准后数据模长在部分方向仍然偏差很大
现象:正放时模长接近设定值,翻转 90 度后模长偏了 20%,看起来校准了个寂寞。
原因:极值中点法估中心对离群点敏感,或者样本分布不均衡导致中心偏移没收敛。解决:换用协方差均值迭代法重新估中心,并且把采集数据时的离群帧在代码里就剔除掉,不要等拟合完了再动手。另外检查 ST2 的溢出标志有没有被处理,磁力计数据有溢出的帧混进样本集合,拟合出的椭球会整体外扩。
5.6 CubeMX 生成的 I2C 代码卡在 HAL_I2C_Mem_Read 超时
现象:程序跑到读 WIA 时死等,调试器看HAL_I2C_Mem_Read返回 HAL_BUSY 或 HAL_TIMEOUT。
原因:I2C 中断优先级没有配置,或生成的代码里中断优先级分组冲突,导致中断不停进、回调永远不触发。解决:可以先把中断全部关闭,用阻塞模式跑通数据链路;如果必须要用中断,检查 NVIC 的 I2C 中断优先级是否低于时序关键的外部中断。另一个常见诱因是 SCL/SDA 上拉电阻缺失,示波器看波形会看到锯齿状边沿,加上 4.7k 欧姆上拉电阻就能解决。这类问题用逻辑分析仪直接抓总线最能定位,比肉眼反复看代码效率高很多。
6. 一个高阶技巧:把椭球拟合做成滑动窗口的在线重校准
静态校准只能消除出厂时和当前环境的误差,但磁力计偏移会随温度、附近铁磁体位置变化而漂移。我看到很多项目把校准做成一次性流程,设备装进外壳后偏移变了,航向角又开始飘。这里有一个不太复杂却能明显提升稳定性的做法:把采集和拟合改成滑动窗口在线更新。
思路是让系统每采集 50 个新样本,就用窗口内最近 300 个样本重新做一次椭球拟合。窗口向前滑动,老样本被丢弃,新样本保持覆盖。拟合计算量不大,3×3 的雅可比分解在 MCU 上耗时不到 1 ms,完全可以放在主循环的空闲时间片里执行。更新校准参数时,不要直接替换,而是用一阶低通做平滑:
float alpha = 0.2f; // 新参数权重,调大收敛快,调小更稳定 for (int i = 0; i < 3; i++) { cal_offset[i] = (1.0f - alpha) * cal_offset[i] + alpha * new_offset[i]; cal_scale[i] = (1.0f - alpha) * cal_scale[i] + alpha * new_scale[i]; }这个低通系数就是在线重校准的“态度”参数。alpha 太大,航向角在环境突变时会跟着跳一下;alpha 太小,漂移补偿慢得像没做。我一般从 0.1 起步,在整机绕 8 字测试时观察模长误差的收敛速度来微调。还有一个小习惯:在线拟合只更新中心与缩放,主轴方向矩阵保持出厂校准的结果不变,因为软磁方向在实际环境中很少变化,强行更新旋转矩阵反而容易让校准结果抖动。这样做的好处是让算法在恶劣环境下也不至于把姿态解算搞乱。
这套在线思路配合前文的采集代码,算是把椭球拟合从一次性校准工具变成了可以长期运行的嵌入式功能。项目上线前我总会拿一块磁铁靠近设备,观察模长误差在几秒内是否被拉回正常区间。每回调试时看到那条模长曲线重新回到平直,都知道这颗传感器的潜力真正被榨出来了。希望帮到你。
本文还有配套的精品资源,点击获取