Arduino Uno使用MPU6050和卡尔曼滤波实现姿态解算教程

实验效果

Arduin Uno 使用陀螺仪模块MPU6050运用卡尔曼滤波实现姿态解算。

本例程输出的是XYZ轴的角度,范围在正负90度之间。通过使用卡尔曼滤波算法来解算姿态,整体效果相对稳定,但可能会有一些偏移,在此基础上进一步改进。

以下是本例程XYZ轴的输出数据效果图:

(时间曲线展示了以下过程:静止姿态→摆动→恢复到原来的静止姿态→拍动桌子→再次静止)

20160510093704265

元件介绍

WX20240605-2138132x

MPU6050是一款集成了三轴加速度计和三轴陀螺仪的传感器芯片,广泛应用于运动检测、姿态控制等领域。以下是对MPU6050的一些简单介绍:

主要特性

特点

  1. 六轴传感器集成
    • 三轴加速度计
    • 三轴陀螺仪
  2. 内置数字运动处理器(DMP)
    • 支持复杂的姿态解算和运动检测
  3. 低功耗
    • 适用于电池供电的便携设备
  4. 高分辨率
    • 加速度计分辨率:16位
    • 陀螺仪分辨率:16位
  5. 可编程中断功能
    • 支持运动检测、自由落体检测等多种中断源
  6. I2C通信接口
    • 支持标准模式(100kHz)和快速模式(400kHz)
  7. 内置温度传感器
    • 提供温度补偿功能
  8. 小尺寸封装
    • 封装尺寸为4x4x0.9mm,适合空间受限的应用

性能

  1. 加速度计性能
    • 量程:±2g、±4g、±8g、±16g
    • 灵敏度:16384 LSB/g(±2g),8192 LSB/g(±4g),4096 LSB/g(±8g),2048 LSB/g(±16g)
    • 零偏误差:±60mg(最大值)
  2. 陀螺仪性能
    • 量程:±250°/s、±500°/s、±1000°/s、±2000°/s
    • 灵敏度:131 LSB/°/s(±250°/s),65.5 LSB/°/s(±500°/s),32.8 LSB/°/s(±1000°/s),16.4 LSB/°/s(±2000°/s)
    • 零偏误差:±20°/s(最大值)
  3. 温度传感器性能
    • 测量范围:-40°C至+85°C
    • 分辨率:±1°C
  4. 电源电压范围
    • 2.375V至3.46V(VDD)
    • 1.8V至3.3V(VLOGIC)
  5. 工作温度范围
    • -40°C至+85°C
  6. 功耗
    • 加速度计:500µA(典型值)
    • 陀螺仪:3.6mA(典型值)

应用领域

  • 无人机:用于姿态控制和稳定。
  • 机器人:用于运动检测和导航。
  • 手机和平板:用于屏幕旋转和游戏控制。
  • 可穿戴设备:用于运动监测和健康管理。

工作原理

  1. 加速度计:通过检测物体在X、Y、Z方向上的加速度,判断物体的运动状态和方向。
  2. 陀螺仪:通过检测物体在三个轴上的角速度,可以计算出物体的旋转角度和速度。

引脚说明

  1. VCC
    • 功能:电源输入引脚。
    • 电压范围:通常为3.3V或5V(取决于模块的具体设计)。
  2. GND
    • 功能:电源地引脚。
    • 连接:连接到电源负极。
  3. SCL
    • 功能:I2C时钟线(Serial Clock Line)。
    • 连接:连接到主控器的I2C时钟引脚。
  4. SDA
    • 功能:I2C数据线(Serial Data Line)。
    • 连接:连接到主控器的I2C数据引脚。
  5. XDA(可选):
    • 功能:辅助I2C数据线(用于连接其他I2C设备)。
    • 连接:通常不连接,除非需要扩展I2C设备。
  6. XCL(可选):
    • 功能:辅助I2C时钟线(用于连接其他I2C设备)。
    • 连接:通常不连接,除非需要扩展I2C设备。
  7. AD0
    • 功能:I2C地址选择引脚。
    • 连接:接地(GND)时,I2C地址为0x68;连接到高电平(VCC)时,I2C地址为0x69。
  8. INT
    • 功能:中断引脚。
    • 连接:连接到主控器的中断引脚,用于检测特定事件(如运动检测、自由落体等)。

BOM

名称 数量
Arduino 开发板 1
mpu6050 陀螺仪模块 1
跳线 若干
面包板 1

接线图

WX20240605-2104482x

Arduino UNO  -  MPU-6050
  5V        ->  VCC
  GND       ->  GND
  A4        ->  SDA
  A5        ->  SCL

安装库

安装i2cdevlib库

https://github.com/jrowberg/i2cdevlib

在这里说明一下原生的Wire库或使用i2cdevlib库的区别和优缺点:

原生的Wire库

优点:

  1. 标准库:Wire库是Arduino IDE中自带的库,无需额外安装。
  2. 简洁性:包含基本的I2C通信功能,简单明了,易于理解和使用。
  3. 兼容性:与大多数I2C设备兼容,适用于一般的I2C通信需求。

缺点:

  1. 功能相对简单:只提供了基本的I2C通信功能,高级控制和管理功能较少。
  2. 缺少错误处理:对通信错误的处理和报告功能有限。

i2cdevlib库

优点:

  1. 高级特性:提供了更丰富的功能,包括高级I2C协议功能和增强的错误处理。
  2. 设备支持广泛:包含了对多种I2C设备(如MPU6050等传感器)的支持,减少用户编写特定设备驱动的工作量。
  3. 文档及示例丰富:详细的文档和示例代码,帮助用户快速上手和解决问题。

缺点:

  1. 需要额外安装:用户需要手动下载和安装库,增加了初次使用的复杂性。
  2. 相对复杂:由于功能更丰富,代码和库的结构相对复杂,不适合初学者或简单需求的项目。

总结:

  • Wire库适合初学者和简单的I2C通信项目,提供基本的I2C功能即可满足需求。
  • i2cdevlib库适合需要高级功能和设备支持的项目,更适合开发复杂的I2C通信应用和处理多种I2C设备。

代码提点

  1. 引入库和初始化
    • Wire.hI2Cdev.h:用于I2C通信。
    • MPU6050.h:用于MPU6050传感器的操作。
  2. 变量定义
    • 原始数据变量(如ax, ay, az, gx, gy, gz)。
    • 角度变量(如aax, aay, aaz)。
    • 偏移量(如axo, ayo, azo)。
    • 其他辅助变量(如卡尔曼滤波相关变量)。
  3. setup函数
    • 初始化I2C通信和串口通信。
    • 初始化MPU6050传感器。
    • 采样一定次数以计算加速度计和陀螺仪的偏移量。
  4. loop函数
    • 获取当前时间,计算时间差dt
    • 读取MPU6050的六轴原始数据。
    • 计算加速度和角度。
    • 对加速度进行滑动加权滤波。
    • 计算角速度,并进行积分得到角度。
    • 使用卡尔曼滤波算法对角度进行优化。
    • 打印最终计算得到的角度值。

关键步骤解释

  1. 获取原始数据并计算角度
   accelgyro.getMotion6(&ax, &ay, &az, &gx, &gy, &gz); // 读取六轴原始数值
   float accx = ax / AcceRatio; // x轴加速度
   float accy = ay / AcceRatio; // y轴加速度
   float accz = az / AcceRatio; // z轴加速度
   aax = atan(accy / accz) * (-180) / pi; // y轴对于z轴的夹角
   aay = atan(accx / accz) * 180 / pi; // x轴对于z轴的夹角
   aaz = atan(accz / accy) * 180 / pi; // z轴对于y轴的夹角
  1. 滑动加权滤波
   for(int i=1;i<n_sample;i++) {
       aaxs[i-1] = aaxs[i];
       aax_sum += aaxs[i] * i;
       aays[i-1] = aays[i];
       aay_sum += aays[i] * i;
       aazs[i-1] = aazs[i];
       aaz_sum += aazs[i] * i;
   }
   aaxs[n_sample-1] = aax;
   aax_sum += aax * n_sample;
   aax = (aax_sum / (11*n_sample/2.0)) * 9 / 7.0; // 调幅至0-90°
  1. 计算角速度并积分
   float gyrox = - (gx-gxo) / GyroRatio * dt; // x轴角速度
   float gyroy = - (gy-gyo) / GyroRatio * dt; // y轴角速度
   float gyroz = - (gz-gzo) / GyroRatio * dt; // z轴角速度
   agx += gyrox; // x轴角速度积分
   agy += gyroy; // y轴角速度积分
   agz += gyroz; // z轴角速度积分
  1. 卡尔曼滤波
   for(int i=1;i<10;i++) {
       a_x[i-1] = a_x[i];
       Sx += a_x[i];
       a_y[i-1] = a_y[i];
       Sy += a_y[i];
       a_z[i-1] = a_z[i];
       Sz += a_z[i];
   }
   a_x[9] = aax;
   Sx += aax;
   Sx /= 10; // x轴加速度平均值
   a_y[9] = aay;
   Sy += aay;
   Sy /= 10; // y轴加速度平均值
   a_z[9] = aaz;
   Sz += aaz;
   Sz /= 10;
   Rx = Rx / 9; // 得到方差
   Px = Px + 0.0025; // 0.0025是一个常数,用于更新P值
   Kx = Px / (Px + Rx); // 计算卡尔曼增益
   agx = agx + Kx * (aax - agx); // 更新角度
   Px = (1 - Kx) * Px; // 更新P值

这段代码通过读取MPU6050传感器的数据,并结合卡尔曼滤波算法,能够得到更为平滑和准确的角度值。卡尔曼滤波是一种常用的滤波算法,能够有效地融合传感器数据,减少噪声和误差。

完整代码

#include "Wire.h"
#include "I2Cdev.h"
#include "MPU6050.h"

MPU6050 accelgyro;

unsigned long now, lastTime = 0;
float dt;                                   //微分时间

int16_t ax, ay, az, gx, gy, gz;             //加速度计陀螺仪原始数据
float aax=0, aay=0,aaz=0, agx=0, agy=0, agz=0;    //角度变量
long axo = 0, ayo = 0, azo = 0;             //加速度计偏移量
long gxo = 0, gyo = 0, gzo = 0;             //陀螺仪偏移量

float pi = 3.1415926;
float AcceRatio = 16384.0;                  //加速度计比例系数
float GyroRatio = 131.0;                    //陀螺仪比例系数

uint8_t n_sample = 8;                       //加速度计滤波算法采样个数
float aaxs[8] = {0}, aays[8] = {0}, aazs[8] = {0};         //x,y轴采样队列
long aax_sum, aay_sum,aaz_sum;                      //x,y轴采样和

float a_x[10]={0}, a_y[10]={0},a_z[10]={0} ,g_x[10]={0} ,g_y[10]={0},g_z[10]={0}; //加速度计协方差计算队列
float Px=1, Rx, Kx, Sx, Vx, Qx;             //x轴卡尔曼变量
float Py=1, Ry, Ky, Sy, Vy, Qy;             //y轴卡尔曼变量
float Pz=1, Rz, Kz, Sz, Vz, Qz;             //z轴卡尔曼变量

void setup()
{
    Wire.begin();
    Serial.begin(115200);

    accelgyro.initialize();                 //初始化

    unsigned short times = 200;             //采样次数
    for(int i=0;i<times;i++)
    {
        accelgyro.getMotion6(&ax, &ay, &az, &gx, &gy, &gz); //读取六轴原始数值
        axo += ax; ayo += ay; azo += az;      //采样和
        gxo += gx; gyo += gy; gzo += gz;

    }

    axo /= times; ayo /= times; azo /= times; //计算加速度计偏移
    gxo /= times; gyo /= times; gzo /= times; //计算陀螺仪偏移
}

void loop()
{
    unsigned long now = millis();             //当前时间(ms)
    dt = (now - lastTime) / 1000.0;           //微分时间(s)
    lastTime = now;                           //上一次采样时间(ms)

    accelgyro.getMotion6(&ax, &ay, &az, &gx, &gy, &gz); //读取六轴原始数值

    float accx = ax / AcceRatio;              //x轴加速度
    float accy = ay / AcceRatio;              //y轴加速度
    float accz = az / AcceRatio;              //z轴加速度

    aax = atan(accy / accz) * (-180) / pi;    //y轴对于z轴的夹角
    aay = atan(accx / accz) * 180 / pi;       //x轴对于z轴的夹角
    aaz = atan(accz / accy) * 180 / pi;       //z轴对于y轴的夹角

    aax_sum = 0;                              // 对于加速度计原始数据的滑动加权滤波算法
    aay_sum = 0;
    aaz_sum = 0;

    for(int i=1;i<n_sample;i++)
    {
        aaxs[i-1] = aaxs[i];
        aax_sum += aaxs[i] * i;
        aays[i-1] = aays[i];
        aay_sum += aays[i] * i;
        aazs[i-1] = aazs[i];
        aaz_sum += aazs[i] * i;

    }

    aaxs[n_sample-1] = aax;
    aax_sum += aax * n_sample;
    aax = (aax_sum / (11*n_sample/2.0)) * 9 / 7.0; //角度调幅至0-90°
    aays[n_sample-1] = aay;                        //此处应用实验法取得合适的系数
    aay_sum += aay * n_sample;                     //本例系数为9/7
    aay = (aay_sum / (11*n_sample/2.0)) * 9 / 7.0;
    aazs[n_sample-1] = aaz; 
    aaz_sum += aaz * n_sample;
    aaz = (aaz_sum / (11*n_sample/2.0)) * 9 / 7.0;

    float gyrox = - (gx-gxo) / GyroRatio * dt; //x轴角速度
    float gyroy = - (gy-gyo) / GyroRatio * dt; //y轴角速度
    float gyroz = - (gz-gzo) / GyroRatio * dt; //z轴角速度
    agx += gyrox;                             //x轴角速度积分
    agy += gyroy;                             //x轴角速度积分
    agz += gyroz;

    /* kalman start */
    Sx = 0; Rx = 0;
    Sy = 0; Ry = 0;
    Sz = 0; Rz = 0;

    for(int i=1;i<10;i++)
    {                 //测量值平均值运算
        a_x[i-1] = a_x[i];                      //即加速度平均值
        Sx += a_x[i];
        a_y[i-1] = a_y[i];
        Sy += a_y[i];
        a_z[i-1] = a_z[i];
        Sz += a_z[i];

    }

    a_x[9] = aax;
    Sx += aax;
    Sx /= 10;                                 //x轴加速度平均值
    a_y[9] = aay;
    Sy += aay;
    Sy /= 10;                                 //y轴加速度平均值
    a_z[9] = aaz;
    Sz += aaz;
    Sz /= 10;

    for(int i=0;i<10;i++)
    {
        Rx += sq(a_x[i] - Sx);
        Ry += sq(a_y[i] - Sy);
        Rz += sq(a_z[i] - Sz);

    }

    Rx = Rx / 9;                              //得到方差
    Ry = Ry / 9;                        
    Rz = Rz / 9;

    Px = Px + 0.0025;                         // 0.0025在下面有说明...
    Kx = Px / (Px + Rx);                      //计算卡尔曼增益
    agx = agx + Kx * (aax - agx);             //陀螺仪角度与加速度计速度叠加
    Px = (1 - Kx) * Px;                       //更新p值

    Py = Py + 0.0025;
    Ky = Py / (Py + Ry);
    agy = agy + Ky * (aay - agy); 
    Py = (1 - Ky) * Py;

    Pz = Pz + 0.0025;
    Kz = Pz / (Pz + Rz);
    agz = agz + Kz * (aaz - agz); 
    Pz = (1 - Kz) * Pz;

    /* kalman end */

    Serial.print(agx);Serial.print(",");
    Serial.print(agy);Serial.print(",");
    Serial.print(agz);Serial.println();

}