简介:面向嵌入式开发者和电子爱好者,这份基于STM32G030F6P6与HMC5883L磁角度传感器的电子罗盘例程包,可帮助快速搭建磁场角度测量演示。工程在STM32CubeIDE环境下以HAL库编写,采用模拟I2C管脚协议与传感器通信,绕开硬件I2C的引脚限制,完整演示初始化、寄存器配置、数据读取与磁角度换算流程,可直接移植到同类Cortex-M0+平台,也可作为地磁检测、低成本导航、姿态参考等项目的起步代码。压缩包大小64.71MB,目录结构简洁清晰,包含可导入的源码工程与配套说明,便于对照CSDN博文理解模拟I2C时序、寄存器读写和角度计算等应用细节。资源由hwytree整理发布,已有451人学习下载,适合希望掌握模拟I2C实现方式、快速上手电子罗盘开发的初中级嵌入式开发者,也适合在项目选型阶段评估HMC5883L的读取效果。
1. 一颗 32KB Flash 的 M0+ 去读三轴磁力计:这个压缩包到底想干什么
看到 STM32G030F6P6-HMC5883L.rar 这种命名,基本可以断定它是把“主控型号 + 传感器型号”拼在一起的工程包。STM32G030F6P6 是意法半导体 G0 系列里很入门的一颗 Cortex-M0+,主频最高 48MHz,Flash 只有 32KB,SRAM 8KB,TSSOP20 小封装,价格便宜;HMC5883L 则是经典的三轴磁阻传感器,走 I2C 接口,常用于电子罗盘、磁场检测和低成本定向。这两个东西放在一起,要解决的事非常明确:用一颗几块钱的 MCU,通过两根信号线把三轴地磁场数据稳定读出来,再换算成方位角,让设备知道自己在朝哪个方向。适合谁?想做电子罗盘、遥控车定向、气象站风向测量,或者手头正好有一颗 HMC5883L 模块想接进 STM32 工程的开发者。整件事不涉及复杂姿态解算,核心就三块:I2C 通信、寄存器配置、校准补偿。新手能照着把数据读出来,熟手可以跳过基础部分直接看后面的避坑和校准技巧。
2. 把 HMC5883L 接到 STM32G030F6P6:引脚复用、I2C 时序与上拉选择
HMC5883L 是纯 I2C 设备,和 MCU 之间只有两条线,接线看似简单,但引脚复用选错、上拉电阻不匹配、上电时序没算好,都会让后面的调试变得很痛苦。这一章先把硬件层面的选择讲清楚,再把最小验证方法给出来。
2.1 引脚分配:I2C1 的复用点,我建议用 PB6/SCL、PB7/SDA
STM32G030F6P6 内部有 I2C1 外设,但它在 TSSOP20 封装里可以复用到多组引脚上。用 CubeMX 打开 Pinout 视图就能看到 PB6 和 PB7 上标着 I2C1_SCL、I2C1_SDA,复用编号是 AF1。我一般习惯先把这两个脚留给传感器,因为它们和 USART、SPI 的引脚不冲突,调试时不用来回换线。如果你的板子已经把 PB6/PB7 占用了,也可以看 PA9/PA10 或其它带 I2C1 复用标记的引脚,具体复用编号以数据手册的 AF 表为准。
电气连接上,HMC5883L 的 VDD 接 3.3V,GND 与 MCU 共地,SCL 和 SDA 各接一个上拉电阻到 3.3V。原因是 I2C 总线是开漏结构,低电平靠芯片内部拉低,高电平必须靠外部上拉。上拉电阻的值直接决定通信速率和信号质量:100kHz 标准模式用 4.7kΩ,400kHz 快速模式用 2.2kΩ 左右比较稳妥。阻值太大,上升沿变缓,高速下容易出错;阻值太小,灌电流偏大,对传感器和 MCU 的 IO 都有额外负担。注意 SCL/SDA 绝对不要接 5V,HMC5883L 供电上限在 3.6V 附近,5V 电平很可能损坏芯片。
还有一个容易被忽略的点是布局。磁力计对周围磁场极其敏感,PCB 上别让大电流走线从传感器底下穿过,电感、继电器、电机驱动线尽量远离,传感器附近也不建议铺大块铜皮。铜皮里的涡流会削弱外部磁场,导致读数整体偏小。这个问题在原理图阶段看不出来,等打板回来再改就晚了。
2.2 上电时序与 I2C 时钟:为什么第一次读会读到 0xFF
HMC5883L 上电后并不是立刻就能通信的。芯片内部有一个上电复位过程,数据手册建议的上电到第一次数据就绪时间在 50ms 级别。代码里我习惯在初始化函数最开始先 HAL_Delay(100),等传感器内部稳定之后再发 I2C 命令,否则第一次读可能拿回全 0xFF 或者总线无应答。
I2C 时钟方面,CubeMX 里把 I2C1 使能后,Speed Mode 可以选择 Standard Mode(100kHz)或 Fast Mode(400kHz)。HMC5883L 支持 400kHz,但如果你用的是杜邦线连接模块,线长超过 10cm,建议先用 100kHz 调通,再拉到 400kHz。上升时间和下降时间参数在 CubeMX 里会自动根据总线电容估算,一般不用手改。有一点必须注意:I2C 的时钟拉伸(Clock Stretching)功能要开着。从设备在准备数据时会主动拉低 SCL 让主设备等待,如果关闭了这个功能,主设备按自己节奏读数据,读回来的字节可能是错位的。
2.3 最小验证:只读三个 ID 寄存器,把通信问题先排除掉
拿到工程包后,先别急着跑完整的示例程序。我建议第一步只做一件事:读 HMC5883L 的 ID 寄存器。这个芯片在 0x0A、0x0B、0x0C 三个地址存了固定值 0x48、0x34、0x33,读写不受量程、模式影响,是验证 I2C 链路是否正常的最短路径。
uint8_t reg = 0x0A; uint8_t id[3] = {0}; // 发送寄存器地址给从设备,等待应答 HAL_I2C_Master_Transmit(&hi2c1, (uint16_t)(0x1E << 1), ®, 1, 100); // 连续读 3 个字节:0x0A、0x0B、0x0C HAL_I2C_Master_Receive(&hi2c1, (uint16_t)(0x1E << 1) | 1, id, 3, 100); // 如果 id[0]=0x48, id[1]=0x34, id[2]=0x33,说明 I2C 链路正常代码里 0x1E 是 HMC5883L 的 7 位 I2C 地址,HAL 库要求传入的是地址左移一位后的 8 位格式,所以写成 (0x1E << 1)。Transmit 第一个参数是设备地址,第二个参数是要发送的数据缓冲区,这里是寄存器地址 0x0A,第三个参数是长度 1,最后一个 100 是超时毫秒数。Receive 同理,地址末尾或上 1 表示读操作。如果读回来的三个字节不是 48 34 33,先查供电电压、上拉电阻和 SCL/SDA 有没有接反,再用示波器或逻辑分析仪看 SDA 上有没有设备应答信号。如果总线上完全看不到波形,问题多半在 MCU 的 I2C 外设配置或者引脚复用没生效。
3. 寄存器配置与驱动代码:从初始化到连续读出 XYZ
通信链路确认没问题之后,就该写正经的驱动了。HMC5883L 的寄存器不算多,但每个寄存器里的位域含义相互关联,配置错了会出现“数据能读,但数值明显不对”的怪现象。这一章给出一套可以直接抄的初始化配置和读取代码,并解释每一字节的含义。
3.1 HMC5883L 的五个关键寄存器:先记住这张表
驱动 HMC5883L 不需要把所有寄存器背下来,但下面这几个必须清楚,它们是整个驱动的地基。
| 寄存器地址 | 名称 | 作用 |
|---|---|---|
| 0x00 | 配置寄存器 A | 采样平均次数、数据输出速率、测量模式 |
| 0x01 | 配置寄存器 B | 磁场量程和增益 |
| 0x02 | 模式寄存器 | 连续测量、单次测量、空闲 |
| 0x03-0x08 | 数据输出寄存器 | X、Z、Y 三个轴各 2 字节,高位在前 |
| 0x09 | 状态寄存器 | bit0 是数据就绪标志 DRDY |
| 0x0A-0x0C | ID 寄存器 | 固定 0x48、0x34、0x33 |
数据输出寄存器有个常见的迷惑点:顺序不是 X、Y、Z,而是 X 高字节、X 低字节、Z 高字节、Z 低字节、Y 高字节、Y 低字节。也就是说你连续读 6 个字节,第 3、4 个字节是 Z 轴而不是 Y 轴。很多初学者按 XYZ 的顺序拼数据,结果 X 和 Y 的读数永远差一个轴,转个方向读数还是乱的。这点我在 3.3 节会再强调。
3.2 初始化序列:8 次平均、15Hz、±1.3Ga、连续模式
HMC5883L 上电后默认处于单次测量模式,如果不写模式寄存器,数据寄存器里的值不会持续更新。所以初始化必须做三件事:配置采样率和测量模式、设定量程、切入连续测量模式。
#define HMC5883L_ADDR 0x1E void hmc5883l_init(void) { uint8_t cfg[2]; // 配置寄存器 A:0x70 = 0111 0000 // bit7:5 = 011,8 次采样平均 // bit4:2 = 100,数据输出速率 15Hz // bit1:0 = 00,正常测量模式 cfg[0] = 0x00; cfg[1] = 0x70; HAL_I2C_Master_Transmit(&hi2c1, (uint16_t)(HMC5883L_ADDR << 1), cfg, 2, 100); HAL_Delay(5); // 配置寄存器 B:0x20 = 001 0000 // bit7:5 = 001,量程 ±1.3Ga,增益约 1090 LSB/Gauss // 其余位保留,写 0 cfg[0] = 0x01; cfg[1] = 0x20; HAL_I2C_Master_Transmit(&hi2c1, (uint16_t)(HMC5883L_ADDR << 1), cfg, 2, 100); // 模式寄存器:0x00 // bit1:0 = 00,连续测量模式 cfg[0] = 0x02; cfg[1] = 0x00; HAL_I2C_Master_Transmit(&hi2c1, (uint16_t)(HMC5883L_ADDR << 1), cfg, 2, 100); // 等第一个测量周期完成 HAL_Delay(100); }配置 A 里的 0x70 拆开看:bit7 到 bit5 是 011,代表 8 次采样平均,平均次数越多噪声越低,但每次更新数据的时间也变长;bit4 到 bit2 是 100,输出速率 15Hz,这是功耗和数据新鲜度之间的平衡点;bit1 和 bit0 是 00,正常测量模式。配置 B 里的 0x20 是出厂默认增益档,对应 ±1.3Ga 量程,地磁场强度通常在 0.25Ga 到 0.65Ga 之间,室内有钢结构时更高一些,±1.3Ga 是起步推荐值。模式寄存器写 0x00 后芯片进入连续测量模式,数据寄存器会按 15Hz 的节奏自动更新,读数据时不需要手动触发。
3.3 连续读 6 字节:顺序是 X、Z、Y,别按 XYZ 拼
读取数据最直接的做法是从 0x03 开始连续读 6 个字节。HMC5883L 的地址指针在连续读时会自动递增,一次 I2C 突发传输就能把三个轴全部拿回来。
int16_t hmc_mag[3] = {0}; // 0:X, 1:Y, 2:Z uint8_t hmc5883l_read(void) { uint8_t buf[6]; uint8_t reg = 0x03; // 先写入要读的起始寄存器地址 HAL_I2C_Master_Transmit(&hi2c1, (uint16_t)(HMC5883L_ADDR << 1), ®, 1, 100); // 再发起连续读,拿到 6 个字节 HAL_I2C_Master_Receive(&hi2c1, (uint16_t)(HMC5883L_ADDR << 1) | 1, buf, 6, 100); // 注意字节顺序:X 高、X 低、Z 高、Z 低、Y 高、Y 低 int16_t x = (int16_t)((buf[0] << 8) | buf[1]); int16_t z = (int16_t)((buf[2] << 8) | buf[3]); int16_t y = (int16_t)((buf[4] << 8) | buf[5]); hmc_mag[0] = x; hmc_mag[1] = y; hmc_mag[2] = z; return 0; }buf[0] 和 buf[1] 是 X 轴高字节和低字节,拼成一个 16 位整数。buf[0] 是 uint8_t,左移 8 位后系统会先把它提升为 int,再和低字节做或运算,最后强转成 int16_t。这样处理能正确保留负数符号,比如角度转了 180 度时磁场方向反转,读回的高字节可能是 0x80 以上,强转后就能得到正确的负值。Z 轴和 Y 轴同理。如果对时序稳定性有更高要求,可以在读之前先查状态寄存器 0x09 的 bit0,为 1 表示这一帧数据已更新,再发起读取;不过对于 15Hz 输出速率,直接加一个 10ms 延时再读也足够。
4. 从 LSB 到方位角:量程换算、atan2 与硬磁偏移补偿
原始数据读出来只是一串整数,从整数到人能看懂的方位角,中间还有两步换算:量程增益换算和三角函数计算。这章把数学逻辑拆开,并解释一个新手最容易忽略的问题——为什么芯片静止不动时,读到的数值却在飘。
4.1 量程与增益对照:配置寄存器 B 的七个档位怎么选
配置寄存器 B 的高三位决定磁场量程,每个档位对应不同的 LSB/Gauss 增益。增益的含义是:每 1 高斯磁场变化对应多少个 LSB 数值变化。读数除以增益,就能得到实际磁场强度的高斯值。
| 配置寄存器 B 高三位 | 量程 | 增益(LSB/Gauss) |
|---|---|---|
| 000 | ±0.88Ga | 1370 |
| 001 | ±1.3Ga | 1090 |
| 010 | ±1.9Ga | 820 |
| 011 | ±2.5Ga | 660 |
| 100 | ±4.0Ga | 440 |
| 101 | ±4.7Ga | 390 |
| 110 | ±5.6Ga | 330 |
| 111 | ±8.1Ga | 230 |
选择档位的原则很简单:地磁场范围在 0.5Ga 左右,用 ±1.3Ga 档,分辨率最高,噪声也最小。如果读回来的某一轴数值频繁接近 2048 或者 -2048,说明这个方向上的磁场已经超出当前量程,读数被截断了,这时要往下一档调。反过来,如果读数长期只有几十个 LSB,说明分辨率浪费了,可以试着往高一档调。量程和灵敏度是矛盾的,量程越小,同样的磁场变化能产生更大的数值变化,所以能用小量程就不要用大量程。
4.2 方位角计算:atan2 的参数顺序决定 0° 方向
方位角最常用的算法是 atan2(Y, X),它返回的是 X 轴正向与当前磁场水平分量之间的夹角,加 180 度换算后得到 0° 到 360° 的罗盘角度。
#include <math.h> float heading_deg(void) { // Y 轴读数作为第一个参数,X 轴读数作为第二个参数 float angle = atan2f((float)hmc_mag[1], (float)hmc_mag[0]); angle = angle * 180.0f / 3.14159265f; if (angle < 0.0f) { angle += 360.0f; } // 如果当地磁偏角是 4°,这里就加 4.0f angle += 4.0f; if (angle >= 360.0f) { angle -= 360.0f; } return angle; }atan2f 和 atan 的区别在于它能根据 X、Y 的符号自动判断象限,返回值范围是 -π 到 π,所以不需要自己写 if 判断象限。负角度加 360 度是为了把角度统一到 0° 到 360°。磁偏角是地理北极和地磁北极之间的夹角,不同经纬度差异很大,小到几度大到二十几度。从网上查到你所在位置的磁偏角后,直接把它加到计算结果里就行。注意函数里的 4.0f 只是示例,实际要按你的地理位置修改。芯片安装方向不同,X、Y 轴的定义会不一样——比如让 Y 轴朝前就是 atan2f(X, Y),需要自己按安装朝向调整参数顺序,调换参数等价于把 0° 方向旋转了 90 度。
4.3 硬磁偏移:你旋转一圈,轨迹圆心不在原点
把设备水平旋转一圈,把每次读到的 X、Y 值画在坐标系里,正常情况下应该得到一个圆心在原点附近的圆。但现实里这个圆的圆心往往明显偏离原点,这就是硬磁偏移。手机、扬声器、铁质外壳、PCB 上的螺丝,都会产生一个固定的附加磁场,叠加在地磁场上,让三个轴的读数整体偏了一个常数。
硬磁偏移的处理是所有磁力计校准里最基本的一步,公式也不复杂:旋转设备采集若干组数据,记录每个轴的最大值和最小值,偏移量就是 (max + min) / 2,校准时把原始读数减去这个偏移量。
int16_t x_offset, y_offset, z_offset; // 旋转设备一整圈,同时更新最值 int16_t x_min = 32767, x_max = -32768; int16_t y_min = 32767, y_max = -32768; int16_t z_min = 32767, z_max = -32768; for (int i = 0; i < 300; i++) { hmc5883l_read(); if (hmc_mag[0] < x_min) x_min = hmc <p> <a href="https://download.csdn.net/download/hwytree/87856607" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>