学习交流792125321,欢迎一起加入讨论!
在学习iic的时候,我们经常会遇到软件 I²C和硬件 I²C,它两到底有什么区别呢?
软件 I²C(模拟 I²C)和硬件 I²C(外设 I²C)是两种实现 I²C 总线通信的方式,核心区别在于 是否依赖微控制器(MCU)内置的硬件 I²C 外设。以下是详细对比及标准库(以 STM32 标准外设库为例)的实现差异:
1. 核心区别
特性 | 软件 I²C | 硬件 I²C |
---|---|---|
实现方式 | 通过 GPIO 引脚模拟 I²C 时序(软件控制) | 使用 MCU 内置的硬件 I²C 外设(硬件控制) |
CPU 占用 | 高(需 CPU 持续操作 GPIO) | 低(硬件自动完成时序,CPU 可处理其他任务) |
时序精度 | 依赖软件延时,精度较低 | 由硬件时钟控制,精度高且稳定 |
开发复杂度 | 简单(无需配置复杂寄存器) | 复杂(需初始化外设、处理中断/DMA) |
灵活性 | 高(可适配任意 GPIO 引脚) | 低(必须使用硬件 I²C 外设的固定引脚) |
速度 | 较慢(受限于软件延时) | 较快(支持标准模式 100kHz、快速模式 400kHz+) |
兼容性 | 通用性强(可适配不同 MCU) | 依赖具体 MCU 的硬件支持 |
2. 标准库实现对比(以 STM32F1 标准外设库为例)
(1) 硬件 I²C 实现
硬件 I²C 使用 STM32 内置的 I²C 外设,需配置时钟、引脚复用、中断/DMA 等。
代码示例:初始化硬件 I²C1(标准模式,100kHz)
#include "stm32f10x_i2c.h"
void I2C_Hardware_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct;
I2C_InitTypeDef I2C_InitStruct;
// 使能时钟(I2C1 和 GPIOB)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
// 配置 GPIOB6 (SCL) 和 GPIOB7 (SDA) 为复用开漏模式
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD; // 复用开漏
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
// 配置 I2C1
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2; // 占空比 16:9
I2C_InitStruct.I2C_OwnAddress1 = 0xA0; // 主机地址(可忽略)
I2C_InitStruct.I2C_Ack = I2C_Ack_Enable; // 启用应答
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStruct.I2C_ClockSpeed = 100000; // 100kHz
I2C_Init(I2C1, &I2C_InitStruct);
// 启用 I2C1
I2C_Cmd(I2C1, ENABLE);
}
// 发送数据函数(需处理状态标志和中断)
void I2C_WriteByte(uint8_t devAddr, uint8_t regAddr, uint8_t data) {
I2C_GenerateSTART(I2C1, ENABLE);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1, devAddr, I2C_Direction_Transmitter);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
I2C_SendData(I2C1, regAddr);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_SendData(I2C1, data);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_GenerateSTOP(I2C1, ENABLE);
}
(2) 软件 I²C 实现
通过 GPIO 手动控制 SCL 和 SDA 引脚电平,模拟 I²C 时序。
代码示例:模拟 I²C 时序(使用 GPIOB8 和 GPIOB9)
#include "stm32f10x_gpio.h"
// 定义 SCL 和 SDA 引脚
#define SOFT_I2C_SCL_PIN GPIO_Pin_8
#define SOFT_I2C_SDA_PIN GPIO_Pin_9
#define SOFT_I2C_PORT GPIOB
// 初始化 GPIO
void Soft_I2C_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
// 配置 SCL 和 SDA 为开漏输出模式
GPIO_InitStruct.GPIO_Pin = SOFT_I2C_SCL_PIN | SOFT_I2C_SDA_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD; // 开漏输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(SOFT_I2C_PORT, &GPIO_InitStruct);
// 初始拉高 SCL 和 SDA
GPIO_SetBits(SOFT_I2C_PORT, SOFT_I2C_SCL_PIN);
GPIO_SetBits(SOFT_I2C_PORT, SOFT_I2C_SDA_PIN);
}
// 微秒级延时函数(需根据实际时钟调整)
void Delay_us(uint32_t us) {
us *= 72; // 假设主频为 72MHz
while (us--) __NOP();
}
// 发送起始信号
void Soft_I2C_Start(void) {
GPIO_ResetBits(SOFT_I2C_PORT, SOFT_I2C_SDA_PIN);
Delay_us(5);
GPIO_ResetBits(SOFT_I2C_PORT, SOFT_I2C_SCL_PIN);
}
// 发送停止信号
void Soft_I2C_Stop(void) {
GPIO_ResetBits(SOFT_I2C_PORT, SOFT_I2C_SDA_PIN);
Delay_us(5);
GPIO_SetBits(SOFT_I2C_PORT, SOFT_I2C_SCL_PIN);
Delay_us(5);
GPIO_SetBits(SOFT_I2C_PORT, SOFT_I2C_SDA_PIN);
}
// 发送一个字节
void Soft_I2C_WriteByte(uint8_t data) {
for (int i = 0; i < 8; i++) {
if (data & 0x80) {
GPIO_SetBits(SOFT_I2C_PORT, SOFT_I2C_SDA_PIN);
} else {
GPIO_ResetBits(SOFT_I2C_PORT, SOFT_I2C_SDA_PIN);
}
Delay_us(2);
GPIO_SetBits(SOFT_I2C_PORT, SOFT_I2C_SCL_PIN);
Delay_us(5);
GPIO_ResetBits(SOFT_I2C_PORT, SOFT_I2C_SCL_PIN);
data <<= 1;
}
// 等待从机应答(省略应答检查)
GPIO_SetBits(SOFT_I2C_PORT, SOFT_I2C_SDA_PIN);
Delay_us(2);
GPIO_SetBits(SOFT_I2C_PORT, SOFT_I2C_SCL_PIN);
Delay_us(5);
GPIO_ResetBits(SOFT_I2C_PORT, SOFT_I2C_SCL_PIN);
}
3. 适用场景
场景 | 推荐方式 | 原因 |
---|---|---|
高速通信(>100kHz) | 硬件 I²C | 依赖硬件时序精度,避免软件延时误差 |
多任务系统 | 硬件 I²C | 减少 CPU 占用,支持 DMA/中断 |
引脚资源紧张 | 硬件 I²C | 必须使用固定引脚,避免浪费 GPIO |
适配非标准 I²C 设备 | 软件 I²C | 可灵活调整时序(如长延时、非标准协议) |
硬件 I²C 外设不可用 | 软件 I²C | 解决硬件资源冲突或兼容性问题 |
4. 常见问题
-
硬件 I²C 初始化失败:
检查时钟配置、引脚复用、上拉电阻(硬件 I²C 需要外部上拉,通常 4.7kΩ)。 -
软件 I²C 通信不稳定:
调整延时函数精度,确保 SCL/SDA 边沿时间符合设备要求。 -
速度瓶颈:
软件 I²C 通常无法超过 100kHz,硬件 I²C 可支持 400kHz(Fast Mode)或更高。
总结
- 硬件 I²C:适合高速、高稳定性场景,但开发复杂且依赖固定引脚。
- 软件 I²C:灵活简单,但占用 CPU 资源且速度受限。
根据项目需求选择合适方案:优先使用硬件 I²C 提升性能,若硬件资源不足或需要特殊时序,则用软件模拟。