375 lines
12 KiB
C
375 lines
12 KiB
C
#include "IMU.h"
|
||
#include <math.h>
|
||
|
||
//==================================================================================================
|
||
// 常量
|
||
//==================================================================================================
|
||
|
||
#ifndef PI
|
||
#define PI 3.141592654f
|
||
#endif
|
||
|
||
#define TWO_KP_DEF (2.0f * 0.5f) // Mahony 比例增益(默认 1.0)
|
||
#define TWO_KI_DEF (2.0f * 0.0f) // Mahony 积分增益(默认 0, 关闭)
|
||
|
||
//==================================================================================================
|
||
// Mahony 滤波器状态(静态,本文件内可见)
|
||
//==================================================================================================
|
||
|
||
static float inv_sample_freq; // 1 / 采样频率
|
||
static float two_kp = TWO_KP_DEF; // 比例增益
|
||
static float two_ki = TWO_KI_DEF; // 积分增益
|
||
static float q0 = 1.0f, q1 = 0.0f, q2 = 0.0f, q3 = 0.0f; // 姿态四元数
|
||
static float integral_fbx = 0.0f, integral_fby = 0.0f, integral_fbz = 0.0f; // 积分误差项
|
||
|
||
//==================================================================================================
|
||
// 陀螺仪零偏(由 imu_calibrate_gyro 填入)
|
||
//==================================================================================================
|
||
|
||
static float gyro_bias_x = 0.0f;
|
||
static float gyro_bias_y = 0.0f;
|
||
static float gyro_bias_z = 0.0f;
|
||
|
||
//==================================================================================================
|
||
// 姿态输出
|
||
//==================================================================================================
|
||
|
||
volatile imu_attitude_t imu_angle;
|
||
|
||
volatile int yaw_round_count = 0;
|
||
|
||
volatile static float yaw_angle_last = 0.0f;
|
||
|
||
|
||
//==================================================================================================
|
||
// 双二阶滤波器状态
|
||
//==================================================================================================
|
||
|
||
static biquad_state_t bq_gyro_x, bq_gyro_y, bq_gyro_z;
|
||
static biquad_state_t bq_accel_x, bq_accel_y, bq_accel_z;
|
||
|
||
//==================================================================================================
|
||
// 公开接口实现
|
||
//==================================================================================================
|
||
|
||
/** @brief 初始化 IMU
|
||
* @param sample_freq_hz 采样频率(Hz),等于定时器中断频率
|
||
* 1. 初始化 ICM-42688 硬件
|
||
* 2. 配置 Mahony 采样周期
|
||
* 3. 初始化各轴陀螺仪/加速度计双二阶滤波器
|
||
* 滤波器参数:
|
||
* 陀螺仪 X/Y/Z: 低通 30/20/30Hz → 滤除高频振动
|
||
* 加速度计 X/Y/Z: 低通 10Hz → 重力矢量提取(响应慢但稳)
|
||
*/
|
||
void imu_init(int sample_freq_hz)
|
||
{
|
||
icm42688_init();
|
||
|
||
inv_sample_freq = 1.0f / sample_freq_hz;
|
||
|
||
biquad_init(&bq_gyro_x, BIQUAD_LOWPASS, sample_freq_hz, 30, 0.7071f);
|
||
biquad_init(&bq_gyro_y, BIQUAD_LOWPASS, sample_freq_hz, 20, 0.5773f);
|
||
biquad_init(&bq_gyro_z, BIQUAD_LOWPASS, sample_freq_hz, 30, 0.5773f);
|
||
biquad_init(&bq_accel_x, BIQUAD_LOWPASS, sample_freq_hz, 10, 0.5773f);
|
||
biquad_init(&bq_accel_y, BIQUAD_LOWPASS, sample_freq_hz, 10, 0.5773f);
|
||
biquad_init(&bq_accel_z, BIQUAD_LOWPASS, sample_freq_hz, 10, 0.5773f);
|
||
}
|
||
|
||
/** @brief 更新姿态(定时器 ISR 中按固定频率调用)
|
||
* 数据流:传感器 → 零偏修正 → 双二阶滤波 → Mahony → 欧拉角 → Yaw 连续角度
|
||
* 传感器坐标系直通,无坐标轴转换
|
||
* 陀螺仪数据不进 Mahony 前只做偏置修正(Mahony 内部自带角速度积分处理)
|
||
* 加速度数据经低通滤波后送入 Mahony 做重力矢量参考
|
||
*/
|
||
void imu_update(void)
|
||
{
|
||
icm42688_data_t raw;
|
||
icm42688_read_all(&raw);
|
||
|
||
// 零偏修正
|
||
raw.gyro_x -= gyro_bias_x;
|
||
raw.gyro_y -= gyro_bias_y;
|
||
raw.gyro_z -= gyro_bias_z;
|
||
|
||
// 滤波(传感器坐标系,不做轴转换)
|
||
imu_angle.gyro_x = biquad_apply(&bq_gyro_x, raw.gyro_x);
|
||
imu_angle.gyro_y = biquad_apply(&bq_gyro_y, raw.gyro_y);
|
||
imu_angle.gyro_z = biquad_apply(&bq_gyro_z, raw.gyro_z);
|
||
|
||
float ax_f = biquad_apply(&bq_accel_x, raw.accel_x);
|
||
float ay_f = biquad_apply(&bq_accel_y, raw.accel_y);
|
||
float az_f = biquad_apply(&bq_accel_z, raw.accel_z);
|
||
|
||
// Mahony AHRS 姿态解算
|
||
mahony_ahrs_update(raw.gyro_x, raw.gyro_y, raw.gyro_z, ax_f, ay_f, az_f);
|
||
|
||
// Yaw 连续角度跟踪(处理 ±180° 跳变)
|
||
if (imu_angle.yaw - yaw_angle_last > 180.0f) {
|
||
yaw_round_count--;
|
||
} else if (imu_angle.yaw - yaw_angle_last < -180.0f) {
|
||
yaw_round_count++;
|
||
}
|
||
yaw_angle_last = imu_angle.yaw;
|
||
}
|
||
|
||
/** @brief 陀螺仪零偏校准
|
||
* 必须在设备完全静止时调用,采集 3 秒陀螺仪原始数据
|
||
* 每秒采样 1000 点取均值,3 秒的均值再取平均,乘以比例系数得到 °/s 零偏
|
||
* 校准结果自动存入 gyro_bias_x/y/z,在后续 imu_update() 中生效
|
||
*/
|
||
void imu_calibrate_gyro(void)
|
||
{
|
||
#define CALIB_SECONDS 3
|
||
#define CALIB_SAMPLES 1000
|
||
|
||
static float gyro_history[3][CALIB_SECONDS];
|
||
int32_t gyro_sum[3] = {0, 0, 0};
|
||
uint32_t sec_cnt = 0, sample_cnt = 0;
|
||
|
||
system_delay_ms(1000);
|
||
system_delay_ms(1000);
|
||
|
||
do {
|
||
system_delay_us(1000);
|
||
int16_t gx_r, gy_r, gz_r;
|
||
icm42688_read_gyro_raw(&gx_r, &gy_r, &gz_r);
|
||
|
||
if (sample_cnt < CALIB_SAMPLES) {
|
||
gyro_sum[0] += gx_r;
|
||
gyro_sum[1] += gy_r;
|
||
gyro_sum[2] += gz_r;
|
||
sample_cnt++;
|
||
} else {
|
||
gyro_history[0][sec_cnt] = (float)gyro_sum[0] / CALIB_SAMPLES;
|
||
gyro_history[1][sec_cnt] = (float)gyro_sum[1] / CALIB_SAMPLES;
|
||
gyro_history[2][sec_cnt] = (float)gyro_sum[2] / CALIB_SAMPLES;
|
||
gyro_sum[0] = 0;
|
||
gyro_sum[1] = 0;
|
||
gyro_sum[2] = 0;
|
||
sample_cnt = 0;
|
||
sec_cnt++;
|
||
}
|
||
} while (sec_cnt < CALIB_SECONDS);
|
||
|
||
float sum[3] = {0, 0, 0};
|
||
float k = icm42688_gyro_scale() / CALIB_SECONDS;
|
||
for (uint8_t i = 0; i < CALIB_SECONDS; i++) {
|
||
sum[0] += gyro_history[0][i];
|
||
sum[1] += gyro_history[1][i];
|
||
sum[2] += gyro_history[2][i];
|
||
}
|
||
|
||
gyro_bias_x = k * sum[0];
|
||
gyro_bias_y = k * sum[1];
|
||
gyro_bias_z = k * sum[2];
|
||
|
||
}
|
||
|
||
//==================================================================================================
|
||
// 算法实现
|
||
//==================================================================================================
|
||
|
||
/** @brief Mahony AHRS 6轴姿态解算
|
||
* @param gx/gy/gz 陀螺仪角速度 (°/s)
|
||
* @param ax/ay/az 加速度计测量值 (g)
|
||
*
|
||
* 算法流程:
|
||
* 1. 角速度转弧度,加速度归一化
|
||
* 2. 由当前四元数估计重力方向
|
||
* 3. 估计重力与测量重力的叉积 → PI 反馈修正陀螺仪
|
||
* 4. 一阶龙格-库塔积分更新四元数
|
||
* 5. 四元数归一化
|
||
* 6. 提取欧拉角写入 imu_angle
|
||
*
|
||
* 参考: S.O.H. Madgwick "An efficient orientation filter for IMU and MARG sensor arrays"
|
||
*/
|
||
void mahony_ahrs_update(float gx, float gy, float gz, float ax, float ay, float az)
|
||
{
|
||
float recip_norm;
|
||
float halfvx, halfvy, halfvz;
|
||
float halfex, halfey, halfez;
|
||
float qa, qb, qc;
|
||
|
||
// 仅当加速度计有读数时执行修正(静止或匀加速时加速度计才可靠)
|
||
if (!((ax == 0.0f) && (ay == 0.0f) && (az == 0.0f))) {
|
||
// 角速度 → rad/s
|
||
gx *= ANG2RAD;
|
||
gy *= ANG2RAD;
|
||
gz *= ANG2RAD;
|
||
|
||
// 加速度归一化
|
||
recip_norm = inv_sqrt(ax * ax + ay * ay + az * az);
|
||
ax *= recip_norm;
|
||
ay *= recip_norm;
|
||
az *= recip_norm;
|
||
|
||
// 由当前四元数估计重力方向(世界系 Z 轴在传感器系的投影)
|
||
halfvx = q1 * q3 - q0 * q2;
|
||
halfvy = q0 * q1 + q2 * q3;
|
||
halfvz = q0 * q0 - 0.5f + q3 * q3;
|
||
|
||
// 叉积 = 估计方向 × 测量方向 → 姿态误差
|
||
halfex = (ay * halfvz - az * halfvy);
|
||
halfey = (az * halfvx - ax * halfvz);
|
||
halfez = (ax * halfvy - ay * halfvx);
|
||
|
||
// 积分反馈(默认 ki=0, 关闭)
|
||
if (two_ki > 0.0f) {
|
||
integral_fbx += two_ki * halfex * inv_sample_freq;
|
||
integral_fby += two_ki * halfey * inv_sample_freq;
|
||
integral_fbz += two_ki * halfez * inv_sample_freq;
|
||
gx += integral_fbx;
|
||
gy += integral_fby;
|
||
gz += integral_fbz;
|
||
} else {
|
||
integral_fbx = 0.0f;
|
||
integral_fby = 0.0f;
|
||
integral_fbz = 0.0f;
|
||
}
|
||
|
||
// 比例反馈
|
||
gx += two_kp * halfex;
|
||
gy += two_kp * halfey;
|
||
gz += two_kp * halfez;
|
||
}
|
||
|
||
// 四元数一阶积分 (RK1)
|
||
gx *= (0.5f * inv_sample_freq);
|
||
gy *= (0.5f * inv_sample_freq);
|
||
gz *= (0.5f * inv_sample_freq);
|
||
qa = q0; qb = q1; qc = q2;
|
||
q0 += (-qb * gx - qc * gy - q3 * gz);
|
||
q1 += ( qa * gx + qc * gz - q3 * gy);
|
||
q2 += ( qa * gy - qb * gz + q3 * gx);
|
||
q3 += ( qa * gz + qb * gy - qc * gx);
|
||
|
||
// 四元数归一化
|
||
recip_norm = inv_sqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
|
||
q0 *= recip_norm;
|
||
q1 *= recip_norm;
|
||
q2 *= recip_norm;
|
||
q3 *= recip_norm;
|
||
|
||
// 旋转矩阵 → 欧拉角(传感器坐标系)
|
||
float r11 = 2.0f * (q0 * q1 + q2 * q3);
|
||
float r12 = 1.0f - 2.0f * (q1 * q1 + q2 * q2);
|
||
float r21 = 2.0f * (q0 * q2 - q3 * q1);
|
||
float r31 = 2.0f * (q0 * q3 + q1 * q2);
|
||
float r32 = 1.0f - 2.0f * (q2 * q2 + q3 * q3);
|
||
|
||
imu_angle.pitch = RAD2ANG * asinf(r21);
|
||
imu_angle.roll = RAD2ANG * atan2f(r11, r12);
|
||
imu_angle.yaw = RAD2ANG * atan2f(r31, r32);
|
||
}
|
||
|
||
/** @brief 双二阶滤波器系数初始化
|
||
* @param state 滤波器状态
|
||
* @param type 滤波器类型
|
||
* @param fs 采样率 (Hz)
|
||
* @param fc 截止/中心频率 (Hz)
|
||
* @param q 品质因数
|
||
*
|
||
* 通用双二阶传递函数:
|
||
* H(z) = (b0 + b1*z^-1 + b2*z^-2) / (a0 + a1*z^-1 + a2*z^-2)
|
||
*
|
||
* Q 值参考:
|
||
* 0.5773 Thomson-Bessel → 最平坦群延迟, 相位失真最小
|
||
* 0.7071 Butterworth → 通带最平坦, 常用默认值
|
||
* 0.9565 Chebyshev 1dB → 陡峭过渡带, 允许 1dB 通带波纹
|
||
*/
|
||
void biquad_init(biquad_state_t *state, biquad_type_t type, int fs, float fc, float q)
|
||
{
|
||
float w0 = 2.0f * PI * fc / fs;
|
||
float sin_w0 = sinf(w0);
|
||
float cos_w0 = cosf(w0);
|
||
float alpha = sin_w0 / (2.0f * q);
|
||
|
||
float b0, b1, b2, a0, a1, a2;
|
||
|
||
switch (type) {
|
||
case BIQUAD_LOWPASS:
|
||
b0 = (1.0f - cos_w0) / 2.0f;
|
||
b1 = b0 * 2.0f;
|
||
b2 = b0;
|
||
a0 = 1.0f + alpha;
|
||
a1 = -2.0f * cos_w0;
|
||
a2 = 1.0f - alpha;
|
||
break;
|
||
case BIQUAD_HIGHPASS:
|
||
b0 = (1.0f + cos_w0) / 2.0f;
|
||
b1 = -b0 * 2.0f;
|
||
b2 = b0;
|
||
a0 = 1.0f + alpha;
|
||
a1 = -2.0f * cos_w0;
|
||
a2 = 1.0f - alpha;
|
||
break;
|
||
case BIQUAD_BANDPASS_PEAK:
|
||
b0 = alpha;
|
||
b1 = 0.0f;
|
||
b2 = -alpha;
|
||
a0 = 1.0f + alpha;
|
||
a1 = -2.0f * cos_w0;
|
||
a2 = 1.0f - alpha;
|
||
break;
|
||
case BIQUAD_BANDSTOP_NOTCH:
|
||
b0 = 1.0f;
|
||
b1 = -2.0f * cos_w0;
|
||
b2 = 1.0f;
|
||
a0 = 1.0f + alpha;
|
||
a1 = -2.0f * cos_w0;
|
||
a2 = 1.0f - alpha;
|
||
break;
|
||
}
|
||
|
||
// 归一化系数(除以 a0)
|
||
state->a0 = b0 / a0;
|
||
state->a1 = b1 / a0;
|
||
state->a2 = b2 / a0;
|
||
state->a3 = a1 / a0;
|
||
state->a4 = a2 / a0;
|
||
state->x1 = 0.0f;
|
||
state->x2 = 0.0f;
|
||
state->y1 = 0.0f;
|
||
state->y2 = 0.0f;
|
||
}
|
||
|
||
/** @brief 双二阶滤波器单步迭代
|
||
* @param state 滤波器状态
|
||
* @param data 当前输入
|
||
* @return 当前输出
|
||
*
|
||
* 差分方程:
|
||
* y[n] = a0*x[n] + a1*x[n-1] + a2*x[n-2] - a3*y[n-1] - a4*y[n-2]
|
||
*/
|
||
float biquad_apply(biquad_state_t *state, float data)
|
||
{
|
||
float result = state->a0 * data
|
||
+ state->a1 * state->x1
|
||
+ state->a2 * state->x2
|
||
- state->a3 * state->y1
|
||
- state->a4 * state->y2;
|
||
state->x2 = state->x1;
|
||
state->x1 = data;
|
||
state->y2 = state->y1;
|
||
state->y1 = result;
|
||
return result;
|
||
}
|
||
|
||
/** @brief 快速平方根倒数 (Fast Inverse Square Root)
|
||
* @param x 输入正数
|
||
* @return 1/sqrt(x) 近似值
|
||
*
|
||
* 使用 Quake III 的经典 magic number 0x5f3759df
|
||
* 精度约 0.1%, 在无 FPU 的 M0+ 上远快于 1.0f/sqrtf(x)
|
||
*/
|
||
float inv_sqrt(float x)
|
||
{
|
||
float halfx = 0.5f * x;
|
||
float y = x;
|
||
long i = *(long *)&y;
|
||
i = 0x5f3759df - (i >> 1);
|
||
y = *(float *)&i;
|
||
y = y * (1.5f - (halfx * y * y)); // 一次 Newton 迭代
|
||
return y;
|
||
}
|