江协科技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);
    	}
    }

    作者:傍晚冰川

    物联沃分享整理
    物联沃-IOTWORD物联网 » 江协科技STM32软件I2C协议层读写MPU6050驱动层详解

    发表回复