前几天做了6050原始数据的串口输出和上位机波形的查看。这篇博客我们来看一下对原始数据的处理。

任务:利用STC89C52RC对MPU6050原始数据进行滤波与姿态融合。

首先我摘抄了一段别人在昨晚这个实验的写的最后总结。
1.尽量不要用MPU6050内置的LPF滤波。虽然相比于原始加速度计输出,该LPF可以平滑输出,但是在FFT频谱上的表现相当差劲。
2.广泛使用的窗口平均滑动滤波无论在FFT还是RMSE表现上都有不错的表现,所以一般基础应用(低速运动或四轴初学者)采用窗口平均滤波是比较明智的选择。
3.想要达到更好的滤波效果,FIR或者IIR滤波器是更好的选择。笔者测试2阶30Hz的Butterworth滤波器虽然在平滑性RMSE只比窗口平均滑动差了一点(但是比LPF要好),但是数据实时性性能指标上比前者响应速度提高了近一倍。因此在制作四轴的进阶阶段,可以考虑将窗口平均滑动换成Butterworth滤波器。

开发日记,不想看的可以跳过,仅仅是为了记录自己的过程。
//----------------------------------------------------------------------------------------------------------------------------------------
2017.12.5 晚
刚刚写完6050的零偏矫正和滑动窗口滤波,就已经超过51单片机内部最大RAM允许范围了。具体表现为:
USERS\MPU6050.C(241): error C249: 'DATA': SEGMENT TOO LARGE
定义的数据段大于单片机内部RAM,

查了资料,原来这段程序并没有把stc89c52内部的512字节的RAM全部用完!而是用完了开头的128字节的数据(0X00-0X7F)后面还有128*3的字节量没有被使用。

另外,当我们程序中总变量的字节数超过128字节时,必须对程序中所有变量进行初始化,否则没有被初始化的变量默认值不在是0,而是不确定的随机数。这时我们必须在keil中设置存储器的存储模式。但是当你的变量总字节在128字节以内的话,并且存储模式为small模式时,没有初始化的变量的默认值才是0.

1.Small模式
所有缺省变量参数均装入内部 128字节 RAM,优点是访问速度快,缺点是空间有限,只适用于小程序。
2. Compact 模式
所有缺省变量均位于外部RAM区的一页(256Byte)。和small模式中使用pdata定义变量的效果相同。如:uchar pdata a[100]; 该模式下,总变量不得超过256字节。
3. Large 模式
所有缺省变量可放在多达 64KB 的外部RAM 区,优点是空间大,可存变量多,缺点是速度较慢。在large模式下,和在small模式中使用关键字xdata 定义变量的效果一样。如:char xdata a[100];这样显式的规定了变量的存放区域,以定义为准,不受存储模式的影响。在此模式下编写程序时,定义的变量不要超过单片机规定的内部最大RAM数,因为即使超过了,编译器也不会报错,但是程序运行一定出错。

注意: 为了程序安全,当程序中定义超过128字节的数据时,最好申明为xdata
型,为其在扩展RAM中分配内存。

2017.12.6 下午
一般我们最好配置为small模式,然后必要的情况下,使用xdata对变量进行说明。

/************
注意看编译后的这个,他们的单位时字节B。而1k = 1024B
Program Size: data=16.1 xdata=247 code=3424
/************

由于在6050数据处理函数中多次使用静态局部变量,所以加深一下对他的理解。
对静态局部变量的说明:
(1) 静态局部变量在静态存储区内分配存储单元。在程序整个运行期间都不释放。而自动变量(即动态局部变量)属于动态存储类别,存储在动态存储区空间(而不是静态存储区空间),函数调用结束后即释放。
(2) 为静态局部变量赋初值是在编译时进行值的,即只赋初值一次,在程序运行时它已有初值。以后每次调用函数时不再重新赋初值而只是保留上次函数调用结束时的值。而为自动变量赋初值,不是在编译时进行的,而是在函数调用时进行,每调用一次函数重新给一次初值,相当于执行一次赋值语句。
(3) 如果在定义局部变量时不赋初值的话,对静态局部变量来说,编译时自动赋初值0(对数值型变量)或空字符(对字符型变量)。而对自动变量来说,如果不赋初值,则它的值是一个不确定的值。这是由于每次函数调用结束后存储单元已释放,下次调用时又重新另分配存储单元,而所分配的单元中的值是不确定的。
(4) 虽然静态局部变量在函数调用结束后仍然存在,但其他函数是不能引用它的,也就是说,在其他函数中它是“不可见”的。

不管怎么修改量程,输出的波形震动范围总是在+-20000波动,但是当我把加速度计的量程设置为+-16G的时候,理论上其LSB应该为2048左右,但是发现输出的波形也是震动范围很大。而且陀螺仪的数据比一开始不处理的时候震动更加明显。

内存覆盖:
以覆盖的(某个函数的局部变量空间在退出该函数是就释放,由别的函数的局部变量覆盖),可以提高内存利用率。当然静态局部变量除外,其内存使用方式与全局变量相同;

2017.12.7 晚
今天发现我的程序确实存在问题,就是运算时的数据溢出。在进行零偏数据的计算时,需要将200个原始数据相加,但是一开始我的总和定义的还是一个int型的数据,这时200个数据的和可能已经超过了int型数据的取值范围,所以产生了溢出。导致数据的不准确。那么后面计算的数据也是不准确的。所以我晃动传感器曲线都没有什么变化。唉,学单片机都得过数据类型、内存管理这道坎啊。

2017.12.11 下午
如果全是自己开发的话,没有人指导的情况下,每一个小小的进步,都是非常不容易的!!

2017.12.12 晚
目前的问题就是静止状态下陀螺仪输出的数据不为零。还有就是由于单片机运算能力的限制,输出的波形延迟较大。
//---------------------------------------------------------------------------------------------------------------
经过大概一个星期的尝试,终于用51单片机对6050进行了正确的读取与姿态融合。其实现在想想自己这几天所做的所有事情,并不多。程序前两天就写完了,后面的大部分时间都是在对程序进行调整,对波形进行分析。从刚开始凌乱的波形,到后面使用一阶互补滤波和卡尔曼滤波得出一个姿态角的流畅波形。这中间的苦恼和困难只有自己知道。也行对于一个老手来说,我这几天所做的所有事情,他能在一上午或者几个小时内完成,但是这几天所有的思考与疑惑都对我有很大的帮助。慢慢的养成了分析思考问题的方法。遇到问题不要慌张,一点点的去分析。不要放过任何一个细节。其实我们常说,一个经验丰富的老手能很快的解决一个棘手的问题。他们靠的是什么?所有人都知道是经验。但是经验又是什么?说的通俗一点,经验就是他们所有踩过的坑,进行过的所有思考。
想说一个我在做这个实验小小的插曲,有一天的时间,波形出来的都是方波,稍微动一下就满量程,波形很难看。想了很久,检查了不知多少遍代码,逻辑没问题啊,也没有报错,怎么波形就不对呢?突然一瞬间,发现了一点小问题。一个计数变量,定义了一个unsigned char i;来进行零偏数据的计数,每500个数据进行一次平均,刚开始愣是没发现不对。unsigned char 变量的最大计数是256!!!
一个计数变量 ,平时根本就不会去注意他的,但是关键时候就是他搅的局。改了这个错误之后,波形看起来好了很多。

首先在上次博客的基础上,先进行一次滑动平均滤波,滤波窗口大致为6个数据。

/*
**2017.12.5
**对6050的原始数据进行处理,滤波,角度转换。
**滑动平均滤波
*/ void mpu6050_data_process(void)
{ unsigned char i = , j = ;
static unsigned char acc_filter_cnt = ;//滤波次数计数
static unsigned char gyro_filter_cnt = ;
long int xdata acc_temp[] = {};//三轴加速度,注意,这个不能定义为静态
long int xdata gyro_temp[] = {};
static int acc_x_buf[ACC_FILTER_NUM] = {}, //滑动窗口缓存,每调用一次,里面数据多一个。
acc_y_buf[ACC_FILTER_NUM] = {},
acc_z_buf[ACC_FILTER_NUM] = {}; static int gyro_x_buf[GYRO_FILTER_NUM] = {}, //滑动窗口缓存,每调用一次,里面数据多一个。
gyro_y_buf[GYRO_FILTER_NUM] = {},
gyro_z_buf[GYRO_FILTER_NUM] = {}; float xdata init_ax = ,init_ay = ,init_az = ;
float xdata acc_angle_x = ,gyro_angle_x = ; //一阶互补的参数传递 mpu6050_read_acc(); //获取原始数据
mpu6050_read_gyro(); init_ax = (float)mpu_data.acc_data.x / ACC_G; //计算单位为G的各轴重力加速度分量
init_ay = (float)mpu_data.acc_data.y / ACC_G;
init_az = (float)mpu_data.acc_data.z / ACC_G; acc_angle_x = atan(init_ax/init_az) * / PI ; //加速度计x轴角度
gyro_angle_x = -(float)mpu_data.gyro_data.y / 7510.0; //陀螺仪计算x轴角度,7150?? mpu_data.acc_angle.x = (int)one_filter(acc_angle_x,gyro_angle_x); //一阶互补滤波,获取偏x轴角度
mpu_data.acc_angle.y = (int)kalman_filter(acc_angle_x,gyro_angle_x); //获取卡尔曼滤波 //加速度计数据处理
//-----------------------------------------------------
acc_x_buf[acc_filter_cnt] = mpu_data.acc_data.x;//平均滤波 ,更新数据,一开始这里写成了加,数据一直不对
acc_y_buf[acc_filter_cnt] = mpu_data.acc_data.y;
acc_z_buf[acc_filter_cnt] = mpu_data.acc_data.z; for(i=;i<ACC_FILTER_NUM;i++) //滑动滤波,取平均值
{
acc_temp[]+= acc_x_buf[i];
acc_temp[]+= acc_y_buf[i];
acc_temp[]+= acc_z_buf[i];
} //在此进行 六面校准
//---------------------------
mpu_data.acc_filter.x = (acc_temp[] / ACC_FILTER_NUM); //得出沿x轴方向的重力加速度, 单位G
mpu_data.acc_filter.y = (acc_temp[] / ACC_FILTER_NUM);
mpu_data.acc_filter.z = (acc_temp[] / ACC_FILTER_NUM);
//-------------------------- acc_filter_cnt++;
if(acc_filter_cnt == ACC_FILTER_NUM)
{
acc_filter_cnt = ;
} //陀螺仪数据处理
//-----------------------------------------------------------
gyro_x_buf[gyro_filter_cnt] = mpu_data.gyro_data.x;
gyro_y_buf[gyro_filter_cnt] = mpu_data.gyro_data.y;
gyro_z_buf[gyro_filter_cnt] = mpu_data.gyro_data.z; for(j=;j<GYRO_FILTER_NUM;j++) //滑动滤波,取平均值
{
gyro_temp[]+= gyro_x_buf[j];
gyro_temp[]+= gyro_y_buf[j];
gyro_temp[]+= gyro_z_buf[j];
} //在此进行 六面校准
//-----------------------------
mpu_data.gyro_filter.x = gyro_temp[]/GYRO_FILTER_NUM;
mpu_data.gyro_filter.y = gyro_temp[]/GYRO_FILTER_NUM;
mpu_data.gyro_filter.z = gyro_temp[]/GYRO_FILTER_NUM;
//-------------------------- gyro_filter_cnt++;
if(gyro_filter_cnt == GYRO_FILTER_NUM)
{
gyro_filter_cnt = ;
} //化为度制
// mpu_data.gyro_dps.x = (float)mpu_data.gyro_filter.x * GYRO_A;
// mpu_data.gyro_dps.y = (float)mpu_data.gyro_filter.y * GYRO_A;
// mpu_data.gyro_dps.z = (float)mpu_data.gyro_filter.z * GYRO_A;
//
// //化为弧度制
// mpu_data.gyro_rad.x = (float)mpu_data.gyro_filter.x * GYRO_R;
// mpu_data.gyro_rad.y = (float)mpu_data.gyro_filter.y * GYRO_R;
// mpu_data.gyro_rad.z = (float)mpu_data.gyro_filter.z * GYRO_R; }

数据波形为:

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

然后就可以利用这个加速度计和陀螺仪的数据进行姿态融合。姿态融合一方面对数据进行了滤波,一方面计算出了角度值。所以也可以利用原始数据进行姿态融合。因为我使用的是51系列的单片机,运算速率很慢,在此我直接用原始数据除精度后进行一阶互补滤波,得出了x轴的偏移角度。然后再用卡尔曼滤波对姿态进行融合,对比两种融合方式的波形。

loat xdata one_filter_angle = ;
float xdata kalman_filter_angle = , kalman_filter_angle_dot = ; //----------------
//在进行滤波之前,需要获取原始数据
float one_filter(float angle_m,float gyro_m)
{ float xdata K1 =0.1; // 对加速度计取值的权重
float xdata dt=0.005;//注意:dt的取值为滤波器采样时间 one_filter_angle = K1 * angle_m+ (-K1) * (one_filter_angle + gyro_m * dt);
return one_filter_angle;
} //------------------
float kalman_filter(float angle_m,float gyro_m)
{ //滤波参数
float xdata dt = 0.005; //卡尔曼采样时间
float xdata P[][] = {{,},{,}};
float xdata Pdot[] = {,,,};
float xdata Q_angle = 0.001;//角度数据置信度,陀螺仪协方差
float xdata Q_gyro = 0.005; //角速度数据置信度,陀螺仪飘移噪声协方差
float xdata R_angle = 0.5; //加速度计协方差
char xdata C_0 = ;
float xdata q_bias = ,angle_err = ; //q_bias为陀螺仪飘移
float xdata PCt_0 = ,PCt_1 = ,E = ;
float xdata K_0 = , K_1 = , t_0 = , t_1 = ; kalman_filter_angle+= (gyro_m - q_bias) * dt; //卡尔曼预测方程,认为每次飘移相同, Pdot[]=Q_angle - P[][] - P[][];
Pdot[]=- P[][];
Pdot[]=- P[][];
Pdot[]=Q_gyro; P[][] += Pdot[] * dt;
P[][] += Pdot[] * dt;
P[][] += Pdot[] * dt;
P[][] += Pdot[] * dt; PCt_0 = C_0 * P[][]; //矩阵乘法中间变量
PCt_1 = C_0 * P[][]; E = R_angle + C_0 * PCt_0; //分母 K_0 = PCt_0 / E; //增益值
K_1 = PCt_1 / E; angle_err = angle_m - kalman_filter_angle;
kalman_filter_angle += K_0 * angle_err; //对状态的卡尔曼估计,最优角度
q_bias += K_1 * angle_err;
kalman_filter_angle_dot = gyro_m-q_bias;//最优角速度 t_0 = PCt_0; //矩阵计算中间变量
t_1 = C_0 * P[][]; P[][] -= K_0 * t_0;
P[][] -= K_0 * t_1;
P[][] -= K_1 * t_0;
P[][] -= K_1 * t_1; return kalman_filter_angle;
}

融合之后的波形:

MPU6050滤波、姿态融合(一阶互补、卡尔曼)的更多相关文章

  1. 谈一谈 MPU6050 姿态融合(转)

    姿态角(Euler角)pitch yaw roll飞行器的姿态角并不是指哪个角度,是三个角度的统称.它们是:俯仰.滚转.偏航.你可以想象是飞机围绕XYZ三个轴分别转动形成的夹角. 地面坐标系(eart ...

  2. 第44章 MPU6050传感器—姿态检测—零死角玩转STM32-F429系列

    第44章     MPU6050传感器—姿态检测 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.co ...

  3. [stm32] MPU6050 HMC5883 Kalman 融合算法移植

    一.卡尔曼滤波九轴融合算法stm32尝试 1.Kalman滤波文件[.h已经封装为结构体] /* Copyright (C) 2012 Kristian Lauszus, TKJ Electronic ...

  4. [体感游戏] 1、MPU6050数据采集传输与可视化

    最近在研究体感游戏,到目前为止实现了基于51单片机的MPU6050数据采集.利用蓝牙模块将数据传输到上位机,并利用C#自制串口数据高速采集软件,并且将数据通过自制的折线图绘制模块可视化地展示出来等功能 ...

  5. [芯片][MPU6050] MPU60X0的DMP相关链接

    标题:发个自己做的UD分解+强跟踪卡尔曼滤波做的双轴姿态测量 链接:http://www.amobbs.com/thread-5511854-1-1.html 关键词:UD分解+强跟踪卡尔曼滤波,采用 ...

  6. VIO回顾:从滤波和优化的视角

    https://mp.weixin.qq.com/s/zpZERtWPKljWNAiASBLJxA 根据以上网页自己做的总结: 在机器人社区中,定位与构图问题属于状态估计问题.主流使用的工具可以对给定 ...

  7. 六轴加速度传感器MPU6050官方DMP库到瑞萨RL78/G13的移植

    2015年的电赛已经结束了.赛前接到器件清单的时候,看到带防护圈的多旋翼飞行器赫然在列,又给了一个瑞萨RL78/G13的MCU,于是自然联想到13年的电赛,觉得多半是拿RL78/G13做四旋翼的主控, ...

  8. 视觉和imu融合的算法研究

    融合方式概述 同SLAM发展过程类似,视觉融合IMU问题也可以分成基于滤波和基于优化两大类. 同时按照是否把图像特征信息加入状态向量来进行分类,可以分为松耦合和紧耦合两大类. 一.基于滤波的融合算法 ...

  9. 开源三轴云台EVVGC(simple BGC)分析

    一. 主程序分析 主程序结构清晰,流程如图所示,下面将对每个部分做详细分析 二. 系统初始化 系统初始化部分的流程如上图所示,下面对每部分做具体分析 1. 时钟初始化 该部分主要是使能DWT,用DWT ...

随机推荐

  1. 【转】微信公众开发URL和token填写详解

    很多人不明白微信公众号开发者中心服务器配置里面的url和token是什么,不会填写.看了教程也不理解是什么,本文详述一下这个问题. 第一步:作为一名微信公众号开发者,别人进入你的微信公众号,肯定会看见 ...

  2. symfony 命令

    1.创建Bundle php bin/console generate:bundle --namespace=Home/IndexBundle --format=yml  创建bundle会更新app ...

  3. python qq邮箱发送邮件

    使用qq发送邮件 # coding=utf8 """ qq邮箱发送邮件 """ import sys reload(sys) sys.set ...

  4. python使用pyqt写带界面工具

    上篇介绍的使用python自带tkinter包,来写带界面的工具. 此篇介绍使用pyqt来开发测试工具. tkinter的好处是python官方自带,上手容易(但手写控件复杂),布局和摆放都不直观和容 ...

  5. 如何在windows上测试iphone?

    本教程将会让你没有mac照样测试iphone,这是我折腾了几天总结下来的,希望对大家有用. 先来几张效果图吧 方法很简单,但是配置起来说实话有点麻烦,先在电脑上安装vmware,在安装osx系统,在安 ...

  6. spring + Mybatis + pageHelper + druid 整合源码分享

    springMvc + spring + Mybatis + pageHelper + druid 整合 spring 和druid整合,spring 整合druid spring 和Mybatis  ...

  7. TCP三次握手原则

    “已失效的连接请求报文段”的产生在这样一种情况下: client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server. 本来这是一 ...

  8. IDEA 单元测试 导入JUnit4到项目

    一.IDEA自带JUnit4的jar包,现在让我们来导入. Step 1. IDEA最上面一栏的菜单栏中,选File->Project Structure(从上往下第11个),弹出窗口左边有一个 ...

  9. css !important用法以及CSS样式使用优先级判断

    之前一直看到很多css中都有!important这个样式,一直不知道有什么作用的,今天在网上详细了解了一下,看了别人的博客,顺便转载收藏一下 css !important用法CSS样式使用优先级判断 ...

  10. 安装ahci驱动后出现A disk read error occurred如何解决

    注意:因为一般的ghost系统盘都没有ahci驱动,所以,如果用到固态硬盘且想更好的发挥固态硬盘的性能,需要安装ahci驱动,且在bios 中把硬盘模式设置成ahci(否则电脑会蓝屏或进不去系统) 步 ...