qidao123.com技术社区-IT企服评测·应用市场

标题: STM32实现九轴IMU的卡尔曼滤波 [打印本页]

作者: 何小豆儿在此    时间: 2025-5-11 17:06
标题: STM32实现九轴IMU的卡尔曼滤波
在嵌入式系统中,精确的姿态估计对于无人机、机器人和假造实际等应用至关紧张。九轴惯性测量单元(IMU)通过三轴加快度计、陀螺仪和磁力计提供全面的活动数据。然而,这些传感器数据常伴随噪声和漂移,单独利用无法满足高精度需求。卡尔曼滤波作为一种强大的传感器融合算法,能够有用联合多传感器数据,估计系统状态。

九轴IMU通常由以下三部分构成:

这些传感器的互补特性使得传感器融合成为须要。融合算法通过联合各传感器数据,克服单一传感器的局限性,提供更准确的姿态估计。

由于各传感器存在固有缺陷(如陀螺仪漂移、加快度计噪声、磁力计干扰),单独利用无法提供可靠的姿态估计。传感器融合通过数学模子整合多传感器数据,生成更精确的估计结果。常用的融合算法包括互补滤波和卡尔曼滤波,其中卡尔曼滤波因其理论最优性广泛应用于姿态估计。
卡尔曼滤波是一种递归算法,用于从噪声测量中估计动态系统的状态。它假设系统是线性的,噪声为高斯分布。卡尔曼滤波包括两个重要步调:


对于卡尔曼滤波的原理,我们不再细究,网上有很多资料,本篇文章重要讲解嵌入式工程师如何利用代码实现卡尔曼滤波。
在STM32微控制器上实现九轴IMU的卡尔曼滤波需要选择一款支持浮点运算单元(FPU)的STM32微控制器(如STM32F4系列),以高效处理矩阵运算。将九轴IMU(如MPU9250)通过I2C或SPI接口毗连到STM32开发板。确保电源稳固,通信线路正确毗连。
以下是九轴IMU卡尔曼滤波的焦点实现:
  1. // 状态向量:[q0, q1, q2, q3, bgx, bgy, bgz]
  2. float state[7];
  3. // 状态协方差矩阵 P (7x7)
  4. float P[49];
  5. // 过程噪声协方差矩阵 Q (7x7)
  6. float Q[49];
  7. // 观测噪声协方差矩阵 R (6x6):3个加速度计和3个磁力计
  8. float R[36];
  9. // 状态转换矩阵 F (7x7)
  10. float F[49];
  11. // 观测矩阵 H (6x7)
  12. float H[42];
  13. // 卡尔曼增益 K (7x6)
  14. float K[42];
  15. // 预测状态
  16. float state_pred[7];
  17. // 预测协方差
  18. float P_pred[49];
  19. // 残差
  20. float y[6];
  21. // S = H*P*H^T + R
  22. float S[36];
  23. // 初始状态
  24. void init_state(float initial_q[4], float initial_bg[3]) {
  25.     // 初始化状态向量
  26.     memcpy(state, initial_q, 4*sizeof(float));
  27.     memcpy(state+4, initial_bg, 3*sizeof(float));
  28.    
  29.     // 初始化协方差矩阵 P
  30.     memset(P, 0, 49*sizeof(float));
  31.     P[0] = 0.01f; P[7] = 0.01f; P[14] = 0.01f; P[21] = 0.01f; // q的方差
  32.     P[28] = 0.01f; P[35] = 0.01f; P[42] = 0.01f; // bg的方差
  33.    
  34.     // 初始化过程噪声协方差 Q
  35.     memset(Q, 0, 49*sizeof(float));
  36.     Q[0] = 0.001f; Q[7] = 0.001f; Q[14] = 0.001f; Q[21] = 0.001f; // q的噪声
  37.     Q[28] = 0.0001f; Q[35] = 0.0001f; Q[42] = 0.0001f; // bg的噪声
  38.    
  39.     // 初始化观测噪声协方差 R
  40.     memset(R, 0, 36*sizeof(float));
  41.     R[0] = 0.1f; R[7] = 0.1f; R[14] = 0.1f; // 加速度计噪声
  42.     R[21] = 0.1f; R[28] = 0.1f; R[35] = 0.1f; // 磁力计噪声
  43. }
  44. // 预测步骤
  45. void predict(float gyro[3], float dt) {
  46.     // 计算角速度四元数
  47.     float omega[4] = {0, gyro[0], gyro[1], gyro[2]};
  48.     float theta = sqrt(omega[1]*omega[1] + omega[2]*omega[2] + omega[3]*omega[3]) * dt;
  49.     float axis[3];
  50.     if (theta > 1e-6) {
  51.         axis[0] = omega[1]/theta;
  52.         axis[1] = omega[2]/theta;
  53.         axis[2] = omega[3]/theta;
  54.     } else {
  55.         axis[0] = 0;
  56.         axis[1] = 0;
  57.         axis[2] = 0;
  58.     }
  59.     float dq[4] = {cos(theta/2), axis[0]*sin(theta/2), axis[1]*sin(theta/2), axis[2]*sin(theta/2)};
  60.    
  61.     // 预测四元数
  62.     float q[4];
  63.     quaternion_multiply(state, dq, q);
  64.     quaternion_normalize(q);
  65.     memcpy(state_pred, q, 4*sizeof(float));
  66.    
  67.     // 陀螺仪偏置保持不变
  68.     memcpy(state_pred+4, state+4, 3*sizeof(float));
  69.    
  70.     // 计算状态转换矩阵 F
  71.     // (这里简化为恒等矩阵,实际应用中需要正确计算)
  72.     memset(F, 0, 49*sizeof(float));
  73.     for(int i=0; i<7; i++) F[i*8] = 1.0f;
  74.    
  75.     // P_pred = F*P*F^T + G*Q*G^T
  76.     // (这里简化为P_pred = P + Q)
  77.     for(int i=0; i<49; i++) P_pred[i] = P[i] + Q[i];
  78.    
  79.     // 更新状态和协方差
  80.     memcpy(state, state_pred, 7*sizeof(float));
  81.     memcpy(P, P_pred, 49*sizeof(float));
  82. }
  83. // 更新步骤
  84. void update(float acc[3], float mag[3], float ref_mag[3]) {
  85.     // 计算期望的加速度计测量值
  86.     float q_inv[4];
  87.     quaternion_conjugate(state, q_inv);
  88.     float expected_acc[4] = {0, 0, 0, -1};
  89.     quaternion_multiply(state, expected_acc, expected_acc);
  90.     quaternion_multiply(expected_acc, q_inv, expected_acc);
  91.     expected_acc[1] /= expected_acc[0];
  92.     expected_acc[2] /= expected_acc[0];
  93.     expected_acc[3] /= expected_acc[0];
  94.    
  95.     // 计算期望的磁力计测量值
  96.     float expected_mag[4];
  97.     quaternion_multiply(state, ref_mag, expected_mag);
  98.     quaternion_multiply(expected_mag, q_inv, expected_mag);
  99.     expected_mag[1] /= expected_mag[0];
  100.     expected_mag[2] /= expected_mag[0];
  101.     expected_mag[3] /= expected_mag[0];
  102.    
  103.     // 组合观测向量
  104.     float z[6] = {
  105.         acc[0], acc[1], acc[2],
  106.         mag[0], mag[1], mag[2]
  107.     };
  108.    
  109.     float h[6] = {
  110.         expected_acc[1], expected_acc[2], expected_acc[3],
  111.         expected_mag[1], expected_mag[2], expected_mag[3]
  112.     };
  113.    
  114.     // 计算残差
  115.     for(int i=0; i<6; i++) y[i] = z[i] - h[i];
  116.    
  117.     // 计算观测矩阵 H
  118.     // (这里简化为恒等矩阵,实际应用中需要正确计算)
  119.     memset(H, 0, 42*sizeof(float));
  120.     for(int i=0; i<6; i++) H[i*8 + i] = 1.0f;
  121.    
  122.     // 计算 S = H*P*H^T + R
  123.     // (这里简化为 S = H*P*H^T + R)
  124.     memset(S, 0, 36*sizeof(float));
  125.     for(int i=0; i<6; i++) {
  126.         for(int j=0; j<7; j++) {
  127.             if (H[i*7 + j] == 0) continue;
  128.             for(int k=0; k<6; k++) {
  129.                 if (H[k*7 + j] == 0) continue;
  130.                 S[i*6 + k] += H[i*7 + j] * P[j*7 + k] * H[k*7 + j];
  131.             }
  132.         }
  133.     }
  134.     for(int i=0; i<36; i++) S[i] += R[i];
  135.    
  136.     // 计算卡尔曼增益 K = P*H^T*S^{-1}
  137.     // (这里简化为 K = P*H^T / (H*P*H^T + R))
  138.     memset(K, 0, 42*sizeof(float));
  139.     for(int i=0; i<7; i++) {
  140.         for(int j=0; j<6; j++) {
  141.             float sum = 0;
  142.             for(int k=0; k<7; k++) {
  143.                 sum += P[i*7 + k] * H[j*7 + k];
  144.             }
  145.             K[i*6 + j] = sum / S[j*6 + j];
  146.         }
  147.     }
  148.    
  149.     // 更新状态
  150.     for(int i=0; i<7; i++) {
  151.         float sum = 0;
  152.         for(int j=0; j<6; j++) {
  153.             sum += K[i*6 + j] * y[j];
  154.         }
  155.         state[i] += sum;
  156.     }
  157.    
  158.     // 更新协方差
  159.     memset(P, 0, 49*sizeof(float));
  160.     for(int i=0; i<7; i++) {
  161.         for(int j=0; j<7; j++) {
  162.             float sum = 0;
  163.             for(int k=0; k<6; k++) {
  164.                 sum += K[i*6 + k] * H[k*7 + j];
  165.             }
  166.             P[i*7 + j] = (1 - sum) * P_pred[i*7 + j];
  167.         }
  168.     }
  169. }
复制代码
以下是完备的STM32实现框架:
  1. #include "stm32f10x.h"
  2. // 定义MPU9250和HMC5883L的I2C地址
  3. #define MPU9250_ADDR 0x68
  4. #define HMC5883L_ADDR 0x1E
  5. // MPU9250寄存器定义
  6. #define PWR_MGMT_1 0x6B
  7. #define SMPLRT_DIV 0x19
  8. #define CONFIG 0x1A
  9. #define GYRO_CONFIG 0x1B
  10. #define ACCEL_CONFIG 0x1C
  11. #define INT_ENABLE 0x38
  12. #define PWR_MGMT_2 0x3B
  13. // HMC5883L寄存器定义
  14. #define HMC5883L_CTRL_REG_A 0x0A
  15. #define HMC5883L_CTRL_REG_B 0x0B
  16. // 状态向量:[q0, q1, q2, q3, bgx, bgy, bgz]
  17. float state[7];
  18. // 状态协方差矩阵 P (7x7)
  19. float P[49];
  20. // ... 其他变量声明
  21. // I2C写操作
  22. void i2c_write(uint8_t addr, uint8_t reg, uint8_t value) {
  23.     // 实现I2C写操作
  24. }
  25. // I2C读操作
  26. uint8_t i2c_read(uint8_t addr, uint8_t reg) {
  27.     // 实现I2C读操作
  28. }
  29. // 四元数乘法
  30. void quaternion_multiply(float *q1, float *q2, float *result) {
  31.     // 实现四元数乘法
  32. }
  33. // 四元数归一化
  34. void quaternion_normalize(float *q) {
  35.     // 实现四元数归一化
  36. }
  37. // 四元数共轭
  38. void quaternion_conjugate(float *q, float *result) {
  39.     // 实现四元数共轭
  40. }
  41. // MPU9250初始化
  42. void mpu9250_init(void) {
  43.     // 实现MPU9250初始化
  44. }
  45. // 从MPU9250读取加速度计数据
  46. void read_accel(float *accel) {
  47.     // 从MPU9250读取加速度计数据
  48. }
  49. // 从MPU9250读取陀螺仪数据
  50. void read_gyro(float *gyro) {
  51.     // 从MPU9250读取陀螺仪数据
  52. }
  53. // 从HMC5883L读取磁力计数据
  54. void read_magnet(float *magnet) {
  55.     // 从HMC5883L读取磁力计数据
  56. }
  57. // 初始化卡尔曼滤波器
  58. void init_kalman(float initial_q[4], float initial_bg[3]) {
  59.     // 初始化卡尔曼滤波器
  60. }
  61. // 卡尔曼滤波预测步骤
  62. void kalman_predict(float gyro[3], float dt) {
  63.     // 实现卡尔曼滤波预测步骤
  64. }
  65. // 卡尔曼滤波更新步骤
  66. void kalman_update(float accel[3], float magnet[3], float ref_magnet[3]) {
  67.     // 实现卡尔曼滤波更新步骤
  68. }
  69. int main(void) {
  70.     // 初始化STM32外设
  71.     // ...
  72.    
  73.     // 初始化MPU9250
  74.     mpu9250_init();
  75.    
  76.     // 初始化卡尔曼滤波器
  77.     float initial_q[4] = {1, 0, 0, 0}; // 初始姿态为0角度
  78.     float initial_bg[3] = {0, 0, 0}; // 初始陀螺仪偏置为0
  79.     init_kalman(initial_q, initial_bg);
  80.    
  81.     // 参考地磁场值(根据实际位置确定)
  82.     float ref_magnet[3] = {0, 0, -1}; // 南极指向地心
  83.    
  84.     float gyro[3], accel[3], magnet[3];
  85.    
  86.     while(1) {
  87.         // 读取传感器数据
  88.         read_gyro(gyro);
  89.         read_accel(accel);
  90.         read_magnet(magnet);
  91.         
  92.         // 预测步骤
  93.         float dt = 0.01; // 10ms采样周期
  94.         kalman_predict(gyro, dt);
  95.         
  96.         // 更新步骤
  97.         kalman_update(accel, magnet, ref_magnet);
  98.         
  99.         // 处理滤波结果
  100.         // ...
  101.         
  102.         // 延时
  103.         Delay_Ms(10);
  104.     }
  105. }
复制代码
假如不想本身手写代码,可以利用STMicroelectronics的MotionFX库。
它是X-CUBE-MEMS1软件扩展的一部分,内置优化的卡尔曼滤波算法,支持6轴和9轴IMU融合。该库针对STM32微控制器优化,恰当快速开发。
MotionFX库在不同STM32平台上的性能结果如下:

在STM32上实现九轴IMU的卡尔曼滤波是嵌入式系统中实现高精度姿态估计的有用方法。通过明白IMU的工作原理、卡尔曼滤波的理论以及系统建模,开发者可以重新实现EKF算法。或者,利用ST的MotionFX库可以明显简化开发流程,同时保持高性能。 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 qidao123.com技术社区-IT企服评测·应用市场 (https://dis.qidao123.com/) Powered by Discuz! X3.4