做自平衡小车或者室外导航机器人,光靠陀螺仪和编码器,跑久了方向一定会飘。陀螺仪是测角速度的,积分出来的角度误差会一直累积;编码器记里程,轮子打滑一次方向就偏了。要给机器人一个绝对的方向参考,最便宜好用的方案就是三轴电子罗盘——HMC5883L。
这颗霍尼韦尔的三轴磁阻传感器,淘宝GY-273模块几块钱一片,I2C接口只占两个引脚,校准好了能做到1到2度的航向精度。我做四足和小车都用过它,踩过的坑不少,这篇把接线、代码、校准全说透。

HMC5883L这颗芯片到底怎么干活
HMC5883L用的是各向异性磁阻(AMR)技术,芯片上做了三条互相垂直的坡莫合金薄膜,磁场一过来电阻就变,三个轴同时测就能算出地磁场的矢量方向。官方datasheet参数是这样的:三轴测量、量程±0.88到±8.1高斯8档可配、分辨率5毫高斯、输出速率0.75到75Hz、I2C地址固定0x1E、供电2.16到3.6V、工作电流典型100微安。
我们买到的基本都是GY-273小板子,板上带了3.3V稳压和电平转换,5V Arduino直接接就行。但有个大坑必须先说:现在市面上绝大多数GY-273实际焊的不是HMC5883L,是国产兼容芯片QMC5883L,I2C地址是0x0D不是0x1E,寄存器映射也不一样。买了模块先跑个I2C扫描程序确认地址,0x1E是真霍尼韦尔,0x0D就是QMC,库要换。HMC5883L本身其实已经被霍尼韦尔停产了。
接线:四根杜邦线搞定
接线很简单,Arduino Uno/Nano上I2C就是A4(SDA)和A5(SCL),Mega要换到20/21脚。
GY-273的VCC接5V(板上有LDO稳压,不要接3.3V,有些板子LDO压差不够会欠压),GND接GND,SCL接A5,SDA接A4。DRDY那个脚是数据就绪中断,普通轮询用法不用接。大部分模块板上已经焊了4.7k上拉电阻,外面不用再加。

这里提醒一句,罗盘对电磁干扰极其敏感。模块安装位置要离电机、舵机、电池、降压模块至少10厘米,越远越好。我试过把罗盘直接放在舵机上方,读数能偏60度以上,完全没法用。小车一般架个小杆子顶在最上面,四足机器人放在机头最高处,电源线单独走,别和电机线绑一起。
读原始数据:直接操作寄存器更靠谱
网上教程大多让你装Adafruit库,但我更习惯直接Wire库操作寄存器,代码透明,出问题好查。注意HMC5883L数据寄存器的顺序是X、Z、Y,不是X、Y、Z——这个顺序很容易写错,写错了航向角算出来完全不对,盯着数据看半天找不出原因。
下面是最精简的读取代码,配置成8采样平均、15Hz输出、±1.3高斯量程(最高精度档),连续测量模式:
#include "Wire.h"
#define HMC_ADDR 0x1E
void setup() {
Serial.begin(115200);
Wire.begin();
// 配置寄存器A:8采样平均, 15Hz输出, 正常测量
Wire.beginTransmission(HMC_ADDR);
Wire.write(0x00); Wire.write(0x70);
Wire.endTransmission();
// 配置寄存器B:增益±1.3Ga(1090LSB/Gauss)
Wire.beginTransmission(HMC_ADDR);
Wire.write(0x01); Wire.write(0x20);
Wire.endTransmission();
// 模式寄存器:连续测量
Wire.beginTransmission(HMC_ADDR);
Wire.write(0x02); Wire.write(0x00);
Wire.endTransmission();
Serial.println("HMC5883L Ready");
}
void loop() {
Wire.beginTransmission(HMC_ADDR);
Wire.write(0x03); // 从数据寄存器起始地址读
Wire.endTransmission();
Wire.requestFrom(HMC_ADDR, 6);
int16_t x = (Wire.read() << 8) | Wire.read();
int16_t z = (Wire.read() << 8) | Wire.read();
int16_t y = (Wire.read() << 8) | Wire.read();
// 注意atan2参数顺序是(Y,X)不是(X,Y)
float heading = atan2((float)y, (float)x) * 180.0 / PI;
if (heading < 0) heading += 360.0;
Serial.print("X:"); Serial.print(x);
Serial.print(" Y:"); Serial.print(y);
Serial.print(" Z:"); Serial.print(z);
Serial.print(" Heading:"); Serial.print(heading, 1);
Serial.println(" deg");
delay(250);
}上传之后打开串口监视器,转动模块X/Y值应该会变化。如果读数全是0或者固定不动,八成是接线错了(SDA/SCL接反最常见),或者地址不对(QMC5883L是0x0D)。我一开始也在寄存器顺序上栽过跟头,反复检查才发现数据顺序是XZY。

校准:不校准的罗盘不如不用
raw数据直接算航向会偏得离谱,必须校准。罗盘误差分两种:硬铁误差和软铁误差。硬铁误差是模块附近有永磁体(喇叭磁铁、电机磁钢、铁螺丝),相当于给每个轴加了个固定偏移,校准方法是拿着模块装在机器人最终位置上,在各个方向慢慢转,画"8字"转30秒,记录每个轴的最大值和最小值,偏移量就是(max+min)/2,读数减去这个偏移就行。
软铁误差是铁磁材料扭曲了磁场形状,把原本的圆变成椭圆,需要3×3矩阵校正,复杂一些但用"Magneto"这个PC软件可以自动算。一般DIY项目硬铁校正就能做到5度以内,够用。
校准关键:一定要把模块装在机器人最终的安装位置上,带着机器人整体一起转着校,不要手拿着裸板校完再装回去——装上去周围环境就变了,校了等于没校。
倾斜补偿和磁偏角
上面的heading计算假设模块是水平的,实际机器人跑起来一定会倾斜。倾斜5度就能引入10到15度航向误差,要解决需要加一颗加速度计(MPU6050或者MPU9250自带),用加速度算出roll和pitch,再把磁场矢量投影到水平面才能算准航向。公式稍微复杂点:Xh = Bx*cos(pitch)+Bz*sin(pitch),Yh = Bx*sin(roll)*sin(pitch)+By*cos(roll)-Bz*sin(roll)*cos(pitch),最后heading = atan2(Yh, Xh)。真要做导航的必须上。
最后还有磁偏角问题:磁北极和地理北极不是同一个点,国内大部分地区磁偏角是西偏2到6度左右(北京约-6度、上海约-5度、广州约-2度),精度要求不高可以忽略,要做GPS导航配合的话要在heading上加这个角度。NOAA的World Magnetic Model网站可以查任意经纬度的当前磁偏角。
常见坑位总结
第一,地址先I2C扫描确认是0x1E还是0x0D,别拿HMC的库去驱QMC,怎么都跑不起来。第二,安装位置离电机舵机越远越好,电源线加磁珠滤波。第三,数据寄存器顺序是XZY不是XYZ,atan2参数顺序是(Y,X)。第四,必须在最终安装位置做硬铁校准,否则读数没有意义。第五,模块保持水平,倾斜场景必须上加速度计做倾斜补偿。
HMC5883L这颗传感器做DIY机器人导航完全够用,关键是安装位置和校准,硬件本身很皮实,我用了三年还没烧过一片。后面打算再写一篇把它和MPU6050做倾斜补偿、接OLED显示航向的完整方案。











上次用QMC5883L接HMC的库怎么都跑不起来,看了这篇才知道地址是0x0D不是0x1E,终于弄好了,感谢踩坑分享!
校准那段说要在最终安装位置带着机器人一起转,这个我之前完全没注意,难怪装完了偏20度,回去重新校一下
倾斜补偿那段公式能再详细点吗?我做的平衡车一直偏航,应该就是没补偿的问题
磁偏角我都是直接忽略的,做个桌面玩具其实无所谓,导航再考虑
模块离电机10cm够吗?我装在舵机旁边5cm读数抖得厉害,看来还得架杆子