驱动之路 #44:硬件 I2C 和软件 I2C 谁更坑?
**先说结论:**能用硬件 I2C,就优先用硬件 I2C;软件 I2C 不是不能用,而是更适合做补救方案。
前面几篇文章聊过 I2C 通信机制、上拉电阻、SMBus 以及 Linux I2C 子系统架构。实际项目里经常还会遇到一个选择:硬件 I2C 和软件 I2C 到底有什么区别?
1. I2C 通信的两种实现方式
硬件 I2C
使用 SoC 内部集成的 I2C 控制器,例如 RK3576 的 I2C0、I2C1、I2C2 等。
软件 I2C
使用两个普通 GPIO,通过软件手动控制电平变化,模拟 SDA/SCL 时序。
简单理解:
- **硬件 I2C:**专用控制器负责传输。
- **软件 I2C:**CPU 控制 GPIO,模拟总线时序。
两者都能在 SDA/SCL 上产生 I2C 时序并与外设通信,但在实现方式、稳定性、CPU 占用和适用场景上差别很大。
2. 硬件 I2C 是什么?
硬件 I2C 是芯片内部集成的专用 I2C 控制器。以 RK3576 为例,控制器驱动通常位于:
text
kernel-6.1/drivers/i2c/busses/i2c-rk3x.c
开发者不需要手动控制 SDA/SCL 的每一次翻转,通常只需要:
- 在设备树中打开对应的 I2C 控制器;
- 配置 pinctrl、clock、status 等资源;
- 在 I2C 节点下挂载从设备;
- 通过设备驱动、
i2c-tools或i2c_transfer()访问设备。
真正通信时,硬件控制器会自动完成:
- Start 条件;
- 地址发送;
- 读写位处理;
- ACK/NACK 检测;
- 数据收发;
- Stop 条件。
传输完成后,再通过中断或轮询通知 CPU。
硬件 I2C 的核心特点:时序由硬件保证,CPU 不必一直盯着 SDA/SCL 翻转。
3. 软件 I2C 是什么?
软件 I2C 也叫 GPIO 模拟 I2C。既然 I2C 的本质是 SDA/SCL 两根线的电平变化,就可以使用两个普通 GPIO 手动模拟时序。
典型流程如下:
- SCL 高电平时,让 SDA 由高变低,产生 Start;
- SCL 拉低,准备数据;
- SCL 拉高,采样数据;
- 第 9 个时钟读取 ACK;
- SCL 高电平时,让 SDA 由低变高,产生 Stop。
Linux 内核已经提供 GPIO 模拟 I2C 驱动:
text
drivers/i2c/busses/i2c-gpio.c
配置好 SDA/SCL 对应 GPIO 后,可通过 i2c-gpio 注册一个软件模拟的 I2C adapter,通常不需要从零编写 bit-bang 时序。
**优点:**引脚灵活,只要有两个能正常输入输出的 GPIO,理论上就能模拟一条 I2C 总线。
**代价:**时序依赖软件,CPU 需要参与,稳定性更容易受到系统状态影响。
4. 软件 I2C 为什么更容易踩坑?
软件 I2C 最容易出问题的地方是时序敏感。硬件 I2C 的 SCL 高低电平持续时间、SDA 变化时机、ACK 采样等由硬件控制器处理;软件 I2C 则需要 CPU 配合延时函数控制 GPIO。
一次典型的 bit-bang 流程可能是:
text
拉低 SCL
设置 SDA
延时
拉高 SCL
延时
读取 SDA
再拉低 SCL
流程看似简单,但系统负载高、中断频繁或线程被调度时,时序就可能抖动。
- 裸机环境中,CPU 基本只运行当前代码,时序相对可控。
- Linux 是多任务系统,线程可能被调度,中断也可能随时进入。
因此,软件 I2C 通常更适合低速、低数据量、非关键外设。
5. 硬件 I2C 与软件 I2C 对比
| 对比项 | 硬件 I2C | 软件 I2C |
|---|---|---|
| 实现方式 | SoC 内部 I2C 控制器 | GPIO 手动模拟时序 |
| 时序精度 | 硬件保证,比较稳定 | 依赖 CPU 延时,容易抖动 |
| CPU 占用 | 低 | 较高 |
| 通信速率 | 支持 100 kHz、400 kHz 等常见速率 | 通常适合低速场景 |
| 稳定性 | 较高 | 受系统负载影响 |
| 引脚灵活性 | 受 SoC 复用限制 | 两个 GPIO 即可模拟 |
| 调试难度 | 主要检查 DTS、pinctrl、硬件连接 | 还要关注 GPIO 时序、延时和系统负载 |
| 适用场景 | 正式产品、核心外设 | 临时补救、低速外设、控制器不够用 |
直白地说:
- **硬件 I2C:**规矩、稳定、省 CPU;
- **软件 I2C:**灵活、能救急,但更容易出问题。
6. 什么时候应该用硬件 I2C?
绝大多数正式项目里,都应该优先使用硬件 I2C,尤其是:
- PMIC/电源管理芯片;
- 触摸芯片;
- 摄像头 sensor;
- 音频 codec;
- 关键传感器;
- 需要较高通信速率的外设;
- 需要长期稳定运行或数据量较大的链路。
例如 PMIC 异常可能影响电源控制,触摸芯片通信异常会直接影响用户体验,摄像头 sensor 配置失败则可能导致整条图像链路无法启动。这类场景不建议为了省事使用软件 I2C。
7. 什么时候可以考虑软件 I2C?
可以考虑软件 I2C 的情况包括:
- SoC 硬件 I2C 控制器数量不够;
- 对通信速率要求不高;
- 外设数据量很小;
- 只是读取简单状态;
- 临时调试验证;
- 硬件设计已经定型,无法改线;
- 某些 I2C 控制器引脚被其他功能占用。
例如,低速温度传感器几秒钟读取一次数据,使用软件 I2C 通常问题不大。但高频读写或对时序敏感的外设,需要谨慎评估。
8. 工程上的选择原则
可以按下面几条判断:
- 能用硬件 I2C,就不要优先考虑软件 I2C;
- 核心外设、关键链路尽量使用硬件 I2C;
- 软件 I2C 适合低速、低频、低风险场景;
- 软件 I2C 更适合救急,不适合作为默认方案;
- 必须使用软件 I2C 时,最好用示波器确认 SDA/SCL 波形;
- 多设备挂载、长线通信、高速通信不建议使用软件 I2C。
软件 I2C 最大的优势是引脚灵活,不是性能强。若只是因为"配置看起来简单"就用它替代硬件 I2C,后期可能给自己埋坑。
9. Linux 中的软件 I2C 如何接入子系统?
软件 I2C 仍然会注册成一个 i2c_adapter,然后接入 I2C 子系统。从上层看,它和硬件 I2C 很像。
底层可能是:
i2c-rk3x.c:硬件控制器;i2c-gpio.c:GPIO 模拟。
最终都可以向上提供一个 I2C adapter,上层设备驱动通常不需要关心底层究竟是哪一种实现,仍可通过下面的接口访问外设:
c
i2c_transfer();
i2c_smbus_read_byte_data();
i2c_smbus_write_byte_data();
区别在于执行 master_xfer 的实现不同:
- 硬件 I2C:由 SoC I2C 控制器驱动完成;
- 软件 I2C:由
i2c-gpio通过 GPIO 翻转完成。
这正是 Linux 子系统分层的好处。
10. 总结
硬件 I2C 和软件 I2C 都能实现 I2C 通信,但定位完全不同:硬件 I2C 是首选方案,软件 I2C 是补救方案。
硬件 I2C 的优势
- 时序稳定;
- CPU 占用低;
- 速率更高;
- 异常处理更完善;
- 适合正式产品和关键外设。
软件 I2C 的优势
- 引脚灵活;
- 不依赖专用 I2C 控制器;
- 适合低速、低频、临时补救场景。
如果必须做选择:
- 能用硬件 I2C,就用硬件 I2C;
- 硬件资源确实不够,再考虑 GPIO 模拟;
- 核心外设不要轻易使用软件 I2C;
- 软件 I2C 一定要看波形,不要只看日志。
很多时候,软件 I2C 不是不能用,而是后期调试变量太多。硬件 I2C 主要查设备树、pinctrl、上拉、电源和地址;软件 I2C 除此之外,还要担心 GPIO 翻转时序、系统调度和中断干扰。
硬件 I2C 坑在配置,软件 I2C 坑在不确定性。