STM32与GY87九轴传感器读取实战:寄存器配置、HAL库实现与避坑指南
2026/9/7 10:15:45 网站建设 项目流程

简介:这套STM32读取GY87模块的源码包,专为需要快速获取MPU6050六轴姿态、HMC5883地磁及BMP180气压温度数据的嵌入式开发者准备。资源内置完整驱动,共6个文件(3个头文件与3个C源文件),压缩包仅4KB,结构精简,便于直接移植到现有工程。目前已1162人学习,代码覆盖I2C接口配置、传感器初始化、原始数据读取及校准处理等关键环节,并以中断方式保证实时读取。针对MPU6050实现了加速度与角速度解算,针对HMC5883完成磁场强度采集,针对BMP180则包含温度与气压换算流程,可帮助开发者避开常见寄存器配置陷阱。无论是用于无人机姿态导航、可穿戴设备还是物联网环境监测,这套代码都能显著降低多传感器驱动开发门槛,让使用者快速完成数据融合与上层应用调试,整体代码风格清晰,注释简明,便于二次开发。 把 STM32 和 GY87 这颗九轴传感器接起来读取数据,是很多做四轴飞行器、平衡小车、姿态解算项目的朋友绕不开的第一步。GY87 看起来只是一块蓝底小板子,实际集成了 MPU6050 陀螺仪加速度计和 HMC5883L 磁力计,能输出加速度、角速度、磁场强度三类原始数据,配合算法之后就是稳定的姿态角。我自己最开始做平衡小车时,想着 GM87 就是个大号 MPU6050,照着教程抄代码,结果加速度计和陀螺仪输出正常,磁力计却一直读到 0,排查了整整一个晚上,最后发现是数据输出路径理解错了。这篇文章就把从硬件接线、寄存器配置到代码实现、常见坑位一次性讲清楚,适合刚入手 STM32 传感器的同学,也适合想快速移植到项目里的老手直接抄作业。

1. 拆解GY87模块:两颗芯片与数据传输路径

GY87 这个名字本身不是芯片型号,而是模块商给 PCB 起的名字。拿到板子后先别急着接线,把上面的丝印看清楚。绝大多数 GY87 模块上可以看到 U1 是 MPU6050,U2 是 HMC5883L,一颗负责六轴惯性数据,一颗负责三轴地磁数据,两组合起来才是九轴。

1.1 板载芯片:MPU6050与HMC5883L

MPU6050 集成三轴加速度计和三轴陀螺仪,内部还有 DMP 运动处理器,可以直接输出四元数,但绕不开的是初始化和配置。HMC5883L 则是一颗早期的三轴磁力计,I2C 接口、测量范围从 ±0.88Ga 到 ±8.1Ga,虽然性能比不上现在的磁力计芯片,但做航向角、做电子罗盘完全够用。

这两颗芯片在模块上通常共用一条 I2C 总线,但地址不同:MPU6050 的 7 位地址是 0x68(如果 AD0 拉高则是 0x69),HMC5883L 是 0x1E。注意 HMC5883L 的从机地址是 0x1E,换算成 8 位写地址就是 0x3C,但 STM32 的 HAL 库里HAL_I2C_Mem_Read第一个参数传 7 位地址即可,别在这里多移一位,否则数据全不对。

1.2 数据输出路径与地址规划

这里有个 GY87 系列模块最容易隐藏的差异:部分新版 GY87 把 HMC5883L 直接挂在主 I2C 总线上,而部分旧版设计是把 HMC5883L 接在 MPU6050 的 AUX I2C 辅助总线上。如果你在代码里直接读 0x1E 地址读不到磁力计数据,但读 MPU6050 一切正常,就要怀疑模块是不是走了辅助 I2C 通路。

判断方法很简单:用万用表量 HMC5883L 的 SCL/SDA 引脚是否与模块排针的 SCL/SDA 连通,如果连通说明是直连方案,直接访问 0x1E 就行;如果不通,则说明需要先配置 MPU6050 的 I2C Master 模式,通过0x6A0x240x25等寄存器转发访问。对新手来说,我建议一律先把模块型号和原理图查清楚再动代码,不然一晚上搭进去都排查不出来。

从实际项目角度考虑,九轴传感器比六轴多出来的三轴磁力计主要用来解决航向角漂移问题。陀螺仪长时间积分会产生累积误差,加速度计可以修正横滚和俯仰,但无法修正偏航,这时候就需要磁力计提供一个绝对的地磁参考。这也是为什么许多姿态解算库(如 Madgwick、Mahony)输入都包含磁力计分量。

2. 硬件接线与上电前的检查

STM32 读取 GY87 的接线非常固定,但越是简单越容易在细节上翻车。很多同学上来就把模块插到面包板上,杜邦线随便一怼,然后发现读不出数据,首先怀疑代码,实际上问题往往出在电平和共地上。

2.1 供电、电平与滤波

GY87 模块的 VCC 通常支持 3.3V 或 5V,但板上芯片的逻辑电平是 3.3V。如果使用 5V 供电,某些模块的 HMC5883L 可能会因为内部 LDO 压降发抖,导致读数不稳。我的建议是直接用 STM32 开发板的 3.3V 输出给模块供电,虽然磁力计工作时会有几毫安的电流波动,但 3.3V LDO 通常扛得住。

SCL 和 SDA 引脚属于开漏结构,需要上拉电阻。GY87 板上一般已经焊了 10kΩ 上拉电阻,但如果你的 I2C 总线上挂了很多设备,线一长波形就会被拉垮,此时可以在 SCL、SDA 对地并联 100pF 电容帮助滤除高频噪声,或者在模块端再补一个 4.7kΩ 上拉。放心,I2C 对多上拉并不敏感,别补到 1kΩ 以下就行。

另外一点:接线时让 GND 线尽可能粗短。GY87 的磁场测量对电流噪声非常敏感,如果 GND 线细长,电机驱动或继电器动作时地弹噪声会被磁力计直接读出来,表现就是磁场强度数据里跳出一个很大的尖峰。我调试平衡小车时遇到过这个问题,后来把所有传感器地线单独汇总到一个星形接地点,数据立刻干净了很多。

2.2 I2C上拉电阻与地址冲突

GPIO 配置成开漏输出时,I2C 需要外部上拉才能产生高电平。如果用的是 STM32 内部上拉,虽然寄存器配置里能开,但内部上拉阻值偏大,在 400kHz 高速模式下波形上升沿会很慢,容易出现通信不稳定。稳妥做法是板载上拉不动,外部再加一路 4.7kΩ 到 10kΩ 上拉,总线速度先用 100kHz,确认没问题后再尝试 400kHz。

如果总线上还有其他 I2C 设备,比如 OLED、EEPROM、温湿度传感器,就需要提前规划好地址。HMC5883L 地址固定为 0x1E,MPU6050 为 0x68 或 0x69 取决于 AD0 引脚。我把 GY87 和 0x3C 的 OLED 挂在同一条 I2C1 上,三个地址互不冲突,实测完全没问题。但若你的板子把 MPU6050 的 AD0 默认拉高了,地址就会变成 0x69,代码里写死 0x68 就会一直 NACK,读不到任何数据。焊接的问题也要注意:GY87 的引脚间距其实是标准的 2.54mm,但有些模块焊盘上会涂一层薄薄的助焊剂,插到面包板前最好用酒精擦一下,否则接触不良导致 SDA 偶尔拉不低,折腾半天排除不了。

3. 寄存器配置与单位换算:这是很多人翻车的地方

STM32 读取 GY87,本质上就是通过 I2C 读写芯片内部寄存器。理解几个关键寄存器,比抄一百行代码都有用。传感器初始化不到位、量程设错、灵敏度系数算错,都会让后续数据完全没法用。

3.1 MPU6050的初始化寄存器序列

MPU6050 上电后处于睡眠模式,必须先唤醒。初始化顺序通常是这样:

首先写电源管理寄存器 1(地址 0x6B),把 bit6 清零,即解除睡眠。这个寄存器复位值默认是 0x40,也就是睡眠模式,如果不写这一步,后面读出来的全是 0 或者固定值,这也是新手最常见的“数据全零”原因。接着写采样率分频寄存器(0x19),比如要得到 1kHz 采样率,写 0x04 表示把内部 1kHz 除以 5 得到 200Hz。再写配置寄存器(0x1A),把数字低通滤波器设为 0x03,对应 44Hz 带宽,可以有效滤除机械振动带来的高频噪声。

然后是量程配置,这个非常关键。加速度计配置寄存器是 0x1C,取值 0x00 对应 ±2g,0x08 对应 ±4g,0x10 对应 ±8g,0x18 对应 ±16g。陀螺仪配置寄存器是 0x1B,取值 0x00 对应 ±250°/s,0x08 对应 ±500°/s,0x10 对应 ±1000°/s,0x18 对应 ±2000°/s。我调试平衡小车时,陀螺仪用的是 ±2000°/s,因为小车倾倒瞬间角速度常常很大,用 ±250°/s 会直接削顶。

功能寄存器地址常用配置值量程灵敏度
电源管理10x6B0x00唤醒-
采样率分频0x190x04200Hz-
数字低通滤波0x1A0x0344Hz-
加速度计量程0x1C0x00±2g16384 LSB/g
陀螺仪量程0x1B0x18±2000°/s16.4 LSB/(°/s)

初始化完成后,可以通过读 0x75(WHO_AM_I)寄存器确认通信正常,MPU6050 的返回值应当是 0x68。这不是必须步骤,但强烈建议加上,能帮你快速区分接线问题还是芯片故障。

3.2 HMC5883L的量程配置与读数顺序

HMC5883L 的配置相对简单。配置寄存器 A(0x00)控制采样平均次数和数据输出速率,典型值是 0x70,表示 8 次平均、15Hz 输出。配置寄存器 B(0x01)控制增益,也就是量程,0x20 对应 ±1.3Ga,这是最常用且精度较高的档位。模式寄存器(0x02)写 0x00 表示连续测量模式。

这里有个容易忽略的细节:HMC5883L 的数据寄存器从 0x03 开始,连续 6 个字节依次是 X 高、X 低、Z 高、Z 低、Y 高、Y 低,注意顺序是 X、Z、Y,不是 X、Y、Z。很多教程直接照搬 MPU6050 的六字节顺序,导致 X、Y、Z 轴互相错位。另外,HMC5883L 的测量输出是 16 位有符号数,每次读取前最好先读状态寄存器(0x09)的 bit0,如果为 1 表示数据已就绪再读取,可以避免读到新旧数据混拼的半截结果。

增益系数的换算,我直接用 ti 手册里的典型值:±1.3Ga 档位下,每 LSB 对应 1.16mGa(有些版本标 1090 LSB/Gauss,本质一样)。如果你想校准得更精细,可以把传感器放水平,旋转 360 度,记录 X、Y 的最大最小值,用(max - min) / 2做零偏补偿,这样航向角至少能准确到 ±3 度以内。

3.3 把原始ADC值换算成物理量

MPU6050 和 HMC5883L 的原始输出都是 16 位有符号补码,范围为 -32768 到 32767。加速度计的换算公式是g = raw / 16384(当量程为 ±2g 时),因为满量程 32768 对应 2g。陀螺仪同理,deg_per_s = raw / 16.4(量程 ±2000 时)。磁力计的换算公式是gauss = raw * 1.16 / 1000,或反向使用灵敏度系数。

我这里给一个实际计算例子:读到的加速度原始值是 8192,那么实际加速度为 8192 / 16384 = 0.5g;陀螺仪原始值是 164,那么角速度为 164 / 16.4 = 10°/s。注意陀螺仪输出的是角速度,不是角度,要得到角度必须积分,而积分过程要非常小心,连续积分几十秒后零偏会被不断放大。我在代码里给陀螺仪做零偏校准:上电静止时连续采样 200 次求平均,作为初始零偏,之后再减去这个平均值再积分。

需要留意的是,磁力计原始输出对温度变化存在漂移,但短期使用影响不大。如果你做的是航向保持或导航项目,建议在代码里定期执行一次性硬磁校准,把周围电机磁性物质带来的固定偏置消掉。

4. 基于HAL库的读取代码实现

说到具体代码,STM32 开发环境建议直接用 CubeMX 生成 I2C 基础工程,HAL 库封装的HAL_I2C_Mem_ReadHAL_I2C_Mem_Write很适合做传感器寄存器读写。下面是我在实际项目中验证过的实现方案。

4.1 用CubeMX生成基础工程

在 CubeMX 里选择你的 STM32 型号,启用 I2C1,默认参数即可,如果需要更稳定可以把时钟速度设为 100kHz。同时打开 USART1 的串口中断模式,方便通过 printf 打印传感器原始数据。GPIO 部分不用手动配置,CubeMX 会自动把 I2C 引脚设为开漏复用模式。

生成的工程里,main.cwhile循环前需要进行传感器初始化。初始化顺序建议是:先确认 MPU6050 的 WHO_AM_I,再分别写入 0x6B、0x19、0x1A、0x1C、0x1B 寄存器,HMC5883L 则写 0x00、0x01、0x02 三个寄存器。如果是在 FreeRTOS 工程里操作,I2C 初始化建议放在任务创建前一次性完成,不要在多个任务里并发访问同一个 I2C 外设。

4.2 读寄存器的底层函数封装

#include "main.h" extern I2C_HandleTypeDef hi2c1; #define MPU6050_ADDR (0x68 << 1) #define HMC5883L_ADDR (0x1E << 1) uint8_t MPU_ReadReg(uint8_t reg, uint8_t *buf, uint16_t len) { return HAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR, reg, I2C_MEMADD_SIZE_8BIT, buf, len, 100); } uint8_t MPU_WriteReg(uint8_t reg, uint8_t val) { return HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, reg, I2C_MEMADD_SIZE_8BIT, &val, 1, 100); } uint8_t HMC_ReadReg(uint8_t reg, uint8_t *buf, uint16_t len) { return HAL_I2C_Mem_Read(&hi2c1, HMC5883L_ADDR, reg, I2C_MEMADD_SIZE_8BIT, buf, len, 100); }

注意 HAL 库里HAL_I2C_Mem_Read的内部会在地址参数上自动完成 7 位到 8 位的转换,所以传给函数的地址一定要用 7 位地址(0x68、0x1E),而不是左移后的值。我在代码注释里保留了<< 1的写法是为了提醒自己这里传的是从机地址,实际使用中直接用 0x68 和 0x1E 即可。

4.3 主循环读取与数据验证

读取流程并不复杂,用结构体把三组数据组织起来最直观:

typedef struct { int16_t ax; int16_t ay; int16_t az; int16_t gx; int16_t gy; int16_t gz; int16_t mx; int16_t my; int16_t mz; } mpu9axis_t;

初始化和读取函数可以这样写:

void GY87_Init(void) { uint8_t id = 0; MPU_ReadReg(0x75, &id, 1); if (id != 0x68) { printf("MPU6050 not found\\r\\n"); } MPU_WriteReg(0x6B, 0x00); // 唤醒 MPU_WriteReg(0x19, 0x04); // 采样率200Hz MPU_WriteReg(0x1A, 0x03); // 低通44Hz MPU_WriteReg(0x1C, 0x00); // 加速度计±2g MPU_WriteReg(0x1B, 0x18); // 陀螺仪±2000°/s uint8_t cfg[3] = {0x70, 0x20, 0x00}; HAL_I2C_Mem_Write(&hi2c1, HMC5883L_ADDR, 0x00, I2C_MEMADD_SIZE_8BIT, cfg, 3, 100); } void GY87_ReadRaw(mpu9axis_t *data) { uint8_t buf[14]; MPU_ReadReg(0x3B, buf, 14); // MPU6050: 加速度6字节+温度2字节+陀螺仪6字节 style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />

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

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

立即咨询