☰
STM32 LSM303磁力计程序实战:从原始数据到稳定航向角
2026/10/15 18:40:19 网站建设 项目流程

简介:这份资源是面向嵌入式开发初学者与STM32学习者的LSM303磁力计驱动程序包,围绕三轴磁强计的数据采集与姿态解发展开,可解决电子罗盘、导航定位及运动设备定向中的磁场测量问题。包内共125个文件,以35个h头文件、34个c源文件为核心,配合12个o目标文件、12个d依赖文件及uvproj、uvopt等Keil工程文件,另有hex烧录文件、map映射文件与sct分散加载文件,覆盖从源码到编译产物的完整工程结构,压缩包约2.38MB。目前已有1244人学习下载。程序重点实现STM32的I²C外设初始化、LSM303与MPU6050设备地址定义、数据读写与解析函数,并涉及中断处理及卡尔曼滤波、互补滤波等数据融合思路,读者可据此掌握多传感器协同采集与姿态解算的完整流程,积累嵌入式软硬件交互与调试经验。

1. 拿到 LSM303 磁力计程序,先搞清楚它到底在算什么

很多人第一次接触「基于 STM32 的 LSM303 磁力计程序」,脑子里想的是“读个寄存器、把 XYZ 打出来就完事”。真上手才发现,串口里跳出来的三个数根本没法用:转个方向数值乱飘,倾斜一下航向角直接崩掉,静止时读数还在几十 LSB 上下抖。问题不在 STM32,也不在 LSM303,而在于这套程序真正要交付的不是“三个轴的数据”,而是一个能用的航向角——也就是把磁力计、加速度计和 STM32 的采样时序、校准、姿态解算串成一条链路。

LSM303 是一颗把三轴加速度计和三轴磁力计封在一起的组合传感器,常见型号分数字接口版本(I²C/SPI)和模拟输出后缀版本,做嵌入式方案时绝大多数人用的是 I²C 数字版。它便宜、好焊、资料多,是电子罗盘、云台指向、姿态记录这类小项目的常客。但“便宜好焊”不等于“好出数据”,磁力计天生怕硬铁软铁干扰,加速度计天生怕振动,STM32 这边还要处理 I²C 时序、数据就绪判断、量程匹配。这套程序的价值,就是把这些脏活累活一次性封装好,让你拿到的是稳定航向角,而不是一堆需要自己猜的原始码值。

这篇文章面向两类人:一类是刚画完板子、准备把 LSM303 跑起来的新手,跟着步骤能复现最小可用版本;另一类是已经能读出数据、但航向角一直不准的熟手,重点看校准和解算那几章。下面从硬件连接讲到校准参数,再到避坑和进阶技巧,全部按能直接抄作业的粒度写。

2. 硬件连接与 I²C 初始化:把 LSM303 挂上 STM32 总线

2.1 先确认你手上是哪一版 LSM303

这一步看着废话,实际翻车最多。LSM303 家族里,数字接口版本走 I²C 或 SPI,寄存器地址固定;而带模拟输出后缀的版本输出的是模拟电压,必须接 STM32 的 ADC,程序结构完全不同。如果你拿到的程序里出现的是LSM303_ReadReg这类函数,那基本是数字版;如果出现的是HAL_ADC_GetValue,那就是模拟版。两者不能混用,接错总线怎么调都读不出数据。

数字版里还要再分:有的模块把加速度计和磁力计做成两个独立 I²C 从地址,有的做成单一地址加内部寄存器切换。常见做法是加速度计地址0x32(7 位地址左移后),磁力计地址0x3C。上电后先读WHO_AM_I寄存器确认通信,这一步能挡掉一大半“接线没问题但读不到”的玄学问题。

项目数字 I²C 版模拟输出版
接口I²C / SPIADC 模拟电压
地址固定从地址无地址概念
程序入口寄存器读写ADC 采样 + 换算
典型用途电子罗盘、姿态低成本角度检测

2.2 STM32 侧 I²C 初始化的最小配置

以常见的 HAL 库为例,I²C 初始化要关注三件事:时钟频率、上拉电阻、地址位宽。LSM303 支持标准模式 100kHz 和快速模式 400kHz,新手建议先用 100kHz 跑通,稳定后再提速。上拉电阻模块一般自带 4.7kΩ,如果自己画板子,SDA/SCL 各接一个 4.7kΩ 到 3.3V,别省。

/* I2C1 初始化,100kHz,7 位地址模式 */ static void MX_I2C1_Init(void) { hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; /* 先跑 100kHz,稳定后再改 400kHz */ hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(&hi2c1) != HAL_OK) { Error_Handler(); /* 初始化失败先查供电和上拉 */ } }

这段代码里ClockSpeed是最常被乱改的参数。有人一上来就设 400kHz,结果杜邦线长一点就通信失败,还以为是芯片坏了。NoStretchMode建议保持关闭,LSM303 在转换期间会拉低 SCL 做时钟拉伸,关掉它容易读到脏数据。初始化失败时,先量 SDA/SCL 静态电平,正常应该是 3.3V;如果一直是低电平,检查是不是地址冲突或者焊反了。

2.3 读 WHO_AM_I 确认链路通不通

通信建立后第一件事不是读数据,是读身份寄存器。加速度计和磁力计各有一个WHO_AM_I,返回值是固定常量。这一步相当于握手,握手不过后面全是白费。

/* 读取加速度计 WHO_AM_I,预期返回 0x33 一类固定值 */ uint8_t who = 0; HAL_I2C_Mem_Read(&hi2c1, LSM303_ACC_ADDR, LSM303_WHO_AM_I, I2C_MEMADD_SIZE_8BIT, &who, 1, 100); if (who != LSM303_ACC_ID) { printf("ACC 握手失败, 读到 0x%02X\r\n", who); }

HAL_I2C_Mem_Read的第二个参数是从地址,注意 HAL 库要求传入的是左移一位后的 8 位地址。很多人在这里传了 7 位地址,结果一直返回 HAL_ERROR。第三个参数是寄存器地址,第四个参数说明寄存器地址是 8 位还是 16 位,LSM303 用 8 位。超时给 100ms 足够,给太长会拖慢启动。握手失败时,先确认供电是 3.3V 而不是 5V,再确认地址有没有写错,最后才怀疑芯片。

3. 数据采集与量程配置:让原始码值先变得可信

3.1 加速度计和磁力计的量程怎么选

量程选错是“数据看起来有但没法用”的头号原因。加速度计常见量程 ±2g / ±4g / ±8g / ±16g,磁力计常见 ±1.3 / ±1.9 / ±2.5 / ±4.0 / ±4.7 / ±5.6 / ±8.1 高斯。选量程的原则是:让实际信号落在量程的 30%~70% 区间,既不削顶也不浪费分辨率。

做电子罗盘时,加速度计主要用来测倾角做倾斜补偿,人体或小车场景 ±2g 基本够用;如果设备会摔会撞,选 ±4g 留余量。磁力计在地球表面磁场约 0.25~0.65 高斯,选 ±1.3 高斯分辨率最高,但靠近电机或扬声器时容易饱和,这时要升到 ±4.7 高斯。量程不是越大越好,量程越大,每 LSB 代表的值越大,分辨率越差。

传感器量程档位每 LSB 对应值适用场景
加速度计±2g约 1mg静态倾角、罗盘补偿
加速度计±4g约 2mg有振动的小车
磁力计±1.3 高斯约 0.1 微特远离干扰的罗盘
磁力计±4.7 高斯约 0.4 微特靠近电机的环境

3.2 配置寄存器的顺序不能乱

LSM303 上电后处于掉电或低功耗状态,必须先写控制寄存器打开对应轴和输出数据率,再读数据。顺序错了会出现“配置写了但数据不动”的情况。常见做法是:先设加速度计数据率,再设磁力计工作模式,最后设磁力计数据率。

/* 加速度计:100Hz 输出,开启 XYZ 三轴 */ LSM303_WriteReg(LSM303_ACC_ADDR, LSM303_CTRL_REG1_A, 0x57); /* 磁力计:连续转换模式,15Hz 输出 */ LSM303_WriteReg(LSM303_MAG_ADDR, LSM303_MR_REG_M, 0x00); /* 磁力计:设置增益,对应 ±1.3 高斯 */ LSM303_WriteReg(LSM303_MAG_ADDR, LSM303_CRA_REG_M, 0x10);

0x57拆开看:低四位0111表示 XYZ 三轴使能,高四位0101表示 100Hz 数据率。磁力计的MR_REG_M写0x00是连续转换模式,写0x01是单次转换,写0x02是休眠。CRA_REG_M的0x10对应 15Hz 和默认增益,增益档位要和你选的量程对上,对不上算出来的微特值就是错的。写完配置别急着读,等一个数据周期再读,否则拿到的是旧值。

3.3 读数据要判数据就绪,别盲读

新手最容易犯的错是定时器一到就读,不管传感器转没转完。LSM303 有状态寄存器可以查数据就绪位,加速度计看STATUS_REG_A的 ZYXDA 位,磁力计看SR_REG_M的 DRDY 位。盲读的后果是同一个值读两遍,或者读到半更新的数据,航向角会跳。

/* 等待加速度计新数据就绪,超时 10ms 返回错误 */ uint8_t status = 0; uint32_t tick = HAL_GetTick(); do { LSM303_ReadReg(LSM303_ACC_ADDR, LSM303_STATUS_REG_A, &status); if (HAL_GetTick() - tick > 10) return -1; } while ((status & 0x08) == 0); /* bit3 是 ZYXDA */

这段轮询逻辑里,0x08是 ZYXDA 位掩码,置位表示 XYZ 三轴新数据都好了。超时给 10ms 是经验值,100Hz 下数据周期 10ms,超时说明传感器没在转或者配置没生效。读磁力计同理,只是寄存器名和位不同。轮询会占 CPU,如果系统还有其他任务,建议放到定时器中断里做,或者用传感器的中断引脚触发。

4. 校准与航向角解算:从原始码值到能用的角度

4.1 硬铁和软铁校准到底在校什么

磁力计原始数据里混了两类误差:硬铁偏移是固定叠加的直流分量,来源是 PCB 上的永磁体或焊盘;软铁畸变是随方向变化的缩放和旋转,来源是铁磁材料。不校准的磁力计画出来的轨迹是个偏离原点的椭圆,校准后应该是个圆心在原点、半径均匀的圆。

硬铁校准最简单:把设备水平转一圈,记录每个轴的最大最小值,偏移量就是(max + min) / 2,之后每次读数减去这个偏移。软铁校准复杂一些,常见做法是采集多个姿态的数据,拟合椭球,求出变换矩阵。新手先把硬铁校准做扎实,航向角就能从“完全没法看”变成“勉强能用”。

/* 硬铁校准:转动一圈后计算各轴偏移 */ float offset_x = (mag_max[0] + mag_min[0]) / 2.0f; float offset_y = (mag_max[1] + mag_min[1]) / 2.0f; float offset_z = (mag_max[2] + mag_min[2]) / 2.0f; /* 应用偏移 */ mx = raw_x - offset_x; my = raw_y - offset_y; mz = raw_z - offset_z;

采集最大最小值时,要保证设备在每个方向都停留足够时间,让磁力计完成一次转换。offset_z在水平旋转时变化不大,但倾斜使用时必须一起校。校准值建议存到 STM32 的 Flash 里,每次上电加载,不然每次重启都要重转一圈,体验很差。

4.2 倾斜补偿:为什么平放准、一歪就废

只靠磁力计算航向角,前提是设备严格水平。一旦倾斜,磁力计的 X/Y 轴会混入重力方向的分量,算出来的角度直接偏几十度。解决办法是用加速度计测出俯仰和横滚,把磁力计数据旋转到水平面再算航向。

/* 用加速度计算俯仰和横滚,单位弧度 */ float pitch = atan2f(-ax, sqrtf(ay * ay + az * az)); float roll = atan2f(ay, az); /* 把磁力计数据补偿到水平面 */ float mx_h = mx * cosf(pitch) + mz * sinf(pitch); float my_h = mx * sinf(roll) * sinf(pitch) + my * cosf(roll) - mz * sinf(roll) * cosf(pitch); /* 计算航向角,转成 0~360 度 */ float heading = atan2f(my_h, mx_h) * 180.0f / M_PI; if (heading < 0) heading += 360.0f;

atan2f比atan好用的地方在于它能自动处理象限,返回值范围是 -π 到 π。pitch和roll的公式里符号很容易搞反,建议先用静态已知角度验证:设备平放时 pitch 和 roll 应该接近 0,抬头 30 度时 pitch 应该接近 30 度。补偿公式里的三角函数顺序也不能乱,写错了角度会随倾斜剧烈变化。航向角算完做一次 0~360 归一化,方便后续显示。

4.3 加一点低通滤波,但别滤过头

磁力计噪声和加速度计振动会让航向角抖动。最简单的是滑动平均或一阶低通,代价是响应变慢。系数怎么选:如果设备转动慢,系数可以小一点,滤波强;如果要做实时指向,系数要大,宁可抖一点也别延迟。

/* 一阶低通滤波,alpha 越小越平滑但越滞后 */ #define ALPHA 0.2f static float heading_filt = 0.0f; heading_filt = ALPHA * heading + (1.0f - ALPHA) * heading_filt;

ALPHA取 0.2 时,大约 5 个采样周期跟上真实值,100Hz 下就是 50ms 延迟。如果发现转动时角度“拖尾巴”,把ALPHA调到 0.4 或 0.5。滤波只能压噪声,压不了校准误差,校准没做好先别指望滤波救场。

5. 避坑与排查:那些让航向角发疯的细节

5.1 现象:静止时航向角缓慢漂移

原因通常是磁力计零偏没校干净,或者附近有缓慢变化的磁场源(比如正在充电的电池、发热的电源芯片)。解决方法是重新做硬铁校准,并且把设备远离电源模块再校。如果漂移是周期性的,检查是不是有电机或 PWM 走线从传感器下方经过,磁力计对几十毫安级的电流都敏感。

5.2 现象:一靠近金属桌子角度就偏

这是典型的软铁干扰,桌子本身被磁化或者导磁。解决方法是把校准后的设备拿到实际使用环境再校一次,或者在使用时保持固定距离。如果无法避免,考虑加屏蔽罩,但屏蔽罩本身也可能带磁性,要选无磁材料。更实际的做法是在结构上把传感器尽量远离金属件,哪怕挪 2 厘米,效果都明显。

5.3 现象:I²C 读数据偶尔返回 HAL_BUSY

总线被其他任务占用,或者上一次传输没正确结束。检查是不是有多个任务同时访问同一个 I²C,加互斥锁或者把传感器读取集中到一个任务里。另外,杜邦线接触不良也会导致偶发 BUSY,尤其是 SCL 线。如果用的是软件 I²C,检查延时是否够,LSM303 在快速模式下对时序有要求。

5.4 现象:航向角在特定角度突然跳 180 度

多半是atan2f的输入符号问题,或者补偿公式里某个轴符号写反。用已知方向验证:设备指向北时航向角应该接近 0 或 360,指向东接近 90。如果只在某个象限跳,重点查my_h的计算。还有一种可能是磁力计某个轴饱和了,检查量程是不是选小了。

5.5 现象:上电后第一次读数总是错的

传感器上电后需要时间稳定,磁力计尤其明显。解决方法是初始化后延时 100ms 再读,或者丢弃前几组数据。另外,配置寄存器写完到数据更新有一个转换周期,别写完立刻读。如果用的是连续模式,等两个周期再取数更稳。

6. 进阶技巧:把航向角做稳的最后几招

校准数据持久化是第一个值得做的进阶。把硬铁偏移和软铁矩阵存到 STM32 内部 Flash 的末尾扇区,上电时读出来直接用。写 Flash 要注意先擦后写,擦除粒度通常是一页,别把程序区擦了。存的时候加一个魔数和校验和,防止读到空白 Flash 时用了一组垃圾参数。

第二个技巧是动态校准。设备在使用中如果长时间处于静止,可以趁机更新零偏估计。做法是检测加速度计模长接近 1g 且角速度接近 0 时,把当前磁力计读数纳入滑动统计,缓慢修正偏移。这样能补偿温漂,但更新要慢,否则会把真实转动当成偏移吃掉。

第三个技巧是融合。单纯用加速度计做倾斜补偿,在振动环境下俯仰横滚会抖,进而污染航向角。如果系统里还有陀螺仪,可以用互补滤波或卡尔曼滤波把三者融合,得到更稳的姿态。没有陀螺仪的话,至少对加速度计做低通,截止频率设在 5Hz 左右,既能滤振动又不至于把真实倾斜滤没。

验证方法上,我习惯用一个简单的转台或者干脆用手匀速转圈,把航向角打到串口绘图工具里看。理想曲线是一条斜率均匀的斜线,如果出现平台或台阶,说明那个角度区间有干扰或校准缺陷。另一个验证是 8 字校准法:拿着设备在空中画几个 8 字,覆盖各个姿态,然后看三个轴的模长是否接近常数,偏差大就说明软铁没校好。

最后说个血泪经验:磁力计这东西,布局阶段省的事,调试阶段都要加倍还回来。传感器离电源远一点、离螺丝远一点、离扬声器远一点,比任何算法都管用。我一般会在 PCB 上给 LSM303 单独划一块安静区域,周围不放任何磁性元件,走线也尽量短。程序层面,校准值一定要能存能读,别每次上电都让用户转圈。把这两件事做扎实,基于 STM32 的 LSM303 磁力计程序就能从“能跑”变成“能用”。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询