为什么要融合多个传感器
做姿态估算(无人机、平衡车、机械臂末端姿态)最经典的问题:单一传感器都不准。加速度计能测重力方向,知道设备"大概朝哪",但一加运动加速度就飘。陀螺仪测转动角速度,短期很准,但积分时间一长就漂移,角度越算越偏。
两个传感器单独用都不行,合起来互补,就是所谓传感器融合。
这篇讲的是工业界最常用的融合方法:互补滤波,以及它的升级版卡尔曼滤波。
MPU6050:加速度计 + 陀螺仪
MPU6050 是一颗 6 轴 IMU:三轴加速度计(测 X/Y/Z 方向的线加速度)+ 三轴陀螺仪(测 X/Y/Z 轴的角速度)。一颗芯片十几块钱,I2C 接口,几乎是所有姿态项目的入门选择。
// 从 MPU6050 读原始数据(简化)
struct imu_raw { int16_t ax, ay, az; int16_t gx, gy, gz; };
void imu_read(struct imu_raw * r) {
i2c_read(0x68, 0x3B, r, sizeof(*r));
// 换算成物理量:
// accel: ±2g 量程,1g = 16384 LSB
// gyro: ±250°/s 量程,1°/s = 131 LSB
r->ax /= 16384.0f;
r->gx /= 131.0f;
}
读数只是第一步,真正的工作是把这些原始数据合成一个稳定的姿态角。
加速度计算姿态的局限
静止时,加速度计测到的就是重力。把 X/Y/Z 三个方向的重力分量做反正切,就能算出俯仰角和横滚角。
// 由加速度计算俯仰角 pitch
pitch_acc = atan2f(ay, az) * 57.3f;
roll_acc = atan2f(-ax, sqrtf(ay*ay + az*az)) * 57.3f;
问题是:设备一旦动起来,测到的是重力加运动加速度,算出来的角度就飘。所以加速度计只能当"长期参考",不能当实时角度用。
互补滤波:最实用的融合算法
思路很简单:陀螺仪短期积分得到当前角度,加速度计算出的角度当参考,用一个系数把两者加权平均。系数一般取 0.98,也就是相信陀螺仪 98%、相信加速度计 2%。
float pitch = 0, dt = 0.01f;
void complementary_filter(float gx, float pitch_acc) {
// 陀螺仪积分:当前角 = 上一帧角 + 角速度 * 时间
float gyro_pitch = pitch + gx * dt;
// 互补融合:98% 信陀螺,2% 信加速度
pitch = 0.98f * gyro_pitch + 0.02f * pitch_acc;
}
互补滤波为什么有效
- 陀螺仪高频准、低频飘。
- 加速度计低频准、高频噪。
- 融合后高频走陀螺、低频走加速度,刚好互补。
卡尔曼滤波:更准但更重
卡尔曼滤波是更系统的解法:把系统状态和噪声都建模,递推估计。精度比互补滤波高,但要算矩阵、调参数,MCU 上占用也大。
实际项目里,互补滤波能做到 1 度以内的稳态精度,已经够大多数用。只有在无人机云台、机械臂这种高精度场景才值得上卡尔曼。
运动控制里怎么用姿态角
姿态角算出来干嘛?最常见的几个用途:
- 自平衡车:角度偏离目标值,PID 输出电机力矩纠正。
- 无人机:俯仰角换算成电机差速,控制飞行方向。
- 机械臂:末端姿态反馈,做逆运动学解算。
- 体感游戏:设备倾斜换算成摇杆输入。
一个可跑的互补滤波代码
把上面所有步骤拼起来,就是一个最小可跑的姿态解算。
void loop() {
imu_read(&r);
float pitch_acc = atan2f(r.ay, r.az) * 57.3f;
float gx_deg = r.gx; // 已经换算成 °/s
complementary_filter(gx_deg, pitch_acc);
// 现在 pitch 就是稳定的俯仰角,可以拿去控制电机了
delay_ms(10);
}
传感器融合的精髓不是算法多高深,是搞清楚每个传感器擅长什么、不擅长什么。
把这段代码烧到板子上,串口打印 pitch,手拿着板子倾斜,看数值跟着动,就算跑通了。