Skip to content

IMU 姿态传感器

IMU(Inertial Measurement Unit,惯性测量单元)是机器人感知自身姿态的核心传感器。GSRL 使用 BMI088(6 轴 IMU)+ IST8310(3 轴磁力计)组成 9 轴姿态测量系统。本章从 MEMS 传感器原理讲起,一直到 GSRL 中完整的姿态解算数据流。

上一篇:CAN 通信原理与实践


1. 概念——IMU 如何感知运动

1.1 加速度计——测"比力"

加速度计测量的不是"加速度",而是比力(Specific Force)——即除去重力之外的所有外力产生的加速度。

静止平放时:
  加速度计测量值 = [0, 0, +1g]  ← 不是"向上的加速度",而是支撑力抵消重力的结果

自由落体时:
  加速度计测量值 = [0, 0, 0]     ← 没有支撑力,真正失重

向前加速时:
  加速度计测量值 = [+a, 0, +1g]  ← X 轴测到向前的惯性力

MEMS 加速度计的物理原理:微机械结构中的质量块在加速度作用下发生位移,通过电容变化检测位移量。

简化模型:
         ┌────────────┐
   弹簧 ←│  质量块 m  │→ 弹簧
         └────────────┘
              │
         ┌────┴────┐
         │ 电容检测 │  → 位移 → 电压 → 加速度值
         └─────────┘

为什么不能只用加速度计测角度? 加速度计测量的是比力(重力 + 运动加速度的合力),而非纯重力方向。当机器人向前加速时,加速度计会"看到"一个斜向后的"伪重力",误以为机体发生了倾斜。例如,机器人以 1g 向前加速时,加速度计读数相当于向后倾斜了 45°——但机体实际上是水平的。陀螺仪直接测量旋转角速度,不受线性加速度干扰,因此不存在这个问题。

1.2 陀螺仪——测角速度

陀螺仪测量的是绕各轴的旋转角速度(单位:°/s 或 rad/s),而非角度。

陀螺仪输出 = [ωx, ωy, ωz]  绕 X/Y/Z 轴的角速度

对时间积分得到角度变化:
  Δθ = ∫ ω dt

MEMS 陀螺仪利用科里奥利力(Coriolis Force)原理:一个振动的质量块在旋转时受到垂直于振动方向和旋转轴的力,检测这个力即可得到角速度。

为什么不能只用陀螺仪测角度? 陀螺仪通过积分角速度得到角度,但微小的零偏误差(如 0.1°/s)会在积分后持续累积:1 分钟累积 6° 误差,10 分钟就是 60°——完全错误。而且这个误差会随时间无限增长。加速度计和磁力计虽然瞬时有噪声,但它们提供的是绝对参考(重力方向永远指向地心、地磁场方向固定),可以用来不断校正陀螺仪的累积漂移。

1.3 磁力计——测地磁场方向

磁力计测量地球磁场在三个轴上的分量,用于修正偏航角(Yaw)的漂移。

地磁场 ≈ 0.5 高斯,从磁南极指向磁北极
在北半球,磁力线从地下斜向上射出(磁倾角约 45°~70°)

为什么需要磁力计:陀螺仪的积分误差会随时间累积,导致偏航角持续漂移。磁力计提供绝对的地磁参考方向,可以消除这种漂移。

1.4 六轴 vs 九轴

配置 传感器 能测量 局限性
6 轴 加速度计 + 陀螺仪 Roll, Pitch, Yaw(短时) Yaw 随时间漂移
9 轴 加速度计 + 陀螺仪 + 磁力计 Roll, Pitch, Yaw(长期稳定) 磁力计易受电机磁场干扰

为什么需要 9 轴(加磁力计)而非 6 轴就够了? 加速度计可以修正 Roll 和 Pitch 的漂移,但无法修正 Yaw:重力矢量始终指向下方,绕垂直轴的旋转不会改变重力在加速度计上的投影——换言之,加速度计对 Yaw 是"盲"的。陀螺仪的 Yaw 通道没有绝对参考,漂移无法被加速度计抑制。磁力计通过测量地磁场方向,为 Yaw 提供了唯一的绝对参考,使偏航角长期稳定。


2. BMI088——GSRL 使用的 IMU 芯片

2.1 芯片特性

BMI088 是 Bosch 的高性能 6 轴 IMU,专为无人机和机器人设计:

参数 加速度计 陀螺仪
量程 ±3g / ±6g / ±12g / ±24g ±125°/s ~ ±2000°/s
分辨率 16-bit 16-bit
接口 SPI(最高 10MHz) SPI(最高 10MHz)
特点 抗振动、低噪声 低漂移、抗振动
加速度计和陀螺仪是两颗独立芯片 各有自己的 SPI 片选

为什么选择 BMI088 而非常见的 MPU6050? BMI088 是 Bosch 专门为无人机和机器人场景设计的工业级 IMU,其振动抑制能力远超消费级芯片——在电机高速运转的强振动环境下仍能输出稳定数据。关键设计:加速度计和陀螺仪封装在同一模块但分属两块独立硅片,各自拥有独立的 SPI 接口和片选信号,可同时读取而无总线竞争,这在 1kHz 高频读取场景下至关重要。此外,BMI088 的噪声密度显著低于同级产品,姿态解算精度更高。

2.2 SPI 连接拓扑

 STM32F407                          BMI088
┌──────────┐                   ┌──────────────┐
│          │──SCLK─────────────│SCL_ACC       │
│  SPI1    │──MOSI─────────────│MOSI_ACC      │
│          │──MISO─────────────│MISO_ACC/GYRO │ ← 共用 MISO
│          │                   │              │
│  GPIO_CS1│───────────────────│CS_ACC        │ ← 加速度计片选
│  GPIO_CS2│───────────────────│CS_GYRO       │ ← 陀螺仪片选
│          │                   │              │
│  GPIO    │──INT1_ACC─────────│INT1_ACC      │ ← 加速度计数据就绪
│  GPIO    │──INT1_GYRO────────│INT1_GYRO     │ ← 陀螺仪数据就绪
└──────────┘                   └──────────────┘

注意:加速度计和陀螺仪的 MISO 引脚可以共用,因为它们通过不同的 CS 信号分时选通。只有被 CS 拉低的芯片才会驱动 MISO。

2.3 寄存器读写协议

BMI088 使用标准的 SPI 寄存器读写协议(CPOL=0, CPHA=0):

读寄存器(2 字节帧):
  Master 发送: [0 | 1 (R) | A6..A0]  [dummy (0xFF)]
  Slave  返回: [dummy               ]  [寄存器的值]

写寄存器(2 字节帧):
  Master 发送: [0 | 0 (W) | A6..A0]  [要写入的值]

bit7: 1=读, 0=写

对应 GSRL 中的实现(dvc_imu.cpp):

inline void BMI088::readSingleReg(const SPIConfig &spiConfig,
                                   uint8_t reg, uint8_t &rxData)
{
    uint8_t txData[2] = {static_cast<uint8_t>(reg | 0x80), 0xFF};
    uint8_t rxBuf[2];
    HAL_GPIO_WritePin(spiConfig.csGPIOGroup, spiConfig.csGPIOPin, GPIO_PIN_RESET);
    HAL_SPI_TransmitReceive(spiConfig.hspi, txData, rxBuf, 2, 100);
    HAL_GPIO_WritePin(spiConfig.csGPIOGroup, spiConfig.csGPIOPin, GPIO_PIN_SET);
    rxData = rxBuf[1];  // 第 2 字节是有效数据
}

3. 姿态解算原理

3.1 为什么需要姿态解算

加速度计可以算出 Roll 和 Pitch(基于重力方向),陀螺仪可以积分得到角度变化,磁力计可以给出偏航参考。但三者各有缺陷:

传感器 优点 缺点
加速度计 长期稳定(重力方向不变) 对振动敏感,运动时测量的是"比力"而非重力
陀螺仪 瞬时响应快,不受线性加速度影响 积分误差累积,长期漂移
磁力计 绝对方向参考 响应慢,极易受电机/铁磁材料干扰

姿态解算的本质:融合三者数据,取长补短——用陀螺仪的短期精度修正加速度计的振动噪声,用加速度计和磁力计的长期稳定性消除陀螺仪的积分漂移。

3.2 互补滤波 vs Mahony vs EKF

算法 原理 复杂度 精度 GSRL 中
互补滤波 陀螺仪高通 + 加速度计低通 = 全频段 一般
Mahony 基于 PI 补偿器的显式互补滤波 较好 Mahony
EKF 扩展卡尔曼滤波,最优状态估计 最好 QuaternionEKF

AHRS(姿态航向参考系统)在 GSRL 的 Algorithm 层中实现,为上层 Task 提供欧拉角和四元数输出。GSRL 中的姿态解算架构:

// 抽象接口
class AHRS {
public:
    virtual void init() = 0;
    const Vector3f &update(const Vector3f &gyro,
                           const Vector3f &accel,
                           const Vector3f &magnet = Vector3f());
    const Vector3f &getEulerAngle() const;
    const fp32 *getQuaternion() const;
};

// Mahony 实现(PI 补偿器)
class Mahony : public AHRS { ... };

// 四元数扩展卡尔曼滤波(EKF)实现
class QuaternionEKF : public AHRS { ... };

3.3 四元数 vs 欧拉角

姿态可以用两种方式表示:

表示法 形式 优点 缺点
欧拉角 (Roll, Pitch, Yaw) 直观,人类可读 万向节死锁(Gimbal Lock)
四元数 (q0, q1, q2, q3) 无死锁,适合插值,计算高效 不直观

为什么姿态解算内部使用四元数而非欧拉角? 欧拉角存在万向节死锁(Gimbal Lock):当 Pitch 达到 ±90° 时,Roll 轴和 Yaw 轴重合,丢失一个旋转自由度——四旋翼做垂直翻转时就会遇到这个问题,导致姿态解算崩溃。而四元数在任何姿态下都是奇点自由的,不会死锁。此外,四元数乘法(姿态更新)只需 16 次乘法和 12 次加法,比 3×3 旋转矩阵乘法更高效,这对每毫秒都要做姿态更新的嵌入式系统意义重大。

GSRL 内部使用四元数进行姿态更新,对外提供欧拉角接口:

// 姿态解算后自动转换
void AHRS::convertQuaternionToEulerAngle()
{
    // 四元数 → 欧拉角
    // Roll  = atan2(2*(q0*q1 + q2*q3), 1 - 2*(q1*q1 + q2*q2))
    // Pitch = asin(2*(q0*q2 - q3*q1))
    // Yaw   = atan2(2*(q0*q3 + q1*q2), 1 - 2*(q2*q2 + q3*q3))

    m_eulerAngle[0] = atan2f(2*(q0*q1 + q2*q3), 1-2*(q1*q1 + q2*q2));  // Roll
    m_eulerAngle[1] = asinf(2*(q0*q2 - q3*q1));                        // Pitch
    m_eulerAngle[2] = atan2f(2*(q0*q3 + q1*q2), 1-2*(q2*q2 + q3*q3));  // Yaw
}

4. GSRL 中的 IMU 数据流

4.1 完整流程

┌─────────────────────────────────────────────────────────────┐
│                     数据采集 (中断驱动)                       │
│                                                             │
│  BMI088 INT1 ──→ EXTI4 中断                                 │
│    │                                                        │
│    ├── readRawData()                                        │
│    │   ├── SPI 读加速度计 X/Y/Z (6 字节)                     │
│    │   └── SPI 读陀螺仪 X/Y/Z (6 字节,跳过 chipID)          │
│    │                                                        │
│    ├── dataCalibration()                                    │
│    │   ├── 修正安装误差旋转矩阵 (installSpinMatrix)           │
│    │   └── 减去零飘偏移 (gyroOffset / accelOffset)           │
│    │                                                        │
│    └── IMU::solveAttitude()                                 │
│        │                                                    │
│        ▼                                                    │
│  ┌──────────────────────────────────────────────┐          │
│  │        AHRS 姿态解算 (solveAttitude)         │          │
│  │                                              │          │
│  │  AHRS::update(gyro, accel, magnet)           │          │
│  │    │                                         │          │
│  │    ├── 第一次调用:initQuaternion()           │          │
│  │    │   用加速度计估算初始 Roll/Pitch          │          │
│  │    │   用磁力计估算初始 Yaw                   │          │
│  │    │                                         │          │
│  │    └── 后续调用:dataProcess()                │          │
│  │        ├── 陀螺仪数据更新四元数(预测)       │          │
│  │        ├── 加速度计修正 Roll/Pitch(观测)    │          │
│  │        ├── 磁力计修正 Yaw(观测,可选)       │          │
│  │        └── 四元数归一化 → 欧拉角              │          │
│  │                                              │          │
│  │  输出: m_eulerAngle [Roll, Pitch, Yaw]       │          │
│  │        m_quaternion [q0, q1, q2, q3]        │          │
│  └──────────────────────────────────────────────┘          │
└─────────────────────────────────────────────────────────────┘

4.2 中断驱动的读取时机

GSRL 中的 IMU 数据读取由 BMI088 的 INT1 引脚触发:

// stm32f4xx_it.c 中的加速度计数据就绪中断
void EXTI4_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(INT1_ACCEL_Pin);
}

// 用户实现的 HAL 回调
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if (GPIO_Pin == INT1_ACCEL_Pin) {
        imu.readRawData();        // 读取原始数据
        imu.dataCalibration();    // 校准
        imu.solveAttitude();      // 姿态解算
    }
}

关键设计决策:BMI088 的加速度计 INT1 设为 2kHz 输出,陀螺仪 INT1 也是 2kHz。实际使用中可能不会每次都解算(计算量大),而是在任务中以固定频率(如 1kHz)读取 IMU 对象的最新姿态即可——因为姿态解算已经在中断中完成。

4.3 BMI088 的初始化流程

GSRL 中 BMI088::init() 的完整初始化和自检流程:

BMI088::init()
    │
    ├── selfTestAccel()     ← 加速度计自检
    │   └── 检测芯片 ID、配置寄存器读写验证
    │
    ├── initAccel()          ← 加速度计初始化
    │   ├── 配置量程 (±3g)
    │   ├── 配置输出速率 (2kHz)
    │   ├── 配置 INT1 输出
    │   └── 配置低通滤波器
    │
    ├── selfTestGyro()       ← 陀螺仪自检
    │   └── 检测芯片 ID、配置寄存器读写验证
    │
    ├── initGyro()           ← 陀螺仪初始化
    │   ├── 配置量程 (±2000°/s)
    │   ├── 配置带宽
    │   ├── 配置 INT1 输出
    │   └── 配置低通滤波器
    │
    └── 启动温度控制 PWM(可选)
        └── HAL_TIM_PWM_Start()

4.4 校准(Calibration)

校准信息在 BMI088::CalibrationInfo 结构体中定义:

struct CalibrationInfo {
    Vector3f gyroOffset;          // 陀螺仪零飘补偿(静止时角速度不为零)
    Vector3f accelOffset;         // 加速度计零飘补偿
    Vector3f magnetOffset;        // 磁力计零飘补偿
    Matrix33f installSpinMatrix;  // 安装误差修正旋转矩阵
};

校准过程:

① 陀螺仪零飘校准:
   静止时采集 N 秒数据 → 取均值 → 作为 gyroOffset

② 加速度计零飘校准:
   多个姿态下采集数据 → 椭球拟合 → 得到 accelOffset

③ 安装误差修正:
   IMU 芯片可能未完美对齐机器人坐标轴
   installSpinMatrix = 绕各轴的微小旋转修正

④ 磁力计校准:
   绕 8 字旋转采集数据 → 椭球拟合 → 得到 magnetOffset
   (在 GSRL 中,磁力计校准可能在外部完成,烧录到 IST8310 配置中)

4.5 温度控制

为什么 IMU 需要温度控制? MEMS 陀螺仪和加速度计的输出会随温度变化而产生显著漂移:10°C 的温差可能导致陀螺仪零偏漂移数度/秒,足以让姿态在几秒内完全错误。BMI088 内置温度传感器,GSRL 可以配合加热电阻和 PID 温控回路,将芯片维持在恒定温度(通常 45~55°C),从根本上消除温度对传感器输出的影响,保证全季节、全工况下姿态数据的稳定一致。


5. IMU 与 GSRL 代码结构的关系

5.1 在分层架构中的位置

Task 层:
  test_task() → imu.getEulerAngle()  // 直接获取欧拉角

Device 层 (dvc_imu.hpp/cpp):
  IMU (抽象基类)
    └── BMI088 (SPI 驱动 + 数据校准 + 调用 AHRS)

Algorithm 层 (alg_ahrs.hpp/cpp):
  AHRS (姿态解算抽象接口)
    ├── Mahony (PI 互补滤波)
    └── QuaternionEKF (扩展卡尔曼滤波)

Algorithm 层 (alg_filter.hpp/cpp):
  KalmanFilter / LowPassFilter (EKF 底层使用的滤波器)

Driver 层 (drv_spi.c):
  SPI_Init / SPI_TransmitReceive_DMA / HAL_SPI_TxRxCpltCallback

5.2 依赖关系

BMI088
  ├── 依赖 drv_spi (SPI 通信)
  ├── 依赖 AHRS (姿态解算)
  │     └── 依赖 KalmanFilter / LowPassFilter
  ├── 依赖 IST8310 (磁力计,通过 I2C)
  └── 依赖 HAL (GPIO 片选、TIM PWM 温度控制)

6. 常见问题

问题 原因 排查方法
Roll/Pitch 抖动剧烈 加速度计受振动影响 降低加速度计低通滤波截止频率
Yaw 持续漂移 陀螺仪零飘未校准 重新进行陀螺仪零飘标定
姿态解算发散 四元数未归一化或 EKF 发散 检查 AHRS 初始化是否正确,启用卡方检验
Yaw 角不准 磁力计受电机磁场干扰 将磁力计远离电机安装,或使用硬/软铁校准
初始化后角度错误 初始四元数计算错误 确保 IMU 在初始化时保持静止
SPI 读回全 0xFF 或全 0x00 SPI 通信失败 检查 CS 引脚电平切换、SPI 模式和时钟配置
温度漂移大 陀螺仪/加速度计温漂 启用温度控制(BMI088 的恒温加热功能)

7. 总结

要点 说明
加速度计 测量比力(重力 + 运动),静止时 = [0, 0, g]
陀螺仪 测量角速度,积分得角度,但有漂移
磁力计 测量地磁场方向,修正 Yaw 漂移
姿态解算 融合三传感器数据,互补滤波或 EKF
四元数 内部中间表示,无万向节死锁
BMI088 双 SPI 接口(加速度计 + 陀螺仪),INT1 中断驱动读取
校准 零飘 + 安装误差 + 磁力计椭球拟合
GSRL 架构 IMU(Base) → BMI088(SPI) + AHRS(算法) + IST8310(磁力计)

与 GSRL 的关系

dvc_imu.hpp/cpp 中的 IMU 基类和 BMI088 派生类是 GSRL 姿态感知的核心。它组合了 SPI 驱动(数据采集)、校准参数(零飘/安装误差修正)、AHRS 算法(姿态解算),向上提供简洁的 getEulerAngle() / getGyro() / getAccel() 接口。机器人底盘平衡、云台稳定、导航定位等所有上层控制都依赖于这一层提供的姿态信息。

下一篇:GSRL 工程结构与运行机制

作者: Qing | 修改日期: 2026-07-18