MPU6050 - 陀螺仪 - 技术总结

2023-05-16

博主福利:100G+电子设计学习资源包!

http://mp.weixin.qq.com/mp/homepage?__biz=MzU3OTczMzk5Mg==&hid=7&sn=ad5d5d0f15df84f4a92ebf72f88d4ee8&scene=18#wechat_redirect
--------------------------------------------------------------------------------------------------------------------------

 

基于51的MPU6050模块通信简介(入门级)

因为是入门级,就先最简单的介绍如何利用51从MPU6050中读取数据吧(对于想知道卡尔曼滤波、俯角仰角、距离测量、摔倒检测、记步等算法的可能要在接下来介绍)。既然要和MPU6050通信,那么必不可少的是阅读芯片手册,如果您觉得亲自去看又长又多而且都是英文的手册很费时,不仿看看我找的简要版:

MPU-60X0是全球首例9轴运动处理器。它集成了3轴MEMS陀螺仪,3轴MEMS加速计,以及1个可扩展的数字运动处理器DMP(Digital Motion Processor),可用I2C接口连接一个第三方的数字传感器,比如磁力计。扩展之后就可以通过其I2C或SPI接口输出一个9轴的信号。MPU-60X0也可以通过其I2C接口连接非惯性的数字传感器,比如压力传感器。

    

MPU-60X0对陀螺仪和加速计分别用了三个16位的ADC,将其测量的模拟量转化为可输出的数字量。为了精确跟踪快速和慢速运动,传感器的测量范围是可控的,陀螺仪可测范围为±250,±500,±1000,±2000°/秒(dps),加速计可测范围为±2,±4,±8,±16g(重力加速度)。

注:下图是采用串口助手将MPU6050采集的数据显示在上位机上,其中前三列输出为三维的加速度(这里的加速度包括地球本身的重力加速度),后三列为三维的角速度。

但是这里的输出值并不是真正的加速度和角速度的值,上面说过,MPU是一个16位AD量程可程控的设备,这里设置的加速度传感器的测量量程为正负2g(这里的g为重力加速度),陀螺仪的量程为正负2000°/s。所以要用下面的公式进行转化:

好了,有了上面的基础知识之后咱们就能尝试用51的I2C总线从MPU6050读取实时的3轴加速度和3轴角速度了。由于51本身不带有I2C总线通信协议,所以我们要自己实现一个I2C通信协议,下面是我从网上找的并稍加修改的一个I2C总线通信的代码:

复制代码

 1 #include <REG52.H>
 2 #include <INTRINS.H>
 3     
 4 typedef unsigned char  uchar;
 5 typedef unsigned short ushort;
 6 typedef unsigned int   uint;
 7 
 8 //-----------------------------------------
 9 // 定义MPU6050内部地址
10 //-----------------------------------------
11 #define    SMPLRT_DIV      0x19    //陀螺仪采样率,典型值:0x07(125Hz)
12 #define    CONFIG          0x1A    //低通滤波频率,典型值:0x06(5Hz)
13 #define    GYRO_CONFIG     0x1B    //陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
14 #define    ACCEL_CONFIG    0x1C    //加速计自检、测量范围及高通滤波频率,典型值:0x01(不自检,2G,5Hz)
15 #define    ACCEL_XOUT_H    0x3B
16 #define    ACCEL_XOUT_L    0x3C
17 #define    ACCEL_YOUT_H    0x3D
18 #define    ACCEL_YOUT_L    0x3E
19 #define    ACCEL_ZOUT_H    0x3F
20 #define    ACCEL_ZOUT_L    0x40
21 #define    TEMP_OUT_H      0x41
22 #define    TEMP_OUT_L      0x42
23 #define    GYRO_XOUT_H     0x43
24 #define    GYRO_XOUT_L     0x44    
25 #define    GYRO_YOUT_H     0x45
26 #define    GYRO_YOUT_L     0x46
27 #define    GYRO_ZOUT_H     0x47
28 #define    GYRO_ZOUT_L     0x48
29 #define    PWR_MGMT_1      0x6B    //电源管理,典型值:0x00(正常启用)
30 #define    WHO_AM_I        0x75    //IIC地址寄存器(默认数值0x68,只读)
31 #define    SlaveAddress    0xD0    //IIC写入时的地址字节数据,+1为读取
32 
33 //-----------------------------------------
34 // I2C总线通信函数
35 //-----------------------------------------
36 void  I2C_Start();                  //I2C起始信号
37 void  I2C_Stop();                   //I2C停止信号
38 void  I2C_SendACK(bit ack);         //I2C发送应答信号[入口参数:ack (0:ACK 1:NAK)]
39 bit   I2C_RecvACK();                //I2C接收应答信号
40 void  I2C_SendByte(uchar dat);      //向I2C总线发送一个字节数据
41 uchar I2C_RecvByte();               //从I2C总线接收一个字节数据
42 void  Single_WriteI2C(uchar REG_Address,uchar REG_data);//向I2C设备写入一个字节数据
43 uchar Single_ReadI2C(uchar REG_Address);                //从I2C设备读取一个字节数据
44 
45 //-----------------------------------------
46 // 通过I2C和MPU6050通信的函数
47 //-----------------------------------------
48 void InitMPU6050();                //初始化MPU6050
49 int GetData(uchar REG_Address);    //合成数据

复制代码

如果你没搞过硬件又从未听说过I2C,那么想想socket的握手再看看上面36~43行的有关ACK、Send、Write的函数大概能明白I2C的功能。当我们实现I2C的通信函数之后就可以与带有I2C通信接口的芯片进行通信,那么怎样通信呢?其实很简单——你可以把每个芯片比做为一个巨大的储物柜,储物柜里每个抽屉里存着相应的东西,你想让佣人帮你去拿个东西,只要告诉佣人对应的抽屉号就行了。这里I2C总线相当于这个佣人,每个抽屉相当于芯片中的寄存器,抽屉号相当于寄存器地址。当你想设置芯片的某些属性时是向对应的寄存器内写数据,当想从芯片内获取相关数据时,就要通过I2C向对应的地址写数据然后接收芯片返回的数据。这里的8~31行为MPU-6050芯片内几个常用的寄存器地址,前四个常用来作为设置芯片工作属性,15~28共14个寄存器地址用来获取传感器的3轴加速度、3轴角速度和温度的数据(这里每一种信息都包括H和L两位,是由于8位表示不完该数据,于是分高低两部分)

这样我们便不难理解InitMPU6050()和GetData(uchar REG_Address)函数:初始化函数是向相应的地址写初始化配置数据(关于0x00\0x07等意思请参看MPU6050寄存器版说明书),而GetData则是传入想获得数据项的低地址,然后连续读取当前地址数据和下一地址数据合成为想要的项目数据(上面讲了数据分高低部分)。

复制代码

 1 //-----------------------------------------
 2 //初始化MPU6050
 3 //-----------------------------------------
 4 void InitMPU6050()
 5 {
 6     Single_WriteI2C(PWR_MGMT_1, 0x00);    //解除休眠状态
 7     Single_WriteI2C(SMPLRT_DIV, 0x07);
 8     Single_WriteI2C(CONFIG, 0x06);
 9     Single_WriteI2C(GYRO_CONFIG, 0x18);
10     Single_WriteI2C(ACCEL_CONFIG, 0x01);
11 }
12 //-----------------------------------------
13 //合成数据
14 //-----------------------------------------
15 int GetData(uchar REG_Address)
16 {
17     uchar H,L;
18     H=Single_ReadI2C(REG_Address);
19     L=Single_ReadI2C(REG_Address+1);
20     return (H<<8)+L;   //合成数据
21 }

复制代码


 

2、陀螺仪数据采集与传输及帧格式介绍(小技巧)

上面我们已经知道单片机如何利用I2C设置MPU6050的工作属性,以及从MPU6050获得3轴加速度和3轴角速度的数据。那么接下来将介绍单片机是如何将数据通过蓝牙发送给上位机的。如下图左半部分,下位机部分包括一个MPU6050、一个单片机、一个电源模块,以及一个蓝牙模块。对于蓝牙模块我不想做过多的讲解(我记得我已经写了不下于3次关于手机、PC等和下位机通信的教程了:(如果是想用安卓手机和蓝牙模块通信来实现遥控功能的话,可以参考:http://www.cnblogs.com/zjutlitao/p/4231635.html;想用笔记本和蓝牙模块通信来实现遥控功能的话可以参考:http://www.cnblogs.com/zjutlitao/p/3886826.html)

 

其实,利用串口蓝牙模块单片机要做的工作和对串口进行的操作一样,对串口写数据则送至蓝牙模块将数据发出,当外部有数据传送过来时,单片机可以用相应的中断捕获该事件,然后接收消息。因此主函数中初始化串口和MPU6050之后就进入循环数据发送状态,在循环中GetData是上面介绍的获得3轴加速度、3轴角速度或温度的值的函数,SendData则是将int类型的值转换为字符串然后一位一位的发送出去,而最开始和最后分别发送一个#和$作为该帧的开始和结束标志位,具体格式如下:

#  12354-21332-21125$

 

 

注:符号位要么为'-',要么为空。

复制代码

 1 //-----------------------------------------
 2 //主程序
 3 //-----------------------------------------
 4 void main()
 5 { 
 6     delay(500);        //上电延时        
 7     init_uart();
 8     InitMPU6050();    //初始化MPU6050
 9     delay(150);
10     while(1)
11     {
12         SeriPushSend('#');//
13         SendData(GetData(0x3B));    //X轴加速度
14         SendData(GetData(0x3D));    //Y轴加速度
15         SendData(GetData(0x3F));    //Z轴加速度
16         SeriPushSend('$'); //结束
17         delay(20);
18     }
19 }

复制代码


 

3、基于C#的串口接收函数(C#基本知识)

上面讲到下位机通过串口蓝牙将数据发送给上位机,那么上位机如何接收蓝牙信号呢?其实以我的笔记本为例,因为笔记本内置蓝牙模块,所以无需在上位机上独立安装一个USB-蓝牙模块。而上位机操作蓝牙模块和操作串口几乎一模一样。如下面的C#程序,当点击连接按钮时实例化SerialPort,设置端口号、读超时、然后实例化一个串口数据接收事件句柄(这里PortDataReceived作为数据接收的回调函数)。

复制代码

 1 //Create a serial port for Connection
 2 SerialPort Connection = new SerialPort();
 3 private void btn_link_Click(object sender, EventArgs e)
 4 {
 5     if (!Connection.IsOpen)
 6     {
 7         //Start
 8         //Status = "正在连接...";
 9         Connection = new SerialPort();
10         btn_link.Enabled = false;
11         Connection.PortName = PortList.SelectedItem.ToString();
12         Connection.Open();
13         Connection.ReadTimeout = 10000;
14         Connection.DataReceived += new SerialDataReceivedEventHandler(PortDataReceived);
15         //Status = "连接成功";
16         timer1.Start();
17     }
18 }

复制代码

在PortDataReceived中,只要简单调用Connection.Read(data, 0, length);就能从串口缓冲区读取数据到data中。

复制代码

1 private void PortDataReceived(object o, SerialDataReceivedEventArgs e)
2 {
3     byte[] data = new byte[length];
4     int num=Connection.Read(data, 0, length);
5     datepool.push_back(data,num);//实际接收的不一定是length,之前一直错
6     Connection.DiscardInBuffer();
7     Connection.DiscardOutBuffer();
8 }

复制代码

注:本来是每次读取1byte放入数据池,结果出现程序运行速度越来越慢,本以为是上面的数据池设计的有问题,结果把数据池里的线程注释掉改为ask函数来每次需要数据时才获得,但是问题并不在于此;于是想到可能是绘制折线图的函数有问题,但是重查了一遍发现问题不在于此;于是仔细测量每个过程耗时,发现每个模块耗时正常,最后发现是由于串口缓冲区数据积累造成程序变慢,(因为下位机每20ms发送一次20byte的数据给上位机,上位机若一次不接收完所有数据,将会造成每次都有剩余而逐渐变慢),于是直接改成每次接收20byte,问题得到解决。


 

4、多线程数据池解决高速串口实时性问题(难点)

由于下位机10ms发送一次20byte的数据,上位机一方面要做好接收工作,保证数据不拥挤在串口接收缓冲区;另一方面也要实时获取当前从串口读到的最新数据。如果采用传统多线程+锁的机制是可以的,但是当多线程中加入锁势必会影响程序执行效率,通过综合分析该问题最终抽象出一个特殊的数据模型——自动更新的环形栈:

这样,当采用多线程时,用一个类似于栈的环状栈结构体(实时从串口读数据放入数据池,数据池用p_write标记最新数据存储位置,当外部程序想得到最新数据时,调用ask程序,ask程序从当前p_write向前取40个数据(因为有效数据长度为20,一次取40保证至少有一个有效数据),然后从这40个数据中找出有效信息,赋值给X,Y,Z;然后外部程序可以直接用对象访问X,Y,Z),通过适当调节环的容量达到自我覆盖的效果,同时根据p_write指针可以实时取得最新数据。

复制代码

 1 /// <summary>
 2 /// 询问当前值
 3 /// </summary>
 4 /// <returns>如果解析到则返回真</returns>
 5 public bool ask()
 6 {
 7     i = 0;//立刻将相应的40个字符复制出来
 8     p_read_from = p_write - 40;
 9     while (i < 40)
10     {
11         str[i] = pool[(p_read_from + pool_size) % pool_size];
12         i++;
13         p_read_from++;
14     }
15     i = 39;
16     while (i > 18 && str[i] != '$') i--;
17     if (i == 18) return false;
18     i--;
19     data_Z = 0;
20     for (int j = 4; j > -1; j--)
21     {
22         data_Z *= 10;
23         data_Z += (str[i - j] - '0');
24     }
25     if (str[i - 5] == '-') data_Z = -data_Z;
26     i -= 6;
27 
28     data_Y = 0;
29     for (int j = 4; j > -1; j--)
30     {
31         data_Y *= 10;
32         data_Y += (str[i - j] - '0');
33     }
34     if (str[i - 5] == '-') data_Y = -data_Y;
35     i -= 6;
36 
37     data_X = 0;
38     for (int j = 4; j > -1; j--)
39     {
40         data_X *= 10;
41         data_X += (str[i - j] - '0');
42     }
43     if (str[i - 5] == '-') data_X = -data_X;
44 
45     X = data_X;
46     Y = data_Y;
47     Z = data_Z;
48     return true;
49 }
50 
51 /// <summary>
52 /// 将数据输入数据池
53 /// </summary>
54 /// <param name="date">数据</param>
55 /// <param name="length">长度</param>
56 internal void push_back(byte[] date, int length)
57 {
58     for (int i = 0; i < length; i++)
59     {
60         pool[p_write++] = date[i];
61         if (p_write == pool_size) p_write = 0;
62     }
63 }

复制代码


 

5、折线图可视化模块(程序员基本功)

通过上面几步我们已经可以将下位机的陀螺仪3轴的加速度收集过来了,但是如果先将数据收集好,然后再用matlab绘制,我们很难知道哪个动作对应哪个数据,不利于我们观察效果(虽然matlab上自带串口接口,但是LZ就是任性!有一张好看的脸,还是想着靠实力赢得地位,哈哈哈~)。

如本节小标题括号内所示,在C#里写一个绘制折线图的程序应该属于我们的基本功(我可不是调用相应的绘图接口哦!),其大致思想就是用一个List存储num个数据,当list中的数据少于num个时则不断添加,当list内的数据大于num个时,则从尾部进来一个的同时从头部删除一个(这样才能实现perfect的效果)。

注:其实中间还出现了一个逻辑错误性小插曲:原初写好之后,本以为能够实现高效数据采集显示,但是仔细观察发现还是有很大延时,但是旁边的数据显示却非常实时。这是为什么呢?查找了一会最终发现问题出在折线图绘制上——本来采用固定的模式(一张图能存放多少数据点就用vector<int>P/Q/R在初始化的时候存放这么多点,然后每次有一个新的数据过来时就会将新数据加到vector后面,同时删除最前面的一个数据,这样做是为了方便初始vector里没有数据绘制折线图错误的问题),可是问题就出在这!咋一看这种思路很好,初始化vector中放num个点,每次新的来到将最前面一个数据冲掉,这样这个vector始终保持着num个点,且最新的在最后面,整个折线图能反应实时情况。但是由于我为了“安全”起见,在vector初始化时多Add几个数据,这样导致vector中的数据量N>折线图一次能呈现的数据量num,所以最新的数据总会在之后出现!当时没有想到是这个原因,就直接改了下DateLineChar函数,实现根据vector大小自动绘制的算法(这样就不用预先在vector中装入一定量的值了)

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MPU6050 - 陀螺仪 - 技术总结 的相关文章

  • MPU6050 6轴姿态传感器的分析与使用(一)

    一 MPU6050简介 MPU6050是一个6轴姿态传感器 xff08 3轴加速度计和3轴陀螺仪传感器 xff09 xff0c 可以测量芯片自身X Y Z轴的加速度 角度参数 xff0c 通过数据融合 xff0c 可以得到姿态角 二 简介分
  • MPU6050

    简介 xff1a MPU6050是InvenSense 公司的 MPU6050 作为主芯片 xff0c 能同时检测三轴加速度 三轴陀螺仪 三轴角速度 的运动数据以及温度数据 利用 MPU6050 芯片内部的 DMP 模块 xff08 Dig
  • MPU6050-扫盲

    文章部分内容转载于 https blog csdn net zmjames2000 article details 88379640 管脚名称说明VCC3 3 5V xff08 内部有稳压芯片 xff09 GND地线SCLMPU6050作为
  • MPU6050软件姿态解算

    我是勤劳的搬运工 转自 http blog sina com cn s blog c5a00db10102wd7d html http mini eastday com mobile 180306210610472 html 使用MPU60
  • STM32读取MPU6050陀螺仪

    目录 一 硬件设计 1 模块说明 2 电气原理图 二 程序设计 1 工程创建 2 程序设计 xff08 1 xff09 main程序 xff08 2 xff09 IIC初始化 xff08 3 xff09 串口1配置 xff08 4 xff0
  • Jetson nano i2c教程(MPU6050 + PCA9685)

    首先介绍nano板子上的i2c相关的硬件信息 xff1a 安装所需要的i2c库 sudo apt get install l y i2c tools 完成nano中io与i2c设备的硬件接线 本次案例使用的是PCA9685和MPU6050
  • 2021年Linux技术总结(四):Linux 驱动

    一 裸机驱动开发流程 所谓裸机在这里主要是指系统软件平台没有用到操作系统 在基于ARM处理器平台的软件设计中 xff0c 如果整个系统只需要完成一个相对简单而且独立的任务 xff0c 那么可以不使用操作系统 xff0c 只需要考虑在平台上如
  • ROS读取MPU6050数据

    上一篇讲到使用I2C连接Jetson Nano和MPU6050并使用python读取 xff0c 本次基于roscpp连接读取IMU数据 RTIMULib RTIMULib是本次使用的IMU库 xff0c 支持多种常见的IMU模块 xff0
  • mpu6000、mpu6050陀螺仪加速度计互补滤波数据融合算法原理推导

    时间比较紧 xff0c 就只传照片了
  • stm32使用MPU6050读取温度值验证I2C

    通过MPU6050测温来进行I2C的验证学习 关于MPU6050寄存器相关可以参考https blog csdn net he yuan article details 76559569 I2C时序很多 xff0c 我是直接以原子I2C的程
  • 一文讲透缓存方案及常见问题——初篇

    Hello 大家好 今天跟大家聊的一个话题就是 缓存 目前 面向C端的服务架构中 除开管理后台等访问量很少 实时性要求较高的服务可不使用缓存外 缓存已成为高性能分布式系统里不可或缺的一环 本文不打算过多涉及具体的缓存组件如Memcached
  • 【STM32】I2C练习,HAL库读取MPU6050角度陀螺仪

    I2C练习 MPU6050简介 寄存器查询表格 STM32CubeMx配置 代码文件 mpu6050 h文件 mpu6050 c文件 main c文件 总结 MPU6050简介 MPU 6000 6050 为全球首例整合性6轴运动处理组件
  • Spring Cloud Gateway 全局异常处理

    文章目录 Spring Cloud Gateway 全局异常处理 范例 示例 修改前抛出一个运行时异常 示例 全局异常处理抛出一个运行时异常 如何添加 自定义异常处理配置 全局异常处理监听器 总结 Spring Cloud Gateway
  • MPU6050使用心得(简单分享一下)

    前言 选用MPU6050做 倾斜检测 功能 前期准备 开发板 正点原子STM32F103 精英版 STM32F103ZET6 模块 GY 521 MPU6050 其他 杜邦线若干 烧录线 FlyMcu Keil5 正点原子开发板配套的套件
  • Spring Boot 添加拦截器

    文章目录 Spring Boot 添加拦截器 方法1 新增拦截器 配置拦截器 方法2 新增拦截器 配置拦截器 拦截所有响应 Spring Boot 添加拦截器 介绍一下在Spring Boot 2 0 0以上版本如何添加拦截器 方法1 新增
  • MPU6050 获取角度理论推导(一)

    当你搜到这篇文章的时候说明你已经在做陀螺仪的项目了 那么陀螺仪具体的东西应该不用多说 他其实就是一个获取原始数据然后通过DMP或者MCU的计算处理 然后得到角度的一个传感器 MPU6050内部集成了一个陀螺仪一个加速传感器还有DMP计算单元
  • 性能综述方法论

    性能问题和Bug不同 后者的分析和解决思路更清晰 很多时候从应用日志 文中的应用指分布式服务下的单个节点 即可直接找到问题根源 而性能问题 其排查思路更为复杂一些 对应用进行性能优化 是一个系统性的工程 对工程师的技术广度和技术深度都有所要
  • qemu 启动自定义文件系统命令

    kvm qemu aarch64 bin qemu system aarch64 M virt smp 8 cpu cortex a76 m 4G nographic kernel out kernel arm64 Image append
  • Spring Boot —— Security 控制按钮权限

    文章目录 Spring Boot Security 控制按钮权限 前言 实现 引入对应的依赖 配置标签 Spring Boot Security 控制按钮权限 前言 在freemarker中 通过Security根据用户角色控制页面按钮或菜
  • 1264 - Out of range value for column 'id' at row 1

    1 我用的是mysql 在数据插入是报错 原因是我插入的值 超过了数据库中类型和长度设置 1 1 我的插入语句 注意 id 的值 INSERT INTO test id sex name username password classes

随机推荐