Arduino Uno使用MPU6050和卡尔曼滤波实现姿态解算教程
目录
实验效果
Arduin Uno 使用陀螺仪模块MPU6050运用卡尔曼滤波实现姿态解算。
本例程输出的是XYZ轴的角度,范围在正负90度之间。通过使用卡尔曼滤波算法来解算姿态,整体效果相对稳定,但可能会有一些偏移,在此基础上进一步改进。
以下是本例程XYZ轴的输出数据效果图:
(时间曲线展示了以下过程:静止姿态→摆动→恢复到原来的静止姿态→拍动桌子→再次静止)
元件介绍
MPU6050是一款集成了三轴加速度计和三轴陀螺仪的传感器芯片,广泛应用于运动检测、姿态控制等领域。以下是对MPU6050的一些简单介绍:
主要特性
特点
- 六轴传感器集成:
- 三轴加速度计
- 三轴陀螺仪
- 内置数字运动处理器(DMP):
- 支持复杂的姿态解算和运动检测
- 低功耗:
- 适用于电池供电的便携设备
- 高分辨率:
- 加速度计分辨率:16位
- 陀螺仪分辨率:16位
- 可编程中断功能:
- 支持运动检测、自由落体检测等多种中断源
- I2C通信接口:
- 支持标准模式(100kHz)和快速模式(400kHz)
- 内置温度传感器:
- 提供温度补偿功能
- 小尺寸封装:
- 封装尺寸为4x4x0.9mm,适合空间受限的应用
性能
- 加速度计性能:
- 量程:±2g、±4g、±8g、±16g
- 灵敏度:16384 LSB/g(±2g),8192 LSB/g(±4g),4096 LSB/g(±8g),2048 LSB/g(±16g)
- 零偏误差:±60mg(最大值)
- 陀螺仪性能:
- 量程:±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(最大值)
- 温度传感器性能:
- 测量范围:-40°C至+85°C
- 分辨率:±1°C
- 电源电压范围:
- 2.375V至3.46V(VDD)
- 1.8V至3.3V(VLOGIC)
- 工作温度范围:
- -40°C至+85°C
- 功耗:
- 加速度计:500µA(典型值)
- 陀螺仪:3.6mA(典型值)
应用领域
- 无人机:用于姿态控制和稳定。
- 机器人:用于运动检测和导航。
- 手机和平板:用于屏幕旋转和游戏控制。
- 可穿戴设备:用于运动监测和健康管理。
工作原理
- 加速度计:通过检测物体在X、Y、Z方向上的加速度,判断物体的运动状态和方向。
- 陀螺仪:通过检测物体在三个轴上的角速度,可以计算出物体的旋转角度和速度。
引脚说明
- VCC:
- 功能:电源输入引脚。
- 电压范围:通常为3.3V或5V(取决于模块的具体设计)。
- GND:
- 功能:电源地引脚。
- 连接:连接到电源负极。
- SCL:
- 功能:I2C时钟线(Serial Clock Line)。
- 连接:连接到主控器的I2C时钟引脚。
- SDA:
- 功能:I2C数据线(Serial Data Line)。
- 连接:连接到主控器的I2C数据引脚。
- XDA(可选):
- 功能:辅助I2C数据线(用于连接其他I2C设备)。
- 连接:通常不连接,除非需要扩展I2C设备。
- XCL(可选):
- 功能:辅助I2C时钟线(用于连接其他I2C设备)。
- 连接:通常不连接,除非需要扩展I2C设备。
- AD0:
- 功能:I2C地址选择引脚。
- 连接:接地(GND)时,I2C地址为0x68;连接到高电平(VCC)时,I2C地址为0x69。
- INT:
- 功能:中断引脚。
- 连接:连接到主控器的中断引脚,用于检测特定事件(如运动检测、自由落体等)。
BOM
名称 | 数量 |
---|---|
Arduino 开发板 | 1 |
mpu6050 陀螺仪模块 | 1 |
跳线 | 若干 |
面包板 | 1 |
接线图
Arduino UNO - MPU-6050
5V -> VCC
GND -> GND
A4 -> SDA
A5 -> SCL
安装库
安装i2cdevlib库
https://github.com/jrowberg/i2cdevlib
在这里说明一下原生的Wire库或使用i2cdevlib库的区别和优缺点:
原生的Wire库
优点:
- 标准库:Wire库是Arduino IDE中自带的库,无需额外安装。
- 简洁性:包含基本的I2C通信功能,简单明了,易于理解和使用。
- 兼容性:与大多数I2C设备兼容,适用于一般的I2C通信需求。
缺点:
- 功能相对简单:只提供了基本的I2C通信功能,高级控制和管理功能较少。
- 缺少错误处理:对通信错误的处理和报告功能有限。
i2cdevlib库
优点:
- 高级特性:提供了更丰富的功能,包括高级I2C协议功能和增强的错误处理。
- 设备支持广泛:包含了对多种I2C设备(如MPU6050等传感器)的支持,减少用户编写特定设备驱动的工作量。
- 文档及示例丰富:详细的文档和示例代码,帮助用户快速上手和解决问题。
缺点:
- 需要额外安装:用户需要手动下载和安装库,增加了初次使用的复杂性。
- 相对复杂:由于功能更丰富,代码和库的结构相对复杂,不适合初学者或简单需求的项目。
总结:
- Wire库适合初学者和简单的I2C通信项目,提供基本的I2C功能即可满足需求。
- i2cdevlib库适合需要高级功能和设备支持的项目,更适合开发复杂的I2C通信应用和处理多种I2C设备。
代码提点
- 引入库和初始化
Wire.h
和I2Cdev.h
:用于I2C通信。MPU6050.h
:用于MPU6050传感器的操作。
- 变量定义
- 原始数据变量(如
ax
,ay
,az
,gx
,gy
,gz
)。 - 角度变量(如
aax
,aay
,aaz
)。 - 偏移量(如
axo
,ayo
,azo
)。 - 其他辅助变量(如卡尔曼滤波相关变量)。
- 原始数据变量(如
- setup函数
- 初始化I2C通信和串口通信。
- 初始化MPU6050传感器。
- 采样一定次数以计算加速度计和陀螺仪的偏移量。
- loop函数
- 获取当前时间,计算时间差
dt
。 - 读取MPU6050的六轴原始数据。
- 计算加速度和角度。
- 对加速度进行滑动加权滤波。
- 计算角速度,并进行积分得到角度。
- 使用卡尔曼滤波算法对角度进行优化。
- 打印最终计算得到的角度值。
- 获取当前时间,计算时间差
关键步骤解释
- 获取原始数据并计算角度
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轴的夹角
- 滑动加权滤波
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°
- 计算角速度并积分
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轴角速度积分
- 卡尔曼滤波
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();
}