概述
第一块:介绍协议规则,然后用软件模拟的形式来实现协议。
第二块:介绍STM32的iic外设,然后用硬件来实现协议。
程序一现象:通过软件I2C通信,对MPU6050芯片内部的寄存器进行读写,写入到配置寄存器,就可以对外挂的这个模型进行配置,读出数据寄存器,就可以获取外挂模块的数据。I2通信的目的
串口通信没有时钟线的异步全双工的协议。
如果我们想要读写寄存器来控制硬件电路,我们至少需要定义两个字节的数据。一个字节是我们要读写哪个寄存器,也就是指定寄存器的地址,另一个字节就是这个地址下存储器的内容,写入内容就是控制电路,读出内容就是获取电路状态。
要求(4点)
1、全双工变为半双工(不需要同时发送和接收) 只能在同一根线上进行发送和接收;
2、应答机制(安全起见)(每发送和接收都通知一下)
3、一根通讯线能够同时外接多个模块(单片机可以指定和任意一个模块进行通信,同时单片机在跟某个模块进行通信时,其他模块不能对正常的通信产生干扰。)
4、串口是异步时序,也就是发送方和接收方约定的传输速率是非常严格的,时钟不能有过大的偏差,也不能在传输过程中,单片机有事进入中断了,异步时序是不能暂停的,单片机一个字节发一半数据暂停了,接收方是不知道的,它仍然会按照原来的约定速率读取,最终导致传输出错。(异步时序的缺点是依赖硬件外设的支持,必须要有USART电路才能方便使用,如果没有USART硬件电路的支持,那么串口是很难用软件来模拟的),需要将该协议改为同步协议,另外加一条时钟线来指导对方读写。由于存在时钟线,对传输的时间要求就不高了,单片机可以随时暂停传输,去处理其他事情,因为暂停传输的同时,时钟线也暂停了,所以传输双方都能定格在暂停的时刻,可以过一段时间再继续,不会对传输造成影响。(同步时序的好处),使用同步时序可极大降低单片机对硬件电路的依赖。即使没有硬件电路的支持也可以很方便地用软件手动翻转电平来实现通信,而异步时序的好处是省一根时钟线,节省资源,缺点是对时间要求严格,对硬件电路的依赖比较严重。
单片机读写自己的寄存器,可以直接通过内部的数据总线来实现,直接用指针操作就行,不需要我们操心。但是,现在这个模块的寄存器在单片机的外面,那怎么实现单片机读写外部模块寄存器的操作呢
项目要求
通过通信线,实现单片机读写外挂模块寄存器的功能,其中至少要实现在指定的位置读寄存器和在指定的位置写寄存器两个功能。实现读写寄存器也就实现了对外挂模块的完全控制。
同步时序稳定性比异步时序更高,然后只有一根SDA数据线,变全双工为半双工,一根线兼具发送和接收,最大化利用资源。一主多从:单片机作为主机,主导I2C总线的运行,挂在在I2C总线的所有外部模块都是从机,从机只有被主机点名之后才能控制IIC总线,不能在未经允许的情况下去碰I2C总线,防止冲突。
I2C的硬件规定
电路如何链接,端口的输入输出模式是什么样的
1.左边CPU就是单片机,作为总线主机,功能包括对SCL线的完全控制,任何时候都是主机完全掌控SCL线。在空闲状态下,主机可以主动发起对SDA的控制。只有在从机发送数据和从机应答的时候,主机才会转交SDA的控制权给从机。
2.被控IC就是挂载在iic总线上的从机,可以是姿态传感器,OLED,存储器,时钟模块等。
3.从机权利比较小,对SCL时钟线,在任何时刻都只能被动读取,从机不允许控制SCL线,对于SDA数据线,从机不允许主动发起对SDA的控制,只有在主机发送读取从机的命令后,或者从机应答的时候,从机才能短暂的获取SDA的控制权。
4.接线要求所有从机SCL,SDA线都在一条线上与主机相连。
5.主机SCL可以配置成推挽输出,从机的SCL可以配置成浮空输入或者上拉输入,数据流向是主机发送,所有从机接收。
6. 主机SDA在发送的时候是输入,在接收的时候是输出,从机的SDA也在输入和输出之间切换。如果总线时序没协调好,就可能发生两个引脚同时处于输出状态。如果这时一个输出高电平,一个输出低电平,这个状态就是电源短路。需要避免。
7.为了避免这个问题,IIC禁止所有设备输出强上拉的高电平,采用外置弱上拉电阻加开漏输出的电路结构。所以设备的SCL和SDA均要配置成开漏输出模式。并且添加上拉电阻。
SCL和SDA的状态
当SCL和SDA都为高电平,为空闲状态时(起始和终止都是由主机产生的,故空闲时,从机始终放开)
当SCL为高电平,SDA为下降沿的的状态时,为开始发送数据,起始发送数据完成
当SCL为高电平,SDA为上升沿的的状态时,为数据发送完成
发送数据的过程为下图
当时钟线为高电平时,数据线上的数据必须保持稳定,比如时钟线为高时,数据线上的数据始终为高,完成逻辑1的传输,保持低电平则为0。(主机在接受之前,需要释放SDA,释放SDA相当于切换成输入模式,所有设备和主机都处于输入模式,当主机需要发送时,就可以主动去拉低SDA,而主机在接收的时候,必须主动释放SDA)
单片机向从设备写信息
读数据帧
I2C的软件规定
时序是如何定义的,字节如何传输,高位先行还是低位先行,一个完整的时序由哪些构成
起始和终止条件
起始条件:SCL高电平期间,SDA从高电平切换到低电平
即左下角,在IIC处于空闲状态时,SCL和SDA都处于高电平状态,也就是没有设备去碰SCL和SDA,SCL和SDA由外挂的上拉电阻拉高至高电平,总线处于平静的高电平状态,当主机需要进行数据收发时,需要产生起始条件,即SCL处于高电平,把SDA拉底,变成低电平,产生一个下降沿,当从机捕获到SCL高电平,SDA下降沿信号时,就会进行自身的复位,等待主机的召唤,在SDA下降沿之后,主机要把SCL拉底。原因是占用总线,且为了方便基本单元的拼接,即为了保证每个时序单元的SCL都是以低电平开始,低电平结束的,这样这些单元拼接起来,SCL才能续上。
终止条件:SCL高电平期间,SDA从低电平切换到高电平
即SCL先拉高,SDA再拉高,产生一个上升沿,这个上升沿触发终止条件,同时终止条件之后,SCL和SDA都是高电平。回归到平静状态。类似串口的起始位和停止位。
一个完整的数据帧总是以其实条件开始,终止条件结束,起始和终都是由主机产生的。再总线空闲状态时,从机双手放开。不允许触碰总线。
IIC发送一个字节基本时序
发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许数据有变化,依次循环上述过程8次,即可发送一个字节。
起始条件之后,第一个字节必须由主机发送,即最开始SCL低电平,主机如果想发送0,就拉底SDA到低电平,主机如果想发送1,就放手,SDA回弹到高电平。
在SCL低电平期间,允许改变SDA的电平,当交换好数据之后,主机松手时钟线,SCL回弹到高电平,高电平期间,从机读取SDA,在此期间SDA不允许变化。
SCL高电平期间,从机必须尽快读取SDA,一般在上升沿的时刻,从机就已经读取结束了,因为时钟是主机控制的,从机不知道何时会产生下降沿,所以要尽快读取,不然就会错过数据读取。
传输完成后,主机继续拉低SCL传输下一位数据,主机要在SCL下降沿之后尽快把数据放在SDA上,由于主机有主导权,所以只需要在低电平的任意时刻把数据放在SDA上即可。数据放完之后,主机再松手SCL,SCL高电平,从机读取这一位数据
传输数据流程:
主机拉底SCL,把数据放在SDA上,主机松开SCL,从机读取SDA的数据,再SCL的同步下,依次进行主机发送和从机接收,循环8次,就发送了8位数据,也就是一个字节。
由于是高位先行,第一个数据就是一个字节的最高位,B7,最后是B0.
要是突然进中断,时序就会在中断的位置不断拉长,SCL和SDA电平都暂停变化,传输也完全暂停,等中断结束后,主机回来继续操作,传输也不会出问题,这就是同步时序的好处。
由于是主机传输一个字节的数据,所以在整个时序里,SCL和SDA都有主机掌控。从机只能读取。
IIC接收一个字节基本时序
释放SDA相当于切换了输入模式,所有设备,包括主机都处于输入模式,当主机需要发送的时候,可以主动拉底SDA,主机被动接收的时候,必须先释放SDA,以免影响从机发送。
因为总线是线与的特征,任何一个设备拉低了,总线就是低电平,如果接收的时候不释放SDA,无论从机发送什么数据,总线都是低电平,从机就发送不了数据。所以主机在接收之前,需要释放SDA。
发送字节的流程:
低电平,主机放数据,高电平从机读数据。
接收一个字节的流程:
低电平,从机放数据,高电平主机读数据。
发送和接收的区别:主机接收之前要释放SDA,这时,从机取得了SDA的控制权,从机需要发送0,就把SDA拉底,从机需要发送1,就放手,SDA回弹高电平。
同样是低电平变换数据,高电平读取数据。
实线部分表示主机控制的电平,虚线部分表示从机控制的电平。SCL全程由主机控制,SDA,主机在接收之前要释放,交由从机控制。
从机的数据变换基本是贴着SCL下降沿进行的。主机可以在SCL高电平的任意时刻读取。
应答机制的顺序
发送,接收一个字节相当于——发送,接收其中一位。这一位就用来应答
在发送一个字节之后,要紧跟着发送接收应答的时序,用来判断从机有没有收到刚才给它的数据。即右边的图。
如果从机收到了数据,在应答位这里,主机释放SDA的时候,从机应该立刻把SDA拉低,即虚线由高到低的部分,在SCL高电平期间,主机读取应答位,如果应答位为0,就说明从机收到了数据,即SDA为低电平。
这个场景就是主机发送了一个字节,就说,有没有人收到了数据啊,然后把SDA放手,如果有人收到,就把SDA拉低,使它变为低电平。然后主机在SCL高电平的时候读取数据,发现SDA为0,就说明数据被接收到了。
如果主机松手后SDA跟着变为了高电平,说明没有从机接收到 数据,或者接收到了没有回应。这就是发送一个字节接收应答的流程
接受一个字节,发送应答流程:
接受一个字节之后,要给从机发送一个应答位,目的是告诉从机还要不要继续发生数据,如果从机发送一个数据后,得到了主机的应答,从机就继续发送,如果从机没有得到主机的应答,从机就会释放SDA,交出SDA的控制权,防止干扰主机之后的操作。这就是应答位的执行逻辑。
IIC的完整时序
MPU6050
软件I2C读写MPU6050
注意
端口不受限,可以任意指定
在使用软件模拟的I2C通信时,理论上可以将SCL(时钟线)和SDA(数据线)连接到单片机的任意引脚。由于软件实现了I2C通信的协议和时序,因此不依赖于特定的硬件引脚。但是需要注意的是,选择的引脚应具备足够的GPIO功能,包括输入输出控制、上拉电阻等。同时,还需要在软件中正确配置和操作这些引脚,以确保I2C通信的正确性和稳定性。因此,在选择引脚时,需要考虑到单片机的引脚功能和软件实现的复杂度,以及可能的干扰和布线问题。
程序整体框架(分别为应用层-驱动层-协议层)
具体步骤
1、I2C,建立I2C通信层的.c和.h模块,在通讯层里写好I2C底层的GPIO初始化和6个时序基本单元(起始、终止、发送一个字节、接收一个字节、发送应答和接收应答)
2、MPU6050,建立MPU6050的.c和.h模块,在这一层,我们将基于I2C通信的模块,来实现指定地址读、指定地址写,再实现写寄存器对芯片进行配置,读寄存器得到传感器数据
3、最终在main.c调用MPU6050的模块,初始化,拿到数据,显示数据
第一部分
完成软件I2C协议时序
/*单步
1、写SCL
2、写SDA
3、读SDA
*/
/*步骤
1、起始位
2、终止位
3、发送一个字节
4、接收一个字节
5、发送应答位
6、接收应答位
*/
第二部分
基于I2C协议,读写寄存器,来操控MPU6050
I2C部分代码解释
(1)发送字节
void MyI2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i ++)
{
MyI2C_W_SDA(Byte & (0x80 >> i));//先去最高位
MyI2C_W_SCL(1);//驱动时钟走一个脉冲
MyI2C_W_SCL(0);
}
}
- 除了终止条件,SCL以高电平结束,所有单元以低电平结束,方便各个单元的拼接
- 趁着SCL是低电平,先把数据放在SDA上,再MyI2C_W_SCL(1);MyI2C_W_SCL(0);
- SCL是原本是低电平,此时先高电平再低电平,使得SDA走一个时钟,读取SCL的数据
(2)读取字节
SCL低电平期间,从机将数据位依次放到SDA线上(高位先行)
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i, Byte = 0x00;
MyI2C_W_SDA(1);//主机释放SDA,从机把数据放到SDA,这时,主机释放SCL,SCL高电平,主机就能读取数据(高位先行)
for (i = 0; i < 8; i ++)
{
MyI2C_W_SCL(1);//SCL高电平,主机就可能读取数据了
if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}//如果不是高电平,就默认是写进0
MyI2C_W_SCL(0);
}
return Byte;//把接收的字节放回过去
}
接收一个字节,开始时,SCL为低电平,从机把数据放在SDA,为了防止主机干扰从机写入数据。
主机先释放SDA,释放SDA相当于切换为输入模式,故在SCL低电平时,从机会把数据放到SDA。
如果从机想发1,就释放SDA,发0,拉低SDA,然后主机释放SCL,在SCL高电平期间,读取SDA。
即在SCL为低电平的时候,SDA写入数据,等SCL释放时,读SDA数据,即为读写分离的方式。
故在读写数据的时候,SCL是在SDA低电平的时候变化,在高电平的时候不变,
在起始和终止的时候,SCL是在SDA高电平的时候变化
(3)接收应答
- 函数进来时,SCL为低电平,主机释放SDA,防止干扰从机,同时从机把应答位放在SDA上,
- SCL高电平,主机读取应答位,SCL低电平,进入下一个时序单元
AckBit = MyI2C_R_SDA();//此处不一定是1,
原因:I2C的引脚都是开漏输出+弱上拉配置,主机输出1,并不是强制SDA为高电平而是释放SDA
I2C是在进行通信,主机释放SDA,从机在的情况下,有义务将SDA拉低,故读到0,代表从机给了应答,1则从机应答
MyI2C.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
/*引脚配置层*/
/**
* 函 数:I2C写SCL引脚电平
* 参 数:BitValue 协议层传入的当前需要写入SCL的电平,范围0~1
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCL为低电平,当BitValue为1时,需要置SCL为高电平
*/
void MyI2C_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue); //根据BitValue,设置SCL引脚的电平
Delay_us(10); //延时10us,防止时序频率超过要求
}
/**
* 函 数:I2C写SDA引脚电平
* 参 数:BitValue 协议层传入的当前需要写入SDA的电平,范围0~0xFF
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SDA为低电平,当BitValue非0时,需要置SDA为高电平
*/
void MyI2C_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue); //根据BitValue,设置SDA引脚的电平,BitValue要实现非0即1的特性
Delay_us(10); //延时10us,防止时序频率超过要求
}
/**
* 函 数:I2C读SDA引脚电平
* 参 数:无
* 返 回 值:协议层需要得到的当前SDA的电平,范围0~1
* 注意事项:此函数需要用户实现内容,当前SDA为低电平时,返回0,当前SDA为高电平时,返回1
*/
uint8_t MyI2C_R_SDA(void)
{
uint8_t BitValue;
BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11); //读取SDA电平
Delay_us(10); //延时10us,防止时序频率超过要求
return BitValue; //返回SDA电平
}
/**
* 函 数:I2C初始化
* 参 数:无
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,实现SCL和SDA引脚的初始化
*/
void MyI2C_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB10和PB11引脚初始化为开漏输出
/*设置默认电平*/
GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11); //设置PB10和PB11引脚初始化后默认为高电平(释放总线状态)
}
/*协议层*/
/**
* 函 数:I2C起始
* 参 数:无
* 返 回 值:无
*/
void MyI2C_Start(void)
{
MyI2C_W_SDA(1); //释放SDA,确保SDA为高电平
MyI2C_W_SCL(1); //释放SCL,确保SCL为高电平
MyI2C_W_SDA(0); //在SCL高电平期间,拉低SDA,产生起始信号
MyI2C_W_SCL(0); //起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}
/**
* 函 数:I2C终止
* 参 数:无
* 返 回 值:无
*/
void MyI2C_Stop(void)
{
MyI2C_W_SDA(0); //拉低SDA,确保SDA为低电平
MyI2C_W_SCL(1); //释放SCL,使SCL呈现高电平
MyI2C_W_SDA(1); //在SCL高电平期间,释放SDA,产生终止信号
}
}
/**
* 函 数:I2C接收一个字节
* 参 数:无
* 返 回 值:接收到的一个字节数据,范围:0x00~0xFF
*/
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i, Byte = 0x00; //定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到
MyI2C_W_SDA(1); //接收前,主机先确保释放SDA,避免干扰从机的数据发送
for (i = 0; i < 8; i ++) //循环8次,主机依次接收数据的每一位
{
MyI2C_W_SCL(1); //释放SCL,主机机在SCL高电平期间读取SDA
if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);} //读取SDA数据,并存储到Byte变量
//当SDA为1时,置变量指定位为1,当SDA为0时,不做处理,指定位为默认的初值0
MyI2C_W_SCL(0); //拉低SCL,从机在SCL低电平期间写入SDA
}
return Byte; //返回接收到的一个字节数据
}
/**
* 函 数:I2C发送应答位
* 参 数:Byte 要发送的应答位,范围:0~1,0表示应答,1表示非应答
* 返 回 值:无
*/
void MyI2C_SendAck(uint8_t AckBit)
{
MyI2C_W_SDA(AckBit); //主机把应答位数据放到SDA线
MyI2C_W_SCL(1); //释放SCL,从机在SCL高电平期间,读取应答位
MyI2C_W_SCL(0); //拉低SCL,开始下一个时序模块
}
/**
* 函 数:I2C接收应答位
* 参 数:无
* 返 回 值:接收到的应答位,范围:0~1,0表示应答,1表示非应答
*/
uint8_t MyI2C_ReceiveAck(void)
{
uint8_t AckBit; //定义应答位变量
MyI2C_W_SDA(1); //接收前,主机先确保释放SDA,避免干扰从机的数据发送
MyI2C_W_SCL(1); //释放SCL,主机机在SCL高电平期间读取SDA
AckBit = MyI2C_R_SDA(); //将应答位存储到变量里
MyI2C_W_SCL(0); //拉低SCL,开始下一个时序模块
return AckBit; //返回定义应答位变量
}
MPU6050.c
#include "stm32f10x.h" // Device header
#include "MyI2C.h"
#include "MPU6050_Reg.h"
#define MPU6050_ADDRESS 0xD0 //MPU6050的I2C从机地址
/**
* 函 数:MPU6050写寄存器
* 参 数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述
* 参 数:Data 要写入寄存器的数据,范围:0x00~0xFF
* 返 回 值:无
*/
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
MyI2C_Start(); //I2C起始
MyI2C_SendByte(MPU6050_ADDRESS); //发送从机地址,读写位为0,表示即将写入
MyI2C_ReceiveAck(); //接收应答
MyI2C_SendByte(RegAddress); //发送寄存器地址
MyI2C_ReceiveAck(); //接收应答
MyI2C_SendByte(Data); //发送要写入寄存器的数据
MyI2C_ReceiveAck(); //接收应答
MyI2C_Stop(); //I2C终止
}
/**
* 函 数:MPU6050读寄存器
* 参 数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述
* 返 回 值:读取寄存器的数据,范围:0x00~0xFF
*/
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
MyI2C_Start(); //I2C起始
MyI2C_SendByte(MPU6050_ADDRESS); //发送从机地址,读写位为0,表示即将写入
MyI2C_ReceiveAck(); //接收应答
MyI2C_SendByte(RegAddress); //发送寄存器地址
MyI2C_ReceiveAck(); //接收应答
MyI2C_Start(); //I2C重复起始
MyI2C_SendByte(MPU6050_ADDRESS | 0x01); //发送从机地址,读写位为1,表示即将读取
MyI2C_ReceiveAck(); //接收应答
Data = MyI2C_ReceiveByte(); //接收指定寄存器的数据
MyI2C_SendAck(1); //发送应答,给从机非应答,终止从机的数据输出
MyI2C_Stop(); //I2C终止
return Data;
}
/**
* 函 数:MPU6050初始化
* 参 数:无
* 返 回 值:无
*/
void MPU6050_Init(void)
{
MyI2C_Init(); //先初始化底层的I2C
/*MPU6050寄存器初始化,需要对照MPU6050手册的寄存器描述配置,此处仅配置了部分重要的寄存器*/
MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01); //电源管理寄存器1,取消睡眠模式,选择时钟源为X轴陀螺仪
MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00); //电源管理寄存器2,保持默认值0,所有轴均不待机
MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09); //采样率分频寄存器,配置采样率
MPU6050_WriteReg(MPU6050_CONFIG, 0x06); //配置寄存器,配置DLPF
MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); //陀螺仪配置寄存器,选择满量程为±2000°/s
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); //加速度计配置寄存器,选择满量程为±16g
}
/**
* 函 数:MPU6050获取ID号
* 参 数:无
* 返 回 值:MPU6050的ID号
*/
uint8_t MPU6050_GetID(void)
{
return MPU6050_ReadReg(MPU6050_WHO_AM_I); //返回WHO_AM_I寄存器的值
}
/**
* 函 数:MPU6050获取数据
* 参 数:AccX AccY AccZ 加速度计X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
* 参 数:GyroX GyroY GyroZ 陀螺仪X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
* 返 回 值:无
*/
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
uint8_t DataH, DataL; //定义数据高8位和低8位的变量
DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H); //读取加速度计X轴的高8位数据
DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L); //读取加速度计X轴的低8位数据
*AccX = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H); //读取加速度计Y轴的高8位数据
DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L); //读取加速度计Y轴的低8位数据
*AccY = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H); //读取加速度计Z轴的高8位数据
DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L); //读取加速度计Z轴的低8位数据
*AccZ = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H); //读取陀螺仪X轴的高8位数据
DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L); //读取陀螺仪X轴的低8位数据
*GyroX = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H); //读取陀螺仪Y轴的高8位数据
DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L); //读取陀螺仪Y轴的低8位数据
*GyroY = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H); //读取陀螺仪Z轴的高8位数据
DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L); //读取陀螺仪Z轴的低8位数据
*GyroZ = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"
uint8_t ID; //定义用于存放ID号的变量
int16_t AX, AY, AZ, GX, GY, GZ; //定义用于存放各个数据的变量
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
MPU6050_Init(); //MPU6050初始化
/*显示ID号*/
OLED_ShowString(1, 1, "ID:"); //显示静态字符串
ID = MPU6050_GetID(); //获取MPU6050的ID号
OLED_ShowHexNum(1, 4, ID, 2); //OLED显示ID号
while (1)
{
MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ); //获取MPU6050的数据
OLED_ShowSignedNum(2, 1, AX, 5); //OLED显示数据
OLED_ShowSignedNum(3, 1, AY, 5);
OLED_ShowSignedNum(4, 1, AZ, 5);
OLED_ShowSignedNum(2, 8, GX, 5);
OLED_ShowSignedNum(3, 8, GY, 5);
OLED_ShowSignedNum(4, 8, GZ, 5);
}
}
MPU6050_Reg.c
#ifndef __MPU6050_REG_H
#define __MPU6050_REG_H
#define MPU6050_SMPLRT_DIV 0x19//采样率分频
#define MPU6050_CONFIG 0x1A//配置寄存器
#define MPU6050_GYRO_CONFIG 0x1B//陀螺仪配置寄存器
#define MPU6050_ACCEL_CONFIG 0x1C//加速度计配置寄存器
#define MPU6050_ACCEL_XOUT_H 0x3B//加速度寄存器X轴的高8位
#define MPU6050_ACCEL_XOUT_L 0x3C//加速度寄存器X轴的低8位
#define MPU6050_ACCEL_YOUT_H 0x3D
#define MPU6050_ACCEL_YOUT_L 0x3E
#define MPU6050_ACCEL_ZOUT_H 0x3F
#define MPU6050_ACCEL_ZOUT_L 0x40
#define MPU6050_TEMP_OUT_H 0x41
#define MPU6050_TEMP_OUT_L 0x42
#define MPU6050_GYRO_XOUT_H 0x43//陀螺仪的x轴
#define MPU6050_GYRO_XOUT_L 0x44
#define MPU6050_GYRO_YOUT_H 0x45
#define MPU6050_GYRO_YOUT_L 0x46
#define MPU6050_GYRO_ZOUT_H 0x47
#define MPU6050_GYRO_ZOUT_L 0x48
#define MPU6050_PWR_MGMT_1 0x6B//电源管理寄存器1,地址是0x6B
#define MPU6050_PWR_MGMT_2 0x6C//电源管理寄存器2,地址是0x6B
#define MPU6050_WHO_AM_I 0x75
#endif
从机地址
该设备中只有AD0一个引脚,故只有两个名字,若有AD0和AD1两个引脚,则有4个名字
最后一位为0,否则就是把控制权交出去
I2C从机地址:1101000(AD0=0)——>1101 0000 0xD0
1101001(AD0=1)——>1101 0010 0xD2