
1. 先说结论这颗芯片到底解决了什么问题在便携设备、机器人、可穿戴产品的开发里“我现在朝向哪儿”是个绕不开的需求。无论是无人机返航、AGV小车循迹还是手表上的指南针表盘背后都需要一颗能同时感知姿态和地磁方向的传感器。LSM303AGR就是这样一颗把三轴加速度计和三轴磁力计塞进 2mm×2mm×1mm 封装的六轴芯片配合标准 I2C/SPI 接口做一只超紧凑的高性能电子罗盘模块非常顺手。这颗芯片最吸引我的是它的体积和功耗组合。LGA-12 封装比很多独立磁力计芯片还小板级设计几乎不占面积而正常读取模式下整机功耗能压到微安到一百多微安级别对电池供电的穿戴产品来说非常友好。对比过去用分立磁力计加 MCU 自写补偿的方案LSM303AGR 相当于把传感器部分一次性打包齐活加速度计负责测倾角磁力计负责测磁场方向两者配合就能输出稳定的航向角。这个应用笔记记录的是我从模块选型、硬件布局、驱动移植到罗盘算法调试的全过程。文章不会只堆寄存器说明而是会把每一步为什么这么做的逻辑讲清楚尤其是磁力计在 PCB 上常见的干扰坑、校准流程和倾斜补偿算法这些都是实际项目中真正卡人的地方。如果你正在评估这颗芯片或者在做便携罗盘、低成本惯性导航、机器人姿态参考这篇文章可以直接当作落地参考。1.1 关键参数与选型理由LSM303AGR 的几个核心参数值得先列出来因为选型时这些数字决定了方案可行性参数数值实际意义封装LGA-12L2.0×2.0×1.0mm超紧凑适合手表、耳机、标签类产品供电电压1.71V 至 3.6V3.3V 或 1.8V 系统直供不需要额外 LDO接口I2C最高1MHz/ SPI最高10MHz绝大多数 MCU 都能直接对接加速度计量程±2g / ±4g / ±8g / ±16g可配置覆盖静止到剧烈运动场景磁力计满量程±50 gauss地磁场只有约 0.5 gauss余量极大加速度计功耗低功耗模式约 2µA 级别静态检测场景可以长期待机磁力计功耗连续测量约 100µA 级别比很多霍尔方案更有竞争力中断加速度计运动/静止/6D磁力计阈值适合做低功耗唤醒1.2 适合做什么产品这颗芯片的典型场景不是做高精度导航而是“中等精度、超低功耗、极小体积”的方位感知。我做过的手环计步界面需要显示东南西北用的就是这类组合无人机飞控里需要初始航向对准它也能胜任扫地机器人回充时需要知道充电座的大致方向同样是典型应用。它的精度在做好校准和倾斜补偿后静态航向误差可以稳定在 1 到 2 度以内动态场景下的表现主要取决于你的滤波算法和安装位置。2. 硬件部分把传感器“安全”地放上板很多工程师拿到这颗芯片后第一件事就是画板但磁力计对外部磁场极其敏感PCB 布局的小失误会在后面调试时浪费大量时间。硬件设计这部分我按供电、接口、布局三个层面拆开讲。2.1 供电与去耦LSM303AGR 的数字电源 VDD 和 IO 电源 VDD_IO 可以直接接同一个 3.3V但前提是电源纹波要干净。磁力计对电源噪声的敏感度比加速度计高得多如果板上有电机、电磁铁、DCDC 开关噪声建议在传感器电源引脚附近放一个 1µF 100nF 的组合去耦电容摆放位置尽量靠近引脚走线先到电容再到芯片。还有一个容易被忽略的点如果系统里有大电流回路比如驱动电机、加热丝、LED 灯它们和传感器之间不要共用较长地线。地平面上的电流会产生微小的磁场梯度ADC 分辨率再高也救不回来。我在早期版本里把罗盘放在电池充电回路旁边充满电时航向角会偏移接近 5 度这就是地回路电流的影响。2.2 I2C 接口接线细节I2C 接线本身很简单SDA、SCL 两根线各接一个上拉电阻到 VDD_IO。上拉阻值建议 2.2k 到 4.7k具体看总线频率和总线电容。如果走线较长或板上有其他 I2C 设备4.7k 在 400kHz 下可能拉不上去容易出现通信时好时坏的问题这时换成 2.2k 通常就能解决。这里有个非常容易踩的坑LSM303AGR 的加速度计 I2C 地址由 SA0 引脚决定磁力计地址是固定的。加速度计 7 位地址 0x19SA0 接高或 0x18SA0 接低磁力计固定为 0x1E。调试时经常有人把 0x19 写成 0x308位地址导致找不到设备这个在 5.1 节会细说。2.3 布局避坑磁力计最怕什么电子罗盘模块的硬件设计核心就一句话让磁力计感受到的地磁场尽可能纯净。实际工作中最常见的干扰源按严重程度排序是扬声器或马达的永磁体、继电器线圈、大电流走线、以及板上的磁性材料。我踩过一个很典型的坑把 LSM303AGR 放在蜂鸣器旁边蜂鸣器静态时没事一旦响起来线圈里的电流会产生一个和声音频率同步的交变磁场罗盘读数直接乱跳。后来把蜂鸣器移到板边传感器移到对角问题才消失。所以布局时传感器尽量放在板边缘远离任何带线圈或磁铁的器件如果实在避不开可以考虑用坡莫合金屏蔽罩把传感器罩住但成本会上升。另外一个细节是传感器正下方的铺铜。磁力计是通过感应磁场工作的PCB 上铺铜产生涡流会轻微改变磁场分布导致读数有固定偏移。虽然没有严重到完全不能用但建议传感器正下方不要走大电流线地平面保持完整即可。首次打样后最好做一次硬磁校准测量看 X/Y/Z 三个轴的零点偏移是否在合理范围。3. 驱动移植从寄存器到能读出一组有效数据硬件焊好之后就是驱动移植。LSM303AGR 的寄存器不算多但加速度计和磁力计是两套独立的寄存器映射别混在一起看。我开发时用的是 ESP-IDF 的 I2C 驱动下面代码稍作修改就能移植到 STM32、Arduino 或其他平台。3.1 I2C 地址与关键寄存器地图先记住两个地址#define LSM303AGR_ACC_ADDR 0x19 // 7位地址SA01 #define LSM303AGR_MAG_ADDR 0x1E // 7位地址固定加速度计常用寄存器寄存器地址作用WHO_AM_I0x0F身份识别应返回 0x33CTRL_REG1_A0x20ODR 和 X/Y/Z 轴使能CTRL_REG4_A0x23量程、BDU、高分辨率模式OUT_X_L_A0x28加速度 X 轴低字节磁力计常用寄存器寄存器地址作用WHO_AM_I0x4F身份识别应返回 0x40CFG_REG_A_M0x60磁力计 ODR、工作模式CFG_REG_C_M0x62BDU、中断使能OUTX_L_REG_M0x68磁力计 X 轴低字节调试时第一步永远是读 WHO_AM_I。能读到预期值说明 I2C 链路、供电、器件焊接都正常后续配置才有意义。这一步能帮你排除至少一半的硬件问题。3.2 初始化流程与代码初始化顺序我习惯这样先配置加速度计再配置磁力计最后等一小段时间让芯片稳定。// 写寄存器简写i2c_write_reg(addr, reg, val) 由平台提供 // 加速度计CTRL_REG1_A 0x57 // ODR 100HzX/Y/Z 轴全部使能 i2c_write_reg(LSM303AGR_ACC_ADDR, 0x20, 0x57); // 加速度计CTRL_REG4_A 0x88 // BDU1数据块更新防止高低字节错位 // HR1高分辨率模式FS00±2g i2c_write_reg(LSM303AGR_ACC_ADDR, 0x23, 0x88); // 磁力计CFG_REG_A_M 0x0C // ODR100Hz连续测量模式 i2c_write_reg(LSM303AGR_MAG_ADDR, 0x60, 0x0C); // 磁力计CFG_REG_C_M 0x10开启 BDU i2c_write_reg(LSM303AGR_MAG_ADDR, 0x62, 0x10);CTRL_REG1_A 的 0x57 分解开看bit7-4 是 ODR0101 对应 100Hzbit3 是低功耗模式选择位这里关闭bit2-0 分别使能 Z、Y、X 轴。CTRL_REG4_A 的 0x88 是 BDU 和高分辨率模式。BDU 这个位强烈建议打开不然读取数据时如果正好跨在两个采样周期之间高字节和低字节会来自不同时刻的采样航向角会出现偶发跳变。为何选 ±2g 量程因为电子罗盘的倾斜补偿只需要感知重力方向室内设备重力始终是 1g±2g 档位能提供最大灵敏度静态倾角分辨率最好。如果产品会经历剧烈振动或跌落才考虑升到 ±4g 或 ±8g。磁力计这边没有量程选择固定 ±50 gaussODR 选 100Hz 是为了让后续滤波还有余量。低功耗项目可以降到 20Hz 或 10Hz功耗能进一步降低。3.3 读取加速度与磁力数据每次读取需要连续读出 6 个字节再拼成三个 int16_t。以加速度计为例uint8_t buf[6]; int16_t ax_raw, ay_raw, az_raw; i2c_read_regs(LSM303AGR_ACC_ADDR, 0x28, buf, 6); ax_raw (int16_t)((buf[1] 8) | buf[0]); ay_raw (int16_t)((buf[3] 8) | buf[2]); az_raw (int16_t)((buf[5] 8) | buf[4]);磁力计同理从 0x68 开始读 6 字节i2c_read_regs(LSM303AGR_MAG_ADDR, 0x68, buf, 6); int16_t mx_raw (int16_t)((buf[1] 8) | buf[0]); int16_t my_raw (int16_t)((buf[3] 8) | buf[2]); int16_t mz_raw (int16_t)((buf[5] 8) | buf[4]);拼数据时最容易出错的是大小端。LSM303AGR 的数据寄存器是低字节在前也就是小端序所以直接用(buf[1] 8) | buf[0]就能得到正确值。我之前在移植时习惯性用了大端拼接结果三个轴的数据来回跳排查了很久才发现是拼字节的问题。3.4 把原始值换算成物理量拿到原始值之后要转成有意义的单位。加速度计在 ±2g、高分辨率模式下灵敏度是 16384 LSB/g所以float ax ax_raw / 16384.0f; // 单位g float ay ay_raw / 16384.0f; float az az_raw / 16384.0f;磁力计灵敏度固定在 1.5 mG/LSB也就是每个 LSB 代表 1.5 毫高斯转成高斯float mx mx_raw * 0.0015f; // 单位gauss float my my_raw * 0.0015f; float mz mz_raw * 0.0015f;地磁场在不同纬度强弱不同一般在 0.25 到 0.65 gauss 之间。你可以读一下当前数值验证水平放置时XY 两个轴的平方和开根号应该在 0.2 到 0.5 之间Z 轴会有一个接近竖直分量的值。如果读到的三个轴都是几十上百 gauss 的异常值基本可以断定传感器附近有强磁源或者焊接时引入了问题。4. 罗盘算法从原始数据到稳定航向角能读出加速度和磁场原始值只是第一步真正的核心工作是把这些数据变成稳定的方位角。这块内容我按难度递进先讲水平状态下的简化算法再讲倾斜补偿最后讲校准和滤波。4.1 水平状态下的简化航向角如果设备严格保持水平航向角只需要用磁力计的 X/Y 两个轴float heading atan2f(my, mx) * 180.0f / PI; if (heading 0) { heading 360.0f; }这里atan2f(my, mx)的结果范围是 -180 到 180 度加 360 再取余数是为了把角度映射到 0 到 360 度范围。这个公式成立的前提是 X 轴水平指向前方Y 轴水平指向右方。如果你的板子传感器摆放方向不一样可能需要交换 X/Y 或加负号这个后面会详细说。这个简化公式在桌面测试时表现很好但实际使用时设备很难保持绝对水平——手表戴在手腕上有倾角机器人爬坡时有俯仰角这时候直接用atan2f(my, mx)算出来的航向角会严重失真。4.2 倾斜补偿为什么不能只算 atan2倾斜补偿的原理很简单先用加速度计算出设备当前的俯仰角pitch和横滚角roll再把磁力计的三轴向量旋转回水平面最后用水平面上的 X/Y 分量计算航向角。这里需要先定义坐标系X 轴指向设备前方Y 轴指向设备右方Z 轴指向设备下方右手系。在这个约定下重力在静止设备上的分量是ax0, ay0, az1g。根据欧拉角的旋转关系可以解出// 加速度计读数单位 g float norm sqrtf(ax*ax ay*ay az*az); ax / norm; ay / norm; az / norm; float roll atan2f(-ay, sqrtf(ax*ax az*az)); float pitch atan2f(ax, az);roll 是绕 X 轴的旋转角pitch 是绕 Y 轴的旋转角。这两个公式是做了归一化之后推导的所以无论设备加速度读数绝对值是多少只要静止或慢速运动状态下重力方向提取都稳定。有了 roll 和 pitch就可以把磁力计向量旋转到水平面float xh mx * cosf(pitch) my * sinf(roll) * sinf(pitch) - mz * sinf(pitch) * cosf(roll); float yh my * cosf(roll) mz * sinf(roll); float heading atan2f(-yh, xh) * 180.0f / PI; if (heading 0) { heading 360.0f; }这段代码里的xh和yh就是磁力计投影到水平面之后的北向分量。heading 的符号取决于你的坐标系定义如果装出来发现 0 度方向偏了 90 度或者正负反了把atan2f的参数调换顺序或者给xh/yh加负号即可这是正常的适配过程不用怀疑公式错了。实际项目中还有一个细节倾斜补偿用的加速度计数据必须是“纯重力方向”的估计值。如果设备在剧烈运动线加速度会混进重力测量里导致补偿角度失真。对静态罗盘来说这不是问题但对行走中的手表或飞行的无人机建议对加速度计数据做低通滤波后再参与倾斜补偿计算。4.3 硬磁与软磁校准电子罗盘不用校准就直接用误差通常会大到不能接受。这节内容是被问得最多的部分也是整个项目里最影响最终效果的一环。硬磁干扰来自设备上的固定磁场源比如扬声器的磁铁、电池附近的金属件、PCB 上的磁性材料。它的表现是三个轴的磁力计读数整体叠加了一个固定偏移。校正方法是传感器在空间内多方向旋转采集足够多的点然后统计每个轴的最大最小值偏移量就等于(max min) / 2。我用的硬磁校准流程#define CAL_POINTS 500 float mx_min 1e9f, mx_max -1e9f; float my_min 1e9f, my_max -1e9f; float mz_min 1e9f, mz_max -1e9f; for (int i 0; i CAL_POINTS; i) { read_mag(mx_raw, my_raw, mz_raw); mx_min fminf(mx_min, mx_raw); mx_max fmaxf(mx_max, mx_raw); // 其余两轴同理 delay(10); } float mx_offset (mx_max mx_min) / 2.0f; float my_offset (my_max my_min) / 2.0f; float mz_offset (mz_max mz_min) / 2.0f;采集的时候要让设备画“8”字轨迹尽量覆盖所有姿态平放、立放、翻面、倾斜目标是让磁场向量在三维空间画出一个完整的球面。只在一个平面上旋转采集的数据校正结果会有较大残留误差。软磁干扰来自板上的铁磁性材料对磁力线的扭曲会导致各轴灵敏度不一致磁场分布从圆形变成椭圆。完整的软磁校正要用椭球拟合算法解一个 3x3 矩阵。对大多数消费级产品简化的做法是只做各轴缩放float scale_x (mx_max - mx_min) / 2.0f; float scale_y (my_max - my_min) / 2.0f; float scale_z (mz_max - mz_min) / 2.0f; // 以 X 轴为基准 float gain_x