一、基本概念
由于串口通信属于点对点的通信,若通信设备过多会使得线路复杂,因此多设备的通信采用总线进行,如:usb,spi,can,i2c等。i2c通信 有SCL(时钟线),SDL(数据线)这两条通信线。同步,半双工模式。
注:
1.SCL和SDL上的设备都要设置成开漏输出模式。
2.SCL和SDL都要加上一个上拉电阻,阻值为4.7KΩ左右。
3.主设备:控制SCL时钟线,产生时钟,产生起始和终止信号。
4.每个从设备都有一个唯一的设备地址,设备地址有7位和10位的地址,在设备的厂家手册里面可以找到。若一模一样的设备挂载在总线上,可以通过可变地址位进行区分。(一般情况,高位由厂商确定,低位可以通过控制引脚的高低电平改变)。
这样设置的理由:上拉电阻可以使得两条通信线上面的电平默认保持高电平,开漏输出使得两条通信线上的电平可以被拉低。这样做可以起到保护电源,避免引脚模式的频繁切换的作用。(协调主从设备之间的输入输出问题,由于采用半双工模式,若不这样做,容易造成设备之间的短路。)这里要注意,两条通信线上只要任意设备拉低了某条线的电平,这一整条线电平都会拉低。
二、数据帧
1.基本时序单元:
①起始信号:SCL-高电平,SDA-下降沿
②停止信号:SCL-高电平,SDA-上升沿
③逻辑1:SDA-高
④逻辑0:SDA-低
注:数据0/1的发送由SDA控制。
⑤发送一个字节:SCL低电平期间,主机将数据位依次放在SDA线上(高位先行),然后释放SCL,从机在拉高的上升沿瞬间接收SDA的数据位,依次循环8次即可发送一个字节。
注:在SCL高电平期间,不可以改变SDA的数据,在低电平期间可以改变。
⑥接收一个字节:SCL低电平期间,从机将数据位依次放在SDA线上(高位先行),然后释放SCL,主机在拉高的上升沿瞬间接收SDA的数据位,依次循环8次即可接收一个字节。
注:同上。此外,主机对SDA有绝对的控制权,在主机发送给从机数据时就让主机管着,但是当主机接收从机的数据时,由于总线“线与”的特点,为了判断从机发送的数据,需要主机释放SDA。
【注:SCL低电平写,SCL高电平读】
⑦发送应答:主机在接收一个字节后,主机立马在下一个时钟发送一位数据给从机(在SCL高电平读取应答位),判断主机是否应答,若从机发送数据得到主机应答则继续发送,若没得到主机应答则停止发送。数据0表示应答,1表示不应答。
⑧接收应答:主机在发送完一个字节之后,主机立马在下一个时钟接收一位数据(在SCL高电平读取应答位),判断从机是否应答,若从机接收到主机的数据则应答,若没接收到则不应答。数据0表示应答,1表示不应答。(主机在接收应答位之前,要释放SDA)
通俗的讲:
从机接收主机发送来的数据时,要跟接收应答,告诉主机接收到了。->主发从收
主机接收从机发送来的数据时,要跟发送应答,告诉从机发送到了。->主收从发
2.读/写数据帧格式:
(1)指定地址写:
定义:对指定设备,在指定的寄存器,写入指定数据。
格式:起始位 + 指定设备地址(Slave Address-7位)+ 读/写位(0写,1读) + 应答位RA(Receive ACK) + 指定寄存器地址(Reg Address-8位) + 指定数据(Data-8位) + 应答位RA(Receive ACK) + 停止位
(2)当前地址读:
定义:对指定的设备,在当前地址指针下,读取从机数据。
格式:起始位 + 指定设备地址 + 读/写位 + 应答位 + 指定数据 + 应答位 + 停止位
注:这里的当前地址从0开始,每读一次地址自增1。
(3)指定地址读:
定义:对指定设备,在指定寄存器,读取从机数据。
格式:起始位 + 指定设备地址 + 读/写位 + 应答位 + 指定寄存器地址 + 应答位 + 起始位 + 指定设备地址 + 读/写位 + 应答位 + 接受数据 + 停止位(指定地址写但还没来得及写数据+当前地址读的复合)
注:因为读不能指定寄存器的地址,利用读操作,将指针移至指定的寄存器位置,然后对当前指针进行读操作。此外,为了让读操作停止下来,最后的一个应答位要给1不应答!!!
三、stm32利用I2C对MPU6050进行通信(软件)
【注:以下程序是通过软件编写写I2C通信,通过控制引脚的高低电平来控制SCL和SDA总线的数据。所以,SCL和SDA可以是任意引脚。并且引脚采用通用开漏输出模式。】
1.编写I2C通讯:MyI2C.C
①.对SCL总线写字节:MyI2C_W_SCL(uint8_t BitValue)
利用GPIO_WriteBit( )对SCL对应的引脚写电平
②.对SDA总线写字节:MyI2C_W_SDA(uint8_t BitValue)
利用GPIO_WriteBit( )对SDA对应的引脚写电平
③.接收SDA总线的字节:uint8_t MyI2C_R_SDA(void)
利用GPIO_ReadInputDataBit( )获取SDA上每一位的数据
④.初始化I2C总线:MyI2C_Init( )
将SCL和SDA总线对应的GPIO全部设置成开漏输出模式
⑤.起始数据帧: MyI2C_Start( )
⑥.停止数据帧:MyI2C_Stop( )
⑦.发送一个字节:MyI2C_SendByte(uint8_t Byte)
⑧.接收一个字节:uint8_t MyI2C_ReciveByte(void)
⑨.发送应答位:MyI2C_SendAck(uint8_t AckBit)
⑩.接收应答位:uint8_t MyI2C_ReceiveAck(void)
#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);//对PB10进行引脚电平设置,也就是I2C_SCL Delay_us(10); } /** * 函 数:I2C写SDA引脚电平 * 参 数:BitValue 协议层传入的当前需要写入SDA的电平,范围0~0xFF * 返 回 值:无 * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SDA为低电平,当BitValue非0时,需要置SDA为高电平 */ void MyI2C_W_SDA(uint8_t BitValue) { GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue); Delay_us(10); } /** * 函 数: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); Delay_us(10); return BitValue; } /** * 函 数:I2C初始化->对SCL和SDA的引脚的GPIO口进行初始化,这里按照stm32的规则推荐,这是为开漏输出模式 * 参 数:无 * 返 回 值:无 * 注意事项:此函数需要用户实现内容,实现SCL和SDA引脚的初始化 */ void MyI2C_Init(void) { //开启RCC时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //初始化GPIOB GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB,&GPIO_InitStruct); //设置默认电平->都置高电平,释放总线 GPIO_SetBits(GPIOB,GPIO_Pin_10 | GPIO_Pin_11); } /*协议层*/ /** * 函 数:I2C起始 * 参 数:无 * 返 回 值:无 */ void MyI2C_Start(void) { MyI2C_W_SCL(1); MyI2C_W_SDA(1); MyI2C_W_SDA(0); MyI2C_W_SCL(0); } /** * 函 数:I2C终止 * 参 数:无 * 返 回 值:无 */ void MyI2C_Stop(void) { MyI2C_W_SDA(0); MyI2C_W_SCL(1); MyI2C_W_SDA(1); //MyI2C_W_SCL(0);//这里可以拉低也可以不拉低,因为停止位在应答位后面,应答位会拉低SCL } /** * 函 数:I2C发送一个字节 * 参 数:Byte 要发送的一个字节数据,范围:0x00~0xFF * 返 回 值:无 */ void MyI2C_SendByte(uint8_t Byte) { uint8_t i; for(i=0;i<8;i++) { MyI2C_W_SDA(Byte&(0x80>>i));//取出Byte的每一位并且依次写入 MyI2C_W_SCL(1); MyI2C_W_SCL(0); } } /** * 函 数:I2C接收一个字节 * 参 数:无 * 返 回 值:接收到的一个字节数据,范围:0x00~0xFF */ uint8_t MyI2C_ReciveByte(void) { uint8_t i,Byte=0x00; MyI2C_W_SDA(1); for(i=0;i<8;i++) { MyI2C_W_SCL(1); if(MyI2C_R_SDA()==1){Byte|=(0x80>>i);}//如果接收到的数据位等于1就给Byte对于的位写1,不是就跳过保持0 MyI2C_W_SCL(0); } return Byte; } /** * 函 数:I2C发送应答位 * 参 数:Byte 要发送的应答位,范围:0~1,0表示应答,1表示非应答 * 返 回 值:无 */ void MyI2C_SendAck(uint8_t AckBit) { MyI2C_W_SDA(AckBit); MyI2C_W_SCL(1); MyI2C_W_SCL(0); } /** * 函 数:I2C接收应答位 * 参 数:无 * 返 回 值:接收到的应答位,范围:0~1,0表示应答,1表示非应答 */ uint8_t MyI2C_ReceiveAck(void) { uint8_t AckBit; MyI2C_W_SCL(1); MyI2C_W_SDA(1); AckBit=MyI2C_R_SDA(); MyI2C_W_SCL(0); return AckBit; }
2.利用I2C对MPU6050通讯:
MPU6050_Reg.h:对MPU6050相关寄存器重新宏定义地址
#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
①写MUPU6050的指定寄存器:MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
②读MUPU6050的指定寄存器:uint8_t MPU6050_ReadReg(uint8_t RegAddress)
③初始化:MPU6050_Init( )
④MPU6050获取ID号:uint8_t MPU6050_GetID(void)
⑤MPU6050获取数据:MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)这里要返回多个参数,是通过获取主函数当中变量的地址,传递给子函数,然后对子函数地址的内容进行写入。
#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; //数据拼接,通过输出参数返回 }
四、stm32利用I2C对MPU6050进行通信(硬件)
【注:这里是使用的stm32的I2C外设对MPU6050进行通信,引脚只能选择具有I2C复用功能的引脚,并且配置引脚时要选择复用开漏功能】
① 等待MPU6050事件:MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
利用while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)等待事件的发生,并且给予等待时间,如果超时了就不等待了
② 写MPU6050寄存器:MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
步骤:
step1:起始:I2C_GenerateSTART( )
等待EV5:MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT)
step2:发送从机地址:I2C_Send7bitAddress( )
等待EV6: MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)
step3:发送寄存器地址I2C_SendData( )
等待发送:
EV8:MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING)
step4:发送数据:I2C_SendData( )
等待EV8_2:
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED)
step5:停止:I2C_GenerateSTOP( )
③ 读MPU6050寄存器:uint8_t MPU6050_ReadReg(uint8_t RegAddress)
步骤:
step1:起始:I2C_GenerateSTART( )
等待EV5:MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT)
step2:发送从机地址:I2C_Send7bitAddress( )
等待EV6:
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)
step3:发送寄存器地址:I2C_SendData( )
等待EV8_2:
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED)
step4:重复起始位:I2C_GenerateSTART( )
等待EV5事件:MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT)
step5:发送从机地址,方向为接收:I2C_Send7bitAddress( )
等待EV6:
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)
step6:在接收最后一个字节之前提前将应答失能:I2C_AcknowledgeConfig( )
在接收最后一个字节之前提前申请停止条件:I2C_GenerateSTOP( )
step7:等待EV7:MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED)
接收数据寄存器:Data = I2C_ReceiveData(I2C2)
step8:将应答恢复为使能,为了不影响后续可能产生的读取多字节操作 :
I2C_AcknowledgeConfig(I2C2, ENABLE)
④MPU6050初始化:MPU6050_Init( )
步骤:
step1:开始I2C和GPIOB的时钟
step2:配置I2C和GPIOB的结构体:
【注:i2c结构体配置:这里的占空比设置是为适应不同SCL传输频率而设定的,如果SCL的频率过大,在SCL低电平期间,可能还没有完成对SDA总线上数据的改变,就立马变高电平了。所以需要设置占空比使得低电平期间有一定的缓冲时间。在SCL标准速度下,占空比相当于1:1,在SCL快速模式下,占空比相当于2:1。此外,SCL总线是强下拉,弱上拉,所以下降沿波形是立马下降,上升沿波形是缓慢上升。】
typedef struct
{
uint32_t I2C_ClockSpeed; /*设置SCL时钟频,此值不低于40000*/
uint16_t I2C_Mode; /* 指定工作模式,可选I2C模式和SMBUS模式*/
uint16_t I2C_DutyCycle; /*指定时钟占空比,可选 low/high=2:1以及16:9模式*/
uint16_t I2C_OwnAddress1; /*指定自身的I2C设备地址 */
uint16_t I2C_Ack; /*使能或者关闭响应 (一般都是使能) */
uint16_t I2C_AcknowledgedAddress; /*指定地址的长度,可以位7位及10位 */
}I2C_InitTypeDef;
step3:使能I2C
step4:对MPU6050的寄存器初始化
以下为全部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; //定义数据高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; //数据拼接,通过输出参数返回
}