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

【江协科技STM32】软件I2C协议层读写MPU6050驱动层

 回顾知识点: 【STM32】I2C通信协议&MPU6050芯片-学习笔记-CSDN博客

 接线图

整体思路

 

I2C初始化

软件I2C只需要用GPIO读取函数就可以,不用I2C库函数; 

① 把SCL和SDA都初始化成开漏输出模式(开漏输出不只是只能输出、也可以输入;输入时,先输出1,再直接读取输入数据寄存器就可以了)

②把SCL和SDA置高电平

void HerI2C_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);				//将PB10和PB11引脚初始化为开漏输出
	
	GPIO_SetBits(GPIOB,GPIO_Pin_10 | GPIO_Pin_11);		//设置PB10和PB11引脚初始化后默认为高电平(释放总线状态)
}

配置完之后,陀螺仪内部就在连续不断地进行转换,转换后输出的数据就放在数据寄存器里面。

六个时序单元

  • 起始条件:SCL高电平期间,SDA从高电平切换到低电平
  • 终止条件:SCL高电平期间,SDA从低电平切换到高电平  

 首先对操着端口的库函数进行封装:

/**
  * 函    数:I2C写SCL引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入SCL的电平,范围0~1
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCL为低电平,当BitValue为1时,需要置SCL为高电平
  */
void HerI2C_W_SCL(uint8_t BitV)
{
	GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitV);//根据BitValue,设置SCL引脚的电平
    Delay_us(10);                                   //延时10us,防止时序频率超过要求
}



void HerI2C_W_SDA(uint8_t BitV)
{
	GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)BitV);//根据BitValue,设置SDA引脚的电平,BitV要实现非0即1的特性
	Delay_us(10);
}

I2C起始和终止函数:

void HerI2C_Start(void)
{
	HerI2C_W_SDA(1);		//释放SDA,确保SDA为高电平
	HerI2C_W_SCL(1);        //释放SCL,确保SCL为高电平
	HerI2C_W_SDA(0);        //在SCL高电平期间,拉低SDA,产生起始信号
	HerI2C_W_SCL(0);        //起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}

void HerI2C_Stop(void)
{
	HerI2C_W_SDA(0);		//拉低SDA,确保SDA为低电平
	HerI2C_W_SCL(1);        //释放SCL,使SCL呈现高电平
	HerI2C_W_SDA(1);        //在SCL高电平期间,释放SDA,产生终止信号
}

Start函数这里,如果起始条件之前,SCL和SDA已经是高电平了,那先释放哪个都是一样的效果; 但是在后面还要兼容一个重复起始条件Sr,Sr最开始,SCL是低电平,SDA电平不确定,所以保险起见,趁SCL低电平期间,先确保SDA释放为高电平再释放SCL,然后在两个高电平期间再拉低SDA起始,就可以Start兼容起始条件和重复起始条件

Stop函数注意:

实际上,除了终止条件SCL以高电平结束,所有的单元我们都会保证SCL以低电平结束。

I2C读SDA函数: 

/**
  * 函    数:I2C读SDA引脚电平
  * 参    数:无
  * 返 回 值:协议层需要得到的当前SDA的电平,范围0~1
  * 注意事项:此函数需要用户实现内容,当前SDA为低电平时,返回0,当前SDA为高电平时,返回1
  */
uint8_t HerI2C_R_SDA(void)
{
	uint8_t SDABit;									  
	SDABit =GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11); //读取SDA电平
	Delay_us(10);                                     //延时10us,防止时序频率超过要求
	return SDABit;									   //返回SDA电平
}

发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节 

void HerI2C_SendByte(uint8_t Bytevalue)
{
	uint8_t i;
	for(i =0;i <8;i ++)	//循环8次,主机依次发送数据的每一位
	{
		HerI2C_W_SDA(Bytevalue & (0x80 >>i));//使用掩码的方式取出Byte的指定一位数据并写入到SDA线
		HerI2C_W_SCL(1);                     //释放SCL,从机在SCL高电平期间读取SDA
		HerI2C_W_SCL(0);                     //拉低SCL,主机开始发送下一位数据
	}
}

接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA,交给从机控制) 

uint8_t HerI2C_ReceiveByte(void)
{
	uint8_t i,Bytevalue =0x00;
	HerI2C_W_SDA(1);		//接收前,主机先确保释放SDA,避免干扰从机的数据发送
	for(i =0;i <8;i ++)     //循环8次,主机依次接收数据的每一位
	{
		HerI2C_W_SCL(1);	//释放SCL,主机机在SCL高电平期间读取SDA
		if(HerI2C_R_SDA() == 1)		
		{                           //读取SDA数据,并存储到Bytevalue变量
			Bytevalue |= (0x80 >> i);//当SDA为1时,置变量指定位为1,当SDA为0时,不做处理,指定位为默认的初值0
		}							// Bytevalue = Bytevalue | (0x80 >> i)
		HerI2C_W_SCL(0);		//拉低SCL,从机在SCL低电平期间写入SDA
	}
	return Bytevalue;
}

 

/**
  * 函    数:I2C发送应答位
  * 参    数:Askvalue要发送的应答位,范围:0~1,0表示应答,1表示非应答
  * 返 回 值:无
  */
void HerI2C_SendAsk(uint8_t Askvalue)
{
	HerI2C_W_SDA(Askvalue);	//主机把应答位数据放到SDA线
	HerI2C_W_SCL(1);        //释放SCL,从机在SCL高电平期间,读取应答位
	HerI2C_W_SCL(0);        //拉低SCL,开始下一个时序模块
}

uint8_t HerI2C_ReceiveAsk(void)
{
	uint8_t Askvalue;			//定义应答位变量
	HerI2C_W_SDA(1);            //接收前,主机先确保释放SDA,避免干扰从机的数据发送
	HerI2C_W_SCL(1);            //释放SCL,主机机在SCL高电平期间读取SDA
	Askvalue = HerI2C_R_SDA();  //将应答位存储到变量里
	HerI2C_W_SCL(0);            //拉低SCL,开始下一个时序模块
	return Askvalue;            //返回定义应答位变量
}

通过AD0引脚改名功能:

此时从机地址为1101 000 

正常会给应答位000 

然后在MPU6050DA0引脚插上飞线引到Vcc, 置高电平,此时从机地址为1101 010 ,然后按下复位键,应答位就为1,没有应答了。

这是需要在程序中修改寻址,0xD2就又收到应答位了。

MPU6050初始化

#define MPU6050_ADDRESS 0xD0	//MPU6050的I2C从机地址
void MPU6050_Init(void)
{
	HerI2C_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获取数据函数 

/**
  * 函    数:MPU6050获取数据
  * 参    数:AccX AccY AccZ 加速度计X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
  * 参    数:GyroX GyroY GyroZ 陀螺仪X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
  * 返 回 值:无
  */
void MPU6050_GetData(int16_t *AcceX,int16_t *AcceY,int16_t *AcceZ,
								int16_t *GyroX,int16_t *GyroY,int16_t *GyroZ)
{
	uint8_t DataH;
	uint8_t DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);	//读取加速度计X轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);  //读取加速度计X轴的低8位数据
	*AcceX = (DataH << 8) | DataL;                  //数据拼接,通过输出参数返回
													//(DataH << 8) | DataL;这16位是用补码表示的有符号数,直接赋给int_16t也没问题
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);	
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AcceY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);	
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
	*AcceZ = (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);	
	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;
}

 数据寄存器宏定义:

#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

 MPU6050指定地址写寄存器:

/**
  * 函    数:MPU6050写寄存器
  * 参    数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述
  * 参    数:Data 要写入寄存器的数据,范围:0x00~0xFF
  * 返 回 值:无
  */
void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data)
{
	HerI2C_Start();						//I2C起始
	HerI2C_SendByte(MPU6050_ADDRESS);   //发送从机地址,读写位为0,表示即将写入
	HerI2C_ReceiveAsk();                //接收应答
	HerI2C_SendByte(RegAddress);        //发送寄存器地址
	HerI2C_ReceiveAsk();                //接收应答
	HerI2C_SendByte(Data);              //发送要写入寄存器的数据
	HerI2C_ReceiveAsk();                //接收应答
	HerI2C_Stop();                      //I2C终止
}

这里的函数会返回一个应答位,这里没有做处理应答位,如果要处理应答位要增加很多代码,处理比较麻烦,为了时序清晰,方便学习,这里不对返回值进行判断。这里知道有应答位可以判断从机有没有收到数据就可以了。

 MPU6050指定地址读寄存器:

/**
  * 函    数:MPU6050读寄存器
  * 参    数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述
  * 返 回 值:读取寄存器的数据,范围:0x00~0xFF
  */
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
	HerI2C_Start();							
	HerI2C_SendByte(MPU6050_ADDRESS);
	HerI2C_ReceiveAsk();
	HerI2C_SendByte(RegAddress);
	HerI2C_ReceiveAsk();
	
	HerI2C_Start();							//I2C重复起始
	HerI2C_SendByte(MPU6050_ADDRESS | 0x01);//发送从机地址,读写位为1,表示即将读取
	HerI2C_ReceiveAsk();                    //接收应答
	Data = HerI2C_ReceiveByte();            //接收指定寄存器的数据
	HerI2C_SendAsk(1);                      //发送应答,给从机非应答,终止从机的数据输出
	HerI2C_Stop();                          //I2C终止
	
	return Data;
}

测试读写函数

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

 MPU6050获取ID号

uint8_t MMPU6050_GetID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I);	//返回MPU6050_WHO_AM_I寄存器值
}

main函数

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

int main(void)
{
	OLED_Init();
	MPU6050_Init();
	
	ID = MMPU6050_GetID();
	OLED_ShowString(1,1,"ID:");
	OLED_ShowHexNum(1,4,ID,2);
	
	while(1)
	{
		MPU6050_GetData(&AX , &AY, &AZ, &GX, &GY, &GZ);	//获取MPU6050的数据
		
		OLED_ShowSignedNum(2,1,AX,5);
		OLED_ShowSignedNum(3,1,AY,5);
		OLED_ShowSignedNum(4,1,AZ,5);
		OLED_ShowSignedNum(2,9,GX,5);
		OLED_ShowSignedNum(3,9,GY,5);
		OLED_ShowSignedNum(4,9,GZ,5);
	}
}


http://www.kler.cn/a/592576.html

相关文章:

  • 动态代理示例解析
  • 3.19学习总结
  • 递归分治法格雷码
  • 刷题练习笔记
  • 基于SpringBoot + Vue 的图书馆座位预约系统
  • 红日靶场(二)——个人笔记
  • HarmonyOS开发,console.log和hilog的区别,如何选择使用?
  • 两矩阵相乘(点乘和乘的区别)
  • Matrix-breakout-2-morpheus靶机实战攻略
  • 算法、数据结构、计算机网络,编译原理,操作系统常考题
  • Node.js系列(4)--微服务架构实践
  • 数据结构之链表(双链表)
  • 如何在 GoLand 中设置默认项目文件夹
  • JAVA中关于图形化界面的学习(GUI)动作监听,鼠标监听,键盘监听
  • SpringBoot项目controller层接收对应格式请求的相关RequestMapping配置
  • Vue3 核心特性解析:Suspense 与 Teleport 原理深度剖析
  • k8s基础资源管理指令
  • 《鸿蒙开发实战:音频录制、文件读取与播放功能全解析》
  • Java 集合框架
  • linux 内核 定时器(timer)