1. 项目概述
在嵌入式系统开发中,姿态解算是一个常见而重要的需求。ICM42688是一款高性能的6轴IMU(惯性测量单元),集成了3轴加速度计和3轴陀螺仪,广泛应用于无人机、机器人、可穿戴设备等领域。本文将详细介绍如何在ESP32平台上使用ICM42688实现RPY(Roll-Pitch-Yaw)三轴姿态角的计算。
姿态解算的核心在于如何有效融合加速度计和陀螺仪的数据。加速度计可以提供静态姿态信息,但动态响应差;陀螺仪动态响应好,但存在积分漂移问题。我们需要通过算法将两者的优势结合起来。
2. ICM42688传感器配置
2.1 硬件连接与初始化
ICM42688通过I2C接口与ESP32通信,默认I2C地址为0x68。在Arduino环境下,我们可以使用Wire库进行通信:
cpp复制#define ICM42688_ADDR 0x68
#define WHO_AM_I 0x75
#define EXPECTED_WHO_AM_I 0x47
void setup() {
Serial.begin(115200);
Wire.begin();
Wire.setClock(400000); // 使用400kHz I2C速度
// 检查传感器是否连接正常
uint8_t who;
readRegs(ICM42688_ADDR, WHO_AM_I, 1, &who);
if (who != EXPECTED_WHO_AM_I) {
Serial.print("传感器检测失败! WHO_AM_I = 0x");
Serial.println(who, HEX);
while(1);
}
// 配置传感器
writeReg(ICM42688_ADDR, REG_BANK_SEL, 0); // 切换到Bank 0
writeReg(ICM42688_ADDR, DEVICE_CONFIG, 0x01); // 软复位
delay(100);
writeReg(ICM42688_ADDR, ACCEL_CONFIG0, 0x48); // ±4g, 100Hz
writeReg(ICM42688_ADDR, GYRO_CONFIG0, 0x28); // ±1000dps, 100Hz
writeReg(ICM42688_ADDR, PWR_MGMT0, 0x0f); // 开启陀螺仪和加速度计
delay(100);
}
注意:I2C通信前务必检查WHO_AM_I寄存器,这是排查硬件连接问题的第一步。
2.2 数据读取与单位转换
ICM42688的加速度计和陀螺仪数据都是16位有符号整数,需要转换为物理单位:
cpp复制#define GYRO_SENS (1000.0f / 32768.0f) // ±1000°/s -> °/s
#define ACC_SENS (4.0f / 32768.0f) // ±4g -> g
void loop() {
uint8_t buffer[12];
readRegs(ICM42688_ADDR, ACCEL_DATA_X1, 12, buffer);
// 解析加速度计数据
int16_t ax_raw = (int16_t)(buffer[0] << 8 | buffer[1]);
int16_t ay_raw = (int16_t)(buffer[2] << 8 | buffer[3]);
int16_t az_raw = (int16_t)(buffer[4] << 8 | buffer[5]);
float ax = ax_raw * ACC_SENS;
float ay = ay_raw * ACC_SENS;
float az = az_raw * ACC_SENS;
// 解析陀螺仪数据
int16_t gx_raw = (int16_t)(buffer[6] << 8 | buffer[7]);
int16_t gy_raw = (int16_t)(buffer[8] << 8 | buffer[9]);
int16_t gz_raw = (int16_t)(buffer[10] << 8 | buffer[11]);
float gx = gx_raw * GYRO_SENS;
float gy = gy_raw * GYRO_SENS;
float gz = gz_raw * GYRO_SENS;
}
3. 姿态解算算法实现
3.1 Mahony滤波算法
Mahony是一种基于互补滤波原理的姿态解算算法,计算量适中,适合嵌入式平台:
cpp复制// Mahony滤波器参数
#define Kp 2.0f // 比例增益
#define Ki 0.001f // 积分增益
#define sampleFreq 100.0f // 采样频率100Hz
volatile float q0 = 1.0f, q1 = 0.0f, q2 = 0.0f, q3 = 0.0f; // 四元数
volatile float exInt = 0.0f, eyInt = 0.0f, ezInt = 0.0f; // 误差积分
void MahonyAHRSupdateIMU(float gx, float gy, float gz,
float ax, float ay, float az, float dt) {
float norm;
float vx, vy, vz;
float ex, ey, ez;
// 加速度计归一化
norm = sqrt(ax*ax + ay*ay + az*az);
if(norm == 0.0f) return;
ax /= norm; ay /= norm; az /= norm;
// 估计重力方向
vx = 2.0f*(q1*q3 - q0*q2);
vy = 2.0f*(q0*q1 + q2*q3);
vz = q0*q0 - q1*q1 - q2*q2 + q3*q3;
// 计算误差
ex = ay*vz - az*vy;
ey = az*vx - ax*vz;
ez = ax*vy - ay*vx;
// 积分误差
exInt += ex * Ki * dt;
eyInt += ey * Ki * dt;
ezInt += ez * Ki * dt;
// 修正陀螺仪
gx += Kp*ex + exInt;
gy += Kp*ey + eyInt;
gz += Kp*ez + ezInt;
// 四元数更新
float q0_dot = (-q1*gx - q2*gy - q3*gz) * 0.5f;
float q1_dot = (q0*gx + q2*gz - q3*gy) * 0.5f;
float q2_dot = (q0*gy - q1*gz + q3*gx) * 0.5f;
float q3_dot = (q0*gz + q1*gy - q2*gx) * 0.5f;
q0 += q0_dot * dt;
q1 += q1_dot * dt;
q2 += q2_dot * dt;
q3 += q3_dot * dt;
// 四元数归一化
norm = sqrt(q0*q0 + q1*q1 + q2*q2 + q3*q3);
q0 /= norm; q1 /= norm; q2 /= norm; q3 /= norm;
}
提示:Kp和Ki参数需要根据实际应用调整。Kp决定响应速度,Ki决定长期稳定性。
3.2 卡尔曼滤波实现
卡尔曼滤波提供更优的噪声抑制性能,但计算量较大:
cpp复制typedef struct {
float Q; // 过程噪声协方差
float R; // 观测噪声协方差
float x; // 状态估计
float P; // 估计误差协方差
} Kalman1D;
void kalmanInit(Kalman1D *k, float Q, float R, float initAngle) {
k->Q = Q;
k->R = R;
k->x = initAngle;
k->P = 1.0f;
}
float kalmanUpdate(Kalman1D *k, float gyroRate, float accAngle, float dt) {
// 预测
float x_pred = k->x + gyroRate * dt;
float P_pred = k->P + k->Q;
// 更新
float K = P_pred / (P_pred + k->R);
k->x = x_pred + K * (accAngle - x_pred);
k->P = (1 - K) * P_pred;
return k->x;
}
3.3 一阶互补滤波
最简单的融合算法,适合资源受限的系统:
cpp复制#define COMPLEMENTARY_K 0.1f
void complementaryFilter(float *angle, float gyroRate, float accAngle, float dt) {
*angle = COMPLEMENTARY_K * accAngle + (1.0f - COMPLEMENTARY_K) * (*angle + gyroRate * dt);
}
4. 欧拉角计算与输出
将四元数转换为更直观的欧拉角:
cpp复制void quaternionToEuler(float q0, float q1, float q2, float q3,
float* roll, float* pitch, float* yaw) {
// 计算俯仰角
float sinp = 2.0f * (q0*q2 - q3*q1);
if(fabs(sinp) >= 1.0f)
*pitch = copysign(90.0f, sinp);
else
*pitch = asin(sinp) * RAD_TO_DEG;
// 计算横滚角
float sinr_cosp = 2.0f * (q0*q1 + q2*q3);
float cosr_cosp = 1.0f - 2.0f * (q1*q1 + q2*q2);
*roll = atan2(sinr_cosp, cosr_cosp) * RAD_TO_DEG;
// 计算偏航角
float siny_cosp = 2.0f * (q0*q3 + q1*q2);
float cosy_cosp = 1.0f - 2.0f * (q2*q2 + q3*q3);
*yaw = atan2(siny_cosp, cosy_cosp) * RAD_TO_DEG;
}
5. 实际应用中的注意事项
5.1 传感器校准
IMU在使用前需要进行校准,特别是陀螺仪的零偏和加速度计的灵敏度:
cpp复制void calibrateIMU() {
float gx_sum = 0, gy_sum = 0, gz_sum = 0;
float ax_sum = 0, ay_sum = 0, az_sum = 0;
for(int i=0; i<500; i++) {
// 读取原始数据
// ...
gx_sum += gx_raw;
gy_sum += gy_raw;
gz_sum += gz_raw;
ax_sum += ax_raw;
ay_sum += ay_raw;
az_sum += az_raw;
delay(10);
}
gyro_bias_x = gx_sum / 500;
gyro_bias_y = gy_sum / 500;
gyro_bias_z = gz_sum / 500;
acc_scale_x = 1.0f / (ax_sum / 500);
// 同理计算其他轴的scale
}
5.2 动态性能优化
不同应用场景需要不同的滤波器参数:
- 无人机控制:需要快速响应,Kp可以设大些(3.0-5.0)
- 姿态监测:追求稳定性,Ki可以设大些(0.005-0.01)
- 低功耗应用:可以降低采样频率到50Hz或更低
5.3 常见问题排查
-
角度漂移严重:
- 检查陀螺仪零偏校准
- 增大Ki值
- 确保加速度计数据准确
-
响应迟钝:
- 增大Kp值
- 检查采样频率是否足够高
-
数据跳动:
- 检查传感器安装是否牢固
- 适当降低Kp值
- 增加机械滤波(减震)
6. 性能对比与选型建议
三种算法的主要特点对比:
| 算法 | 计算量 | 精度 | 稳定性 | 适用场景 |
|---|---|---|---|---|
| Mahony | 中等 | 高 | 好 | 大多数应用 |
| 卡尔曼 | 大 | 最高 | 最好 | 高性能需求 |
| 互补滤波 | 小 | 一般 | 一般 | 资源受限系统 |
对于ESP32平台,Mahony算法通常是平衡性能与精度的最佳选择。卡尔曼滤波虽然精度更高,但在100Hz采样率下可能会占用较多CPU资源。互补滤波最简单,但长期稳定性较差。