【嵌入式模块】MPU6050
创始人
2025-05-28 12:10:22

文章目录

  • 0 前言
  • 1 MPU6050概述
    • 1.1 基本概述
    • 1.2 引脚和常用原理图
  • 2 代码
  • 3 姿态解算
    • 3.1 欧拉角&旋转矩阵
    • 3.2 DMP
  • 3 校正

0 前言

  作为惯性传感器中入门级别的器件,MPU6050凭借它出色的性价比成为一款非常常用的角度姿态传感器,在很多科创项目中被使用。我之前也接触过很多次这个器件,也收集了不少资料,趁此机会总结一下学习笔记。

1 MPU6050概述

1.1 基本概述

  MPU6050包含3轴陀螺仪和3轴加速度计,其中陀螺仪的主要作用是测量物体绕芯片的三个坐标轴的角速度,其原理是高速旋转的转子指向的方向会保持不变,即所谓陀螺效应,详细介绍建议自行搜索“陀螺仪工作原理”;加速度计则是测量三个轴向的加速度,同样也是指芯片的三个坐标轴。其原理可以想象成一个立方体箱子里面有一个失重悬空的小球,当受到外界压力时,小球会朝着某个方向运动,箱子内壁就会受到对应大小的压力,从而可以计算出各个方向的加速度大小。
在这里插入图片描述

在这里插入图片描述

  当整体受到向左的加速度时,小球会有一个相对箱子向右的加速度,从而右侧“箱壁”会受到对应加速度大小的力,这样就能计算出加速度的大小。

  MPU6050是InvenSense公司推出的全球首款整合性6轴运动处理组件。目前InvenSense已被日本的TDK公司收购,在他官网(https://invensense.tdk.com/),可能是年代久远,MPU6050已经是该公司的边缘产品了,6轴芯片当中,6050和6500两款芯片被排在最后,还都是NOT RECOMMENDED的状态,而且资料支持也不是很完善,找遍了网站,也只找到了一个放数据手册的网页,开发相关的寄存器手册并未找到。因此,建议还是在网上去搜索资料吧,如下图所示,都是网上流传的经典资料。

在这里插入图片描述

  MPU6050的核心就是它内部的寄存器。它内部有118个寄存器(编号从0到117)。其中需要注意,虽然寄存器手册上寄存器编号是从13开始,但实际上13之前的寄存器也是可以使用的(看后面的代码就知道了)。
  这些寄存器有一些是用来设置参数的,可读可写;也有一些是存放一些数据供外部读取的,只可读。它是基于IIC进行通信,因此,在使用时,先找到传输器件地址,然后再传输寄存器地址,最后传输相应的数据或者指令,这也是IIC协议和器件寄存器交互的常用设定。

  既然有这么多寄存器,那用起来岂不是很困难?并不是,虽然寄存器多,但实际使用时也不需要使用如此多的寄存器。而且即使要使用的话,也可以利用C语言中的宏定义,这样也不麻烦。

1.2 引脚和常用原理图

  为了将这个芯片集成到我们需要的系统当中,就需要了解这款芯片的引脚和它常用的原理图,如下图所示,这个是市面上卖的MPU6050模块的原理图。

请添加图片描述

可以看到,这里引出了8个引脚,分别是电源引脚5V和GND,IIC通信引脚SCL和SDA,一般来说,大部分的应用只需要接这四个引脚即可。其中,XCL和XDA是额外的IIC通信引脚,主要用于连接外部的磁力传感器,并利用自带的运动处理器DMP硬件加速引擎,通过主IIC接口,向应用输出完整的9轴融合演算数据。
  而AD0引脚是用来设置IIC通信中的从机地址,如果接地(不接),则从机地址为0x68, 如果接高电平,则从机地址为0x69。而INT引脚主要用于中断,如果要使用中断需要设置相关的寄存器。

2 代码

  了解了MPU6050的基础知识,接下来就是写代码来使用了。如果还没确定使用的微处理器,我推荐先使用Arduino,因为它集成了很多的第三方库,这样在使用一些器件时不用自己再重复造轮子,只需要会调用即可。
  在Arduino中也有MPU6050的库,如下图所示。

在这里插入图片描述

安装好库之后,接下来就找到给出的例子来学习它内部的代码了。

在这里插入图片描述

这里提供了6个例子,基本包含了大部分的使用。

  当然,使用Arduino IDE也存在一个问题,那就是代码不能定位过去,查看库的源码不太方便,因此建议自己基于VS Code配一个Arduino的环境,或者直接下载插件Platform IO这个插件,具体的教程建议自行搜索。
  具体可以看一下这个库的源码,主要是以下几个文件,各自的作用已标注清楚。
在这里插入图片描述
因此,如果不需要使用DMP时,只需要包含"MPU6050.h"即可。

  那如果是其他的微处理器呢?比如51或者STM32等。这个可以考虑在网上找一些现成的,也可以考虑自己根据这个库文件的源码自己写一个适配某个处理器的库。本质就是IIC通信和寄存器的读取。这里放一个基于51的网上流传甚广的代码。

//****************************************
// Update to MPU6050 by shinetop
// MCU: STC89C52
// 2012.3.1
// 功能: 显示加速度计和陀螺仪的10位原始数据
//****************************************
// 使用单片机STC89C52
// 晶振:11.0592M
// 显示:串口
// 编译环境 Keil uVision2
//****************************************
#include 
#include     //Keil library  
#include    //Keil library	
#include  //Keil library	
typedef unsigned char  uchar;
typedef unsigned short ushort;
typedef unsigned int   uint;
//****************************************
// 定义51单片机端口
//****************************************
sbit    SCL = P1 ^ 5;			//IIC时钟引脚定义
sbit    SDA = P1 ^ 4;			//IIC数据引脚定义
//****************************************
// 定义MPU6050内部地址
//****************************************
#define	SMPLRT_DIV		0x19	//陀螺仪采样率,典型值:0x07(125Hz)
#define	CONFIG			0x1A	//低通滤波频率,典型值:0x06(5Hz)
#define	GYRO_CONFIG		0x1B	//陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
#define	ACCEL_CONFIG	0x1C	//加速计自检、测量范围及高通滤波频率,典型值:0x01(不自检,2G,5Hz)
#define	ACCEL_XOUT_H	0x3B
#define	ACCEL_XOUT_L	0x3C
#define	ACCEL_YOUT_H	0x3D
#define	ACCEL_YOUT_L	0x3E
#define	ACCEL_ZOUT_H	0x3F
#define	ACCEL_ZOUT_L	0x40
#define	TEMP_OUT_H		0x41
#define	TEMP_OUT_L		0x42
#define	GYRO_XOUT_H		0x43
#define	GYRO_XOUT_L		0x44
#define	GYRO_YOUT_H		0x45
#define	GYRO_YOUT_L		0x46
#define	GYRO_ZOUT_H		0x47
#define	GYRO_ZOUT_L		0x48
#define	PWR_MGMT_1		0x6B	//电源管理,典型值:0x00(正常启用)
#define	WHO_AM_I		0x75	//IIC地址寄存器(默认数值0x68,只读)
#define	SlaveAddress	0xD0	//IIC写入时的地址字节数据,+1为读取
//**************************************************************************************************
//定义类型及变量
//**************************************************************************************************
uchar dis[6];					//显示数字(-511至512)的字符数组
int	dis_data;					//变量
//**************************************************************************************************
//函数声明
//**************************************************************************************************
void  Delay5us();
void  delay(unsigned int k);										//延时
void  lcd_printf(uchar* s, int temp_data);
//********************************MPU6050操作函数***************************************************
void  InitMPU6050();											//初始化MPU6050
void  I2C_Start();
void  I2C_Stop();
void  I2C_SendACK(bit ack);
bit   I2C_RecvACK();
void  I2C_SendByte(uchar dat);
uchar I2C_RecvByte();
void  I2C_ReadPage();
void  I2C_WritePage();void  display_ACCEL_x();
void  display_ACCEL_y();
void  display_ACCEL_z();
uchar Single_ReadI2C(uchar REG_Address);						//读取I2C数据
void  Single_WriteI2C(uchar REG_Address, uchar REG_data);	   //向I2C写入数据
//********************************************************************************
//整数转字符串
//********************************************************************************
void lcd_printf(uchar* s, int temp_data)
{if(temp_data < 0){temp_data = -temp_data;*s = '-';}else *s = ' ';*++s = temp_data / 10000 + 0x30;temp_data = temp_data % 10000; //取余运算*++s = temp_data / 1000 + 0x30;temp_data = temp_data % 1000; //取余运算*++s = temp_data / 100 + 0x30;temp_data = temp_data % 100; //取余运算*++s = temp_data / 10 + 0x30;temp_data = temp_data % 10;  //取余运算*++s = temp_data + 0x30;
}
//******************************************************************************************************
//串口初始化
//*******************************************************************************************************
void init_uart()
{TMOD = 0x21;TH1 = 0xfd;		//实现波特率9600(系统时钟11.0592MHZ)TL1 = 0xfd;SCON = 0x50;PS = 1;    //串口中断设为高优先级别TR0 = 1;	 //启动定时器TR1 = 1;ET0 = 1;   //打开定时器0中断ES = 1;EA = 1;
}
//*************************************************************************************************
//串口发送函数
//*************************************************************************************************
void  SeriPushSend(uchar send_data)
{SBUF = send_data;while(!TI);TI = 0;
}
//*************************************************************************************************
//************************************延时*********************************************************
//*************************************************************************************************
void delay(unsigned int k)
{unsigned int i, j;for(i = 0; i < k; i++){for(j = 0; j < 121; j++);}
}
//************************************************************************************************
//延时5微秒(STC90C52RC@12M)
//不同的工作环境,需要调整此函数
//注意当改用1T的MCU时,请调整此延时函数
//************************************************************************************************
void Delay5us()
{_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();
}
//*************************************************************************************************
//I2C起始信号
//*************************************************************************************************
void I2C_Start()
{SDA = 1;                    //拉高数据线SCL = 1;                    //拉高时钟线Delay5us();                 //延时SDA = 0;                    //产生下降沿Delay5us();                 //延时SCL = 0;                    //拉低时钟线
}
//*************************************************************************************************
//I2C停止信号
//*************************************************************************************************
void I2C_Stop()
{SDA = 0;                    //拉低数据线SCL = 1;                    //拉高时钟线Delay5us();                 //延时SDA = 1;                    //产生上升沿Delay5us();                 //延时
}
//**************************************************************************************************
//I2C发送应答信号
//入口参数:ack (0:ACK 1:NAK)
//**************************************************************************************************
void I2C_SendACK(bit ack)
{SDA = ack;                  //写应答信号SCL = 1;                    //拉高时钟线Delay5us();                 //延时SCL = 0;                    //拉低时钟线Delay5us();                 //延时
}
//****************************************************************************************************
//I2C接收应答信号
//****************************************************************************************************
bit I2C_RecvACK()
{SCL = 1;                    //拉高时钟线Delay5us();                 //延时CY = SDA;                   //读应答信号SCL = 0;                    //拉低时钟线Delay5us();                 //延时return CY;
}
//*****************************************************************************************************
//向I2C总线发送一个字节数据
//*****************************************************************************************************
void I2C_SendByte(uchar dat)
{uchar i;for(i = 0; i < 8; i++)      //8位计数器{dat <<= 1;              //移出数据的最高位SDA = CY;               //送数据口SCL = 1;                //拉高时钟线Delay5us();             //延时SCL = 0;                //拉低时钟线Delay5us();             //延时}I2C_RecvACK();
}
//*****************************************************************************************************
//从I2C总线接收一个字节数据
//******************************************************************************************************
uchar I2C_RecvByte()
{uchar i;uchar dat = 0;SDA = 1;                    //使能内部上拉,准备读取数据,for(i = 0; i < 8; i++)      //8位计数器{dat <<= 1;SCL = 1;                //拉高时钟线Delay5us();             //延时dat |= SDA;             //读数据SCL = 0;                //拉低时钟线Delay5us();             //延时}return dat;
}
//*****************************************************************************************************
//向I2C设备写入一个字节数据
//*****************************************************************************************************
void Single_WriteI2C(uchar REG_Address, uchar REG_data)
{I2C_Start();                  //起始信号I2C_SendByte(SlaveAddress);   //发送设备地址+写信号I2C_SendByte(REG_Address);    //内部寄存器地址,I2C_SendByte(REG_data);       //内部寄存器数据,I2C_Stop();                   //发送停止信号
}
//*******************************************************************************************************
//从I2C设备读取一个字节数据
//*******************************************************************************************************
uchar Single_ReadI2C(uchar REG_Address)
{uchar REG_data;I2C_Start();                   //起始信号I2C_SendByte(SlaveAddress);    //发送设备地址+写信号I2C_SendByte(REG_Address);     //发送存储单元地址,从0开始I2C_Start();                   //起始信号I2C_SendByte(SlaveAddress + 1);//发送设备地址+读信号REG_data = I2C_RecvByte();     //读出寄存器数据I2C_SendACK(1);                //接收应答信号I2C_Stop();                    //停止信号return REG_data;
}
//******************************************************************************************************
//初始化MPU6050
//******************************************************************************************************
void InitMPU6050()
{Single_WriteI2C(PWR_MGMT_1, 0x00);	//解除休眠状态Single_WriteI2C(SMPLRT_DIV, 0x07);Single_WriteI2C(CONFIG, 0x06);Single_WriteI2C(GYRO_CONFIG, 0x18);Single_WriteI2C(ACCEL_CONFIG, 0x01);
}
//******************************************************************************************************
//合成数据
//******************************************************************************************************
int GetData(uchar REG_Address)
{uchar H, L;H = Single_ReadI2C(REG_Address);L = Single_ReadI2C(REG_Address + 1);return ((H << 8) + L); //合成数据
}
//******************************************************************************************************
//超级终端(串口调试助手)上显示10位数据
//******************************************************************************************************
void Display10BitData(int value)
{uchar i;
//	value/=64;							//转换为10位数据lcd_printf(dis, value);			//转换数据显示for(i = 0; i < 6; i++){SeriPushSend(dis[i]);}// 	DisplayListChar(x,y,dis,4);	//启始列,行,显示数组,显示长度
}
//*******************************************************************************************************
//主程序
//*******************************************************************************************************
void main()
{delay(500);		//上电延时init_uart();InitMPU6050();	//初始化MPU6050delay(150);while(1){Display10BitData(GetData(ACCEL_XOUT_H));	//显示X轴加速度Display10BitData(GetData(ACCEL_YOUT_H));	//显示Y轴加速度Display10BitData(GetData(ACCEL_ZOUT_H));	//显示Z轴加速度Display10BitData(GetData(GYRO_XOUT_H));		//显示X轴角速度Display10BitData(GetData(GYRO_YOUT_H));		//显示Y轴角速度Display10BitData(GetData(GYRO_ZOUT_H));		//显示Z轴角速度SeriPushSend(0x0d);SeriPushSend(0x0a);//换行,回车delay(2000);}
}

3 姿态解算

  前面提到MPU6050有很多的寄存器,但其实最核心的就是陀螺仪和加速度读取的数值,即姿态数据。但是,需要注意的是,前面也强调过,这个读取到的姿态数据是基于元器件坐标系的,而在实际应用中需要的更多是相对于大地坐标系的数据。这样得到的才是真正意义上的姿态。

  首先要搞清楚芯片坐标系的样式,这直接关系到所测数据的正方向。如下图所示。

在这里插入图片描述

芯片水平放置,丝印朝正上方,此时,前方是+Y方向,右侧是+X方向,正上方是+Z方向,三轴符合右手坐标系。至于旋转的角速度的正方向,也是按照右手定则,大拇指指向轴的正方向,四指所指的方向为旋转的正方向。

  关于MPU6050的姿态解算,主要有两种方式,分别是基于欧拉角和旋转矩阵推导直接调用芯片中的DMP模块,这里简要介绍一下。

3.1 欧拉角&旋转矩阵

  关于欧拉角和旋转矩阵,如果不理解基础知识的建议翻阅我之前的一篇博客:

【学习笔记】空间坐标系旋转与四元数

了解基本的旋转矩阵的知识后,接下来就是利用旋转矩阵来计算芯片的姿态角了。这里建议参考这篇文章,写得比较详细。

有一点存在一点疑问,那就是文章中提到的是Z-Y-X欧拉角,但我认为应该叫Z-Y-X固定角更合理,这也符合教材上的矩阵相乘的顺序。

此外,这篇文章当中陀螺仪和加速度计解算是分开的,加速度主要负责静态的姿态角,陀螺仪反应动态的姿态角变化,然后设定不同的权值,加权求和得到。其实数据融合更加合理的应该是使用卡尔曼滤波。这个后续也会跟进补充。

3.2 DMP

  DMP(Digital Motion Processor),即数字运动处理器,是MPU6050芯片内置的一个重要模块。它的作用就是根据测得的陀螺仪和加速度数据计算得到芯片的欧拉角yaw,pitch,roll的值。使用者可以不用关心它内部是怎么实现的。如果想了解的话可以去找找DMP相关的资料。

3 校正

  IMU有一个重要的特性,那就是它的误差是会随时间累积的,最好是隔一段时间校正一次。而所谓校正,就一定要有一个参考对象。对于MPU6050来说,一般参考对象就是地面。

  校正时,首先将MPU6050放置水平,保持静止。然后运行代码,读取陀螺仪和加速度数据。理论上来说,水平静止放置,陀螺仪数值应该为零,不为零则是误差,将误差写入到芯片内部“偏差寄存器”中,再次读取数值,计算误差,如此反复,直到误差值在允许范围之类,记下误差值,写入到偏差寄存器中。
  至于加速度数值,要考虑重力的影响,因为是水平放置,所以重力只在Z轴有分量。即Z轴分量为g,而默认加速度的单位是2g,而加速度数值用16位寄存器表示,且第一位为符号位(有正负之分),因此实际数值为(215−1)2=16383.5≈16384\frac{\left( 2^{15}-1 \right)}{2}=16383.5\approx 163842(215−1)​=16383.5≈16384,但由于这个重力加速度与Z轴正方向相反,故值为负的,即-16384.
  当然,有时候因为安装等原因,IMU校正时不能Z轴保持竖直,比如X轴保持竖直,则将实际数值代入到校正函数中,没啥差别。

  关于校正,实际上也可以看作是一个自动控制系统,因此也可以采用如PID或者是机器学习等控制方法来进行校正。
  关于PID的例子,其实上面提到的Arduino的库中就有使用,即它内部的calibration()函数,具体原理可以去找源码查看。
  关于机器学习的例子,是我找到的一篇博客,用的是梯度下降的方式来取值,思路很清奇,但个人感觉本质上还是迭代,是否使用梯度下降差别不是很大。

相关内容

热门资讯

Linux命令·diff diff 命令是 linux上非常重要的工具,用于比较文件的内容,特别是...
2021蓝桥杯真题公约数(填空... 题目描述 本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果...
智能马桶杀菌以及光传感方案 智能马桶杀菌模组,安装在马桶改版底部,实现座垫区域消毒、池内消毒、臀洗喷...
腿玩年是什么意思 ,腿玩年是什... 腿玩年是什么意思 目录腿玩年是什么意思 腿玩年是什么意思玩年腿什么意思啊?腿可玩年是什么意思腿玩年是...
校园yy小说,求校园YY小说 ... 校园yy小说目录校园yy小说求校园YY小说有什么好看的校园YY小说校园yy小说 求校园YY小说校园狂...
骏派D60和宝骏510哪个好?... 今天给各位分享骏派D60和宝骏510哪个好?的知识,其中也会对骏派和宝骏哪个质量好进行解释,如果能碰...
黄河两大支流(黄河两大支流位置... 今天给各位分享黄河两大支流的知识,其中也会对黄河两大支流位置进行解释,如果能碰巧解决你现在面临的问题...
Android Listvie... 上一篇文章中我们讲了Android Listview SimpleAdapter的使用完整示例&#x...
linux下实现RS485驱动... 提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮...
GoLang string与s... 这篇文章主要介绍了GoLang string与strings.Builder使用对比,...
爱的创可贴大结局 ,爱的创可贴... 爱的创可贴大结局 目录爱的创可贴大结局 爱的创可贴第几集在一起爱的创可贴结局是什么?电视剧爱的创可贴...
爱在春天结局是什么 ,爱在春天... 爱在春天结局是什么 目录爱在春天结局是什么 爱在春天结局是什么爱在春天大结局爱在春天大结局爱在春天结...
数据挖掘(2.3)--数据预处... 目录 三、数据集成和转换 1.数据集成  2.数据冗余性  2.1 皮尔森相关系数 2.2卡方检验 ...
怎么做水印 ,身份证水印怎么做... 怎么做水印 目录怎么做水印 身份证水印怎么做?ps怎么做水印怎么做水印 2. 设计水印:在设计水印时...
夫妻那些事的演员 ,夫妻那些事... 夫妻那些事的演员 目录夫妻那些事的演员 夫妻那些事主演是谁夫妻那些事演员表夫妻那些事演员表介绍夫妻那...
钉钉,下沉进农田 在这个古老的产业里,数字化没有被放到更高的位置,但难点依旧存在。钉钉恰是...
2023年全国最新二级建造师精... 百分百题库提供二级建造师考试试题、二建考试预测题、二级建造师考试真题、二建证考试题库等,...
点金胜手结局 ,点金胜手结局 ... 点金胜手结局 目录点金胜手结局 点金胜手结局点金胜手30黄宗泽最后跟谁在一起点金胜手大结局点金胜手结...
天苍苍野茫茫是什么歌 ,天苍苍... 天苍苍野茫茫是什么歌 目录天苍苍,野茫茫这句歌词的歌名是什么?天苍苍野茫茫风吹草低见牛羊是哪里的民歌...
未来日记漫画结局,《未来日记》... 未来日记漫画结局目录未来日记漫画结局《未来日记》结局是什么?漫画《未来日记》最后的结局是什么样的?未...
投名状讲的是什么 ,投名状说的... 投名状讲的是什么 目录投名状讲的是什么 投名状说的是什么事情投名状这个电影到底表达了个什么意思投名状...
《Spring Boot 趣味... 牛刀小试——五分钟入门 Spring Boot 万物皆可 Hello World 创建一个 Web ...
硬核~ 阿里人都在内卷的Spr... 前言 这份SpringBoot实战文档,结合典型业务场景,全面介绍基于S...
新娘印度电视剧大结局 ,印度电... 新娘印度电视剧大结局 目录新娘印度电视剧大结局 印度电视剧新娘的最后结局是什么?大结局的印度剧《新娘...
当婆婆遇上妈结局 ,《婆婆遇上... 当婆婆遇上妈结局 目录当婆婆遇上妈结局 《婆婆遇上的妈》电视剧结局是什么?《当婆婆遇上妈》的结局是啥...
洪水来临时正确的做法是什么,发... 洪水来临时正确的做法是什么目录洪水来临时正确的做法是什么发生洪水时的正确做法是什么遇到洪水的正确做法...
baci是什么意思 ,baci... baci是什么意思 目录baci是什么意思 baci是什么意思面基是什么意思啊?baci是什么意思 ...
经典卷积模型回顾26—基于知识... ResNet-152 是由微软亚洲研究院 (Microsoft Research Asia) 发布的...
HTTPS 之fiddler抓... Jmeter接口测试和接口自动化测试从入门到精通,全套项目实战!...
Vmware Ubuntu虚拟... 一、背景 先来说一下我的需求背景,我是在VMware中安装的Ubuntu虚拟机...