msp430f5529标准库学习之mpu6050dmp库和卡尔曼滤波移植

文章介绍了使用Gy521模块中的MPU6050传感器进行姿态解算的两种方法。一是通过卡尔曼滤波计算翻滚角和俯仰角,二是移植官方DMP库,获取更全面的偏航、翻滚和俯仰角。代码示例展示了初始化、数据读取及处理过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这里使用的型号是Gy521,图片如下

a2cdf3b2382942a3a44b1d75cf7faa21.png

引脚说明:

VCC:接3.3V电源

GND:电源负极

SCL:I2C时钟线

SDA:I2C数据线

XDA:连接其他i2c设备的主机数据口

XCL:给i2c设备提供时钟

AD0:i2c地址选择位

INT:中断引脚

这里只使用VCC,GND,SCL,SDA4个引脚

方法一、使用卡尔曼滤波进行姿态解算,不过好像只能计算出翻滚角和俯仰角。代码如下

//初始化 MPU6050 以进入可用状态。
void MPU6050_initialize(void) {
    IIC_Init();
    MPU6050_setClockSource(MPU6050_CLOCK_PLL_YGYRO); //设置时钟
    MPU6050_setFullScaleGyroRange(MPU6050_GYRO_FS_2000);//陀螺仪最大量程 +-1000度每秒
    MPU6050_setFullScaleAccelRange(MPU6050_ACCEL_FS_2);    //加速度度最大量程 +-2G
    MPU6050_setSleepEnabled(0); //进入工作状态
    MPU6050_setI2CMasterModeEnabled(0);     //不让MPU6050 控制AUXI2C
    MPU6050_setI2CBypassEnabled(0);     //主控制器的I2C与    MPU6050的AUXI2C    直通。控制器可以直接访问HMC5883L
}


//读取MPU6050内置温度传感器数据
float Read_Temperature(void)
{
    float Temp;
    Temp=(I2C_ReadOneByte(devAddr,MPU6050_RA_TEMP_OUT_H)<<8)+I2C_ReadOneByte(devAddr,MPU6050_RA_TEMP_OUT_L);
    if(Temp>32768) Temp-=65536;
    return (36.53+Temp/340);
}


//得到陀螺仪值(原始值)
u8 MPU_Get_Gyroscope(short *gx,short *gy,short *gz)
{
    u8 buf[6],res;
    res=i2cRead(MPU_ADDR,MPU6050_RA_ACCEL_XOUT_H,6,buf);
    if(res==0)
    {
        *gx=((u16)buf[0]<<8)|buf[1];
        *gy=((u16)buf[2]<<8)|buf[3];
        *gz=((u16)buf[4]<<8)|buf[5];
    }
    return res;
}
//得到加速度值(原始值)
u8 MPU_Get_Accelerometer(short *ax,short *ay,short *az)
{
    u8 buf[6],res;
    res=i2cRead(MPU_ADDR,MPU6050_RA_GYRO_XOUT_H,6,buf);
    if(res==0)
    {
        *ax=((u16)buf[0]<<8)|buf[1];
        *ay=((u16)buf[2]<<8)|buf[3];
        *az=((u16)buf[4]<<8)|buf[5];
    }
    return res;

}

short aacx,aacy,aacz;           //加速度传感器原始数据
short gyrox,gyroy,gyroz;    //陀螺仪原始数据
short temp;                             //温度

short aacx_0=0,aacy_0=0,aacz_0=0;           //加速度计零偏
short gyrox_0=0,gyroy_0=0,gyroz_0=0;    //陀螺仪零偏

#define RAD2DEG 57.295779513

float Angle_X_Final,Angle_Y_Final;

#define SYS_CLOCK   16//系统时钟

//延时函数
static void delay_ms(uint32_t ms)
{
    int i=0;
    for(i=0;i<ms;i++)
        __delay_cycles(SYS_CLOCK*1000); // 延迟1微秒,该函数为MSP430内置的延迟函数
}

//获取陀螺仪加速度计零飘平均值
void Aac_Gyro_Zero_Shift_Init(void)
{
    u16 i;

    long compensate[6]={0};

    delay_ms(100);

    //累计100次,计算平均值
    for(i=0;i<100;i++)
    {
        MPU_Get_Accelerometer(&aacx,&aacy,&aacz);   //得到加速度传感器数据
        MPU_Get_Gyroscope(&gyrox,&gyroy,&gyroz);    //得到陀螺仪数据

        compensate[0]+=aacx;
        compensate[1]+=aacy;
        compensate[2]+=aacz-16384;
        compensate[3]+=gyrox;
        compensate[4]+=gyroy;
        compensate[5]+=gyroz;
    }

    aacx_0=compensate[0]/100;
    aacy_0=compensate[1]/100;
    aacz_0=compensate[2]/100;
    gyrox_0=compensate[3]/100;
    gyroy_0=compensate[4]/100;
    gyroz_0=compensate[5]/100;

}

//陀螺仪加速度计零飘补偿
/*获取MPU6050数据后调用补偿*/
void Aac_Gyro_Zero_Shift_compensate(void)
{
    aacx-=aacx_0;
    aacy-=aacy_0;
    aacz-=aacz_0;

    gyrox-=gyrox_0;
    gyroy-=gyroy_0;
    gyroz-=gyroz_0;

}

//kalman参数
static float Q_angle=0.001;// 过程噪声的协方差
static float Q_gyro=0.003;//0.003 过程噪声的协方差 过程噪声的协方差为一个一行两列矩阵
static float R_angle=0.5;// 测量噪声的协方差 既测量偏差
static float dt=0.005;//计算周期

void Kalman_Filter_X()
{
    static float Accel;
    static float Gyro;
    static char  C_0 = 1;
    static float Q_bias, Angle_err;
    static float PCt_0, PCt_1, E;
    static float K_0, K_1, t_0, t_1;
    static float Pdot[4] ={0,0,0,0};
    static float PP[2][2] = { { 1, 0 },{ 0, 1 } };

    Accel=atan2(aacy,aacz)*RAD2DEG;
    Gyro=gyrox/16.4;


    Angle_X_Final+=(Gyro - Q_bias) * dt; //先验估计
    Pdot[0]=Q_angle - PP[0][1] - PP[1][0]; // Pk-先验估计误差协方差的微分

    Pdot[1]=-PP[1][1];
    Pdot[2]=-PP[1][1];
    Pdot[3]=Q_gyro;
    PP[0][0] += Pdot[0] * dt;   // Pk-先验估计误差协方差微分的积分
    PP[0][1] += Pdot[1] * dt;   // =先验估计误差协方差
    PP[1][0] += Pdot[2] * dt;
    PP[1][1] += Pdot[3] * dt;

    Angle_err = Accel - Angle_X_Final;  //zk-先验估计

    PCt_0 = C_0 * PP[0][0];
    PCt_1 = C_0 * PP[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 * PP[0][1];

    PP[0][0] -= K_0 * t_0;       //后验估计误差协方差
    PP[0][1] -= K_0 * t_1;
    PP[1][0] -= K_1 * t_0;
    PP[1][1] -= K_1 * t_1;

    Angle_X_Final   += K_0 * Angle_err;  //后验估计
    Q_bias  += K_1 * Angle_err;  //后验估计
}

void Kalman_Filter_Y()
{
    static float Accel;
    static float Gyro;
    static char  C_0 = 1;
    static float Q_bias, Angle_err;
    static float PCt_0, PCt_1, E;
    static float K_0, K_1, t_0, t_1;
    static float Pdot[4] ={0,0,0,0};
    static float PP[2][2] = { { 1, 0 },{ 0, 1 } };

    Accel=-atan2(aacx,aacz)*RAD2DEG;
    Gyro=gyroy/16.4;


    Angle_Y_Final+=(Gyro - Q_bias) * dt; //先验估计
    Pdot[0]=Q_angle - PP[0][1] - PP[1][0]; // Pk-先验估计误差协方差的微分

    Pdot[1]=-PP[1][1];
    Pdot[2]=-PP[1][1];
    Pdot[3]=Q_gyro;
    PP[0][0] += Pdot[0] * dt;   // Pk-先验估计误差协方差微分的积分
    PP[0][1] += Pdot[1] * dt;   // =先验估计误差协方差
    PP[1][0] += Pdot[2] * dt;
    PP[1][1] += Pdot[3] * dt;

    Angle_err = Accel - Angle_Y_Final;  //zk-先验估计

    PCt_0 = C_0 * PP[0][0];
    PCt_1 = C_0 * PP[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 * PP[0][1];

    PP[0][0] -= K_0 * t_0;       //后验估计误差协方差
    PP[0][1] -= K_0 * t_1;
    PP[1][0] -= K_1 * t_0;
    PP[1][1] -= K_1 * t_1;

    Angle_Y_Final   += K_0 * Angle_err;  //后验估计
    Q_bias  += K_1 * Angle_err;  //后验估计
}

//卡尔曼处理数据
void Angle_Calcu(void)
{
    MPU_Get_Accelerometer(&aacx,&aacy,&aacz);   //得到加速度传感器数据
    MPU_Get_Gyroscope(&gyrox,&gyroy,&gyroz);    //得到陀螺仪数据

    Aac_Gyro_Zero_Shift_compensate();                   //陀螺仪加速度计零飘补偿

    Kalman_Filter_X();
    Kalman_Filter_Y();
    UART_printf(USCI_A0_BASE,"X:%f,Y:%f\r\n",Angle_X_Final,Angle_Y_Final);
}

 效果如图

7bc515eaaf55422f8072cbe04adbd2f6.png

 方法二、移植官方dmp库,可以得到偏航角,翻滚角,俯仰角。

主要代码如下:

#define PRINT_ACCEL     (0x01)
#define PRINT_GYRO      (0x02)
#define PRINT_QUAT      (0x04)
#define ACCEL_ON        (0x01)
#define GYRO_ON         (0x02)
#define MOTION          (0)
#define NO_MOTION       (1)
#define DEFAULT_MPU_HZ  (200)
#define FLASH_SIZE      (512)
#define FLASH_MEM_START ((void*)0x1800)
#define q30  1073741824.0f
short gyro[3], accel[3], sensors;
float q0=1.0f,q1=0.0f,q2=0.0f,q3=0.0f;
static signed char gyro_orientation[9] = {-1, 0, 0,
                                           0,-1, 0,
                                           0, 0, 1};

//设置  MPU6050 的时钟源
void MPU6050_setClockSource(uint8_t source){
    IICwriteBits(devAddr, MPU6050_RA_PWR_MGMT_1, MPU6050_PWR1_CLKSEL_BIT, MPU6050_PWR1_CLKSEL_LENGTH, source);

}

// 设置满量程陀螺仪范围.
void MPU6050_setFullScaleGyroRange(uint8_t range) {
    IICwriteBits(devAddr, MPU6050_RA_GYRO_CONFIG, MPU6050_GCONFIG_FS_SEL_BIT, MPU6050_GCONFIG_FS_SEL_LENGTH, range);
}

//设置  MPU6050 加速度计的最大量程
void MPU6050_setFullScaleAccelRange(uint8_t range) {
    IICwriteBits(devAddr, MPU6050_RA_ACCEL_CONFIG, MPU6050_ACONFIG_AFS_SEL_BIT, MPU6050_ACONFIG_AFS_SEL_LENGTH, range);
}


//设置  MPU6050 是否进入睡眠模式
void MPU6050_setSleepEnabled(uint8_t enabled) {
    IICwriteBit(devAddr, MPU6050_RA_PWR_MGMT_1, MPU6050_PWR1_SLEEP_BIT, enabled);
}

//读取  MPU6050 WHO_AM_I 标识     将返回 0x68
uint8_t MPU6050_getDeviceID(void) {

    IICreadBytes(devAddr, MPU6050_RA_WHO_AM_I, 1, buffer);
    return buffer[0];
}

//检测MPU6050 是否已经连接
uint8_t MPU6050_testConnection(void) {
    if(MPU6050_getDeviceID() == 0x68)  //0b01101000;
        return 1;
    else 
        return 0;
}

//设置 MPU6050 是否为AUX I2C线的主机
void MPU6050_setI2CMasterModeEnabled(uint8_t enabled) {
    IICwriteBit(devAddr, MPU6050_RA_USER_CTRL, MPU6050_USERCTRL_I2C_MST_EN_BIT, enabled);
}

//设置 MPU6050 是否为AUX I2C线的主机
void MPU6050_setI2CBypassEnabled(uint8_t enabled) {
    IICwriteBit(devAddr, MPU6050_RA_INT_PIN_CFG, MPU6050_INTCFG_I2C_BYPASS_EN_BIT, enabled);
}

//初始化 MPU6050 以进入可用状态。
void MPU6050_initialize(void) {
    IIC_Init();
    MPU6050_setClockSource(MPU6050_CLOCK_PLL_YGYRO); //设置时钟
    MPU6050_setFullScaleGyroRange(MPU6050_GYRO_FS_2000);//陀螺仪最大量程 +-1000度每秒
    MPU6050_setFullScaleAccelRange(MPU6050_ACCEL_FS_2);    //加速度度最大量程 +-2G
    MPU6050_setSleepEnabled(0); //进入工作状态
    MPU6050_setI2CMasterModeEnabled(0);     //不让MPU6050 控制AUXI2C
    MPU6050_setI2CBypassEnabled(0);     //主控制器的I2C与    MPU6050的AUXI2C    直通。控制器可以直接访问HMC5883L
}

//MPU6050内置DMP的初始化
void DMP_Init(void)
{ 
    u8 temp[1]={0};
    i2cRead(0x68,0x75,1,temp);
     UART_printf(USCI_A0_BASE,"mpu_set_sensor complete ......\r\n");
    if(temp[0]!=0x68)
        PMMCTL0 = PMMPW | PMMSWPOR;
    if(!mpu_init())
    {
      if(!mpu_set_sensors(INV_XYZ_GYRO | INV_XYZ_ACCEL))
         UART_printf(USCI_A0_BASE,"mpu_set_sensor complete ......\r\n");
      if(!mpu_configure_fifo(INV_XYZ_GYRO | INV_XYZ_ACCEL))
         UART_printf(USCI_A0_BASE,"mpu_configure_fifo complete ......\r\n");
      if(!mpu_set_sample_rate(DEFAULT_MPU_HZ))
         UART_printf(USCI_A0_BASE,"mpu_set_sample_rate complete ......\r\n");
      if(!dmp_load_motion_driver_firmware())
        UART_printf(USCI_A0_BASE,"dmp_load_motion_driver_firmware complete ......\r\n");
      if(!dmp_set_orientation(inv_orientation_matrix_to_scalar(gyro_orientation)))
         UART_printf(USCI_A0_BASE,"dmp_set_orientation complete ......\r\n");
      if(!dmp_enable_feature(DMP_FEATURE_6X_LP_QUAT | DMP_FEATURE_TAP |
            DMP_FEATURE_ANDROID_ORIENT | DMP_FEATURE_SEND_RAW_ACCEL | DMP_FEATURE_SEND_CAL_GYRO |
            DMP_FEATURE_GYRO_CAL))
         UART_printf(USCI_A0_BASE,"dmp_enable_feature complete ......\r\n");
      if(!dmp_set_fifo_rate(DEFAULT_MPU_HZ))
         UART_printf(USCI_A0_BASE,"dmp_set_fifo_rate complete ......\r\n");
      run_self_test();
      if(!mpu_set_dmp_state(1))
         UART_printf(USCI_A0_BASE,"mpu_set_dmp_state complete ......\r\n");
    }
}
int Read_Date(float *Pitch,float *Roll,float *Yaw)
{
    uint8_t data[16];
    long quat[4];
    long quat_q14[4], quat_mag_sq;

    //获取数据
    if (i2cRead(MPU_ADDR,MPU6050_RA_FIFO_R_W,16,data))
            return -1;

    //处理数据
    quat[0] = ((long)data[0] << 24) | ((long)data[1] << 16) |
        ((long)data[2] << 8) | data[3];
    quat[1] = ((long)data[4] << 24) | ((long)data[5] << 16) |
        ((long)data[6] << 8) | data[7];
    quat[2] = ((long)data[8] << 24) | ((long)data[9] << 16) |
        ((long)data[10] << 8) | data[11];
    quat[3] = ((long)data[12] << 24) | ((long)data[13] << 16) |
        ((long)data[14] << 8) | data[15];

    //数据校验
    quat_q14[0] = quat[0] >> 16;
    quat_q14[1] = quat[1] >> 16;
    quat_q14[2] = quat[2] >> 16;
    quat_q14[3] = quat[3] >> 16;
    quat_mag_sq = quat_q14[0] * quat_q14[0] + quat_q14[1] * quat_q14[1] +
        quat_q14[2] * quat_q14[2] + quat_q14[3] * quat_q14[3];
    if ((quat_mag_sq < QUAT_MAG_SQ_MIN) ||
        (quat_mag_sq > QUAT_MAG_SQ_MAX)) {
        /* Quaternion is outside of the acceptable threshold. */
        mpu_reset_fifo();
        return -1;
    }

    //计算数据
    q0=quat[0] / q30;
    q1=quat[1] / q30;
    q2=quat[2] / q30;
    q3=quat[3] / q30;

    //得到结果
    *Pitch = asin(-2 * q1 * q3 + 2 * q0* q2)*57.3;
    *Roll = atan2(2 * q2 * q3 + 2 * q0 * q1, -2 * q1 * q1 - 2 * q2* q2 + 1)*57.3; // roll
    *Yaw   = atan2(2*(q1*q2 + q0*q3),q0*q0+q1*q1-q2*q2-q3*q3) *57.3;//yaw
    return 0;
}

效果如下

### 卡尔曼滤波在STM32上的实现 卡尔曼滤波是一种高效的递归滤波方法,在许多应用领域都有广泛的应用,特别是在传感器数据处理方面。对于STM32平台而言,利用标准外设可以有效地简化开发过程并提升代码质量[^2]。 #### 使用标准外设的优势 通过使用STM32的标准外设来实现卡尔曼滤波算法,不仅可以减少底层硬件编程的工作量,还能增强程序的稳定性效率。该提供了丰富的API接口用于配置定时器、ADC转换等功能模块,这些功能对于实时采集传感器数据至关重要。 #### 示例代码结构说明 下面是一个基于STM32标准外设编写的简单一维线性卡尔曼滤波器的例子: ```c #include "stm32f1xx.h" // 定义变量 float Q_angle = 0.001; // 过程噪声协方差矩阵Q中的角度误差项 float R_measure = 0.1; // 测量噪声协方差R float angle = 0, bias = 0; float P[2][2] = { { 0, 0 }, { 0, 0 } }; float K[2]; // Kalman增益向量K=[k1 k2] void Kalman_Filter_Init(void){ /* 初始化P矩阵 */ } void Kalman_Filter(float newAngle,float newRate,int deltaTime){ // 预测更新阶段 float dt = (float)deltaTime / 1000; angle += dt * (newRate - bias); P[0][0] += dt * (dt*P[1][1]-P[0][1]-P[1][0]+Q_angle); P[0][1] -= dt * P[1][1]; P[1][0] -= dt * P[1][1]; P[1][1] += Q_bias * dt; // 计算Kalman Gain float S = P[0][0] + R_measure; K[0] = P[0][0]/S; K[1] = P[1][0]/S; // 更新测量值 angle += K[0]*(newAngle-angle); bias += K[1]*(newAngle-angle); // 更新估计协方差 float temp_P00 = P[0][0]; P[0][0] -= K[0]*temp_P00; P[0][1] -= K[0]*P[1][0]; P[1][0] -= K[1]*temp_P00; P[1][1] -= K[1]*P[1][0]; } ``` 此段代码展示了如何初始化以及迭代计算卡尔曼滤波的过程。需要注意的是,实际应用场景可能涉及到更复杂的模型设计参数调整工作。
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值