基于【软件I2C
】驱动
模块
增加【硬件I2C】驱动代码
-
配置硬件I2C驱动用到的函数
void I2C_Init();//初始化I2C
void I2C_StructInit();//给I2C结构体赋一个默认的初始值
void I2C_Cmd();//使能I2C
I2C_GenerateSTART();//生成起始条件
I2C_GenerateSTOP();//生成终止条件
I2C_AcknowledgeConfig();//收到一个字节后是否给从机应答ACK
I2C_SendData();//发送数据
I2C_ReceiveData();//接收数据
I2C_Send7bitAddress();//发送7位地址
I2C_CheckEvent();//I2C状态监控函数(同时判断一个或多个标志位)
//【EVx_x】事件可能同时置多个标志位
//用GetFlagStatus函数读多次进行判断太麻烦
//之前写的【MyI2C】文件中通过手动翻转GPIO口实现软件I2C
//本例使用硬件进行I2C底层驱动,因此首先将
【MyI2C】移除
配置I2C外设参考【硬件电路框图】
![](https://i-blog.csdnimg.cn/blog_migrate/7edf02b2218293471451c0cba6f35a00.png)
![](https://i-blog.csdnimg.cn/blog_migrate/e360a57ec660744befa8687bde6212b4.png)
在MPU6050.c中初始化函数
MPU6050_Init
void MPU6050_Init(void){//第一步:RCC开启I2C和GPIO的时钟(I2C1和I2C2都是APB1的外设)RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);RCC_APB1PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//第二步:把两个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);//第三步:初始化I2C外设I2C_InitTypeDef I2C_InitStructure;I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;//应答位配置(接收一个字节后是否给从机应答)//(默认给应答,之后可以单独用函数修改)I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//STM32作为从机可以响应几位地址I2C_InitStructure.I2C_ClockSpeed = 50000;//SCL时钟频率I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;//时钟占空比(快速100~400Hz有用)I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;//I2C模式I2C_InitStructure.I2C_OwnAddress1 = 0x00;//STM作为从机时自身的地址I2C_Init(I2C2,&I2C_InitStructure);//第四步:使能I2CI2C_Cmd(I2C2,ENABLE);//配置电源管理寄存器1MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);//配置电源管理寄存器2MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00);//采样率分频MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);//配置寄存器MPU6050_WriteReg(MPU6050_CONFIG,0x06);//陀螺仪配置寄存器MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);//加速度计配置寄存器MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);//配置之后陀螺仪内部开始连续不断地进行数据转换//输出的数据存放在对应的数据寄存器中}
实现写时序参考【主机发送流程图】
![](https://i-blog.csdnimg.cn/blog_migrate/4f2822fcf93bd8cbbc71ae9f2b6ebeaf.png)
在MPU6050.c中编写【指定地址写】函数
MPU6050_WriteReg
//指定地址写寄存器/*参数1:8位寄存器地址参数2:8位数据与软件I2C不同,在函数结束之后需要等待相应标志位以确保函数执行到位//【接收应答】并不需要一个函数来操作————发送数据自带了【接收应答】的过程*/void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data){//调用Start产生起始条件开始传输I2C_GenerateSTART(I2C2,ENABLE);while( I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS );//等待指定事件发生(未发生EV5事件就一直空循环等待)//第一个字节时序(必须是发送)——字节内容:从机地址(7位)+读写位(1位)I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);while( I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS);//等待指定事件发生(未发生EV6事件[发送模式]就一直空循环等待)//EV6事件之后是EV8_1事件(直接写入【数据寄存器DR】进行数据发送)I2C_SendData(I2C2,RegAddress);//指定具体写的寄存器地址,这个字节存放在MPU6050的当前地址指针里while( I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS);//等待指定事件发生(未发生EV8事件就一直空循环等待)//发生了EV8事件后可以继续写数据2I2C_SendData(I2C2,Data);while( I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS );//等待指定事件发生(未发生EV8_2事件就一直空循环等待)//调用Stop产生终止条件结束传输I2C_GenerateSTOP(I2C2,ENABLE);/*总结:有连续的数据需要发送时,在发送过程中需要等待EV8事件当发送完最后一个字节时,则需要等待EV8_2事件*/}
实现读时序参考【主机读取流程图】
![](https://i-blog.csdnimg.cn/blog_migrate/914ae7a46842315702496c7335705b54.png)
在MPU6050.c中编写【指定地址读】函数
MPU6050_ReadReg
//指定地址读寄存器/*参数:指定主机读取的地址//【发送应答】并不需要一个函数来操作————接收数据自带了【发送应答】的过程复合格式写+读*/uint8_t MPU6050_ReadReg(uint8_t RegAddress){//定义变量存储DRuint8_t Data;//调用Start产生起始条件开始传输I2C_GenerateSTART(I2C2,ENABLE);while( I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS );//等待指定事件发生(未发生EV5事件就一直空循环等待)//第一个字节时序(必须是发送)——字节内容:从机地址(7位)+写位(1位)I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);while( I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS);//等待指定事件发生(未发生EV6事件就一直空循环等待)//EV6事件之后是EV8_1事件(直接写入【数据寄存器DR】进行数据发送)I2C_SendData(I2C2,RegAddress);//指定具体写的寄存器地址,这个字节存放在MPU6050的当前地址指针里while( I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS);//等待指定事件发生(未发生EV8事件就一直空循环等待)//调用Start产生【重复起始】(重新指定读写位)I2C_GenerateSTART(I2C2,ENABLE);while( I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS );//等待指定事件发生(未发生EV5事件就一直空循环等待)//第一个字节时序(选择接收)——字节内容:从机地址(7位)+读位(1位)I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Receiver);while( I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != SUCCESS);//等待指定事件发生(未发生EV6事件[接收模式]就一直空循环等待)I2C_AcknowledgeConfig(I2C2,DISABLE);//ACK置0,不给从机应答I2C_GenerateSTOP(I2C2,ENABLE);//STOP置1,产生终止条件//在最后一个字节数据接收之前,提前把ACK置0,STOP置1while( I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);//等待指定事件发生(未发生EV7事件[数据2整体转移到【数据寄存器】]就一直空循环等待)//读取数据寄存器DRData = I2C_ReceiveData(I2C2);I2C_AcknowledgeConfig(I2C2,ENABLE);//恢复默认状态ACK位为1,给从机应答return Data;}
//主函数和读取DR函数不变,硬件I2C仅改变初始化和底层读写函数
实现功能:上电后初始化I2C和OLED模块,显示芯片ID号,在循环中通过软件I2C不断读取加速度计和陀螺仪数据寄存器,并通过OLED显示对应XYZ轴数值(与软件I2C实现功能相同)
需要改进的地方:当前程序中存在大量while死循环等待,一旦有个事件一直没有产生会导致程序卡死
//由于STM32的硬件I2C存在缺陷,这种情况确实存在(比如我的硬件I2C就会卡在发送的EV5事件)
-
解决方法:使用【超时退出机制】
//超时退出机制函数void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT){uint32_t Timeout;Timeout = 1000;//自定义最大等待值while(I2C_CheckEvent(I2C2,I2C_EVENT) != SUCCESS){Timeout --;if(Timeout == 0){break;}}}
-
用超时退出机制函数替换掉之前的while循环
例://调用Start产生起始条件开始传输I2C_GenerateSTART(I2C2,ENABLE);while( I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS );//等待指定事件发生(未发生EV5事件就一直空循环等待)//调用Start产生起始条件开始传输I2C_GenerateSTART(I2C2,ENABLE);MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//等待指定事件发生(未发生EV5事件就一直空循环等待)