STM32学习笔记(十)丨使用I2C实现MPU6050和STM32之间通信

本篇文章包含的内容

  • 一、I2C原理简介
  • 1.1 I2C通信协议
  • 1.2 STM32的I2C外设
  • 二、MPU6050简介
  • 三、代码实现
  • 3.1 软件模拟的I2C通信
  • 3.1.1 I2C软件模拟通信(协议)层
  • 3.1.2 MPU6050设备操作层
  • 3.1.3 主函数逻辑层
  • 3.2 使用STM32的I2C外设实现I2C通信
  • 3.2.1 常用库函数
  • 3.2.2 代码实现
  • ​  本次课程采用单片机型号为STM32F103C8T6。(鉴于笔者实验时身边只有STM32F103ZET6,故本次实验使基于ZET6进行的)
    ​  课程链接:江协科技 STM32入门教程


      往期笔记链接:
      STM32学习笔记(一)丨建立工程丨GPIO 通用输入输出
      STM32学习笔记(二)丨STM32程序调试丨OLED的使用
      STM32学习笔记(三)丨中断系统丨EXTI外部中断
      STM32学习笔记(四)丨TIM定时器及其应用(定时中断、内外时钟源选择)
      STM32学习笔记(五)丨TIM定时器及其应用(输出比较丨PWM驱动呼吸灯、舵机、直流电机)
      STM32学习笔记(六)丨TIM定时器及其应用(输入捕获丨测量PWM波形的频率和占空比)
      STM32学习笔记(七)丨TIM定时器及其应用(编码器接口丨用定时器实现编码器测速)
      STM32学习笔记(八)丨ADC模数转换器(ADC单、双通道转换)
      STM32学习笔记(九)丨DMA直接存储器存取(DMA数据转运、DMA+AD多通道转换)


    一、I2C原理简介

    1.1 I2C通信协议

    1.2 STM32的I2C外设

      STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担。

  • 支持多主机模型(STM32的I2C是基于多主机模型设计的,如果在使用时不加改变,默认上电时STM32的I2C处于从模式)
  • 支持7位/10位地址模式
  • 支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)(由于是同步通信方式,I2C通信对时序的要求并不像串口通信那样严格)
  • 支持DMA
  • 兼容SMBus协议
  •   STM32F103C8T6 硬件I2C资源:I2C1、I2C2。(本次实验使用STM32F103ZET6)




    二、MPU6050简介

      MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,结合PID算法,常应用于小车走直线,平衡车、飞行器等需要检测自身姿态的场景。

  • 3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度。测量出的加速度具有静态稳定性,不具有动态稳定性。
  • 3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度。测量出的角速度具有动态稳定性,不具有静态稳定性。
  •   加速度和角速度都无法单独得到当前仪器的姿态,如果要得到仪器的姿态可以将所得数据进行数据解算。MPU6050拥有内部的数字运动处理器DMP(Digital Motion Processor),它是MPU6050自带的硬件姿态解算算法。可以使用官方的DMP库方便地实现姿态解算得到角度。
      MPU6050可以外扩更多的测量计,例如三轴磁力计来矫正方向,气压计来测量高度。添加更多的测量计可以使其成为9轴/10轴的姿态传感器。

  • 16位ADC采集传感器的模拟信号,量化范围:-32768~32767

  • 加速度计满量程选择:±2、±4、±8、±16(g)

  • 陀螺仪满量程选择: ±250、±500、±1000、±2000(°/sec)

  • 可配置的数字低通滤波器

  • 可配置的时钟源

  • 可配置的采样分频

  • I2C从机地址:1101000(AD0=0),1101001(AD0=1)


  •   套件中使用的模块电路图如下所示:

    三、代码实现

    3.1 软件模拟的I2C通信

    3.1.1 I2C软件模拟通信(协议)层

  • MyI2C.h
  • #ifndef __MYI2C_H_
    #define __MYI2C_H_
    
    void MyI2C_Init(void);
    void MyI2C_Start(void);
    void MyI2C_Stop(void);
    void MyI2C_SendByte(uint8_t Byte);
    uint8_t MyI2C_ReceiveByte(void);
    void MyI2C_SendAck(uint8_t AckBit);
    uint8_t MyI2C_ReceiveAck(void);
    
    #endif
    
    
  • MyI2C.c
  • #include "stm32f10x.h"                  // Device header
    
    // 更改GPIO和引脚时,只需要更改以下的宏定义即可
    // 需要注意:请检查使用的GPIO是否是APB2总线的外设,如果不是,则需要更改MyI2C_Init函数中的RCC_APB2PeriphClockCmd函数名称
    #define SCL_GPIO_Port_CLK	RCC_APB2Periph_GPIOA
    #define SDA_GPIO_Port_CLK	RCC_APB2Periph_GPIOA
    #define SCL_GPIO_Port		GPIOA
    #define SDA_GPIO_Port		GPIOA
    #define SCL_Pin				GPIO_Pin_6
    #define SDA_Pin				GPIO_Pin_7
    
    /**
      * @brief  软件I2C的GPIO端口初始化函数
      * @param  无
      * @retval 无
      */
    void MyI2C_Init(void)
    {
    	// 开启SCL和SDA对应GPIO的时钟
    	RCC_APB2PeriphClockCmd(SCL_GPIO_Port_CLK, ENABLE);
    	RCC_APB2PeriphClockCmd(SDA_GPIO_Port_CLK, ENABLE);
    	
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;	
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_InitStructure.GPIO_Pin = SCL_Pin;
     	GPIO_Init(SCL_GPIO_Port, &GPIO_InitStructure);
    	
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;	
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_InitStructure.GPIO_Pin = SDA_Pin;
     	GPIO_Init(SDA_GPIO_Port, &GPIO_InitStructure);
    	
    	GPIO_SetBits(SCL_GPIO_Port, SCL_Pin);
    	GPIO_SetBits(SDA_GPIO_Port, SDA_Pin);
    }
    
    /**
      * @brief  控制SCL线的下拉与释放
      * @param  BitValue	其值可以是0或1,0为下拉,1为释放
      * @retval 
      */
    void MyI2C_W_SCL(uint8_t BitValue)
    {
    	GPIO_WriteBit(SCL_GPIO_Port, SCL_Pin, (BitAction)BitValue);
    	// Delay_us(10);
    }
    
    /**
      * @brief  控制SDA线的下拉与释放
      * @param  BitValue	其值可以是0或1,0为下拉,1为释放
      * @retval 无
      */
    void MyI2C_W_SDA(uint8_t BitValue)
    {
    	GPIO_WriteBit(SDA_GPIO_Port, SDA_Pin, (BitAction)BitValue);
    	// Delay_us(10);
    }
    
    /**
      * @brief  读取SDA
      * @param  无
      * @retval 读取到SDA的高低电平值
      */
    uint8_t MyI2C_R_SDA(void)
    {
    	return GPIO_ReadInputDataBit(SDA_GPIO_Port, SDA_Pin);
    	// Delay_us(10);
    }
    
    /**
      * @brief  软件I2C的起始信号
      * @param  无
      * @retval 无
      */
    void MyI2C_Start(void)
    {
    	// 这里先释放SDA,再释放SCL的原因是为了使Start信号兼容重复起始信号RS
    	MyI2C_W_SDA(1);
    	MyI2C_W_SCL(1);
    	MyI2C_W_SDA(0);
    	MyI2C_W_SCL(0);
    }
    
    /**
      * @brief  软件I2C的结束信号
      * @param  无
      * @retval 无
      */
    void MyI2C_Stop(void)
    {
    	MyI2C_W_SDA(0);
    	MyI2C_W_SCL(1);
    	MyI2C_W_SDA(1);
    }
    
    /**
      * @brief  发送一个字节
      * @param  Byte	发送的字节数据
      * @retval 无
      */
    void MyI2C_SendByte(uint8_t Byte)
    {
    	uint8_t i;
    	for (i = 0; i < 8; i ++)
    	{
    		MyI2C_W_SDA(Byte & (0x80 >> i));
    		// SCL产生一个正脉冲,让从机读取数据
    		MyI2C_W_SCL(1);
    		MyI2C_W_SCL(0);
    	}
    }
    
    /**
      * @brief  接收一个字节
      * @param  无
      * @retval 接收的字节数据
      */
    uint8_t MyI2C_ReceiveByte(void)
    {
    	uint8_t i, Byte = 0x00;
    	MyI2C_W_SDA(1);		// 主机释放SDA,交出SDA控制权
    	for (i = 0; i < 8; i ++)
    	{
    		// 在SCL高电平期间读取SDA,如果SDA为1,则将Byte对应位置1(高位先行)
    		MyI2C_W_SCL(1);
    		if (MyI2C_R_SDA() == 1)
    		{
    			Byte |= (0x80 >> i);
    		}
    		MyI2C_W_SCL(0);
    	}
    	return Byte;
    }
    
    /**
      * @brief  发送应答,以通知从机数据是是否发送结束
      * @param  AckBit		0为发送未结束,1为发送已结束
      * @retval 无
      */
    void MyI2C_SendAck(uint8_t AckBit)
    {
    	MyI2C_W_SDA(AckBit);
    	// SCL产生一个正脉冲,让从机读取数据
    	MyI2C_W_SCL(1);
    	MyI2C_W_SCL(0);
    }
    
    /**
      * @brief  接受应答,主机读取该信号以确认从机是否接受到数据
      * @param  无
      * @retval 应答信号,0为已收到(从机受到后下拉SDA),1为未收到
      */
    uint8_t MyI2C_ReceiveAck(void)
    {
    	uint8_t AckBit;
    	MyI2C_W_SDA(1);		// 主机释放SDA,交出SDA控制权
    	// 在SCL高电平期间读取SDA,如果SDA为1,则将AckBit置1
    	MyI2C_W_SCL(1);
    	AckBit = MyI2C_R_SDA();
    	MyI2C_W_SCL(0);
    	
    	return AckBit;
    }
    
    
    // I2C地址应答测试程序,在main函数中可以循环以下过程来遍历I2C地址,以检查从机是否能正常应答
    	uint8_t ACK;
    	MyI2C_Start();
    	MyI2C_SendByte(0xD0);	// 0xD0为MPU6050的I2C地址和读写操作复合而成的地址
    	ACK = MyI2C_ReceiveAck();
    	MyI2C_Stop();
    

    3.1.2 MPU6050设备操作层

  • MPU6050.h
  • #ifndef __MPU6050_H_
    #define __MPU6050_H_
    
    void MPU6050_Init(void);
    
    void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
    uint8_t MPU6050_ReadReg(uint8_t RegAddress);
    uint8_t MPU6050_ReadID(void);
    void MPU6050_ReadData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, 
    					  int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ);
    
    #endif
    
    
  • MPU6050_Reg.h
  • #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	//电源管理1
    #define	MPU6050_PWR_MGMT_2		0x6C	//电源管理2
    #define	MPU6050_WHO_AM_I		0x75	//ID寄存器(默认数值0x68,只读)
    
    #endif
    
    
  • MPU6050.c
  • #include "stm32f10x.h"                  // Device header
    #include "MyI2C.h"
    #include "MPU6050_Reg.h"
    
    #define MPU6050_ADDRESS_W			0xD0
    #define MPU6050_ADDRESS_R			0xD1
    
    /**
      * @brief  MPU6050 向芯片内部的指定地址写
      * @param  RegAddress	芯片内部的寄存器地址
      * @param  Data		要写入的数据
      * @retval 无
      */
    void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
    {
    	MyI2C_Start();
    	MyI2C_SendByte(MPU6050_ADDRESS_W);
    	MyI2C_ReceiveAck();		// 这里可以对获取到的回应信号进行处理
    	MyI2C_SendByte(RegAddress);
    	MyI2C_ReceiveAck();
    	MyI2C_SendByte(Data);
    	MyI2C_ReceiveAck();
    	MyI2C_Stop();
    }
    
    /**
      * @brief MPU6050 向芯片内部的指定地址读
      * @param  RegAddress	要读取的寄存器在芯片内部的地址
      * @retval 读取的数据
      */
    uint8_t MPU6050_ReadReg(uint8_t RegAddress)
    {
    	uint8_t Data;
    	
    	// 向MPU6050发送将要读的地址
    	MyI2C_Start();
    	MyI2C_SendByte(MPU6050_ADDRESS_W);
    	MyI2C_ReceiveAck();
    	MyI2C_SendByte(RegAddress);
    	MyI2C_ReceiveAck();
    	
    	MyI2C_Start();		// 重复启动信号
    	MyI2C_SendByte(MPU6050_ADDRESS_R);		// 发送读地址,让出SDA控制权
    	MyI2C_ReceiveAck();
    	Data = MyI2C_ReceiveByte();
    	MyI2C_SendAck(1);	// 向从机发送应答信号(不响应),从机终止数据发送
    	MyI2C_Stop();
    	
    	return Data;
    }
    
    /**
      * @brief  MPU6050初始化函数
      * @param  无
      * @retval 无
      */
    void MPU6050_Init(void)
    {
    	MyI2C_Init();
    	MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);		// 不复位,关闭睡眠模式,不循环,使能温度传感器,选择X轴陀螺仪的内部震荡电路作为系统时钟
    	MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);		// 不需要设置循环模式的唤醒频率, 六个轴都不需要待机
    	MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);		// 设置采样分频, 这里选择10分频
    	MPU6050_WriteReg(MPU6050_CONFIG, 0x06);			// 不需要外部同步, 数字低通滤波设置为最高(最平滑)
    	MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);	// 角速度计配置:不自测(高三位为自测使能, 手册有遗漏), 设计为最大量程
    	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);	// 加速度计配置:不自测,选择为最大量程,不使用高通滤波器
    }
    
    /**
      * @brief  获取MPU6050的ID值,可根据ID值检查STM32和MPU6050之间是否正常通信
      * @param  无
      * @retval ID值
      */
    uint8_t MPU6050_ReadID(void)
    {
    	return MPU6050_ReadReg(MPU6050_WHO_AM_I);
    }
    
    /**
      * @brief  获取并返回MPU6050六轴传感器的返回值
      * @param  无
      * @retval 通过参数指针操作(返回)6个返回值
      */
    void MPU6050_ReadData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, 
    					  int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
    {
    	*AccX = (MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H) << 8)
    			| MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
    	
    	*AccY = (MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H) << 8)
    			| MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
    	
    	*AccZ = (MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H) << 8)
    			| MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
    	
    	*GyroX = (MPU6050_ReadReg(MPU6050_GYRO_XOUT_H) << 8)
    			| MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
    	
    	*GyroY = (MPU6050_ReadReg(MPU6050_GYRO_YOUT_H) << 8)
    			| MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
    	
    	*GyroZ = (MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H) << 8)
    			| MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
    }
    
    

    3.1.3 主函数逻辑层

  • main.c
  • #include "stm32f10x.h"                  // Device header
    #include "OLED.h"
    #include "MPU6050.h"
    
    uint8_t ID;
    int16_t AX, AY, AZ, GX, GY, GZ;
    
    int main(void)
    {
    	OLED_Init();
    	MPU6050_Init();
    	
    	OLED_ShowString(1, 1, "ID:0x");
    	ID = MPU6050_ReadID();
    	OLED_ShowHexNum(1, 6, ID, 2);
    	while (1)
    	{
    		MPU6050_ReadData(&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);
    	}
    }
    
    

    3.2 使用STM32的I2C外设实现I2C通信

    3.2.1 常用库函数

    // I2C外设缺省配置
    void I2C_DeInit(I2C_TypeDef* I2Cx);
    
    // I2C外设初始化函数
    void I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct);
    
    // 初始化结构体的缺省初始化
    void I2C_StructInit(I2C_InitTypeDef* I2C_InitStruct);
    
    // I2C外设的开关控制函数
    void I2C_Cmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
    
    // 生成起始信号
    void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState);
    // 生成结束信号
    void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState);
    // 在主机接收数据后是否相应从机配置
    void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState);
    
    // 主机发送数据
    void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data);
    // 主机接收数据
    uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);
    // 7位地址模式发送函数(该函数功能也可由数据发送函数实现)
    void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction);
    
  • 关于I2C状态监测方案(来自文件stm32f10x_i2c.h
      该I2C驱动程序提供了三种不同的I2C状态监测方法,根据应用需求和限制而定:
    1. 基本状态监测(本次实验采用此方案)
        使用I2C_CheckEvent()函数:它将状态寄存器(SR1和SR2)的内容与给定的事件进行比较(可以是一个或多个标志的组合)。如果当前状态包含给定的标志,则返回SUCCESS,如果当前状态中缺少一个或多个标志,则返回ERROR。
  • 使用时机:
      对于大多数应用程序以及启动活动,此函数是合适的,因为在产品参考手册(RM0008)中对事件进行了详细描述。
      对于需要定义自己的事件的用户也是合适的。
  • 限制:
      如果发生错误(即除了被监测的标志之外,设置了错误标志),I2C_CheckEvent()函数可能会返回SUCCESS,尽管通信暂停或实际状态已损坏。在这种情况下,建议使用错误中断来监测错误事件,并在中断IRQ处理程序中处理它们。对于错误管理,建议使用以下函数:
      I2C_ITConfig()用于配置和使能错误中断(I2C_IT_ERR)。
      I2Cx_ER_IRQHandler(),在发生错误中断时调用该函数。其中x是外设实例(I2C1、I2C2等)。
      在I2Cx_ER_IRQHandler()中调用I2C_GetFlagStatus()或I2C_GetITStatus(),以确定发生了哪个错误。
      调用I2C_ClearFlag()或I2C_ClearITPendingBit()和/或I2C_SoftwareResetCmd(),和/或I2C_GenerateStop()以清除错误标志和源,并恢复正确的通信状态。
    1. 高级状态监测:
        使用函数I2C_GetLastEvent(),它以一个单独的字(uint32_t)返回两个状态寄存器的图像(状态寄存器2的值左移16位并连接到状态寄存器1)。
  • 使用时机:
      对于上述相同的应用程序,该函数也是合适的,但它允许克服I2C_GetFlagStatus()函数的限制(见下文)。返回的值可以与库(stm32f10x_i2c.h)中已定义的事件进行比较,或与用户定义的自定义值进行比较。
      当同时监测多个标志时,该函数是合适的。
  • 限制:
      用户可能需要定义自己的事件。
      如果用户决定仅检查常规通信标志(并忽略错误标志),则与该函数相关的错误管理的相同备注适用。
    1. 基于标志的状态监测:
        使用函数I2C_GetFlagStatus(),它简单地返回一个单独标志的状态(如I2C_FLAG_RXNE …)。
  • 使用时机:
      该函数可用于特定应用程序或调试阶段。
      当只需要检查一个标志时,它是合适的(大多数I2C事件通过多个标志进行监测)。
  • 限制:
      调用该函数时,将访问状态寄存器。访问状态寄存器时,某些标志会被清除。因此,检查一个标志的状态可能会清除其他标志。
      为了监测一个单一事件,可能需要调用该函数两次或更多次。
  •   下面是和状态检测相关的库函数:

    // 获取当前事件是否发生
    ErrorStatus I2C_CheckEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT);
    // 获取当前状态寄存器的值(两个16位寄存器拼接而成的数据)
    uint32_t I2C_GetLastEvent(I2C_TypeDef* I2Cx);
    
    // 获取当前的状态标志位
    FlagStatus I2C_GetFlagStatus(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG);
    // 清除当前的状态标志位
    void I2C_ClearFlag(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG);
    // 获取中断标志位
    ITStatus I2C_GetITStatus(I2C_TypeDef* I2Cx, uint32_t I2C_IT);
    // 清除中断标志位
    void I2C_ClearITPendingBit(I2C_TypeDef* I2Cx, uint32_t I2C_IT);
    
    

    3.2.2 代码实现

      软件和硬件I2C通信在本次实验中实验数据和现象完全相同,仅在通信层有区别。在算法实现时,MPU6050.c模块不在需要继承软件通信协议MyI2C.h,所以在工程文件中可以直接删除MyI2C.cMyI2C.h文件。
    新的MPU6050.c的代码如下所示:

  • MPU6050.c
  • #include "stm32f10x.h"                  // Device header
    #include "MPU6050_Reg.h"
    
    #define MPU6050_ADDRESS			0xD0	// 0x68 + 读写位,注意这里的定义和软件模拟I2C有些许区别
    
    // 用宏定义在一定程度上实现解耦
    // 重定义GPIO端口,需要注意使用的GPIO如果不是APB2的外设需要更改MPU6050_Init函数
    #define GPIO_Periph_CLK			RCC_APB2Periph_GPIOB
    #define GPIO_Periph				GPIOB
    #define GPIO_Pin_SCL			GPIO_Pin_6
    #define GPIO_Pin_SDA			GPIO_Pin_7
    // 重定义I2C外设端口,这里同样需要检查使用的I2C外设是否是APB1的外设
    #define I2C_Periph_CLK			RCC_APB1Periph_I2C1
    #define I2C_Periph				I2C1
    
    /**
      * @brief  I2C硬件读写的事件等待发生函数,即等待某事件发生
      * @param  I2Cx		操作的I2Cx外设
      * @param  I2C_EVENT	要等待的事件,该参数的可取值在stm32f10x_i2c.c文件中
      * @retval 无
      */
    void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
    {
    	uint32_t TimeOut = 10000;	// 等待的时间值,可以由多次实验调试确定
    	while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)
    	{
    		TimeOut --;
    		if (TimeOut == 0)
    		{
    			/* 可在此进行错误和故障处理 */
    			break;
    		}
    	}
    }
    
    /**
      * @brief  MPU6050 向芯片内部的指定地址写
      * @param  RegAddress	芯片内部的寄存器地址
      * @param  Data		要写入的数据
      * @retval 无
      */
    void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
    {
    	I2C_GenerateSTART(I2C_Periph, ENABLE);
    	MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_MODE_SELECT);		// EV5, 等待起始条件已发送,事件发生(主模式已选择)
    	
    	I2C_Send7bitAddress(I2C_Periph, MPU6050_ADDRESS, I2C_Direction_Transmitter);
    	/* 在库函数中,发送函数都自带接收应答的过程,接收函数都自带发送应答的过程 */
    	MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);	// EV6, I2C地址和写命令已发送
    	
    	I2C_SendData(I2C_Periph, RegAddress);		// 发送寄存器地址
    	MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_BYTE_TRANSMITTING);	// EV8, 数据正在发送(DR非空)
    	I2C_SendData(I2C_Periph, Data);			// 发送寄存器数据
    	MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_BYTE_TRANSMITTED);		// EV8_2, 数据发送已完成
    	
    	I2C_GenerateSTOP(I2C_Periph, ENABLE);
    }
    
    /**
      * @brief MPU6050 向芯片内部的指定地址读
      * @param  RegAddress	要读取的寄存器在芯片内部的地址
      * @retval 读取的数据
      */
    uint8_t MPU6050_ReadReg(uint8_t RegAddress)
    {
    	uint8_t Data;
    	
    	I2C_GenerateSTART(I2C_Periph, ENABLE);
    	MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_MODE_SELECT);	// EV5, 起始条件已发送(主模式已选择)
    	
    	I2C_Send7bitAddress(I2C_Periph, MPU6050_ADDRESS, I2C_Direction_Transmitter);
    	MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);	// EV6, I2C地址和写命令已发送
    	
    	I2C_SendData(I2C_Periph, RegAddress);
    	MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_BYTE_TRANSMITTED);		// EV8_2, 等待数据(寄存器地址)发送已完成
    	
    	I2C_GenerateSTART(I2C_Periph, ENABLE);		// 重复起始条件
    	MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_MODE_SELECT);	// EV5, 起始条件已发送(主模式已选择)
    	
    	I2C_Send7bitAddress(I2C_Periph, MPU6050_ADDRESS, I2C_Direction_Receiver);
    	MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);	// EV6, I2C地址和读命令已发送
    	
    	// 如果要读取的数据使最后一个数据, 则在执行读命令之前, 就将ACK置0(不响应), STOP置1(结束条件)
    	I2C_AcknowledgeConfig(I2C_Periph, DISABLE);
    	I2C_GenerateSTOP(I2C_Periph, ENABLE);
    	
    	MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_BYTE_RECEIVED);	// EV7, RxNE = 1, 已收到一个字节
    	Data = I2C_ReceiveData(I2C_Periph);	// 取走DR的值, 并存放在Data变量中
    
    	return Data;
    }
    
    /**
      * @brief  MPU6050初始化函数
      * @param  无
      * @retval 无
      */
    void MPU6050_Init(void)
    {
    	RCC_APB1PeriphClockCmd(I2C_Periph_CLK, ENABLE);
    	RCC_APB2PeriphClockCmd(GPIO_Periph_CLK, ENABLE);
    	
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;		// 复用开漏模式, 将GPIO端口的控制权交给片上外设
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_SCL | GPIO_Pin_SDA;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIO_Periph, &GPIO_InitStructure);
    	
    	I2C_InitTypeDef I2C_InitStructure;
    	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;	
    	I2C_InitStructure.I2C_ClockSpeed = 100000;			// 标准模式,时钟频率为100kHz(该参数最大不能超过400kHz)
    	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;	// 快速模式下的时钟占空比, 在标准模式下该参数没有作用
    	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;			// 主机接收一个数据后是否响应从机, 该参数也可以由独立的函数进行配置
    	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;		// 地址的位数
    	I2C_InitStructure.I2C_OwnAddress1 = 0x00;			// STM32从模式下的自身地址, 主模式下没有作用
    	I2C_Init(I2C_Periph, &I2C_InitStructure);
    	
    	I2C_Cmd(I2C_Periph, ENABLE);
    	
    	MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);		// 不复位,关闭睡眠模式,不循环,使能温度传感器,选择X轴陀螺仪的内部震荡电路作为系统时钟
    	MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);		// 不需要设置循环模式的唤醒频率, 六个轴都不需要待机
    	MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);		// 设置采样分频, 这里选择10分频
    	MPU6050_WriteReg(MPU6050_CONFIG, 0x06);			// 不需要外部同步, 数字低通滤波设置为最高(最平滑)
    	MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);	// 角速度计配置:不自测(高三位为自测使能, 手册有遗漏), 设计为最大量程
    	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);	// 加速度计配置:不自测,选择为最大量程,不使用高通滤波器
    }
    
    /**
      * @brief  获取MPU6050的ID值,可根据ID值检查STM32和MPU6050之间是否正常通信
      * @param  无
      * @retval ID值
      */
    uint8_t MPU6050_ReadID(void)
    {
    	return MPU6050_ReadReg(MPU6050_WHO_AM_I);
    }
    
    /**
      * @brief  获取并返回MPU6050六轴传感器的返回值
      * @param  无
      * @retval 通过参数指针操作(返回)6个返回值
      */
    void MPU6050_ReadData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, 
    					  int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
    {
    	*AccX = (MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H) << 8)
    			| MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
    	
    	*AccY = (MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H) << 8)
    			| MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
    	
    	*AccZ = (MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H) << 8)
    			| MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
    	
    	*GyroX = (MPU6050_ReadReg(MPU6050_GYRO_XOUT_H) << 8)
    			| MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
    	
    	*GyroY = (MPU6050_ReadReg(MPU6050_GYRO_YOUT_H) << 8)
    			| MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
    	
    	*GyroZ = (MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H) << 8)
    			| MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
    }
    
    

    ​  课程链接:江协科技 STM32入门教程,欢迎大家一起交流学习。
      持续更新完善中……


      原创笔记,码字不易,欢迎点赞,收藏~ 如有谬误敬请在评论区不吝告知,感激不尽!博主将持续更新有关嵌入式开发、机器学习方面的学习笔记~

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32学习笔记(十)丨使用I2C实现MPU6050和STM32之间通信

    发表评论