第2章:传感器基础(一):惯性测量单元(IMU)原理、加速度计与陀螺仪的工作机制、IMU数据特性与误差来源

各位同学,欢迎来到传感器融合实战的第二讲。

今天咱们聊聊IMU——惯性测量单元。这东西在机器人、无人机、手机里到处都是。说白了,它就是你的设备感知自身运动的「内耳前庭」。没有它,你的设备就是个瞎子,不知道自己在动还是没动。

2.1 IMU是什么?

IMU,全称Inertial Measurement Unit。它通常由三个加速度计和三个陀螺仪组成。三轴加速度计测线加速度,三轴陀螺仪测角速度。有些IMU还会集成磁力计,那就是九轴了。

我个人习惯把IMU分成两类:

  • 消费级IMU:比如MPU6050、ICM-20948。便宜,但噪声大,温漂明显。
  • 工业/战术级IMU:比如ADIS系列、霍尼韦尔的HG系列。贵,但精度高一个数量级。

你想想看,手机里那个IMU成本可能不到两块钱,而导弹上用的IMU能顶一辆车。差距就在这。

2.2 加速度计的工作原理

加速度计测的是什么?很多人以为是「速度变化率」。其实不对。它测的是「比力」——也就是物体受到的惯性力与重力的合力。

内部结构呢?最常见的是MEMS电容式加速度计。里面有个微小的质量块,用弹簧悬着。当芯片加速时,质量块会偏移,导致电容变化。测出电容变化,就能反推出加速度。

关键点:静止时,加速度计测到的是重力加速度g。所以如果你把IMU平放,Z轴读数应该是+1g(约9.8 m/s²)。

我在项目中遇到过一件事。有个学生把IMU竖直放置,发现X轴读数是1g,他以为传感器坏了。其实没坏——重力方向变了而已。嗯,这里要注意:加速度计永远分不清「重力」和「运动加速度」。

2.3 陀螺仪的工作机制

陀螺仪测角速度,单位是°/s。MEMS陀螺仪用的是科里奥利效应。简单说,让一个质量块高速振动,当它旋转时,会产生一个垂直于振动方向的力。测这个力的大小,就知道转得多快。

听起来很玄乎?其实你想想看,小时候玩过那种旋转木马吧?你站在上面往外扔球,球会偏转。那就是科里奥利力在起作用。

我的经验:陀螺仪对温度极其敏感。我曾经在-20°C到60°C环境下测试同一颗IMU,零偏变化超过5°/s。所以量产产品必须做温补。

2.4 IMU数据特性

IMU的数据有几个典型特征:

  • 高频噪声大:尤其是加速度计,高频抖动很明显。
  • 低频漂移严重:陀螺仪的积分会随时间发散,因为零偏在累积。
  • 动态响应快:IMU的采样率可以做到几百甚至上千Hz,比GPS快得多。

说白了,IMU适合短时间高动态的跟踪,不适合长时间绝对定位。这就是为什么我们要做传感器融合——用GPS或视觉来修正IMU的漂移。

2.5 误差来源分析

做IMU融合,不懂误差来源就是瞎搞。我列几个最常见的:

误差类型 来源 影响 典型值(消费级)
零偏(Bias) 制造工艺、温度 积分后角度漂移 ±10°/s(陀螺)
比例因子误差 标定不准确 测量值缩放偏差 ±1%
交叉轴耦合 安装不正 一个轴的运动串扰到另一轴 ±2%
随机游走 噪声积分 角度估计方差随时间增大 0.01°/√Hz
温度漂移 温度变化 零偏和比例因子变化 0.1°/s/°C

避坑指南:我曾经在一个项目中忽略了交叉轴耦合,结果IMU在水平旋转时,垂直轴竟然也读出了角速度。后来花了三天时间做六面标定才搞定。所以,量产前一定要做多位置标定。

2.6 如何读取原始数据?

以MPU6050为例,通过I2C读取原始数据。代码很简单:

// 伪代码示例:读取MPU6050加速度计和陀螺仪数据
#include <Wire.h>

#define MPU_ADDR 0x68

void setup() {
    Wire.begin();
    Wire.beginTransmission(MPU_ADDR);
    Wire.write(0x6B);  // 电源管理寄存器
    Wire.write(0x00);  // 唤醒
    Wire.endTransmission();
}

void loop() {
    Wire.beginTransmission(MPU_ADDR);
    Wire.write(0x3B);  // 加速度计数据起始地址
    Wire.endTransmission(false);
    Wire.requestFrom(MPU_ADDR, 14, true);

    int16_t ax = Wire.read() << 8 | Wire.read();
    int16_t ay = Wire.read() << 8 | Wire.read();
    int16_t az = Wire.read() << 8 | Wire.read();
    int16_t gx = Wire.read() << 8 | Wire.read();
    int16_t gy = Wire.read() << 8 | Wire.read();
    int16_t gz = Wire.read() << 8 | Wire.read();

    // 原始值转物理量
    float accel_scale = 16384.0;  // ±2g量程
    float gyro_scale  = 131.0;    // ±250°/s量程

    float ax_g = ax / accel_scale;
    float ay_g = ay / accel_scale;
    float az_g = az / accel_scale;
    float gx_dps = gx / gyro_scale;
    float gy_dps = gy / gyro_scale;
    float gz_dps = gz / gyro_scale;

    delay(10);
}

注意:原始数据是16位有符号整数。要转成物理量,必须除以对应的量程系数。我见过有人忘了除,结果角度算出来几千度——那肯定不对。

2.7 小结

这一章我们聊了IMU的基本原理。加速度计测比力,陀螺仪测角速度。它们各有各的毛病——加速度计怕振动,陀螺仪怕漂移。但组合起来,就能互补。

下一章我会讲怎么用卡尔曼滤波把这些数据融合起来。到时候你会发现,理解了误差模型,滤波器调参才有方向。否则就是瞎调。

课后思考:如果你把IMU放在匀速直线运动的车上,加速度计读数是多少?陀螺仪呢?想清楚这个问题,你就理解了IMU的本质。