当前位置: 首页 > news >正文

STM32单片机入门学习——第33节: [10-3] 软件I2C读写MPU6050

写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难,但我还是想去做!

本文写于:2025.04.10

STM32开发板学习——第33节: [10-3] 软件I2C读写MPU6050

  • 前言
  • 开发板说明
  • 引用
  • 解答和科普
  • 一、硬件接线
  • 二、软件实现
  • 问题
  • 总结

前言

   本次笔记是用来记录我的学习过程,同时把我需要的困难和思考记下来,有助于我的学习,同时也作为一种习惯,可以督促我学习,是一个激励自己的过程,让我们开始32单片机的学习之路。
   欢迎大家给我提意见,能给我的嵌入式之旅提供方向和路线,现在作为小白,我就先学习32单片机了,就跟着B站上的江协科技开始学习了.
   在这里会记录下江协科技32单片机开发板的配套视频教程所作的实验和学习笔记内容,因为我之前有一个开发板,我大概率会用我的板子模仿着来做.让我们一起加油!
   另外为了增强我的学习效果:每次笔记把我不知道或者问题在后面提出来,再下一篇开头作为解答!

开发板说明

   本人采用的是慧净的开发板,因为这个板子是我N年前就买的板子,索性就拿来用了。另外我也购买了江科大的学习套间。
   原理图如下
1、开发板原理图
在这里插入图片描述
2、STM32F103C6和51对比
在这里插入图片描述
3、STM32F103C6核心板
在这里插入图片描述

视频中的都用这个开发板来实现,如果有资源就利用起来。另外也计划实现江协科技的套件。

下图是实物图
在这里插入图片描述

引用

【STM32入门教程-2023版 细致讲解 中文字幕】
还参考了下图中的书籍:
STM32库开发实战指南:基于STM32F103(第2版)
在这里插入图片描述
数据手册
在这里插入图片描述

解答和科普

一、硬件接线

第一部分:完成软件I2C协议的时序;
第二部分:基于I2C协议读写寄存器,来操控MPU6050;

在这里,VCC和GND分别接到电源正负极进行供电,然后SCL,这里我引到了STM32的PB1O号引脚,SDA接在了PB11引脚,由于这个代码实用的是软件I2C,就是用普通的GPIO口,手动翻转电平实现的协议,它并不需要STM32内部的外设资源支持,所以这个端口可以任意指定。然后配置并操作SCL和SDA对应的端口就行了。
根据I2C协议的硬件规定,SCL和SDA都应该外挂一个上拉电阻,但是这里并没有外挂上拉电阻,因为上一节讲过了,这个模块内部自带了上拉电阻。AD0因为模块内置下拉电阻,悬空的时候是接地,最后INT,中断信号输出脚,暂时不用,可以不接;

在这里插入图片描述

二、软件实现

代码框架:这个框架和之前51单片机的I2C是一样的,这里首先建立I2C通信层的.C和.H模块,在通信层里,写好I2C底层的GPIO初始化,和6个时序基本单元,也就是起始、终止,发送一个字节,接收一个字节,发送应答和接受应答,写好I2C通信协议之后,再建立MPU6050模块的.C和.H模块,在这一层,我们讲基于I2C通信的模块,来实现指定地址读、指定地址写,再实现写寄存器对芯片进行配置,读寄存器得到传感器数据,最后在main.c里,调用MPU6050的模块,初始化,拿到数据,显示数据,这就是程序的整体框架结构。

I2C初始化:当前接线SCL(PB10),SDA(PB11)
第一个任务,把SCL和SDA都初始化为开漏输出模式;
虽然开漏输出,名字带了个输出,但这并不代表只能输出,开漏输出模式仍然可以输入,输入时,先输出1,再直接读取输入数据寄存器就行了,这个过程,讲I2C硬件规定时介绍过。
第二个任务,把SCL和SDA置高电平;
调用SetBits,把GPIO的Pin_10和Pin_11,都置高电平,这样I2C初始化就完成了。

void MyI2C_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	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);
	
	GPIO_SetBits(GPIOB,GPIO_Pin_10|GPIO_Pin_11);
}

调用MyI2C_Init()函数,PB10和PB11两个端口,就被初始化为开漏输出模式,然后释放总线,SCL和SDA处于高电平,此时I2C总线处于空闲模式,然后接下来,就根据波形,来完成I2C的6个时序基本单元。

起始单元:
在这里插入图片描述
我们首先把SCL和SDA都确保释放,然后先拉低SDA,再拉低SCL,这样就能产生起始条件了,那在这里,可以不断地调用SetBits 和ResetBits来手动翻转高低电平,但是这样做的话,会在后面的程序中,出现非常多的地方,来制定这个GPIO的端口号,一方面这样做语义不是很明显,另一方面如果我们之后需要换一个端口,那就需要改动非常多的地方,所以这时,我们就需要在上面做个定义,把这个端口号统一替换一个名字,这样无论是语义还是端口的修改,都会非常方便,那给端口号换一个名字呢,有很多方法都能实现功能,在51单片机中,我们一般用Sbit来定义端口的名称,但是Sbit并不是标准C语言的语法,STM32也不支持这样做,那这里,一种简单的替换方法就是宏定义,

#define SCL_PORT  GPIOB
#define SCL_Pin   GPIO_Pin_10

之后如果想释放SCL,就GPIO_SetBits(SCL_PORT, SCL_Pin);
而且修改引脚的时候,直接在上面修改一下宏定义,下面所有引用宏定义的地方,都会自动更改,这时一种简单可行的方法。如果你觉得每次定义都要定义PORT和PIN,比较麻烦,还可以把这整个函数用宏定义进行替换,

/*引脚配置*/
#define OLED_W_SCL(x)		GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))
#define OLED_W_SDA(x)		GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))

这里,直接用宏定义把GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x)),包括参数在内的整个函数,用宏定义替换了个名字,新的名字叫做OLED_W_SCL(x),之后再需要操作SCL的时候,就可以使用这个新名字,这样函数比较简短,语义比较明确,并且这里使用了带参数的宏定义,也就是有参宏,在宏定义后面加一个括号,里面写入形参,那在实际引用的时候,比如这里调用OLED_W_SCL(1);实参给1,那替换的时候,这里的实参1就对应这里的形参x,然后再进一步替换到函数里的x,经过有参宏替换之后,这句话就相当于,GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(1)),然后把x替换为1,这两句是一样的效果;
还有就这这种方法,在移植到其他库或者其他种类单片机时,不知道怎么修改,另外这种宏定义的方法,如何换到一个主频很高的单片机中,需要对软件时序进行延迟操作的时候,也不太方便进一步修改,所以综合上遇到的宏定义替换的缺点,在这里就直接再套个函数得了,这样即容易理解又方便加软件延迟,所以这里,直接定义函数,对操作端口的库函数进行封装。

void MyI2C_W_SCL (uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue );
}

这样套一个函数替换之后,我后面再调用这个W_SCL,参数给1或0,就可以释放或拉低SCL了,如果说你要把这个程序移植到别的单片机,就可以把这个函数里的操作,替换为其他单片机对应的操作,比如SCL是51单片机P10口,就可以把这句替换为P10=BitValue,这样就行了,

void MyI2C_W_SCL (uint8_t BitValue)
{
	P10= BitValue;
}

另外如果你主频比较快,这里也非常方便加一些延时,比如这里要求每次操作引脚后,都要延迟10us,那可以这样,先#include Delay.h

void MyI2C_W_SCL (uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue );
	Delay_us(10);
}

这样就能很方便地进行引脚延时操作了。
对于MPU6050来说,经过实测,对于STM32F1系列这里即使不加任何延时,这个引脚翻转速度,MPU6050也能跟的上,但是为了保险起见,我们还是延时10us吧,I2C可以慢一些,多慢都行,但是快的话,就是要看一下手册里对时序时间的要求;

void MyI2C_W_SCL (uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue );
	Delay_us(10);
}

void MyI2C_W_SDA (uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)BitValue );
	Delay_us(10);
}

另外我们还要再来个读SDA的函数,因为在STM32库函数中,读和写不是同一个寄存器,

uint8_t MyI2C_R_SDA(void)
{
	uint8_t BitValue;
	BitValue=GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);
	Delay_us(10);
	return BitValue;
}

返回读到SDA线的电平。

有了这三个函数的封装,就实现了函数名称、端口号的替换、同时也很方便地修改时序的延时,当我们需要替换端口,或者把这个程序移植到别的单片机中时,就只需要对这前四个函数里的操作对应更改,后面的函数,我们都调用这里的封装的新名称进行操作,这样在移植到时候,后面的部分就不需要再进行修改了。
在这里插入图片描述
我们需要把SCL和SDA都释放,也就是都输出1,然后复制一下,先拉低SDA,再拉低SCL,这就是起始条件的执行逻辑,这里注意一下,在这前面,我们最好把释放SDA的放在前面,这样保险一点,如果起始条件之前SCL和SDA都是高电平了,那先释放哪一个都是一样的效果,但是在这里,我们这个Start还要兼容这里的重复起始条件SR,Sr最开始,SCL是低电平,SDA电平不敢确定,所以保险起见,我们趁SCL是低电平,先确保释放SDA,再释放SCL,这时SDA和SCL都是高电平,然后再拉低SDA,拉低SCL,这样这个Start就可以兼容起始条件和重复起始条件了,所以我们这个起始条件是这个逻辑。

void MyI2C_Start(void)
{	
	MyI2C_W_SDA(1);
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(0);
	MyI2C_W_SDA(0);
}

接下来,是终止条件:
如果Stop开始时,SCL和SDA都已经是低电平了,那就先释放SCL,再释放SDA就行了,但是在这个时序单元开始时,SDA并不一定是低电平,所以为了确保之后释放SDA能产生上升沿,就要在时序单元开始时,先拉低SDA,然后再释放SCL、释放SDA,所以在程序里Stop的逻辑是:先拉低SDA,再释放SCL,再释放SDA,这就是终止条件;

void MyI2C_Stop(void)
{
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(1);
}

终止条件后,SCL和SDA都回归到高电平。

发送一个字节:
发送一个字节时序开始时,SCL是低电平,实际上除了终止条件,SCL以高电平结束,所有的单元我们都会保证SCL以低电平结束,这样方便各个单元的拼接。
在这里插入图片描述
这里,SCL低电平,变换数据,高电平,保持数据稳定,由于是高位先行,所以变化数据的时候,按照先放高位,再放次高位,等等,最后最低位,这样的顺序,依次把一个字节的每一位放在SDA线上,每放完一位后,执行释放SCL,拉低SCL的操作,驱动时钟运转,那在程序中的操作就是,首先趁SCL低电平,先把Byte的最高位放在SDA线上,也就是这样写SDA, MyI2C_W_SDA(Byte&0x80);这是常见操作,用按位与的方式,取出数据的某一位或某几位,如果Byte的最高位为1,结果就是0x80,如果Byte的最高位为0,结果就是0x00,这就相当于把Byte的最高位取出来了,这个式子计算结果为0x 80或0x00,而不是1或0,考虑到调用的函数,具有非0即1的特性,所以即使传入0x80,也相当于传入1,不放心的话可以写 if(Byte &0x80==0),MyI2C_W_SDA(0); else { MyI2C_W_SDA(1)};这样最高位就放好了,我们再释放SCL,释放SCL后,从机就会立刻把我刚才放在SDA的数据读走,再拉低SCL,我们可以继续放下一位数据了,下一位是次高位,然后就是0x40,然后再驱动SCL,来一个时钟,所以这里写个for循环,循环8次;

void MyI2C_SendByte(uint8_t Byte)
 {
	 MyI2C_W_SDA(Byte&0x80);
	 MyI2C_W_SCL(1);
	 MyI2C_W_SCL(0);
 }
 void MyI2C_SendByte(uint8_t Byte)
 {
	 uint8_t i=0;
	 for(i=0;i<8;i++)
	 {
	  MyI2C_W_SDA(Byte&(0x80>>i));
	  MyI2C_W_SCL(1);
	  MyI2C_W_SCL(0);
	 }
 }

接收一个字节:
在这里插入图片描述
SCL低电平,此时从机需要把数据放到SDA上,为了防止主机干扰从机写入数据,主机需要先释放SDA,释放SDA也相当于切换为输入模式,那在SCL低电平时,从机会把数据放到SDA,如果从机想发1,就释放SDA,如果从机想发0,就拉低SDA,然后主机释放SCL,在SCL高电平期间,读取SDA,再拉低SCL,低电平期间,从机就会把下一位数据放到SDA上,这样重复8次,主机就能读到一个字节了,可以发现:SCL低电平变化数据,高电平读取数据,实际上就是一种读写分离的设计,低电平时间定义为写的时间,高电平时间定义为读的时间。那在SCL高电平时间,如果你非要动SDA,来破坏游戏规则的话,那这个信号就是起始条件和终止条件,SCL高电平时,SDA下降沿为起始条件,SDA上升沿为终止条件,这个设计也保证了起始和终止的特异性,能够让我们在连续不断地波形中,快速定位起始和终止,因为起始和终止和数据传输的波形有本质区别,数据传输,SCL高电平不许动SDA,起始终止,SCL高电平必须动SDA.

接受一个自己,进来之后SCL是低电平,主机释放SDA,从机把数据放到SDA,这时主机释放SCL,SCL高电平,主机就能读取数据了,读取数据用uint8_t MyI2C_R_SDA(void)。

uint8_t MyI2C_ReceiveByte(void)
 {
	 uint8_t Byte=0x00;
	MyI2C_W_SDA(1);
	MyI2C_W_SCL(1); 
	if(MyI2C_R_SDA()==1){Byte|=0x80;}
	MyI2C_W_SCL(0)
 }

写个for循环,

uint8_t MyI2C_ReceiveByte(void)
 {
	uint8_t i,Byte=0x00;
	MyI2C_W_SDA(1);
	 for(i=0;i<8;i++)
	 {
		MyI2C_W_SCL(1); 
	   if(MyI2C_R_SDA()==1){Byte|=(0x80>>i);}
	   MyI2C_W_SCL(0);
	 }
	return Byte;
 }

发送应答和接收应答:这里发送应答和接收应答,起始就是发送一个字节和接收一个自己的简化版,发送一个字节是发8位,发送应答就是发1位,接收一个字节是收8位,接受应答就是收1位,
在这里插入图片描述

uint8_t MyI2C_ReceiveAck(void)
 {
	uint8_t AckBit;
	MyI2C_W_SDA(1);
	MyI2C_W_SCL(1); 
	AckBit=MyI2C_R_SDA();
	MyI2C_W_SCL(0);
	
	return AckBit;
 }

为什么你SDA置后,再读取肯定是1呀。
第一, I2C的引脚都是开漏输出+弱上拉的配置,主机输出1,并不是强制SDA为高电平,而是释放SDA,
第二, 你要明白I2C是在进行通信,主机释放了SDA,从机又不是在外面看戏,从机如果在的话,它是有义务在此时把SDA再拉低的,所以这里,即使之前主机把SDA置1了,之后在读取SDA,读到的值也可能是0,读到0,代表从机给了应答。
还有就是主机每次循环读取SDA的时候,这个读取到的数据是从机控制的,也正是从机想要给我们发送的数据,所以这个时序叫做接收一个字节。
测试:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
#include "OLED.h"
#include "MyI2C.h"


int main(void)
{
							
	OLED_Init();
	MyI2C_Init();
	MyI2C_Start();
	MyI2C_SendByte(0xD0);			//½Óµ½¸ßµçƽÔÙ²âÊÔ
	uint8_t ACK=MyI2C_ReceiveAck();
	MyI2C_Stop();
	
	OLED_ShowNum(2,1,ACK,3);
	
	OLED_ShowString(1,1,"Hello STM32 MCU");


	while(1)
	{
	
		

	}
}

接下来写MPU6050上的程序:
初始化:

void MPU6050_Init(void)
{
	MyI2C_Init();
}

字节写:

void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data)
{
	MyI2C_Start();
	MyI2C_SendByte(MPU6050_ADDRESS);
	MyI2C_ReceiveAck();
	MyI2C_SendByte(RegAddress);	//地址指针
	MyI2C_ReceiveAck();
	MyI2C_SendByte(Data);
	MyI2C_ReceiveAck();
	MyI2C_Stop();
}

字节读:

uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	MyI2C_Start();
	MyI2C_SendByte(MPU6050_ADDRESS);
	MyI2C_ReceiveAck();
	MyI2C_SendByte(RegAddress);	//µØÖ·Ö¸Õë
	MyI2C_ReceiveAck();
	
	MyI2C_Start();
	MyI2C_SendByte(MPU6050_ADDRESS|0x01);
	MyI2C_ReceiveAck();
	Data=MyI2C_ReceiveByte();
	MyI2C_SendAck(1);	//Èç¹ûÏë¶Á¶à¸ö×Ö½Ú£¬¾ÍÒª¸øÓ¦´ð0
	MyI2C_Stop();
	
	return  Data;
}

测试

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
#include "OLED.h"
#include "MPU6050.h"


int main(void)
{
							
	OLED_Init();
	/*
	MyI2C_Init();
	MyI2C_Start();
	MyI2C_SendByte(0xD0);	//0xD2 //½Óµ½¸ßµçƽÔÙ²âÊÔ
	uint8_t ACK=MyI2C_ReceiveAck();
	MyI2C_Stop();
	
	OLED_ShowNum(2,1,ACK,3);
	
	OLED_ShowString(1,1,"Hello STM32 MCU");
*/
	
	MPU6050_Init();
	uint8_t ID = MPU6050_ReadReg(0x75);
	OLED_ShowHexNum(1,1,ID,2);
	/*д¼Ä´æÆ÷£¬Ê×ÏȽӴ¥Ë¯Ãßģʽ*/
	MPU6050_WriteReg(0x6B,0x00);
	MPU6050_WriteReg(0x19,0xAA);
	uint8_t ID1 = MPU6050_ReadReg(0x75);
	OLED_ShowHexNum(1,5,ID1,2);
	
	while(1)
	{
	
		

	}
}

到这里起始已经完成了对存储器的读和写和AT24C02是一样的,寄存器也是一种存储器,只不过普通的存储器只能读和写,里面的数据并没有赋予什么实际意义,但是寄存器就不一样了,寄存器的每一位数据,都对应了硬件电路的状态,寄存器和外设的硬件电路,是可以进行互动的,所以程序到这里,我们就可以通过寄存器来控制电路了。

#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
#define	MPU6050_ACCEL_XOUT_L	0x3C
#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
#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
#define	MPU6050_PWR_MGMT_2		0x6C
#define	MPU6050_WHO_AM_I		0x75

#endif

==写配置寄存器初始状态==
void MPU6050_Init(void)
{
	MyI2C_Init();
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);		//½â³ý˯Ãß £¬Ñ¡ÔñÍÓÂÝÒÇʱÖÓ
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00);		//6¸öÖá¾ù²»´ý»ú
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);		//²ÉÑù·ÖƵΪ10
	MPU6050_WriteReg(MPU6050_CONFIG	,0x06);			//Â˲¨²ÎÊý¸ø×î´ó
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);		//ÍÓÂÝÒÇ×î´óÁ¿³Ì
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);	//¼ÓËٶȼÆ×î´óÁ¿³Ì
}

在这里插入图片描述
想获取数据的话,写一个获取数据的函数,根据任务需求,这个函数需要返回6个int16_t的数据,分别表示XYZ的加速度值和陀螺仪值,但是C语言中,函数的返回值只能有一个,所以这里就需要一些特殊的操作来实现返回6个值的任务,多函数返回值的方法有很多:
第一种,最简单的方法就是,在函数外面定义6个全局变量,子函数读到的数据直接写入到全局变量里,然后6个全局变量在主函数里进行共享,这样就相当于返回了6个值。
第二种,用指针,进行变量的地址传递,来实现多返回值。
第三种,用结构体,对多个变量进行打包,然后再统一进行传递,这种方法,就是STM32的库函数里,这里使用到的。

指针,这6个参数,均是int16_t的指针类型,之后我们会在主函数里定义变量,通过指针,把主函数变量的地址传递到子函数来,子函数中,通过传递过来的地址,操作主函数的变量,这样子函数结束之后,主函数变量的值,就是子函数想要返回的值,这就是使用指针,实现函数多返回值的设计。

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;
	DataH=MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
	DataL=MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
	*AccX=(DataH<<8)|DataL;
	
	DataH=MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
	DataL=MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AccY=(DataH<<8)|DataL;
	
	DataH=MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
	DataL=MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
	*AccZ=(DataH<<8)|DataL;
	
	DataH=MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
	DataL=MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
	*GyroX=(DataH<<8)|DataL;
	
	DataH=MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
	DataL=MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
	*GyroY=(DataH<<8)|DataL;
	
	DataH=MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
	DataL=MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
	*GyroZ=(DataH<<8)|DataL;

}

逻辑是分别读取6个轴数据寄存器的高位和低位,拼接成16位的数据,在通过指针变量返回,或者使用I2C读取多个字节的时序,从一个基地址开始,连续读取一片的寄存器,因为是连续的可以连续的读取。
int16_t AX,AY,AZ,GX,GY,GZ;之后,在主循环里,不断地读取数据,

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
#include "OLED.h"
#include "MPU6050.h"

int16_t AX,AY,AZ,GX,GY,GZ;

int main(void)
{
							
	OLED_Init();
	MPU6050_Init();
	
	while(1)
	{
	
		MPU6050_GetData(&AX,&AY,&AZ,&GX,&GY,&GZ);
		OLED_ShowSignedNum(2,1,AX,5);
		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);
	}
}

问题

总结

本节课主要是学了了低层I2C的写,对函数的GPIO进行了封装,调用函数即可完成写,然后写了I2C的六个模块,然后在I2C的基础上完成了MPU6050的写,选择模式,先唤醒,然后写入寄存器相应的值,最后读取多函数返回值,运用指针传递变量的值,最终显示在OLED上,完成了软件I2C读写MPU6050.

相关文章:

  • SSRF漏洞公开报告分析
  • 深度解析SOCKS5代理节点:原理、搭建与安全实践
  • vue自定义颜色选择器
  • 接口请求控制工具
  • Modules模块NamespaceManagement命名空间管理
  • AI应用企业研发方案
  • 旋转位置编码
  • OpenHarmony-5.0.0-Risc-V架构搭建DeepSeek-R1
  • SS Block
  • Android Coil 3默认P3色域图加载/显示不出来
  • STM32 模块化开发指南 · 第 5 篇 STM32 项目中断处理机制最佳实践:ISR、回调与事件通知
  • Windows 录音格式为什么是 M4A?M4A 怎样转为 MP3 格式
  • 面向对象的需求分析与UML构造块详解
  • 设计模式:里氏代换原则 - 继承设计的稳定之道
  • 搜索插入位置 -- 二分查找
  • 每日一题(小白)暴力娱乐篇29
  • 新能源车「大三电」与「小三电」
  • GitLab之搭建(Building GitLab)
  • 【数据结构】堆排序详细图解
  • Python实现浏览器模拟访问及页面解析的全面指南
  • wordpress主题cms博客/安卓优化大师下载安装到手机
  • 网站建设的费用预算/爱链
  • 北京ifc大厦/太原seo推广
  • php旅游网站模板下载/100%上热门文案
  • 网站的优点/站长域名查询
  • 个人网站备案拍照/网站产品推广