2.32 软件I2C读写MPU6050

MPU6050接线图

软件I2C

初始化

软件I2C只需要用GPIO的读写函数就行了。

软件I2C初始化要做两个任务:

1.将SCL和SDA都初始化为开漏输出模式。

2.将SCL和SDA置高电平。

在STM32中,将一个GPIO配置为输出模式后,通常不能直接读取外部输入信号。但你仍然可以读取GPIO的输出数据寄存器来检查当前输出状态。如果需要同时输入,可以将GPIO配置为开漏输出,并使用上拉电阻,这样可以读取输入状态。

开漏输出虽然名字带了个输出,但也能输入,输入时,先输出1,再直接读取输入数据寄存器就行了。(在开漏输出配置中,输出高电平(1)实际上是高阻态,这样外部电路能够驱动引脚电平,从而可以读取外部输入信号。如果输出低电平(0),引脚会被拉低,无法读取输入信号。因此,先输出1,使引脚处于高阻态,才能正确读取外部输入。)

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); 
	//此时I2C总线处于空闲状态。
} 

 

写SCL/读写SDA

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


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


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

SCL是主机专属控制的,所以无需读SCL。

 

起始条件

在写起始条件时,最好先将SDA置高电平,再将SCL置高电平。在刚开始时,SDA和SCL都处于空闲状态,都是高电平,所以先释放哪一个都是无所谓的;但是,通信的时候,会有重复起始条件Sr,Sr最开始的SCL是低电平,而SDA的高低电平不确定,所以保险起见,我们趁SCL是低电平,先确保释放SDA,再释放SCL,这时SDA和SCL都是高电平,然后再拉低SDA、拉低SCL,这样这个Start就可以兼容起始条件和重复起始条件了。

因为如果不把SDA先变成高电平,那么在重复起始条件时,SDA的值如果是低电平,SCL被拉高至高电平后,再把SDA拉高,相当于在SCL高电平时拉高SDA,变成了终止条件。

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

 

 

终止条件

当SCL和SDA都已经是低电平了,那就先释放SCL,再释放SDA就行了,但是在时序单元开启时,SDA并不一定是低电平,所以为了确保释放SDA能产生上升沿,我们要在时序单元开始时,先拉低SDA,再释放SCL、SDA。

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

 

发送一字节

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

 

接收一字节

uint8_t MyI2C_ReceiveByte(void)
{
	uint8_t Byte = 0x00;
	MyI2C_W_SDA(1); //主机释放SDA,从机把数据放入SDA中。
	
	for(uint8_t Bit = 0;Bit<8;Bit++)
	{
		MyI2C_W_SCL(1);//主机释放SCL,SCL高电平,主机读取SDA。
		if(MyI2C_R_SDA() == 1)
		{
			Byte |= (0X80)>>Bit;
		}
		MyI2C_W_SCL(0);
	}
	
	return Byte;
}

 

发送ACK应答

void MyI2C_SendAck(uint8_t AckBit)
{
	MyI2C_W_SDA(AckBit);
	MyI2C_W_SCL(1);
	MyI2C_W_SCL(0);
}

 

接收ACK应答

uint8_t MyI2C_ReceiveAck(void)
{
	uint8_t AckBit;
	MyI2C_W_SDA(1);//主机释放SDA,让从机更改SDA数据。
	MyI2C_W_SCL(1);
	AckBit = MyI2C_R_SDA();
	MyI2C_W_SCL(0);
	return AckBit;
}

MPU6050代码

MPU6050寄存器地址

#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

 

#define MPU6050_ADDRESS 0XD0

//指定地址写
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)
{
	MyI2C_Start();
	MyI2C_SendByte(MPU6050_ADDRESS);
	MyI2C_ReceiveAck();
	MyI2C_SendByte(RegAddress);
	MyI2C_ReceiveAck();
	
	MyI2C_Start();
	MyI2C_SendByte(MPU6050_ADDRESS | 0x01);
	MyI2C_ReceiveAck();

	uint8_t Data = MyI2C_ReceiveByte();
	
	MyI2C_SendAck(1);
	MyI2C_Stop();
	return Data; 
}

//初始化MPU6050
void MPU6050_Init(void)
{
	MyI2C_Init();
	
	/*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
}

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

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);		//读取加速度计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;						//数据拼接,通过输出参数返回
}