stm32学习笔记:I2C通信协议原理和软件I2C读写MPU6050

概述

第一块:介绍协议规则,然后用软件模拟的形式来实现协议。

第二块:介绍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

 验证结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/431780.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

OJ输入问题+准备

写在之前&#xff1a; 发现题目输入是这样的&#xff1a; 我的问题&#xff1a;如何通过空格分割这些输入的字符串并分别保存&#xff01;&#xff01;&#xff08;C语言scanf好解决一点但我选择C....&#xff09; C引入了ostringstream、istringstream、stringstream这三个类…

【无标题】计算机主要应用于哪些领域

科学计算&#xff08;或称为数值计算&#xff09;、数据处理&#xff08;信息管理&#xff09;、辅助工程、生产自动化、人工智能。1、科学计算&#xff08;或称为数值计算&#xff09;&#xff1a;早期的计算机主要用于科学计算。目前&#xff0c;科学计算仍然是计算机应用的一…

计算机组成原理-累加器实验——沐雨先生

一、实验目的 1.理解累加器的概念和作用 2.连接运算器、存储器和累加器&#xff0c;熟悉计算机的数据通路 3.掌握使用微命令执行各种操作的方法。 二、实验要求 1.做好实验预习&#xff0c;读懂实验电路图&#xff0c;熟悉实验元器件的功能特性和使用方法。在实验之前设计…

自动控制原理——根轨迹法

本文中所有的截图都来自于西北工业大学卢京潮教授的ppt&#xff0c;侵删。 根轨迹的基本概念 根轨迹——系统性能指标 举例说明&#xff1a; 在使用根轨迹法时&#xff0c;一般说根轨迹就说说闭环意义上的根轨迹&#xff0c;没有开环根轨迹一说。我们习惯使用首1标准型&#…

【prompt四】Domain Prompt Learning for Efficiently Adapting CLIP to Unseen Domains

motivation 领域泛化(DG)是一个复杂的迁移学习问题,旨在学习未知领域的可泛化模型。最近的基础模型(FMs)对许多分布变化都具有鲁棒性,因此,应该从本质上提高DG的性能。在这项工作中,我们研究了采用视觉语言基础模型CLIP来解决图像分类中的DG问题的通用方法。虽然ERM使用标…

Node.js安装及环境配置详细教程

一、下载Node.js安装包 官网下载链接[点击跳转] 建议下载LTS版本&#xff08;本教程不适用于苹果电脑&#xff09; 二 、安装Node.js 2.1 下载好安装包后双击打开安装包&#xff0c;然后点击Next 2.2 勾选同意许可后点击Next 2.3 点击Change选择好安装路径后点击Next&#…

Ubuntu下anaconda迁移到另外的目录

文章目录 前言一、原因二、迁移1.复制到指定迁移目录2. 修改复制后的anaconda3 内容3. 修改对应搭建的每个环境的pip4.修改系统配置文件&#xff0c;使得设置生效 三、实际测试四、总结 前言 好记性不如烂笔头&#xff0c;简单的记录下在ubantu18.04下迁移anaconda的目录 一、…

SpringBoot自定义注解+反射实现 excel 导入的数据组装及字段校验

在前段时间的开发工作中&#xff0c;接手了一个很简单&#xff0c;很普通的开发任务。 要求实现一个单表的基础数据的批量导入功能。 评估下来&#xff0c;用户每次批量导入的数据量也就几千条&#xff0c;也不大。 是不是很简单&#xff0c;没有骗你们吧。但是呢&#xff0…

常用工具——Gradle

前言 实践是最好的学习方式&#xff0c;技术也如此。 文章目录 前言一、Gradle 简介二、文件结构详解 一、Gradle 简介 Gradle 文件是一个独立于 android 之外的一个东西&#xff1b; 是什么 gradle 就是编译、打包 Android 工程的一个构建工具&#xff1b;build.gradle 文件&…

中仕公考:非应届生能考三支一扶吗?

如果是非应届生身份能参加三支一扶考试吗? “三支一扶”是一项公益性的就业计划&#xff0c;全称为“支持教育、支持农村、支持医疗和扶贫”。该计划主要是针对大学生毕业生设置的&#xff0c;通过招募他们到基层单位工作&#xff0c;以解决基层单位人才短缺的问题&#xff0…

MapReduce内存参数自动推断

MapReduce内存参数自动推断。在Hadoop 2.0中&#xff0c;为MapReduce作业设置内存参数非常繁琐&#xff0c;涉及到两个参数&#xff1a;mapreduce.{map,reduce}.memory.mb和mapreduce.{map,reduce}.java.opts&#xff0c;一旦设置不合理&#xff0c;则会使得内存资源浪费严重&a…

java中this关键字的使用

this关键字的使用 this的用法1&#xff09;this.data2&#xff09;this.method&#xff1b;3&#xff09;this() this的用法 1&#xff09;this.data&#xff1b; &#xff08;访问属性&#xff09; 2&#xff09;this.method&#xff1b; &#xff08;访问方法&#xff09; 3&…

wait() 、notify()、notifyAll() 的详细用法

文章目录 &#x1f490;wait() 讲解&#x1f490;notify() 讲解&#x1f490;notifyAll()&#x1f4a1;wait() 和 sleep() 的区别 首先&#xff0c;我们知道&#xff0c;线程的执行顺序是随机的(操作系统随机调度的&#xff0c;抢占式执行)&#xff0c;但是有时候&#xff0c;我…

软件测试面试题(全)

【软件测试面试突击班】2024吃透软件测试面试最全八股文攻略教程&#xff0c;一周学完让你面试通过率提高90%&#xff01;&#xff08;自动化测试&#xff09; 1.B/S架构和C/S架构区别 B/S 只需要有操作系统和浏览器就行&#xff0c;可以实现跨平台&#xff0c;客户端零维护&a…

CSS全局样式的设置,web开发交流

面试题 HTML 1&#xff0c;html5有哪些新特性&#xff1f; 2&#xff0c;html5移除了那些元素&#xff1f; 3&#xff0c;如何处理HTML5新标签的浏览器兼容问题 戳这里领取完整开源项目&#xff1a;【一线大厂前端面试题解析核心总结学习笔记Web真实项目实战最新讲解视频】…

智能硬件 | AI PC新市场,英特尔、高通、AMD、苹果谁能拔得头筹?

我们普通人和大模型的距离有多远&#xff1f;AI发展到2024年&#xff0c;已经附着在各种智能硬件上了&#xff0c;什么AI PC&#xff0c;AI手机&#xff0c;AI蓝牙音箱&#xff0c;AI学习机&#xff0c;AI鼠标等等&#xff0c;但其实虽然很多产品加上了个AI的名头&#xff0c;但…

xss.haozi:0x00

0x00没有什么过滤所以怎么写都没有关系有很多解 <script>alert(1)</script>

实现session共享的方法总结完整版

文章目录 实现session共享的方法总结完整版1、使用共享数据库&#xff1a;2、使用粘性会话&#xff08;Sticky Session&#xff09;&#xff1a;3、使用缓存系统&#xff1a;4、使用分布式文件系统&#xff1a;5、使用中央认证服务&#xff1a;6、使用会话复制&#xff1a;7、使…

LLM 模型量化推理速度评测

最近了解了下些常见的推理和加速方案&#xff1a; 1、量化方案&#xff1a; gptq、quantization、int8、int4、AWQ、Speculative Decoding、GGUF 2、Attention加速方案&#xff1a; atten的不同种类fused attention 3、内存层面&#xff1a; kv_cache策略、page_attention…

3.4作业

课上代码复习&#xff1a; 广播接收端代码: #include<myhead.h> int main(int argc, const char *argv[]) {//创建套接字int rfd socket(AF_INET,SOCK_DGRAM,0);if(rfd -1){perror("socket error");return -1;}printf("rfd %d\n",rfd);//填充地…
最新文章