1. IIC通信协议基础认知
I²C(Inter-Integrated Circuit)总线是Philips半导体(现NXP)在1980年代开发的双线式串行通信协议。两根信号线就能实现完整的主从式通信——这可能是现代电子工程中最优雅的设计之一。SDA(数据线)和SCL(时钟线)通过上拉电阻连接到VCC,这种开漏输出结构允许多个设备并联在同一条总线上。
硬件I²C和软件模拟I²C的本质区别在于时钟生成方式。硬件I²C由MCU的专用外设控制器生成精确时序,而软件方案则需要通过GPIO翻转手动控制时序。以STM32为例,其硬件I²C控制器可自动处理起始条件、停止条件和ACK/NACK响应,但实际项目中我们常遇到:
- 硬件I²C的时钟配置复杂
- 某些MCU的I²C外设存在已知缺陷
- 需要兼容不同电压等级的器件
这就引出了软件模拟I²C的实用价值。通过GPIO模拟,我们可以:
- 精确控制时序参数以适应不同从设备
- 规避硬件外设的兼容性问题
- 实现跨平台移植
- 在同一总线上混合不同速率的设备
2. 模拟I²C的底层驱动实现
2.1 GPIO端口配置要点
在STM32 HAL库环境中,模拟I²C的GPIO初始化需要特别注意:
c复制GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = I2C_SCL_PIN | I2C_SDA_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; // 开漏输出
GPIO_InitStruct.Pull = GPIO_PULLUP; // 使能内部上拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(I2C_PORT, &GPIO_InitStruct);
关键细节:必须配置为开漏输出(GPIO_MODE_OUTPUT_OD)而非推挽输出,否则总线冲突时可能损坏器件。我曾在一个电机控制项目中因误用推挽模式导致三个传感器芯片过热损坏。
2.2 时序精确控制技巧
标准模式(100kHz)下关键时序参数:
- 起始条件保持时间:>4.7μs
- 数据保持时间:>250ns
- 时钟高电平周期:>4μs
通过SysTick实现的微秒级延时函数:
c复制void I2C_Delay(uint32_t us)
{
uint32_t ticks = us * (SystemCoreClock / 1000000);
uint32_t start = SysTick->VAL;
while((start - SysTick->VAL) < ticks);
}
实际项目中发现的隐藏问题:当系统中断频繁时,SysTick可能被抢占导致时序错乱。解决方案是:
- 提升I²C任务优先级
- 在关键时序段禁用中断
- 改用硬件定时器生成延时
3. 协议层的完整实现
3.1 起始与停止条件
起始条件(START)的规范实现:
c复制void I2C_Start(void)
{
SDA_HIGH();
SCL_HIGH();
I2C_Delay(5); // 确保总线空闲
SDA_LOW(); // 下降沿触发起始条件
I2C_Delay(4);
SCL_LOW(); // 准备数据传输
}
`
