从IIC通信原理到使用 —— MPU6050

IIC通信详解 —— 基于MPU6050模块

从IIC通信原理到使用 —— MPU6050

IIC通信可以简单地理解成就是数据的通信,就是单片机(主机)与设备(从机)之间的一种通信协议,两者必须遵从这个协议才能正确的相互读写数据,它这个通信过程只要两根线就能给完成,分别是SCL(时钟线)和SDA(数据线);

这个主机与从机的关系一定要理解好,因为下一次不一定是单片机,有些从机也可以作为主机,可以先把主机理解成能够向其他设备发送指令/命令的一方,比如如果你作为主机,你是想读的时候才去读的,想写的时候就去写,比较主动;而如果你作为从机,这就由不得你,你只能听从主机的命令,主机说要读,你就要把数据给出去,主机说要写,你就得乖乖被写

现在假设单片机作为主机,连接在SDA和SCL的两条线上的设备均是它的从机,比如下面图所示:
主从直接的IIC连线示意图
到这里我们已经能够有一个IIC主从设备的概念了,那么假如单片机想要通过IIC来读取从设备的信息应该都要做什么呢?也就是说,整个IIC通信过程应该是怎么样子实现的?

一、IIC通信协议

在解释之前,可以先知道一个这个协议不协议的到底是怎么实现的,打比喻说只有一个主机一个从机时,大家先定一个协议,如果某根线的电平怎么怎么样,同时另外一根线的电平又怎么怎么样的时候,对应的是要读?还是要写?还是其他什么?上面这个过程也就相当于主机发出的指令,从机通过两根线的电平状态来确定主机的指令是什么。(注意,不要简单地理解为 00 01 10 11 四种状态)。指令是通过两根线来发送,里面的数据也是通过那两根线,所以你至少要定义你什么时候是指令什么时候是数据吧?所以就会有协议这种东西。

IIC总线在传输数据的过程中一共有三种类型信号(指令),分别为:开始信号、结束信号和应答信号。这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。同时我们还要介绍其空闲状态、数据的有效性、数据传输。看到协议不要害怕,我们只需要先知道它内部的原理,至于怎么来实现,后面再看代码进行解释。

①. 空闲状态
当SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。这种状态就相当于没有设备在占用通信通道,大家各自在自家互不搭理。正常情况下,当系统开始工作,如果主从不通信时就希望它是空闲状态,也就是说开机后默认是空闲状态,那么这两个高电平可以由上拉电阻来完成。
空闲状态
②. 开始信号
当SCL为高期间,SDA由高到低的跳变;启动信号是一种电平跳变时序信号,而不是一个电平信号。
③. 结束信号
当SCL为高期间,SDA由低到高的跳变;停止信号也是一种电平跳变时序信号,而不是一个电平信号。
开启和停止信号
④. 应答信号
发射器 定义要发送数据的一端为发射器,当主机往从机写命令时,可以理解成主机作为发射器,当主机读取从机数据时,可以理解成从机往主机写数据,这时候从机就是发射器;
实现过程 发送器每发送一个字节(1byte = 8bit),8个时钟脉冲用来储存数据内容,在第9个时钟脉冲释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
在这里插入图片描述
⑤. 数据的有效性
在数据传送时,SCL为高电平期间,SDA上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。 即:数据在SCL的上升沿到来之前就需准备好。并在在下降沿到来之前必须稳定。

以上就是协议的一些内容,在发送数据时必须得遵守协议,开启读取、读取完成之后一定要发送停止信号来释放总线。我们可以接着看数据的发送过程是怎么实现的。

二、通过IIC协议实现收发

因为在总线上可能会有很多从设备,不止一个,所以主机在发送或者接受数据的时候,必须要知道从机的设备地址(不同设备有自己不同的设备物理地址),相当于如果你要去某个人的家,至少你要先知道他们家的地址。

知道了这个物理地址后,也就相当于主机能在众多从设备中找到了想要找的从设备,但是一个完整设备意味着它有自己的芯片,有自己的储存单元,所以主机不仅仅只是要找到这个设备,还要找到该设备里的主机想要读写的存储单元(寄存器),相当于你要去某个人的家找一个人,你到了他家之后,还要在他众多的家人中找到你要找的那个人。

当主机确定了从机设备、从机设备的寄存器,就能往里面进行读写数据了,而这个读写数据的过程就必须得按照这个IIC协议来完成,这么说可能还比较抽象。下面先介绍MPU6050,再结合IIC来实现单片机从MPU6050读取数据。

三、MPU6050的介绍

MPU-60X0由以下几个关键块和功能组成
1、带有16位ADC和信号调理的三轴MEMS速率陀螺仪传感器
2、具有16位ADC和信号调理的三轴MEMS加速度传感器
3、数字运动处理器(DMP)引擎
4、传感器数据寄存器
5、FIFO
6、中断
7、数字输出温度传感器

首先要知道6050的内置模块。包括一个三轴MEMS(微机电系统(MEMS, Micro-Electro-Mechanical System))陀螺仪、三轴MEMS加速度计,一个数字运动处理引擎(DMP)、还有用于第三方数字传感器接口的辅助IIC端口,(GY-521上面的xda,xcl),常用于扩展磁力计,当辅助IIC接口连接到一个三轴磁力计上面时,6050能提供一个完整的九轴融合输出到其主IIC接口上。同时,6050内部还内置了一个可编程的低通滤波器,可用于传感器数据的滤波。

系统结构图
通过系统结构图只需要你大概知道有什么在里面就行了。
系统结构图
接线图
当不使用磁力计时(使用磁力计时,MPU6050作为主机去读取磁力计的数据,然后再以从机的身份向单片机主机发送加速度、陀螺仪和从磁力计读取的数据),只需要接电源线(5V)、地线、SCL和SDA即可,其他引脚均闲空。
接线图
关键功能介绍
带有16位ADC和信号调理的三轴MEMS陀螺仪

MPU-60X0由三个独立的振动MEMS速率陀螺仪组成,可检测旋转角度X轴,Y轴和Z轴。 当陀螺仪围绕任何感应轴旋转时,科里奥利效应就会产生电容式传感器检测到的振动。 所得到的信号被放大,解调和滤波产生与角速度成比例的电压。 该电压使用单独的片内数字化16位模数转换器(ADC)对每个轴进行采样。 陀螺仪传感器可以全面范围的被数字编程为每秒±250,±500,±1000或±2000度(dps)。 ADC样本速率可以从每秒8,000个采样点编程到每秒3.9个采样点,并且可由用户选择低通滤波器可实现广泛的截止频率。

这就是指模块内部自带了陀螺仪,经其内部的数模转换器可以将陀螺仪的模拟量转成可传输的数字量,并且存储在16位寄存器里(由两个8位寄存器组成),关于获取陀螺仪数据时,只需直接从对应的寄存器里读出数据即可,注意此时的数据并非是倾角。

简而言之,陀螺仪就是角速度检测仪。比如,一块板,以X轴为轴心,在一秒钟的时间转到了90度,那么它在X轴上的角速度就是 90度/秒 (DPS, 角速度单位,Degree Per Second的缩写°/S ,体现了转动的快慢)

具有16位ADC和信号调理的三轴MEMS加速度计

MPU-60X0的3轴加速度计为每个轴使用单独的检测质量。 加速沿着一条特定轴在相应的检测质量上引起位移,并且电容式传感器检测到该位移位移有差别。 MPU-60X0的架构降低了加速度计的敏感度制造变化以及热漂移。 当设备放置在平坦的表面上时,将进行测量在X和Y轴上为 0g,在Z轴上为+ 1g。 加速度计的比例因子在工厂进行校准并且在名义上与电源电压无关。 每个传感器都有一个专用的sigma-delta ADC来提供数字输出。 数字输出的满量程范围可以调整到±2g,±4g,±8g或±16g。

总而言这,加速度传感器,其实是力传感器。用来检查上下左右前后哪几个面都受了多少力(包括重力),然后计算角度。

数字运动处理器(DMP)

DMP就是指MPU6050内部集成的处理单元,可以直接运算出四元数和姿态,而不再需要另外进行数学运算。四元数就是4个数,可以表征姿态,经过几个数学公式之后就可以的出姿态,姿态包括pitch,roll,yaw。DMP的使用大大简化了代码设计,可想而知mpu9150(mpu6050)并不单单是一款传感器,其内部还包含了可以独立完成姿态解算算法的处理单元。
由DMP实现姿态解算算法将单片机从算法处理的压力中解放出来,单片机所要做的是等待DMP解算完成后产生的外部中断,在外部中断里去读取姿态解算的结果。这样单片机有大量的时间来处理其他任务,提高了系统的实时性。综上,dmp之后直接出结果,可以直接用,当然你如果有特殊需要自己还要加滤波也没有问题。

但是听说DMP的参考平面有点蛋疼。他解算出来的姿态角是以上电时的平面为基准平面的。也就是说每次上电都要把装置摆到绝对水平。但这有点困难。

传感器数据寄存器

传感器数据寄存器包含最新的陀螺仪,加速度计,辅助传感器和温度测量数据。 它们是只读寄存器,可通过串行接口访问。 这些寄存器的数据可以随时读取。 但是,可以使用中断函数来确定新数据何时可用。

存储这些数据的寄存器均只是可读状态,称之为数据寄存器,注意:关于陀螺仪和加速度的寄存器均是16位的,也就是说在读取他们数据时,需要知道加速度/陀螺仪对应的两个寄存器的内部地址,并且读取出来的数据需要通过转换合成。
陀螺仪数据寄存器
上图是陀螺仪数据寄存器,通过读取这6个寄存器,就可以读到陀螺仪 x/y/z 轴的值,比如 x 轴的数据,可以通过读取 0X43(高 8 位)和 0X44(低 8 位)寄存器得到,其他轴以此类推。
加速度数据寄存器
上图是加速度数据寄存器,通过读取这6个寄存器,就可以读到加速度传感器 x/y/z 轴的值,比如读 x 轴的数据,可以通过读取 0X3B(高 8 位)和0X3C(低8位)寄存器得到,其他轴以此类推。

关于其他寄存器的设置及介绍
我是从这篇帖子里学习的,我已经码出来分享给大家
--------》 传送门
其他寄存器的配置
知道以上寄存器很重要,要知道寄存器每个位什么值对应着什么样的功能,还要知道其对应的内存地址,以供主机进行寻找。

FIFO缓冲器

MPU-60X0包含一个可通过串行接口访问的1024字节FIFO寄存器。 FIFO配置寄存器决定哪个数据写入FIFO。 可能的选择包括陀螺仪数据,加速计数据,温度读数,辅助传感器读数和 FSYNC 输入。 FIFO 计数器跟踪 FIFO 中包含的有效数据字节数。 FIFO寄存器支持突发读取。 中断功能可用于确定新数据何时可用。

中断

中断功能通过中断配置寄存器进行配置。 可配置的项目包括INT引脚配置,中断锁存和清除方法以及中断触发器。 可触发中断的项目有:
(1)时钟发生器锁定到新的参考振荡器(用于切换时钟源);
(2)可以读取新数据(来自FIFO和数据寄存器);
(3)加速度计事件中断;
(4)MPU-60X0 没有收到辅助传感器的确认I2C总线。

中断状态可以从中断状态寄存器读取。

至此,了解了MPU6050的一些基础知识后,我们就可以通过代码建立IIC通信,让单片机与MPU6050进行通信。

四、单片机与MPU6050的IIC实现

在贴代码之前,需要明确以下几点:

A. 关于地址
---- 从设备的地址是用7bit来表示的,但是一个寄存器里有8个位,所以最终这个设备的地址是由已知的7位和第8位组成,那么第8位到底是什么呢?

规定在IIC通信开始发送一帧数据时,前面7bit是设备或者寄存器的地址,第8位则是读或者写的方向命令位,0为写命令,1为读命令。

就比如从官方所给资料来看,MPU6050的地址是0x68或者0x69,拿0x68举例,其表示如下:

7bit 6bit 5bit 4bit 3bit 2bit 1bit 0bit
1 1 0 1 0 0 0 AD0

前面7位(110 1000)构成从设备的地址0x68,而最后一位是未知的,或者说,是由用户自己定义设置的(AD0引脚),比如如果在MPU6050模块的AD0引脚接地,则最后一位表示0,那么加进去以后,地址就变成了1101 0000,转换成十六进制也就是0xD0,此处也代表写命令。

所以以后在主机寻找MPU6050这个设备时,需要写入一帧数据以表示寻找从机,这一帧数据就是0xD0,说明白点,0xD0是由设备物理地址+命令来构成最终的“门牌”,主机以后就是通过这个门牌来找到MPU6050,主机只要把“门牌”发送出去,系统的状态就代表着主机向从机写数据,一直持续到停止信号。

与设备物理地址不同,设备内部的寄存器地址是由8位构成,因此只需要在开启信号发起时,发送寻找MPU6050的“门牌”(相当于告诉了设备主机打算写入数据,只要还没有停止信号就代表后面的操作一直都是写信号),接着就可以继续发送寄存器的内存地址(8位地址,最后一位不再是表示读写命令,因为停止信号还没到来,所以状态一直是写的命令)。


B. 关于通信过程

主机写数据给从机时,先“广播”从机地址(相当于找到对应从机),再发送从机内部寄存器的地址(相当于找到对应从机的对应寄存器),然后主机再发送数据到该寄存器,主机发送完一字节数据后,从机需要发出一个应答信号(这个过程是从机把应答信号发送至主机的CY位)来告诉主机已接收完成一帧数据。

主机读从机数据时,要先"广播"从机地址,再发送从机内部寄存器的地址(相当于要找到数据的来源),然后直接读取一字节数据,读取完一字节数据后,主机向从机发送应答信号(相当于主机告诉从机已经读取到一字节)

综上,不管是从机还是主机,只要是被写入数据,当被写入完一字节数据时,都需要向对方发送应答信号。(相当于读别人的数据,这个过程比如:可以把主机给从机写数据的过程理解成从机读取主机的数据,同样,主机读取从机数据时可以理解成从机往主机里些数据)


代码实现及注释

STC89C52 + MPU6050 + LCD1602.

// 功能: 显示加速度计和陀螺仪的10位原始数据
//****************************************
// 晶振:11.0592M
// 显示:LCD1602

#include <REG52.H>  
#include <math.h>    //Keil library  
#include <stdio.h>   //Keil library 
#include <INTRINS.H>  //这个库需要_nop_()函数

typedef unsigned char  uchar; //类型定义
typedef unsigned short ushort;
typedef unsigned int   uint;

#define DataPort P0         //LCD1602数据端口
sbit    SCL=P1^0;           //IIC时钟引脚定义
sbit    SDA=P1^1;           //IIC数据引脚定义
sbit    LCM_RS=P2^0;        //LCD1602命令端口       
sbit    LCM_RW=P2^1;        //LCD1602命令端口       
sbit    LCM_EN=P2^2;        //LCD1602命令端口
uchar dis[4];               //显示数字(-511至512)的字符数组
int dis_data;               //变量

//看以下变量的时候对应着寄存器里面名称,容易有印象
#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    //X轴加速度数据寄存器1地址
#define ACCEL_XOUT_L    0x3C    //X轴加速度数据寄存器2地址
#define ACCEL_YOUT_H    0x3D    //Y轴加速度数据寄存器1地址
#define ACCEL_YOUT_L    0x3E    //Y轴加速度数据寄存器2地址
#define ACCEL_ZOUT_H    0x3F    //Z轴加速度数据寄存器1地址
#define ACCEL_ZOUT_L    0x40    //Z轴加速度数据寄存器2地址
#define TEMP_OUT_H      0x41    //温度传感器数据寄存器1地址
#define TEMP_OUT_L      0x42    //温度传感器数据寄存器2地址
#define GYRO_XOUT_H     0x43    //X轴陀螺仪数据寄存器1地址
#define GYRO_XOUT_L     0x44    //X轴陀螺仪数据寄存器2地址    
#define GYRO_YOUT_H     0x45    //Y轴陀螺仪数据寄存器1地址
#define GYRO_YOUT_L     0x46    //Y轴陀螺仪数据寄存器2地址
#define GYRO_ZOUT_H     0x47    //Z轴陀螺仪数据寄存器1地址
#define GYRO_ZOUT_L     0x48    //Z轴陀螺仪数据寄存器2地址
#define PWR_MGMT_1      0x6B    //电源管理,典型值:0x00(正常启用)
#define SlaveAddress    0xD0    //IIC写入时的地址字节数据,+1为读取
//#define WHO_AM_I        0x75    //IIC地址寄存器(默认数值0x68,只读)

//****************************************
//函数声明
//****************************************
void  delay(unsigned int k);   //延时
void  InitLcd();     //初始化lcd1602
void  lcd_printf(uchar *s,int temp_data); //把整数转成字符串
void  WriteDataLCM(uchar dataW);         //向LCD写入数据
void  WriteCommandLCM(uchar CMD,uchar Attribc);  //LCD指令
void  DisplayOneChar(uchar X,uchar Y,uchar DData); //显示一个字符
void  DisplayListChar(uchar X,uchar Y,uchar *DData,L); //显示字符串
void  InitMPU6050();    //初始化MPU6050
void  Delay5us();   //IIC协议用到的一些延迟
void  I2C_Start(); //开始信号
void  I2C_Stop();  //停止信号
void  I2C_SendACK(bit ack);  //单片机向MPU6050发送应答
bit   I2C_RecvACK();   //单片机接收MPU6050应答
void  I2C_SendByte(uchar dat);   //通过IIC,单片机向从机发送一字节数据
uchar I2C_RecvByte();  //通过IIC,单片机接收从机一字节数据
void  I2C_ReadPage();  
void  I2C_WritePage();
void  display_ACCEL_x(); //LCD显示X轴加速度信息
void  display_ACCEL_y(); //LCD显示Y轴加速度信息
void  display_ACCEL_z(); //LCD显示Z轴加速度信息
uchar Single_ReadI2C(uchar REG_Address);    //读取I2C数据
void  Single_WriteI2C(uchar REG_Address,uchar REG_data); //向I2C写入数据


//****************************************
//lcd相关
//****************************************
void lcd_printf(uchar *s,int temp_data)  //整数转字符串
{
    if(temp_data<0)
    {
        temp_data=-temp_data;
        *s='-';
    }
    else *s=' ';
    *++s =temp_data/100+0x30;
    temp_data=temp_data%100;     //取余运算
    *++s =temp_data/10+0x30;     //转成ASCII码
    temp_data=temp_data%10;      //取余运算
    *++s =temp_data+0x30;   
}

void delay(unsigned int k)  //延迟函数
{                       
    unsigned int i,j;               
    for(i=0;i<k;i++)
    {           
        for(j=0;j<121;j++);
    }                       
}

void WaitForEnable(void)    //LCD1602写允许
{                   
    DataPort=0xff;      
    LCM_RS=0;LCM_RW=1;_nop_();
    LCM_EN=1;_nop_();_nop_();
    while(DataPort&0x80);   
    LCM_EN=0;               
}  

void WriteCommandLCM(uchar CMD,uchar Attribc)  //LCD1602写入命令
{                   
    if(Attribc)WaitForEnable(); 
    LCM_RS=0;LCM_RW=0;_nop_();
    DataPort=CMD;_nop_();   
    LCM_EN=1;_nop_();_nop_();LCM_EN=0;
} 

void WriteDataLCM(uchar dataW)  //LCD1602写入数据
{                   
    WaitForEnable();        
    LCM_RS=1;LCM_RW=0;_nop_();
    DataPort=dataW;_nop_(); 
    LCM_EN=1;_nop_();_nop_();LCM_EN=0;
}

void DisplayOneChar(uchar X,uchar Y,uchar DData)  //LCD1602写入一个字符
{                       
    Y&=1;                       
    X&=15;                      
    if(Y)X|=0x40;                   
    X|=0x80;            
    WriteCommandLCM(X,0);       
    WriteDataLCM(DData);        
} 

void DisplayListChar(uchar X,uchar Y,uchar *DData,L)  //LCD1602显示字符串
{
    uchar ListLength=0; 
    Y&=0x1;                
    X&=0xF;                
    while(L--)             
    {                       
        DisplayOneChar(X,Y,DData[ListLength]);
        ListLength++;  
        X++;                        
    }    
}

void InitLcd()   //LCD1602初始化           
{           
    WriteCommandLCM(0x38,1);  //设置8位格式,2行,5x7  
    WriteCommandLCM(0x08,1);  //关闭显示,检测忙信号  
    WriteCommandLCM(0x01,1);  //清除屏幕显示  
    WriteCommandLCM(0x06,1);  //设定输入方式,增量不移位  
    WriteCommandLCM(0x0c,1);  //整体显示,关光标,不闪烁
    DisplayOneChar(0,0,'A');  //显示A字符在第0行第0位
    DisplayOneChar(0,1,'G');  //总共有0,1两行,每行0~15个字符
} 


//****************************************
//IIC相关
//****************************************
void Delay5us()  //延时5微秒(STC90C52RC@12M)
{
    _nop_();_nop_();_nop_();_nop_();
    _nop_();_nop_();_nop_();_nop_();
    _nop_();_nop_();_nop_();_nop_();
    _nop_();_nop_();_nop_();_nop_();
    _nop_();_nop_();_nop_();_nop_();
    _nop_();_nop_();_nop_();_nop_();
}

void I2C_Start()  //I2C起始信号
{
    SDA = 1;                    //拉高数据线
    SCL = 1;                    //拉高时钟线
    Delay5us();                 //延时
    SDA = 0;                    //产生下降沿
    Delay5us();                 //延时
    SCL = 0;                    //拉低时钟线
}

void I2C_Stop()   //I2C停止信号
{
    SDA = 0;                    //拉低数据线
    SCL = 1;                    //拉高时钟线
    Delay5us();                 //延时
    SDA = 1;                    //产生上升沿
    Delay5us();                 //延时
}

void I2C_SendACK(bit ack)   //I2C发送应答信号,(0:ACK 1:NAK)
{
    SDA = ack;                  //写应答信号
    SCL = 1;                    //拉高时钟线
    Delay5us();                 //延时
    SCL = 0;                    //拉低时钟线
    Delay5us();                 //延时
}

bit I2C_RecvACK()  //I2C接收应答信号
{
    SCL = 1;                    //拉高时钟线
    Delay5us();                 //延时
    CY = SDA;                   //读应答信号,存在CY进位位
    SCL = 0;                    //拉低时钟线
    Delay5us();                 //延时
    return CY;
}

void I2C_SendByte(uchar dat)  //向I2C总线发送一个字节数据
{
    uchar i;
    for (i=0; i<8; i++)         //8位计数器
    {
        dat <<= 1;              //移出数据的最高位
        SDA = CY;               //送数据口
        SCL = 1;                //拉高时钟线
        Delay5us();             //延时
        SCL = 0;                //拉低时钟线
        Delay5us();             //延时
    }
    I2C_RecvACK();
}

uchar I2C_RecvByte()  //从I2C总线接收一个字节数据
{
    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;
}

void Single_WriteI2C(uchar REG_Address,uchar REG_data)//向I2C设备写入一个字节数据
{
    I2C_Start();                  //起始信号
    I2C_SendByte(SlaveAddress);   //发送设备地址+写信号
    I2C_SendByte(REG_Address);    //内部寄存器地址,这里的写信号由上面一行得来
    I2C_SendByte(REG_data);       //内部寄存器数据,
    I2C_Stop();                   //发送停止信号,持续到停止信号
}

uchar Single_ReadI2C(uchar REG_Address)//从I2C设备读取一个字节数据
{
    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;
}

void InitMPU6050()  //初始化MPU6050
{
    Single_WriteI2C(PWR_MGMT_1, 0x00);  //解除休眠状态
    Single_WriteI2C(SMPLRT_DIV, 0x07);  //设置陀螺仪采样率,典型值:0x07(125Hz)
    Single_WriteI2C(CONFIG, 0x06);      //低通滤波频率,典型值:0x06(5Hz)
    Single_WriteI2C(GYRO_CONFIG, 0x18); //陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
    Single_WriteI2C(ACCEL_CONFIG, 0x01);//加速计自检、测量范围及高通滤波频率,典型值:0x01(不自检,2G,5Hz)
}


//****************************************
//主程序相关
//****************************************
int GetData(uchar REG_Address) //将MPU6050内部的两个数据寄存器的数据进行合成
{
    char H,L;
    H=Single_ReadI2C(REG_Address);
    L=Single_ReadI2C(REG_Address+1);
    return (H<<8)+L;   //合成数据,返回十六进制数
}

void Display10BitData(int value,uchar x,uchar y)//在1602上显示10位数据
{
    value /= 64;                          //转换为10位数据
    lcd_printf(dis, value);         //转换数据显示,dis是dis[4]数组的头地址
    DisplayListChar(x,y,dis,4); //启始列,行,显示数组,显示长度
}

void main()
{ 
    delay(500);     //上电延时      
    InitLcd();      //液晶初始化
    InitMPU6050();  //初始化MPU6050
    delay(150);
    while(1)
    {
        Display10BitData(GetData(ACCEL_XOUT_H),2,0);    //显示X轴加速度,第0行第2列
        Display10BitData(GetData(ACCEL_YOUT_H),7,0);    //显示Y轴加速度
        Display10BitData(GetData(ACCEL_ZOUT_H),12,0);   //显示Z轴加速度
        Display10BitData(GetData(GYRO_XOUT_H),2,1); //显示X轴角速度
        Display10BitData(GetData(GYRO_YOUT_H),7,1); //显示Y轴角速度
        Display10BitData(GetData(GYRO_ZOUT_H),12,1);    //显示Z轴角速度
        delay(500);
    }
}

直接复制以上代码,即可成功运行。
如有理解错误,往大家多多指正,谢谢!

发布了4 篇原创文章 · 获赞 15 · 访问量 1835

猜你喜欢

转载自blog.csdn.net/fengge2018/article/details/104009015