• 作者:老汪软件技巧
  • 发表时间:2024-10-12 00:02
  • 浏览量:

1. 简介

I2C (Inter-Integrated Circuit 集成电路总线)

I2C协议是一种同步半双工的协议,具有数据应答机制,支持多设备挂载,它利用一根时钟线SCL(Serial Clock,串行时钟线)和一根数据线SDA(Serial Data,串行数据线)在连接总线的两个器件之间进行信息的传递,为设备之间数据交换提供了一种简单高效的方法。每个连接到总线上的器件都有唯一的地址,任何器件既可以作为主机也可以作为从机,但同一时刻只允许有一个主机

2. 硬件层

I2C总线在物理连接上非常简单,分别由SDA和SCL及上拉电阻组成,输出模式配置为开漏输出。在总线空闲状态时,SCL和SDA被上拉电阻Rp拉高,使得SDA和SCL线都保持高电平。

“线与”判断总线占用状态

可以将多个开漏输出的Pin脚,连接到一条线上,形成“与逻辑”关系,即“线与”功能,任意一个变低后,开漏线上的逻辑就为0了。这也是I2C,SMBus等总线判断总线占用状态的原理。如果总线上的一个A设备准备将SDA拉高,这时总线上另一个B设备已将SDA拉低,这时由于1&0=0,所以A设备检查SDA的时候会发现不是高电平而是低电平,表明总线上已经有其他设备占用总线了,A只好放弃,如果检测是高电平那就可以使用。

3. 协议层

IC2支持

3.1 三种信号

I2C总线在传递数据的过程中共有三种类型的信号:开始信号,结束信号,应答信号

3.2 从机设备地址

每个挂在总线上的I2C器件都有一个唯一的地址,并可以通过软件寻址

I2C通讯支持:7位寻址和10位寻址两种模式。

3.2 数据传输

主机或从机根据R/W位的状态进行数据传输。传输的数据每个字节都有相应的ACK或NACK应答。

为了实现主设备对从设备数据的写入和读取,通常方式是通过从设备中的寄存器来实现的。寄存器作为从设备的数据存储单元,包含某些特定的信息。主设备无论是准备写入配置信息,或是读取数据信息,必须先向从设备的寄存器写入特定指令,以指示从设备完成相应任务。

向从机设备写数据

由于线与的特性,在主机发送R/W位后,立马松开了SDA,但是从机为了响应ACK又拉住了SDA,所以波形没有产生变化

读取从机设备数据

对从设备的读取与写入非常相似,但需要一些额外的步骤。 为了对从设备读取,主设备必须首先向从设备指示它希望从哪个寄存器读取。 这是由主设备以与写入类似的方式开始传输,通过发送R/W位等于0(表示写入)的地址,然后是希望从中读取的寄存器地址。 一旦从设备确认该寄存器地址,主设备将再次发送START条件,然后是从设备地址,R/W位设置为1(表示读取)。 这次,从设备将确认读取请求,主设备释放SDA总线,但将继续向从设备提供时钟。 在通讯的这一部分期间,主设备将成为主接收器,从设备将成为从发送器

当确认寄存器地址后,会有一个地址指针指向该位置,对这个位置进行读写后,该指针会自动+1

4. 软件I2C读写MPU6050

没有使用软件来模拟串口的时序,每一位的时间要求很严格,虽然可以模拟但是很困难

I2C这种同步时序实现起来简单方便

软件I2C读写操作

MyI2C.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
/*引脚配置层*/
/**
  * 函    数:I2C写SCL引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入SCL的电平,范围0~1
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCL为低电平,当BitValue为1时,需要置SCL为高电平
  */
void MyI2C_W_SCL(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);	//根据BitValue,设置SCL引脚的电平
	Delay_us(10);												//延时10us,防止时序频率超过要求
}
/**
  * 函    数:I2C写SDA引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入SDA的电平,范围0~0xFF
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SDA为低电平,当BitValue非0时,需要置SDA为高电平
  */
void MyI2C_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);	//根据BitValue,设置SDA引脚的电平,BitValue要实现非0即1的特性
	Delay_us(10);												//延时10us,防止时序频率超过要求
}
/**
  * 函    数:I2C读SDA引脚电平
  * 参    数:无
  * 返 回 值:协议层需要得到的当前SDA的电平,范围0~1
  * 注意事项:此函数需要用户实现内容,当前SDA为低电平时,返回0,当前SDA为高电平时,返回1
  */
uint8_t MyI2C_R_SDA(void)
{
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);	//读取SDA电平
	Delay_us(10);												//延时10us,防止时序频率超过要求
	return BitValue;											//返回SDA电平
}
/**
  * 函    数:I2C初始化
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,实现SCL和SDA引脚的初始化
  */
void MyI2C_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	//开启GPIOB的时钟
	
	/*GPIO初始化*/
	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引脚初始化后默认为高电平(释放总线状态)
}
/*协议层*/
/**
  * 函    数:I2C起始
  * 参    数:无
  * 返 回 值:无
  */
void MyI2C_Start(void)
{
	MyI2C_W_SDA(1);	//释放SDA,确保SDA为高电平
	MyI2C_W_SCL(1);	//释放SCL,确保SCL为高电平
	MyI2C_W_SDA(0);	//在SCL高电平期间,拉低SDA,产生起始信号
	MyI2C_W_SCL(0);	//起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}
/**
  * 函    数:I2C终止
  * 参    数:无
  * 返 回 值:无
  */
void MyI2C_Stop(void)
{
	MyI2C_W_SDA(0);	//先拉低SDA,确保SDA为低电平
	MyI2C_W_SCL(1);	//释放SCL,使SCL呈现高电平
	MyI2C_W_SDA(1);	//在SCL高电平期间,释放SDA,产生终止信号
}
/**
  * 函    数:I2C发送一个字节
  * 参    数:Byte 要发送的一个字节数据,范围:0x00~0xFF
  * 返 回 值:无
  */
void MyI2C_SendByte(uint8_t Byte)
{
	uint8_t i;
	for (i = 0; i < 8; i ++) //循环8次,主机依次发送数据的每一位
	{
		MyI2C_W_SDA(Byte & (0x80 >> i)); //使用掩码的方式取出Byte的指定一位数据并写入到SDA线
		MyI2C_W_SCL(1);	//释放SCL,从机立刻在SCL高电平期间读取SDA
		MyI2C_W_SCL(0);	//拉低SCL,主机开始发送下一位数据
	}
}
/**
  * 函    数:I2C接收一个字节
  * 参    数:无
  * 返 回 值:接收到的一个字节数据,范围:0x00~0xFF
  */
uint8_t MyI2C_ReceiveByte(void)
{
	uint8_t i, Byte = 0x00; //定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到
	MyI2C_W_SDA(1); //接收前,主机先确保释放SDA,避免干扰从机的数据发送
	for (i = 0; i < 8; i ++) //循环8次,主机依次接收数据的每一位
	{
		MyI2C_W_SCL(1);	//释放SCL,主机机在SCL高电平期间读取SDA
		if (MyI2C_R_SDA() == 1){ //当SDA为1时,置变量指定位为1,当SDA为0时,不做处理,指定位为默认的初值0
			Byte |= (0x80 >> i); //读取SDA数据,并存储到Byte变量
		}													
		MyI2C_W_SCL(0); //拉低SCL,从机在SCL低电平期间写入SDA
	}
	return Byte; //返回接收到的一个字节数据
}
/**
  * 函    数:I2C发送应答位
  * 参    数:Byte 要发送的应答位,范围:0~1,0表示应答,1表示非应答
  * 返 回 值:无
  */
void MyI2C_SendAck(uint8_t AckBit)
{
	MyI2C_W_SDA(AckBit); //主机把应答位数据放到SDA线
	MyI2C_W_SCL(1); //释放SCL,从机在SCL高电平期间,读取应答位
	MyI2C_W_SCL(0); //拉低SCL,开始下一个时序模块
}
/**
  * 函    数:I2C接收应答位
  * 参    数:无
  * 返 回 值:接收到的应答位,范围:0~1,0表示应答,1表示非应答
  */
uint8_t MyI2C_ReceiveAck(void)
{
	uint8_t AckBit; //定义应答位变量
	MyI2C_W_SDA(1); //接收前,主机先确保释放SDA,避免干扰从机的数据发送
	MyI2C_W_SCL(1);	//释放SCL,主机机在SCL高电平期间读取SDA
	AckBit = MyI2C_R_SDA(); //将应答位存储到变量里
	MyI2C_W_SCL(0); //拉低SCL,开始下一个时序模块
	return AckBit; //返回定义应答位变量
}

使用I2C读写MPU6050

与MPU6050相关的寄存器地址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
#define	MPU6050_PWR_MGMT_2	0x6C
#define	MPU6050_WHO_AM_I	0x75
#endif

_集成总线技术_总线器件

MPU6050.c

#include "stm32f10x.h"                  // Device header
#include "MyI2C.h"
#include "MPU6050_Reg.h"
#define MPU6050_ADDRESS 0xD0 //MPU6050的I2C从机地址
/**
  * 函    数:MPU6050写寄存器
  * 参    数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述
  * 参    数:Data 要写入寄存器的数据,范围:0x00~0xFF
  * 返 回 值:无
  */
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	MyI2C_Start();				//I2C起始
	MyI2C_SendByte(MPU6050_ADDRESS);	//发送从机地址,读写位为0,表示即将写入
	MyI2C_ReceiveAck();			//接收应答
	MyI2C_SendByte(RegAddress);		//发送寄存器地址
	MyI2C_ReceiveAck();			//接收应答
	MyI2C_SendByte(Data);			//发送要写入寄存器的数据
	MyI2C_ReceiveAck();			//接收应答
	MyI2C_Stop();				//I2C终止
}
/**
  * 函    数:MPU6050读寄存器
  * 参    数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述
  * 返 回 值:读取寄存器的数据,范围:0x00~0xFF
  */
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
	MyI2C_Start();				//I2C起始
	MyI2C_SendByte(MPU6050_ADDRESS);	//发送从机地址,读写位为0,表示即将写入
	MyI2C_ReceiveAck();			//接收应答
	MyI2C_SendByte(RegAddress);		//发送寄存器地址
	MyI2C_ReceiveAck();			//接收应答
	
	MyI2C_Start();				//I2C重复起始
	MyI2C_SendByte(MPU6050_ADDRESS | 0x01);	//发送从机地址,读写位为1,表示即将读取
	MyI2C_ReceiveAck();			//接收应答
	Data = MyI2C_ReceiveByte();		//接收指定寄存器的数据
	MyI2C_SendAck(1);			//发送应答,给从机非应答,终止从机的数据输出
	MyI2C_Stop();				//I2C终止
	
	return Data;
}
/**
  * 函    数:MPU6050初始化
  * 参    数:无
  * 返 回 值:无
  */
void MPU6050_Init(void)
{
	MyI2C_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获取ID号
  * 参    数:无
  * 返 回 值:MPU6050的ID号
  */
uint8_t MPU6050_GetID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I);	//返回WHO_AM_I寄存器的值
}
/**
  * 函    数:MPU6050获取数据
  * 参    数:AccX AccY AccZ 加速度计X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
  * 参    数:GyroX GyroY GyroZ 陀螺仪X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
  * 返 回 值:无
  */
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;					//定义数据高8位和低8位的变量
	
	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;				//数据拼接,通过输出参数返回
}

main.c

#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"
uint8_t ID;					//定义用于存放ID号的变量
int16_t AX, AY, AZ, GX, GY, GZ;			//定义用于存放各个数据的变量
int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	MPU6050_Init();		//MPU6050初始化
	
	/*显示ID号*/
	OLED_ShowString(1, 1, "ID:");		//显示静态字符串
	ID = MPU6050_GetID();			//获取MPU6050的ID号
	OLED_ShowHexNum(1, 4, ID, 2);		//OLED显示ID号
	
	while (1)
	{
		MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);	//获取MPU6050的数据
		OLED_ShowSignedNum(2, 1, AX, 5);		//OLED显示数据
		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);
	}
}

5. I2C通信外设

STM32内部集成了硬件I2C收发电路,能够自动完成时钟生成、起始和终止条件生成、应答位收发以及数据传输等功能,从而大幅减轻CPU的负担。它支持多主机模式,并兼容7位和10位地址模式。此外,I2C通信支持多种速度选项,包括标准模式(最高100 kHz)和快速模式(最高400 kHz)。STM32还支持通过DMA进行数据传输,并且兼容SMBus协议。

5.1 硬件框图

数据收发的核心部分是这里的数据寄存器和移位寄存器

时钟控制寄存器写对应的位电路就会执行对应的功能。控制逻辑电路是黑盒子,写入控制寄存器可以对整个电路进行控制,读取状态寄存器可以得知电路的工作状态

然后是中断,当内部有一些标志位置1之后可能事件比较紧急就可以申请中断,如果开启了这个中断,那当这个事件发生后程序就可以跳到这个中断函数来处理这个事件。

最后是DMA请求与响应,在进行很多字节的收发时可以配合DMA来提高效率。

I2C基本结构

移位寄存器和数据寄存器DR的配合是通信的核心部分。因为I2C是高位先行,所以这个移位寄存器是向左移位,在发送的时候最高位先移出去,然后是次高位等等。一个SCL时钟移位一次,移位8次就能把一个字节由高位到低位依次放到SDA线上了。那在接收的时候,数据通过GPIO口从右依次移进来,最终移8次,一个字节就接收完成了。

GPIO口这里,使用硬件I2C的时候这两个对应的GPIO口都要配置成复用开漏输出的模式(复用,就是GPIO口的状态是交由片上外设来控制的,开漏输出,这是I2C协议要求的端口配置)

SCL这里,时钟控制器通过GPIO去控制时钟线,上图简化成一主多从模型,所以时钟这里只画了输出的方向。

SDA这里,输出数据通过GPIO输出到端口,输入数据也是通过GPIO输入到移位寄存器。

5.2 I2C外设时序

这里只讨论主机发送和主机接收的流程(7位地址),从机的部分暂不讨论

在STM32F103C8T6中,当使用I2C外设进行7位地址模式下的主发送时,通信过程遵循特定的时序,并在各关键步骤产生相应的事件,下面就来分析一下主发送模式下的时序以及各个事件的触发过程

主机发送

EV5事件描述:当起始条件在I2C总线上生成后(主机通过设置I2C_CR1寄存器中的START位生成起始信号),I2C_SR1寄存器中的SB(Start Bit)标志位会被置位。此时,表示总线处于活动状态,起始条件已经成功发送

EV6事件描述:在EV5事件发生后,主机将7位从机地址与方向位(RW=0表示写)发送到I2C总线上,从机地址和方向位成功发送并且从机发送应答(ACK)信号时,I2C_SR1寄存器中的ADDR标志位会被置位,标志着地址传输已完成,从机已响应

EV8_1事件描述:为了继续通信,必须读取I2C_SR1寄存器,然后读取I2C_SR2寄存器来清除ADDR标志位。清除该标志后,通信流程可以继续,准备发送数据。EV8_1事件的产生其实就是为了清除EV6事件,为接下来写入数据作准备,写入DR寄存器该事件将被清除。

EV8事件描述:主机将要发送的数据字节写入I2C_DR寄存器中。每当I2C_DR寄存器中的数据被移送至移位寄存器并且I2C_DR寄存器空闲时,I2C_SR1寄存器中的TXE(Transmit Data Register Empty)标志位会被置位。这表明可以写入下一个字节的数据。

EV8_2事件描述:在所有数据发送完毕后,主机可以通过设置I2C_CR1寄存器中的STOP位来生成停止条件(Stop condition),从而结束通信并释放总线。

主机接收

EV5事件描述:主机通过设置I2C_CR1寄存器中的START位来生成起始信号(Start condition)。当起始条件在总线上生成后,I2C_SR1寄存器中的SB(Start Bit)标志位会被置位。这标志着起始信号已经成功发送,并且总线处于活动状态。

EV6事件描述:在EV5事件发生后,主机将从机地址和方向位(读/写)发送到I2C总线上。从机地址左移一位,并将最低位设置为1以指示读方向。当从机地址成功发送并且从机发回应答(ACK)时,I2C_SR1寄存器中的ADDR标志位会被置位。这标志着从机地址传输已完成,并且从机已经响应。

EV6_1事件:为了继续通信,必须读取I2C_SR1和I2C_SR2寄存器来清除ADDR标志位。清除后,通信流程可以继续,准备接收数据。

EV7事件描述:主机开始接收从机发送的数据。每接收到一个字节的数据,I2C_SR1寄存器中的RXNE(Receive Data Register Not Empty)标志位会被置位,表示I2C_DR寄存器中的数据已准备好被读取。此时,主机可以读取I2C_DR寄存器来获取接收到的数据。

EV7_1事件描述:当所有数据接收完毕后,主机需要生成停止信号(Stop condition)来释放I2C总线。通过设置I2C_CR1寄存器中的STOP位,主机会自动生成停止条件。

5.3 硬件I2C读写MPU6050

#include "stm32f10x.h"            // Device header
#include "MPU6050_Reg.h"
#define MPU6050_ADDRESS	0xD0	//MPU6050的I2C从机地址
/**
  * 函    数:MPU6050等待事件
  * 参    数:同I2C_CheckEvent
  * 返 回 值:无
  */
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
	uint32_t Timeout;
	Timeout = 10000;						//给定超时计数时间
	while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)		//循环等待指定事件
	{
		Timeout --;						//等待时,计数值自减
		if (Timeout == 0)					//自减到0后,等待超时
		{
			/*超时的错误处理代码,可以添加到此处*/
			break;						//跳出等待,不等了
		}
	}
}
/**
  * 函    数:MPU6050写寄存器
  * 参    数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述
  * 参    数:Data 要写入寄存器的数据,范围:0x00~0xFF
  * 返 回 值:无
  */
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	I2C_GenerateSTART(I2C2, ENABLE);										//硬件I2C生成起始条件
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);				//等待EV5
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);		//硬件I2C发送从机地址,方向为发送
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);		//等待EV6
	
	I2C_SendData(I2C2, RegAddress);											//硬件I2C发送寄存器地址
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);			//等待EV8
	
	I2C_SendData(I2C2, Data);												//硬件I2C发送数据
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);			//等待EV8_2
	
	I2C_GenerateSTOP(I2C2, ENABLE);							//硬件I2C生成终止条件
}
/**
  * 函    数:MPU6050读寄存器
  * 参    数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述
  * 返 回 值:读取寄存器的数据,范围:0x00~0xFF
  */
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
	I2C_GenerateSTART(I2C2, ENABLE);										//硬件I2C生成起始条件
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);				//等待EV5
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);		//硬件I2C发送从机地址,方向为发送
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);		//等待EV6
	
	I2C_SendData(I2C2, RegAddress);											//硬件I2C发送寄存器地址
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);			//等待EV8_2
	
	I2C_GenerateSTART(I2C2, ENABLE);										//硬件I2C生成重复起始条件
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);				//等待EV5
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);		//硬件I2C发送从机地址,方向为接收
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);		//等待EV6
	
	I2C_AcknowledgeConfig(I2C2, DISABLE);						//在接收最后一个字节之前提前将应答失能
	I2C_GenerateSTOP(I2C2, ENABLE);							//在接收最后一个字节之前提前申请停止条件
	
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);			//等待EV7
	Data = I2C_ReceiveData(I2C2);							//接收数据寄存器
	
	I2C_AcknowledgeConfig(I2C2, ENABLE);									//将应答恢复为使能,为了不影响后续可能产生的读取多字节操作
	
	return Data;
}
/**
  * 函    数:MPU6050初始化
  * 参    数:无
  * 返 回 值:无
  */
void MPU6050_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);		//开启I2C2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);		//开启GPIOB的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_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引脚初始化为复用开漏输出
	
	/*I2C初始化*/
	I2C_InitTypeDef I2C_InitStructure;					//定义结构体变量
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;				//模式,选择为I2C模式
	I2C_InitStructure.I2C_ClockSpeed = 50000;				//时钟速度,选择为50KHz
  I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;			//时钟占空比,选择Tlow/Thigh = 2
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;				//应答,选择使能
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//应答地址,选择7位,从机模式下才有效
	I2C_InitStructure.I2C_OwnAddress1 = 0x00;				//自身地址,从机模式下才有效
	I2C_Init(I2C2, &I2C_InitStructure);					//将结构体变量交给I2C_Init,配置I2C2
	
	/*I2C使能*/
  I2C_Cmd(I2C2, ENABLE);						//使能I2C2,开始运行
	
	/*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获取ID号
  * 参    数:无
  * 返 回 值:MPU6050的ID号
  */
uint8_t MPU6050_GetID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I);		//返回WHO_AM_I寄存器的值
}
/**
  * 函    数:MPU6050获取数据
  * 参    数:AccX AccY AccZ 加速度计X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
  * 参    数:GyroX GyroY GyroZ 陀螺仪X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
  * 返 回 值:无
  */
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;
}