谈谈MPU6050的数据融合 一阶滤波 卡尔曼滤波


   

首先要明确,MPU6050 是一款姿态传感器,使用它就是为了得到待测物体(如四轴、平衡小车) x、y、z 轴的倾角(俯仰角 Pitch、滚转角 Roll、偏航角 Yaw) 。我们通过 I2C 读取到 MPU6050 的六个数据(三轴加速度 AD 值、三轴角速度 AD 值)经过姿态融合后就可以得到 Pitch、Roll、Yaw 角。

本帖主要介绍三种姿态融合算法:四元数法 、一阶互补算法和卡尔曼滤波算法。




一、四元数法

关于四元数的一些概念和计算就不写上来了,我也不懂。我能告诉你的是:通过下面的算法,可以把六个数据转化成四元数(q0、q1、q2、q3),然后四元数转化成欧拉角(P、R、Y 角)。



虽然 MPU6050 自带的 DMP库可以直接输出四元数,减轻 STM32 的运算负担, 这里在此没有使用,因为我是用 STM32 的硬件 I2C 读取 MPU6050 数据的(http://bbs.elecfans.com/forum.ph ... 4&page=1#pid3625735),DMP库需要对 I2C 函数进行修改,如 DMP 库中的 I2C 写:i2c_write(st.hw->addr, st.reg->pwr_mgmt_1, 1, &(data[0]));有4个输入变量,而 STM32 硬件 I2C 的 I2C 写为:void MPU6050_I2C_ByteWrite(u8 slaveAddr, u8 pBuffer, u8 writeAddr),只有 3 个输入量(这之间的差异好像是由于 MPU6050 的 DMP 库是针对 MSP430 单片机写的),所以必须进行修改,但是改固件库是一件很痛苦的事,你们应该都懂。当然,如果你用模拟 I2C 的话,是容易实现的,网上的 DMP 移植几乎都是基于模拟 I2C 的。


复制代码


#include<math.h>

#include "stm32f10x.h"

//---------------------------------------------------------------------------------------------------

// 变量定义


#define Kp 100.0f                        // 比例增益支配率收敛到加速度计/磁强计

#define Ki 0.002f                // 积分增益支配率的陀螺仪偏见的衔接

#define halfT 0.001f                // 采样周期的一半


float q0 = 1, q1 = 0, q2 = 0, q3 = 0;          // 四元数的元素,代表估计方向

float exInt = 0, eyInt = 0, ezInt = 0;        // 按比例缩小积分误差


float Yaw,Pitch,Roll;  //偏航角,俯仰角,翻滚角



void IMUupdate(float gx, float gy, float gz, float ax, float ay, float az)

{

        float norm;

        float vx, vy, vz;

        float ex, ey, ez;  


        // 测量正常化

        norm = sqrt(ax*ax + ay*ay + az*az);      

        ax = ax / norm;                   //单位化

        ay = ay / norm;

        az = az / norm;      


        // 估计方向的重力

        vx = 2*(q1*q3 - q0*q2);

        vy = 2*(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 = exInt + ex*Ki;

        eyInt = eyInt + ey*Ki;

        ezInt = ezInt + ez*Ki;


        // 调整后的陀螺仪测量

        gx = gx + Kp*ex + exInt;

        gy = gy + Kp*ey + eyInt;

        gz = gz + Kp*ez + ezInt;


        // 整合四元数率和正常化

        q0 = q0 + (-q1*gx - q2*gy - q3*gz)*halfT;

        q1 = q1 + (q0*gx + q2*gz - q3*gy)*halfT;

        q2 = q2 + (q0*gy - q1*gz + q3*gx)*halfT;

        q3 = q3 + (q0*gz + q1*gy - q2*gx)*halfT;  


        // 正常化四元

        norm = sqrt(q0*q0 + q1*q1 + q2*q2 + q3*q3);

        q0 = q0 / norm;

        q1 = q1 / norm;

        q2 = q2 / norm;

        q3 = q3 / norm;


        Pitch  = asin(-2 * q1 * q3 + 2 * q0* q2)* 57.3; // pitch ,转换为度数

        Roll = atan2(2 * q2 * q3 + 2 * q0 * q1, -2 * q1 * q1 - 2 * q2* q2 + 1)* 57.3; // rollv

        //Yaw = atan2(2*(q1*q2 + q0*q3),q0*q0+q1*q1-q2*q2-q3*q3) * 57.3;                //此处没有价值,注掉

}




要注意的的是,四元数算法输出的是三个量 Pitch、Roll 和 Yaw,运算量很大。而像平衡小车这样的例子只需要一个角(Pitch 或 Roll )就可以满足工作要求,个人觉得做平衡小车最好不用四元数法。




二、一阶互补算法

MPU6050 可以输出三轴的加速度和角速度。通过加速度和角速度都可以得到 Pitch 和 Roll 角(加速度不能得到 Yaw 角),就是说有两组 Pitch、Roll 角,到底应该选哪组呢?别急,先分析一下。MPU6050 的加速度计和陀螺仪各有优缺点,三轴的加速度值没有累积误差,且通过算 tan()  可以得到倾角,但是它包含的噪声太多(因为待测物运动时会产生加速度,电机运行时振动会产生加速度等),不能直接使用;陀螺仪对外界振动影响小,精度高,通过对角速度积分可以得到倾角,但是会产生累积误差。所以,不能单独使用 MPU6050 的加速度计或陀螺仪来得到倾角,需要互补。一阶互补算法的思想就是给加速度和陀螺仪不同的权值,把它们结合到一起,进行修正。得到 Pitch 角的程序如下:




复制代码


//一阶互补滤波

float K1 =0.1; // 对加速度计取值的权重

float dt=0.001;//注意:dt的取值为滤波器采样时间

float angle;


angle_ax=atan(ax/az)*57.3;     //加速度得到的角度

gy=(float)gyo[1]/7510.0;       //陀螺仪得到的角速度

Pitch = yijiehubu(angle_ax,gy);


float yijiehubu(float angle_m, float gyro_m)//采集后计算的角度和角加速度

{

     angle = K1 * angle_m + (1-K1) * (angle + gyro_m * dt);

     return angle;

}




互补算法只能得到一个倾角,这在平衡车项目中够用了,而在四轴飞行器设计中还需要 Roll 和 Yaw,就需要两个 互补算法,我是这样写的,注意变量不要搞混:


复制代码


//一阶互补滤波

float K1 =0.1; // 对加速度计取值的权重

float dt=0.001;//注意:dt的取值为滤波器采样时间

float angle_P,angle_R;



float yijiehubu_P(float angle_m, float gyro_m)//采集后计算的角度和角加速度

{

     angle_P = K1 * angle_m + (1-K1) * (angle_P + gyro_m * dt);

         return angle_P;

}


float yijiehubu_R(float angle_m, float gyro_m)//采集后计算的角度和角加速度

{

     angle_R = K1 * angle_m + (1-K1) * (angle_R + gyro_m * dt);

         return angle_R;

}

单靠 MPU6050 无法准确得到 Yaw 角,需要和地磁传感器结合使用。






三、卡尔曼滤波

其实卡尔曼滤波和一阶互补有些相似,输入也是一样的。卡尔曼原理以及什么5个公式等等的,我也不太懂,就不写了,感兴趣的话可以上网查。在此给出具体程序,和一阶互补算法一样,每次卡尔曼滤波只能得到一个方向的角度。




复制代码




#include<math.h>

#include "stm32f10x.h"

#include "Kalman_Filter.h"




//卡尔曼滤波参数与函数

float dt=0.001;//注意:dt的取值为kalman滤波器采样时间

float angle, angle_dot;//角度和角速度

float P[2][2] = {{ 1, 0 },

                 { 0, 1 }};

float Pdot[4] ={ 0,0,0,0};

float Q_angle=0.001, Q_gyro=0.005; //角度数据置信度,角速度数据置信度

float R_angle=0.5 ,C_0 = 1;

float q_bias, angle_err, PCt_0, PCt_1, E, K_0, K_1, t_0, t_1;


//卡尔曼滤波

float Kalman_Filter(float angle_m, float gyro_m)//angleAx 和 gyroGy

{

        angle+=(gyro_m-q_bias) * dt;

        angle_err = angle_m - angle;

        Pdot[0]=Q_angle - P[0][1] - P[1][0];

        Pdot[1]=- P[1][1];

        Pdot[2]=- P[1][1];

        Pdot[3]=Q_gyro;

        P[0][0] += Pdot[0] * dt;

        P[0][1] += Pdot[1] * dt;

        P[1][0] += Pdot[2] * dt;

        P[1][1] += Pdot[3] * dt;

        PCt_0 = C_0 * P[0][0];

        PCt_1 = C_0 * P[1][0];

        E = R_angle + C_0 * PCt_0;

        K_0 = PCt_0 / E;

        K_1 = PCt_1 / E;

        t_0 = PCt_0;

        t_1 = C_0 * P[0][1];

        P[0][0] -= K_0 * t_0;

        P[0][1] -= K_0 * t_1;

        P[1][0] -= K_1 * t_0;

        P[1][1] -= K_1 * t_1;

        angle += K_0 * angle_err; //最优角度

        q_bias += K_1 * angle_err;

        angle_dot = gyro_m-q_bias;//最优角速度


        return angle;

}






作个总结:三种融合算法都能够输出姿态角(Pitch 和 Roll ),一次四元数法可以输出 P、R、Y 三个倾角,计算量比较大。一阶互补和卡尔曼滤波每次只能输出一个轴的姿态角。



注:使用“匿名地面站”上位机软件,给调试过程带来了很大便捷。软件可在附件中下载。

通过上位机可以看到,姿态融合成功!




 

### 使用 HAL 庿对 MPU6050 实现一阶互补滤波 对于 STM32 平台上的 MPU6050 传感器,可以利用 HAL (Hardware Abstraction Layer) 库来简化硬件接口编程。为了实现一阶互补滤波器处理加速度计和陀螺仪数据,通常需要读取原始传感器数据并应用适当的算法。 #### 初始化 I2C 接口 首先初始化用于与 MPU6050 进行通信的 I2C 接口: ```c I2C_HandleTypeDef hi2c1; void MX_I2C1_Init(void) { /* USER CODE BEGIN I2C1_Init 0 */ /* USER CODE END I2C1_Init 0 */ /* USER CODE BEGIN I2C1_Init 1 */ /* USER CODE END I2C1_Init 1 */ hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(&hi2c1) != HAL_OK) { Error_Handler(); } } ``` #### 配置 MPU6050 设备 接着配置 MPU6050 的工作模式和其他参数设置: ```c #define MPU6050_ADDR 0xD0 // 地址取决于AD0引脚电平 uint8_t mpu_init(I2C_HandleTypeDef *i2c_handle){ uint8_t who_am_i, data; // 检查设备ID是否正确 HAL_I2C_Mem_Read(i2c_handle, MPU6050_ADDR, WHO_AM_I_REG, 1, &who_am_i, 1, HAL_MAX_DELAY); if(who_am_i != 0x68){return 1;} // 设置低功耗模式下的采样率分频系数,默认为9即1kHz/(1+9)=100Hz data = 0x09; HAL_I2C_Mem_Write(i2c_handle, MPU6050_ADDR, SMPLRT_DIV_REG, 1,&data , 1, HAL_MAX_DELAY); // 启用内部时钟源X轴陀螺仪 data = 0x01; HAL_I2C_Mem_Write(i2c_handle, MPU6050_ADDR, PWR_MGMT_1_REG, 1, &data, 1, HAL_MAX_DELAY); return 0; } ``` #### 数据获取函数 定义一个从 MPU6050 获取加速度和角速度的方法: ```c typedef struct { float ax, ay, az; float gx, gy, gz; } imu_data_t; imu_data_t get_imu_data(I2C_HandleTypeDef *i2c_handle){ int16_t raw_gyro[3],raw_accel[3]; imu_data_t result={}; // 开始传输命令字节到寄存器地址指针位置 HAL_I2C_Master_Transmit(i2c_handle, MPU6050_ADDR, ACCEL_XOUT_H_REG, 1, HAL_MAX_DELAY); // 一次接收全部六个通道的数据 HAL_I2C_Master_Receive(i2c_handle, MPU6050_ADDR, (uint8_t*)raw_accel, sizeof(raw_accel), HAL_MAX_DELAY); // 将接收到的数据转换成实际物理量值 result.ax = ((float)(int16_t)((raw_accel[0]<<8)|raw_accel[1])) / 16384.0f ; result.ay = ((float)(int16_t)((raw_accel[2]<<8)|raw_accel[3])) / 16384.0f ; result.az = ((float)(int16_t)((raw_accel[4]<<8)|raw_accel[5])) / 16384.0f ; // 类似地读取角速率数据... } ``` #### 补偿滤波计算 最后,在主循环里加入如下代码片段完成一阶互补滤波运算: ```c // 定义全局变量保存角度估计值以及时间戳 static float pitch_estimated = 0.f, roll_estimated = 0.f; static uint32_t last_time_us = 0UL; while(true){ ... // 计算当前时刻的时间间隔dt(s) const uint32_t now_us = micros(); // 假设有一个返回微秒级时间戳的micros()函数 const double dt_s = (now_us - last_time_us)*1e-6; last_time_us = now_us; // 获得IMU最新测量结果 imu_data_t new_reading=get_imu_data(&hi2c1); // 更新姿态估算 // 对于俯仰角(pitch),这里假设只考虑绕X轴旋转情况 static constexpr float alpha = 0.98f; // 取决于具体应用场景调整此权重因子 pitch_estimated += new_reading.gy*dt_s*(alpha)+atan(new_reading.ay/sqrt(pow(new_reading.ax,2)+pow(new_reading.az,2)))*(1-alpha); // 对于横滚角(roll),同样仅关注沿Y方向变化的情形 roll_estimated += (-new_reading.gx*dt_s*alpha)+(atan(-new_reading.ax/new_reading.az))*(1-alpha); // 执行其他必要的操作... } ``` 上述代码实现了基于 HAL 库的一阶互补滤波器逻辑[^1]。
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值