STM32 软件模拟IIC实战:从协议解析到代码实现

张开发
2026/4/11 10:39:34 15 分钟阅读

分享文章

STM32 软件模拟IIC实战:从协议解析到代码实现
1. IIC协议基础与STM32模拟的必要性IICInter-Integrated Circuit是飞利浦公司开发的一种串行通信协议它只需要两根信号线SCL时钟线和SDA数据线就能实现设备间的数据交互。在实际项目中我们经常会遇到STM32硬件IIC接口资源不足或者硬件IIC存在兼容性问题的情况。这时候用GPIO口模拟IIC就成为了一个非常实用的解决方案。我第一次用软件模拟IIC是在做一个智能家居项目时需要同时连接多个传感器但STM32F103的硬件IIC只有两个根本不够用。当时尝试用GPIO模拟后发现不仅解决了接口数量问题调试起来反而比硬件IIC更直观。软件模拟最大的优势就是可以完全掌控时序这在调试某些特殊设备时特别有用。IIC协议有几个关键特性需要特别注意它是主从式结构支持多主多从标准模式速度100kHz快速模式400kHz采用开漏输出需要外接上拉电阻数据在SCL高电平时采样变化只能在SCL低电平时发生2. IIC协议时序的软件实现要点2.1 起始和停止信号的实现起始信号和停止信号是IIC通信的开关必须严格符合时序要求。我在实际项目中遇到过因为这两个信号不标准导致设备无响应的情况。起始信号的正确实现应该是先将SDA和SCL都置高保持SCL高电平将SDA从高拉低最后将SCL拉低准备数据传输对应的代码实现要特别注意延时void IIC_Start(void) { SDA_OUT(); IIC_SDA 1; IIC_SCL 1; delay_us(4); // 保持时间大于4.7μs IIC_SDA 0; delay_us(4); IIC_SCL 0; // 钳住总线 }停止信号则是相反的过程先将SDA置低SCL置低拉高SCL在SCL高电平时将SDA从低拉高2.2 数据有效性窗口控制IIC协议规定数据在SCL高电平期间必须保持稳定这个时间窗口特别关键。我曾经调试过一个温湿度传感器就是因为数据保持时间不够导致读数不稳定。在软件模拟时建议采用这样的时序在SCL低电平时改变SDA数据拉高SCL并保持足够时间标准模式至少4.7μs拉低SCL完成一位传输重复上述过程直到8位数据传输完成3. 完整的软件IIC驱动实现3.1 GPIO初始化配置在STM32上实现软件IIC首先要正确配置GPIO。我习惯用PB6和PB7作为SCL和SDA因为这两个引脚位置相邻布线方便。void IIC_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStruct.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStruct.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStruct); IIC_SCL 1; IIC_SDA 1; }这里有几个经验点初始状态要保持SCL和SDA都为高输出模式选择推挽输出(Out_PP)而不是开漏输出速度设置为50MHz确保快速响应3.2 数据收发函数实现发送一个字节的函数需要特别注意数据位的顺序。IIC协议规定先传输最高位(MSB)这个细节很容易出错。void IIC_Send_Byte(u8 txd) { u8 t; SDA_OUT(); IIC_SCL 0; for(t0; t8; t) { IIC_SDA (txd 0x80) 7; txd 1; delay_us(2); IIC_SCL 1; delay_us(2); IIC_SCL 0; delay_us(2); } }接收函数则要考虑主设备在读取完一个字节后是否需要发送应答u8 IIC_Read_Byte(u8 ack) { u8 i, receive 0; SDA_IN(); for(i0; i8; i) { IIC_SCL 0; delay_us(2); IIC_SCL 1; receive 1; if(READ_SDA) receive; delay_us(1); } if(!ack) IIC_NAck(); else IIC_Ack(); return receive; }4. 常见问题与调试技巧4.1 时序不匹配问题调试软件IIC最常见的问题就是时序不匹配。我总结了一个实用的调试方法用逻辑分析仪抓取实际波形对照器件手册的时序要求检查重点关注以下几个参数起始信号保持时间数据建立时间SCL高低电平时间如果发现时序偏差可以通过调整delay_us()的参数来修正。但要注意延时太短可能导致设备无法识别太长又会影响通信速率。4.2 从设备无应答的排查当从设备不返回ACK时可以按照以下步骤排查检查设备地址是否正确很多设备地址与手册标注的不同确认上拉电阻值合适通常4.7kΩ测量电源电压是否稳定检查PCB布线是否有干扰有一次我调试MPU6050时就是因为忘记将AD0引脚接地导致实际地址与预期不符浪费了半天时间。4.3 多设备冲突处理当总线上挂载多个设备时可能会遇到这些问题某个设备异常导致总线锁死地址冲突总线电容过大导致边沿变缓解决方法包括为每个设备单独设计电源开关在代码中加入总线恢复机制适当减小上拉电阻值但不低于1kΩ5. 实际应用案例读取MPU6050数据以常用的MPU6050陀螺仪为例演示如何使用软件IIC读取其数据。MPU6050的器件地址是0x68AD0接地或0x69AD0接VCC。读取加速度计数据的流程如下// 读取MPU6050加速度计数据 void MPU6050_ReadAccel(short *accelData) { u8 buf[6]; IIC_Start(); IIC_Send_Byte(0xD0); // 器件地址写 IIC_Wait_Ack(); IIC_Send_Byte(0x3B); // 加速度计数据起始寄存器 IIC_Wait_Ack(); IIC_Start(); IIC_Send_Byte(0xD1); // 器件地址读 IIC_Wait_Ack(); buf[0] IIC_Read_Byte(1); // X高字节 buf[1] IIC_Read_Byte(1); // X低字节 buf[2] IIC_Read_Byte(1); // Y高字节 buf[3] IIC_Read_Byte(1); // Y低字节 buf[4] IIC_Read_Byte(1); // Z高字节 buf[5] IIC_Read_Byte(0); // Z低字节 IIC_Stop(); accelData[0] (buf[0]8)|buf[1]; accelData[1] (buf[2]8)|buf[3]; accelData[2] (buf[4]8)|buf[5]; }这个例子展示了典型的IIC连续读取操作。有几个关键点需要注意写入时要先发送寄存器地址读取时需要重新发送起始条件最后一个字节读取后要发送NACK数据通常以高位在前的方式组合在实际项目中我发现MPU6050对时序要求相对宽松是练习软件IIC的好选择。但像某些EEPROM芯片对时序要求就严格得多这时候可能需要微调延时参数。

更多文章