MPU9250/MPU6050与运动数据处理与卡尔曼滤波(1)
第一篇——概述和MPU6050及其自带的DMP输出四元数
概述
InvenSense(国内一般译为应美盛)公司产的数字运动传感器在国内非常流行,我用过它的两款,9250和6050。出于被国产芯片惯坏的习惯,我自然而然地认为其封装引脚和寄存器都是兼容的,所以这成功地让我打废两次板,这两款芯片的封装并不是一样的,MPU9250的要小很多,而两者都引脚也不一样,虽然他们都是24pin的,可能是出于MPU9250多一个地磁传感器,AK8963。
所以两者的差异点主要在于:
1,封装(塑体大小);
2,管脚功能;
3,MPU9250较MPU6050多一个三轴地磁传感器AK8963;
4,部分寄存器(待补充);
MPU6050是款加速度和角速度传感器,有人也将因为其角速度传感器的功能将其称为陀螺仪,我其实并不能理解,我觉得能直接输出姿态数据比如欧拉角或者四元数的传感器才是陀螺仪。多年以前我刚进入大学时听过一个东大微电子学院教授的讲座,讲的是MEMS技术,我只记得中间陈提出了一个MEMS能否用来制造晶振的问题,让我记住了这个大二的。后来也有同事说过MEMS技术中封装也是很重要的,我一想也有道理,能把AK8963封装进去肯定不简单啊。MPU6050能测三轴加速度和三轴加速度,加速度的量程为±2/4/6/8/16g,角速度量程为±250/500/1000/2000角度每秒,16位ADC,输出速率好像能达到几千赫兹,当然6050只支持IIC,时钟最快400KHz。3.3V供电,几个毫安的功耗。带一个IIC主机接口,用来外挂其他的IIC传感器比如GNSS或者地磁传感器。
MPU6050的DMP
一般运动传感器都是要靠处理器跑算法来进行角度融合以得到最终能直接使用的表示当前自身姿态的欧拉角或者四元数的。我之前用的是卡尔曼滤波。要自己写代码大家自然会觉得多个流程,当然有时也会觉得自己算的才靠谱,其实也是,靠6050自带的DMP算的并不比单片机算的准,而且DMP算得慢,有时是不够用的。但单片机根据原始的加速度和角速度算四元数逃不过要写数字信号处理算法要用到大量的乘法或者FPU,有时并不能算过来,所以使用DMP算出来的数据也未尝不可。DMP,数字运动处理器,MPU6050/9250内部的一个组成部分,可以用来直接向外部吐四元数。
DMP的使用我并不想去深究,设备并不重要,数据才重要。应美盛针对6050提供了一套代码和文档,加一个跑在MSP430上的例程。例程资源链接如下:
官方提供的代码需要实现其中的延时,IIC序列读和序列写函数。移植起来对于各位来说,只要1,C语言合格,2,对IDE使用熟练,3了解IIC时序,那么应该是非常简单的。
IIC驱动代码
而我使用的是STM32F103C8T6和CH32V103C8T6,后者是RISC-V核的前者兼容MCU。我遇到的三个问题中,IIC的时序确实搞了我一下,写软件的IIC没啥,问题有二,1,速度上到100K后就提不上去了,2,收发会被中断打断,这点很头疼。写单片机程序最头疼的是你要时刻提醒自己你的业务逻辑流程可能在任意时刻被中断打断。所以我还是使用硬件的IIC,32的IIC设计得没必要得复杂,我需要推一个数据到数据寄存器里,然后等相应的标志位被置位,事实上这些标志位总是莫名其妙地等不到被置位,但是CPU刻意去读时它又是置位的,就很神奇。我是按照参考手册的精神去写的,但是这个也未必能每次都不出问题,我实测在72MHz主频,400KHz波特率下还是不怎么会报异常的,如果还有异常你就干脆用延时代替读标志位吧。
这里放下初始化代码:
void IIC_Init( u32 bound, u16 address )
{
GPIO_InitTypeDef GPIO_InitStructure;
I2C_InitTypeDef I2C_InitTSturcture; RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE );
GPIO_PinRemapConfig(GPIO_Remap_I2C1, ENABLE);
RCC_APB1PeriphClockCmd( RCC_APB1Periph_I2C1, ENABLE ); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;/* 注意硬件IIC和模拟IIC的管脚配置时不一致的 */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( GPIOB, &GPIO_InitStructure ); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( GPIOB, &GPIO_InitStructure ); I2C_InitTSturcture.I2C_ClockSpeed = bound;
I2C_InitTSturcture.I2C_Mode = I2C_Mode_I2C;
I2C_InitTSturcture.I2C_DutyCycle = I2C_DutyCycle_16_9;
I2C_InitTSturcture.I2C_OwnAddress1 = address;
I2C_InitTSturcture.I2C_Ack = I2C_Ack_Enable;
I2C_InitTSturcture.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_Init( I2C1, &I2C_InitTSturcture ); I2C_Cmd( I2C1, ENABLE ); I2C_AcknowledgeConfig( I2C1, ENABLE ); }
序列读取:
fn_status IIC_receive_byte(uint8_t devi_addr, uint8_t reg_addr, uint16_t read_length, uint8_t *pbuf)
{
uint16_t timeout_cnt = 0;
uint16_t len = read_length;
volatile uint16_t cnt = 0; // printf("IIC_receive_byte.%d bytes.\n",len);
/* 第一步,等待总线空闲。100毫秒内未等待到其空闲即报错:超时 */
while( I2C_GetFlagStatus( I2C1, I2C_FLAG_BUSY ) != RESET )
{
jiance();
} /* 第二步,发送开始标志。 */
I2C_GenerateSTART( I2C1, ENABLE );/* 发送一个起始信号 */
while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_MODE_SELECT ) )
{
jiance();
}
timeout_cnt = 0;
/* 第三步,发送7位设备地址,写地址。 */
devi_addr=devi_addr<<1;
I2C_Send7bitAddress( I2C1, devi_addr, I2C_Direction_Transmitter ); while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED ) )
{
jiance();
}
timeout_cnt = 0;
/* 第四位,发送八位寄存器地址 */
I2C_SendData( I2C1, reg_addr );/* 发送寄存器地址 */
while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED ))/* 等待上字节发送完毕 */
{
jiance();
}
timeout_cnt = 0;
/* 第六步,重送开始标志。 */
I2C_GenerateSTART( I2C1, ENABLE );/* 发送一个起始信号 */
while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_MODE_SELECT ) )
{
jiance();
}
timeout_cnt = 0;
/* 第七步,发送7位设备地址,读地址。 */
I2C_Send7bitAddress( I2C1, devi_addr, I2C_Direction_Receiver );
while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED ) )
{
// printf("ice I2C_CheckEvent.\n.\n");
jiance();
}
timeout_cnt = 0;
/* 第八步,读取数据 */
if(len==1)
{
I2C_AcknowledgeConfig( I2C1, DISABLE );
while( I2C_GetFlagStatus( I2C1, I2C_FLAG_RXNE ) == RESET )
{
// printf("I2C_GetFlagStatus.cnt:%d\n",cnt);
jiance();
}
pbuf[cnt] = I2C_ReceiveData( I2C1 );
}
else
for(cnt=0;cnt < len;cnt++)
{
while( I2C_GetFlagStatus( I2C1, I2C_FLAG_RXNE ) == RESET )
{
// printf("I2C_GetFlagStatus.cnt:%d\n",cnt);
delay_us(2);
// jiance();
}
pbuf[cnt] = I2C_ReceiveData( I2C1 );
// printf("%d:%02x\n",cnt,pbuf[cnt]);
if( cnt==( len-2) )
I2C_AcknowledgeConfig( I2C1, DISABLE );
}
/* 第九步,发送结束标志 */
// printf("I2C_GenerateSTOP.\n");
I2C_GenerateSTOP( I2C1, ENABLE );
I2C_AcknowledgeConfig( I2C1, ENABLE );
return 0;
}
序列写入:
fn_status IIC_send_byte(uint8_t devi_addr, uint8_t reg_addr, uint16_t send_length, uint8_t *pbuf)
{
volatile uint16_t timeout_cnt = 0;
uint16_t len = send_length;
uint16_t i = 0; while( I2C_GetFlagStatus( I2C1, I2C_FLAG_BUSY ) != RESET ) jiance();
I2C_GenerateSTART( I2C1, ENABLE );/* 发送一个起始信号 */
while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_MODE_SELECT ) ) jiance();
devi_addr=devi_addr<<1;
I2C_Send7bitAddress( I2C1, devi_addr, I2C_Direction_Transmitter ); //发送地址1 byte
while( (I2C1->STAR1 != 0x0082)||(I2C1->STAR2!=0x0007) ) jiance();
I2C_SendData( I2C1, (u8)(reg_addr&0x00FF) );
while( (I2C1->STAR1!=0x0084)||(I2C1->STAR2!=0x0007) ) jiance();//发送寄存器地址 1 byte
/* 发送数据 */
for(i=0;i<len;i++)
{
I2C_SendData( I2C1, pbuf[i]);
delay_us(10);
while( (I2C1->STAR1!=0x0084)||(I2C1->STAR2!=0x0007) ) jiance2();
}
I2C_GenerateSTOP( I2C1, ENABLE );
return 0;
}
里面的jiance()是一个调试用的函数,用来判断等待当前标志位花了多久,如果超过阈值就记录当前的情况并开始下一次的读写。
#define yuzhi 15/* 实测在72M主频下可用 */
#if 1
#define jiance() {\
delay_us(10);\
timeout_cnt++;\
if(timeout_cnt >= yuzhi)\
{\
timeout_cnt=0;\
printf("timeout at line:%d\n",__LINE__);\
printf("STAR1:%04x,STAR2:%04x.\n",I2C1->STAR1,I2C1->STAR2);\
break;\
}\
}
#else
#define jiance()
#endif #define jiance2() {\
delay_us(10);\
timeout_cnt++;\
if(timeout_cnt >= yuzhi)\
{\
timeout_cnt=0;\
break;\
}\
}
实物操作:
原理图直接照抄的官方的。注意电容容值。32侧我随意找了个IIC口。
PCB图没啥好放的,简单地连线。
这里放个CH32V103C8T6的工程,IDE是MRS。读取四元数然后由单片机转成欧拉角。晶振用8MHz的。链接如下。
https://share.weiyun.com/zDZ5EC0P
输出的打印就像这样。
MRS的注意点:
MRS很明显能看出有eclipse的影子,我用了下觉得继承了eclipse的友好的界面,不过没有MDK成熟,使用它还是需要对编译过程或者原理要有些许的了解的。这里专门记录下MRS的一些可能会对你产生困惑的点。
1,无法直接使用math.h,需要在库链接里加上一个m,否则编译器会报缺文件。
2,使用浮点数打印(printf("%f"))时,需要勾选使用定制的库。否则打印不出东西。
以上两条针对1.42版本的MRS。
MPU9250/MPU6050与运动数据处理与卡尔曼滤波(1)的更多相关文章
- 【转】MPU6050的数据获取、分析与处理
摘要 MPU6050是一种非常流行的空间运动传感器芯片,可以获取器件当前的三个加速度分量和三个旋转角速度.由于其体积小巧,功能强大,精度较高,不仅被广泛应用于工业,同时也是航模爱好者的神器,被安装在各 ...
- Arduino教程:MPU6050的数据获取、分析与处理
Arduino教程:MPU6050的数据获取.分析与处理 转载 摘要 MPU6050是一种非常流行的空间运动传感器芯片,可以获取器件当前的三个加速度分量和三个旋转角速度.由于其体积小巧,功能强大,精度 ...
- [体感游戏] 1、MPU6050数据采集传输与可视化
最近在研究体感游戏,到目前为止实现了基于51单片机的MPU6050数据采集.利用蓝牙模块将数据传输到上位机,并利用C#自制串口数据高速采集软件,并且将数据通过自制的折线图绘制模块可视化地展示出来等功能 ...
- SpotMini末端控制策略
相信很多人都注意到了“机器人学家”两天前推送的Boston Dynamics新机器人Spot-Mini.除了一如既往独领风骚的步态控制外,这次BD还给机器人增加了一个机械臂.视频中的一幕挺有趣,就是S ...
- IOS 特定于设备的开发:Core Motion基础
Core Motion框架集中了运动数据处理.该框架是在IOS 4 SDK中引入的,用于取代accelerometer加速计访问.它提供了对3个关键的机载传感器的集中式监测.这些传感器有陀螺仪.磁力计 ...
- MPU6050滤波、姿态融合(一阶互补、卡尔曼)
前几天做了6050原始数据的串口输出和上位机波形的查看.这篇博客我们来看一下对原始数据的处理. 任务:利用STC89C52RC对MPU6050原始数据进行滤波与姿态融合. 首先我摘抄了一段别人在昨晚这 ...
- MPU9250调试
MPU9250 芯片概述 MPU9250芯片是一个9轴姿态传感芯片,其中包含了3轴加速度传感器.3轴角速度传感器以及三轴磁力计. 其本质上是MPU6050芯片+AK8963. 可以获取传感芯片的加速度 ...
- STM32驱动MPU6050
轴 MEMS轴 MEMS 加速度计,以及一个可扩展的数字运动处理器 DMP(Digital Motion Processor),可用 I2C 接口连接一个第三方的数字传感器,比如磁力计.扩展之后就可以 ...
- 树莓派 连接 JY901(MPU9250) python 代码
先说BUG,最近要做项目需要树莓派和陀螺仪,资金充足的话肯定是买一个硬件卡尔曼滤波的传感器类似JY901模块,资金不足的就买MPU6050. 网上关于MPU6050在树莓派上的代码还能用,关于JY90 ...
随机推荐
- 基于Python实现二分查找法实战
二分查找法实战 def binary_search(datasets, find_in): mid = int(len(datasets)/2) if(mid>0): if(find_in> ...
- Net6 Configuration & Options 源码分析 Part2 Options
Net6 Configuration & Options 源码分析 Part2 Options 第二部分主要记录Options 模型 OptionsConfigurationServiceCo ...
- contos配置国内yum源
contos配置国内yum源 前言 rpm管理软件包的命令,很难用,需要手动解决以来关系,所以最好用 yum 的理念是使用一个中心仓库(repository)管理一部分甚至一个distribution ...
- SpringCloudAlibaba 微服务讲解(二)微服务环境搭建
微服务环境搭建 我们这次是使用的电商项目的商品.订单.用户为案例进行讲解 2.1 案例准备 2.1.1 技术选型 maven :3.3.9 数据库:mysql 持久层:SpringData JPA S ...
- fastcgi未授权访问及任意命令执行
1. 漏洞原理 服务端使用fastcgi协议并对外网开放9000端口,攻击者可以构造fastcgi协议包内容,实现未授权访问服务端.php文件以及执行任意命令. 2. 漏洞利用 第一步 搭建vulhu ...
- FPGA设计流程
今天学习了FPGA设计流程的视频,我理解要做一个完整的FPGA系统,所要经历的步骤,先将它简单总结如下: 我在对上面的流程图进行解释: 第一:设计定义就是我们这个FPGA系统或者FPGA设计所要实现的 ...
- FPGA驱动LCD显示红绿蓝彩条
实验目的:先简单熟悉LCD灯的驱动和时序图的代码实现.设计功能是让LCD显示红绿蓝三种颜色,即三个彩带.本次实验比较容易实现,主要是对LCD驱动时序图的理解和时序参数的配置. 实验条件:1.LCD原理 ...
- Java 将CSV转为Excel
CSV(Comma Separated Values)文件是一种纯文本文件,包含用逗号分隔的数据,常用于将数据从一个应用程序导入或导出到另一个应用程序.通过将CSV文件转为EXCEL,可执行更多关于数 ...
- class文件和java文件区别
- Java如何声明一个数组?JS如何声明一个数组?如何获取数组长度
1 Long[] numbers; //一般使用的定义方式,可分为静态和动态两种定义方式,下有说明. 2 Long numbers[]; //跟上面用法一致. 3 Long... numbers; / ...