第一篇——概述和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)的更多相关文章

  1. 【转】MPU6050的数据获取、分析与处理

    摘要 MPU6050是一种非常流行的空间运动传感器芯片,可以获取器件当前的三个加速度分量和三个旋转角速度.由于其体积小巧,功能强大,精度较高,不仅被广泛应用于工业,同时也是航模爱好者的神器,被安装在各 ...

  2. Arduino教程:MPU6050的数据获取、分析与处理

    Arduino教程:MPU6050的数据获取.分析与处理 转载 摘要 MPU6050是一种非常流行的空间运动传感器芯片,可以获取器件当前的三个加速度分量和三个旋转角速度.由于其体积小巧,功能强大,精度 ...

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

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

  4. SpotMini末端控制策略

    相信很多人都注意到了“机器人学家”两天前推送的Boston Dynamics新机器人Spot-Mini.除了一如既往独领风骚的步态控制外,这次BD还给机器人增加了一个机械臂.视频中的一幕挺有趣,就是S ...

  5. IOS 特定于设备的开发:Core Motion基础

    Core Motion框架集中了运动数据处理.该框架是在IOS 4 SDK中引入的,用于取代accelerometer加速计访问.它提供了对3个关键的机载传感器的集中式监测.这些传感器有陀螺仪.磁力计 ...

  6. MPU6050滤波、姿态融合(一阶互补、卡尔曼)

    前几天做了6050原始数据的串口输出和上位机波形的查看.这篇博客我们来看一下对原始数据的处理. 任务:利用STC89C52RC对MPU6050原始数据进行滤波与姿态融合. 首先我摘抄了一段别人在昨晚这 ...

  7. MPU9250调试

    MPU9250 芯片概述 MPU9250芯片是一个9轴姿态传感芯片,其中包含了3轴加速度传感器.3轴角速度传感器以及三轴磁力计. 其本质上是MPU6050芯片+AK8963. 可以获取传感芯片的加速度 ...

  8. STM32驱动MPU6050

    轴 MEMS轴 MEMS 加速度计,以及一个可扩展的数字运动处理器 DMP(Digital Motion Processor),可用 I2C 接口连接一个第三方的数字传感器,比如磁力计.扩展之后就可以 ...

  9. 树莓派 连接 JY901(MPU9250) python 代码

    先说BUG,最近要做项目需要树莓派和陀螺仪,资金充足的话肯定是买一个硬件卡尔曼滤波的传感器类似JY901模块,资金不足的就买MPU6050. 网上关于MPU6050在树莓派上的代码还能用,关于JY90 ...

随机推荐

  1. redis规范(实用)

    redis功能强大,数据类型丰富,再快的系统,也经不住疯狂的滥用.通过禁用部分高风险功能,并挂上开发的枷锁,业务更能够以简洁.通用的思想去考虑问题,而不是绑定在某种实现上. Redis 根据不同的用途 ...

  2. 面试官:Zookeeper是什么,它有什么特性与使用场景?

    哈喽!大家好,我是小奇,一位不靠谱的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 作为一名Java程序员,Zook ...

  3. sum 函数语法与应用

    一.sum 函数语法: SELECT SUM(expression )   FROM tables    WHERE predicates; expression 常量.列或函数,或者是算术.按位与字 ...

  4. 记录Markdown的学习

    目录 1. 引言 2. 标题 这是一级标题 这是二级标题 这是三级标题 这是四级标题 3. 文字相关 3.1 粗体 3.2 斜体 3.3 粗体和斜体 3.4 删除线 3.5 混合使用 3.6 反引号引 ...

  5. error LNK2019: 无法解析的外部符号 _WinMain@16,该符号在函数。。。使用

    一,问题描述 MSVCRTD.lib(crtexew.obj) : error LNK2019: 无法解析的外部符号 _WinMain@16,该符号在函数 ___tmainCRTStartup 中被引 ...

  6. MVC与MVVM?

    model-数据层 view-视图层 controller-控制层 MVC的目的是实现M和V的分离,单向通信,必须通过C来承上启下 MVVM中通过VM(vue中的实例化对象)的发布者-订阅者模式实现双 ...

  7. Kafka学习(二)

    作者:程序员cxuan链接:https://www.zhihu.com/question/53331259/answer/1262483551来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非 ...

  8. IDEA 配置Tomcat乱码解决方法

    问题:页面没有乱码,但是通过http请求的js文件会乱码,原因是由于 CharacterEncodingFilter 只会处理Servlet请求,不会处理静态文件的响应编码,所以这里需要进一步的配置. ...

  9. Mybatis的XML文件调用静态方法

    如果需要在Mapper文件中调用静态方法,需要 <choose> // 需要静态方法返回true还是false <when test="@staticClass@stati ...

  10. 简述 synchronized 和 java.util.concurrent.locks.Lock 的异同?

    Lock 是 Java 5 以后引入的新的 API,和关键字 synchronized 相比主要相同点: Lock 能完成 synchronized 所实现的所有功能:主要不同点:Lock 有比 sy ...