在嵌入式 Linux 板级开发里,接一颗带 I2C 接口的外设时,常常会遇到两种做法:一是走 SoC 自带的 硬件 I2C 控制器,二是用两个 GPIO 软件模拟 成一条 I2C 总线。表面上看,二者在设备树里都能挂出同样的 i2c_client,驱动层也往往无感;但在 速率、波形质量、EMI、休眠与 IO 供电域、以及量产后的稳定性上有所差别。
一,两种方案参数对比
| 硬件i2c(soc外设控制器) | gpio模拟i2c(i2c-gpio) |
|---|---|
| CRU 提供时钟,内核 i2c-designware 等驱动控制 SCL/SDA 时序 | 用两个 GPIO 在软件里 bit-bang,每比特多次 GPIO 翻转 |
| 易做 100k / 400k / 1M(受 clock-frequency 与从设备能力限制) | 实际有效速率明显低于硬件 I2C,且与 delay-us、CPU 负载强相关 |
| 需占用 一组复用为 I2C 功能 的管脚(pinctrl 切到 i2cX xfer) | 占用 任意 GPIO,功能保持为 GPIO 模式即可 |
| 硬件时序规整,上升沿/占空比由控制器与 IO 驱动能力保证 | 依赖 GPIO 驱动强度、走线、外部上拉;delay-us 过小易误码 |
| 控制器可随时钟门控;管脚电平与 IO 电压域(VCCIO)、休眠策略相关 | 同样受 VCCIO 保持 / 掉电 影响;无独立控制器时钟,但 GPIO 域掉电会直接"哑火" |
| 标准 &i2cN { ... };,子节点 reg 为从机地址 | compatible = "i2c-gpio",子设备写法与硬件 I2C 相同(对上层驱动透明) |
总结:能走硬件 I2C、且管脚和复用都允许时,优先硬件 I2C;只有布线/复用冲突、或必须"任意脚"接从设备时,再用 GPIO 模拟,并接受速率、可靠性与休眠场景上的额外工程成本。
二,硬件 I2C
典型硬件 I2C:使能控制器、pinctrl 切到 I2C 复用、下面挂 reg = <0x**> 等子设备。
举个例子:
bash
&i2c6 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2c6m3_xfer>;
sgm41542: sgm41542@3b {
compatible = "sgm,sgm41542";
status = "okay";
reg = <0x3b>;
extcon = <&u2phy0>, <&usbc0>;
pinctrl-names = "default";
pinctrl-0 = <&charger_ok>;
interrupt-parent = <&gpio0>;
interrupts = <RK_PD2 IRQ_TYPE_EDGE_FALLING>;
otg-mode-en-gpios = <&gpio2 RK_PC7 GPIO_ACTIVE_HIGH>;
input-voltage-limit-microvolt = <4500000>;
input-current-limit-microamp = <3000000>;
monitored-battery = <&bat>;
regulators {
vbus5v0_typec: vbus5v0-typec {
regulator-compatible = "otg-vbus";
regulator-name = "vbus5v0_typec";
};
};
};
pinctrl-0 = <&i2c6m3_xfer> ; 表示 SCL/SDA 在 芯片复用功能 上,由 I2C 控制器驱动,而不是软件 toggling。
clock-frequency; 直接约束总线速率;与 i2c-gpio,delay-us 的调参逻辑完全不同。
三、GPIO 模拟 I2C
当硬件I2C控制器资源不足或需要特殊引脚配置时,GPIO模拟方案提供了灵活的替代选择。GPIO子系统支持开漏(OD)模式,可直接模拟I2C总线所需的线"与"特性。
举个例子:
bash
/* GPIO 模拟 I2C 控制器节点:对内核表现为一个 i2c_adapter */
i2c_nfc: i2c-nfc {
compatible = "i2c-gpio";
/* SDA/SCL 必须为开漏语义:ACTIVE_HIGH | OPEN_DRAIN */
sda-gpios = <&gpio2 RK_PD1 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
scl-gpios = <&gpio2 RK_PD0 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
/*
* 每步 GPIO 操作之间的延时;过小易在长线/大电容总线上 NACK/丢包,
* 过大则降低有效速率。需结合示波器与 NFC 芯片手册在板级微调。
*/
i2c-gpio,delay-us = <20>;
#address-cells = <1>;
#size-cells = <0>;
pinctrl-names = "default";
/* 将相关脚固定为 GPIO 功能及 IRQ 脚上下拉 */
pinctrl-0 = <&i2c_nfc_gpio &nfc_int_gpio>;
status = "okay";
/* 下游子设备写法与硬件 I2C 总线上一致 */
nfc@38 {
compatible = "nxp,nxpnfc";
reg = <0x38>;
interrupt-parent = <&gpio2>;
interrupts = <RK_PC6 IRQ_TYPE_LEVEL_HIGH>;
nxp,nxpnfc-irq = <&gpio2 RK_PC6 GPIO_ACTIVE_HIGH>;
};
};
aliases 里把 i2c10 指到 i2c_nfc,方便用户空间或文档用「逻辑编号」指代这条模拟总线:
bash
i2c8 = &i2c8;
i2c9 = &i2c9;
i2c10 = &i2c_nfc;
i3c0 = &i3c0;
硬件 I2C 与 GPIO 模拟 I2C 都可能受 IO 电压域、休眠漏电策略影响;但 GPIO 模拟完全依赖 两根数据线处于合法空闲态(通常靠外部上拉 + 软件释放总线),一旦休眠路径上 IO 断电或态丢失,从设备可能锁死、需要掉电复位。
四,如何「抉择」?
- 优先选择硬件I2C控制器的场景:
高速数据传输(>100kHz)
多设备总线拓扑
实时性要求高的系统
需要DMA支持的批量传输
产品量产方案
- GPIO模拟I2C更合适的场景:
引脚资源紧张时的临时方案
特殊时序要求的设备
原型开发阶段的快速验证
低频(<10kHz)单设备通信
需要动态切换引脚的功能
实际项目里:只要原理图能把从设备的 SCL/SDA 接到 SoC 上可用的硬件 I2C 复用脚、且速率与可靠性有要求,就优先用硬件 I2C;只有在管脚/复用/布线已定型无法改线、或必须用普通 GPIO 才能接到从设备时,再接受 GPIO 模拟 I2C,并预留好上拉、位时序(如 delay-us)和休眠/供电域相关的联调成本。