1. 项目概述:GPIO模拟I2C的必要性与挑战
在嵌入式开发中,I2C总线因其简洁的两线制设计(SCL时钟线和SDA数据线)和主从架构,成为连接传感器、EEPROM等外设的常用接口。理想情况下,我们应当优先使用MCU内置的硬件I2C外设——它们经过芯片厂商优化,具有稳定的时序和中断/DMA支持。但实际工程中,硬件I2C引脚可能已被其他功能占用,或者PCB布局导致无法走线到目标位置。此时,用GPIO模拟I2C(俗称"bit-banging")成为唯一选择。
我曾在一个智能家居项目中遇到典型场景:主控STM32的硬件I2C1引脚连接了OLED,而新增的温湿度传感器SHT30因布局限制只能接到PA8和PA9。这种"引脚冲突"在资源受限的单片机开发中极为常见。通过GPIO模拟,我们成功实现了第二个I2C总线,实测传输速率达到87kHz(理论100kHz),完全满足传感器数据读取需求。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心实现:GPIO模拟I2C的代码解剖
2.1 基础宏定义与硬件抽象层
GPIO模拟的核心是对SCL和SDA线的精确控制。首先需要定义一组硬件操作宏,这些宏的具体实现取决于所用MCU平台:
c复制/* STM32 HAL库示例 */
#define SCL_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET)
#define SCL_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET)
#define SDA_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, GPIO_PIN_SET)
#define SDA_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, GPIO_PIN_RESET)
#define SDA_READ() HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_9)
/* 方向控制:推挽输出 vs 上拉输入 */
#define SDA_OUT() GPIOA->MODER = (GPIOA->MODER & ~GPIO_MODER_MODER9) | GPIO_MODER_MODER9_0
#define SDA_IN() GPIOA->MODER &= ~GPIO_MODER_MODER9
关键细节:SDA线需要双向控制,发送时为输出模式,接收时必须切换为输入模式。STM32的HAL库没有直接的方向切换函数,需要通过修改GPIO的MODER寄存器实现。不同MCU(如ESP32、GD32)的实现方式可能不同。
2.2 时序控制:精确延时的实现艺术
I2C协议对时序有严格要求,标准模式(100kHz)下,SCL高/低电平
