I3C 比 I2C 快 10 倍这个说法,这几年几乎跟着每一颗新 SoC 的发布都会被翻出来聊一遍。我最近在 RK3576 平台上做了一轮总线迁移,把原本挂在 I2C 上的两颗温度传感器和一个触摸屏控制器逐步挪到 I3C 上,顺手把设备树从 I2C 节点改成了 I3C 节点。整个过程走下来,最直观的感受是:接口很快是真的,但很多人只记住了“快 10 倍”这个结论,却忽略了 I3C 在协议层、DTS 配置层和信号完整性上带来的完全不同的约束。这篇文章就把我实测的接口特性、DTS 配置方法,以及那些文档里不会写的坑,一次说清楚。适合正准备评估 I3C 选型、或者已经在 RK3576 上要做这项改动的人参考。
1. 把“快 10 倍”放到速率表里看:结论成立,但口径很重要
1.1 I2C 真正常用的速率,其实连 1MHz 都少见
I2C 协议标准里确实有高速模式(Hs-mode)3.4MHz,但绝大多数嵌入式系统跑的还是 100kHz 标准模式和 400kHz 快速模式。Fast Mode Plus 的 1MHz 都算比较激进的配置了,很多现成传感器模组在原厂参考驱动里给的默认时序还是 400kHz。真正敢一路开到 3.4MHz 的项目,通常会要求非常短的总线长度、极小的上拉电阻和严格控制的 I/O 电容,这在工程上其实是少数场景。
I3C 这边,光最基本的 SDR 单数据速率模式就有 12.5MHz。如果你拿 I2C 快速模式 400kHz 跟 I3C SDR 12.5MHz 去比,倍数直接到了 30 倍以上。这也是网上说“快 10 倍”的底气来源之一。但严谨一点讲,协议标准里 I3C 还有 HDR-DDR、HDR-TSP、HDR-TSL 这些更高阶模式,DDR 模式下时钟 12.5MHz、数据在两个边沿都采样,等效数据率可以到 25MHz 甚至更高。所以“快 10 倍”不是吹牛,但它是一个简化的表达,真正要说准确,必须明确比较的是哪一档对哪一档。
1.2 为什么 I3C 能把时钟拉上去:推挽驱动是核心
I2C 从诞生起就是开漏结构,设备只能把总线拉低,释放后靠外部上拉电阻把电平带回去。开漏的好处是天然支持线与逻辑和多设备仲裁,坏处也很实在:电平上升沿依赖于 RC 充电。总线上挂三五个设备、走线稍微长一点,等效电容就到了几十甚至上百 pF,400kHz 下要让边沿足够陡,上拉电阻已经要压到 1k 到 2.2k 这个区间,功耗和信号质量都要付出代价。
I3C 在 SDR 模式下把 SCL 和 SDA 都改成了推挽驱动,主动输出高电平,不再依赖上拉电阻去“拉”起来。这就解决了高频下上升沿太慢的基础矛盾。DDR 模式更是直接在时钟的两个边沿都采样数据,等效带宽又翻了一倍。代价也很明显,推挽驱动下多个设备同时驱动的碰撞控制只能靠协议层解决,不能再像 I2C 那样靠物理线与去兜底,所以 I3C 控制器内部的时序状态机、CCC 命令队列和总线仲裁逻辑,比 I2C 控制器复杂得多。
1.3 不同对比口径下的真实倍数
我习惯把常用对比口径列成一张表,评估项目时直接查,比背“快 10 倍”有用:
| 比较对象 | I2C 速率 | I3C 速率 | 倍数 |
|---|---|---|---|
| 标准模式对 SDR | 100kHz | 12.5MHz | 125 倍 |
| 快速模式对 SDR | 400kHz | 12.5MHz | 31 倍 |
| Fast Mode Plus 对 SDR | 1MHz | 12.5MHz | 12.5 倍 |
| Hs-mode 对 SDR | 3.4MHz | 12.5MHz | 3.7 倍 |
| Hs-mode 对 HDR-DDR | 3.4MHz | 25MHz | 7.4 倍 |
所以“快 10 倍”这个说法放在 HDR-DDR 对 Hs-mode 的对比口径下,是站得住脚的,但更常见的真实工程环境是 400kHz 对 SDR,实际能拿到的收益远超 10 倍。反过来说,如果你在一条 I3C 总线上挂了不支持 I3C 的老 I2C 设备,控制器会把总线切到 I2C fallback 模式跟那台设备通信,这段交互的速率会被打回 400kHz 甚至更低。这一点在方案评估阶段就要想清楚,否则容易产生“明明上了 I3C,怎么感觉还是慢”的错觉。
2. RK3576 的 I3C 资源:控制器、时钟树和选型判断
2.1 RK3576 片上 I3C 控制器:先看 dtsi,别盲目抄 RK3588
RK3576 作为瑞芯微 2024 年后的中高端平台,I3C 已经成了标配外设。我这边使用的 BSP 源码里,RK3576 的 dtsi 中 i3c 控制器的数量和编号与 RK3588 不完全一样,这一点必须强调:网上一搜能搜到大量 RK3588 的 I3C DTS 片段,直接往 RK3576 上套很危险,因为中断号、基址、时钟名、复位名都可能不同。
以我看到的 RK3576 设备树为例,控制器节点通常长这样(具体基址和中断号以你手里的 TRM 为准,不同批次 BSP 也有差异):
i3c0: i3c@fead0000 { compatible = "rockchip,i3c"; reg = <0x0 0xfead0000 0x0 0x1000>; interrupts = <GIC_SPI 119 IRQ_TYPE_LEVEL_HIGH>; clocks = <&cru CLK_X3C0>, <&cru PCLK_X3C0>; clock-names = "x3c", "pclk"; resets = <&cru SRST_R_X3C0>; reset-names = "x3c"; pinctrl-names = "default"; pinctrl-0 = <&i3c0_scl>, <&i3c0_sda>; status = "disabled"; };有些内核版本里 compatible 写的是snps,dw-i3c-master,这取决于供应商把 Synopsys DesignWare I3C 控制器驱动编到哪个框架下。判断自己该用哪种 compatible,最靠谱的办法不是翻文档,而是看 BSP 自带的arch/arm64/boot/dts/rockchip/rk3576.dtsi里有没有现成的 i3c 节点。有就说明供应商已经把它接入 Linux I3C subsystem 了;没有就要确认内核是否打开了CONFIG_I3C和对应 master 驱动,否则就算你在自己的 dts 里编了一大段节点,内核也不认识。
2.2 引脚复用和 I2C 的关系:同一组脚,别同时点亮两个节点
RK3576 的 I3C 控制器引脚并不是凭空多出来的,很多情况下它和 I2C 的某个控制器共用同一组 IO 的 mux 选项。也就是说,你在 dts 里开启&i3c0的同时,不能再把对应引脚的&i2c0(举例)也设为status = "okay"。否则 pinctrl 子系统在解析时会发现同一组引脚被两个节点申请,轻则警告,重则两个控制器互相干扰,总线上出现无法解释的超时。
我踩过一次类似的坑:I3C 节点和 I2C 节点分别放在了两个不同的设备树 include 文件里,板级 dts 同时&i3c0 {}和&i2c0 {}都没删干净,结果启动后 I3C 设备 probe 成功,但第一次 DAA 动态地址分配就超时。后来查 pinctrl 状态才发现引脚被 I2C 节点占用。所以每次切总线方案时,都要全局搜一下项目 dts 里还有没有同组引脚的 I2C 节点遗留。
另外,引脚复用配置本身也要注意。I3C 高速模式下建议优先选择专门的 I3C 功能复用,而不是 I2C 功能复用。有些 BSP 的 pinctrl 节点只提供了i3c0_x3c这组复用,数据手册上写的是 x3c 这个名字,别因为在 dtsi 里搜 “i3c” 搜不到就放弃,多看一眼寄存器手册的复用表格。
2.3 时钟树和电源域:只配了节点还不够
I3C 控制器通常有两个时钟:一个是操作总线的功能时钟(比如x3c),一个是访问控制器寄存器的外设时钟(比如pclk)。这两个时钟在 dtsi 的clocks属性里都会列出来。有些同学只开CLK_X3C0而忽略了PCLK_X3C0,启动时会发现控制器寄存器可以读,但一发起传输就卡住——因为寄存器访问是通过 pclk 做的,pclk 没开,外设寄存器访问路径并不可靠。
这在 RK3576 上尤其要注意。它不像小芯片那样把所有外设都放在同一个简单时钟域里,I3C 控制器可能挂在特定 power domain。如果控制器所在电源域在系统 suspend/resume 流程里被关掉了,而 dts 里又没有描述清楚依赖关系,就会出现唤醒后 I3C 设备还在,但总线彻底罢工的现象。经验做法是:第一版 dts 就复制供应商评估板上的电源域配置,不要自己凭感觉去掉power-domains属性。
3. DTS 从 I2C 改成 I3C:节点抄法、reg 三 cell 和子设备挂载
3.1 最稳的起步方式:从 dtsi 里拿现成框架,再做增量修改
很多第一次写 I3C DTS 的人,会习惯性打开 I2C 的节点模板,然后把 compatible 改一改。这个思路在 I2C 上没问题,但 I3C 节点的地址 cell 数量和子设备表述方式都不同,硬套容易出错。I3C 总线的控制器节点一般要这样定义:
i3c0: i3c@fead0000 { compatible = "rockchip,i3c"; #address-cells = <3>; #size-cells = <0>; /* 其余属性沿用 dtsi 里的默认值 */ };#address-cells = <3>和 I2C 的<1>是完全不一样的,因为 I3C 子设备节点需要表达动态地址、偏好静态地址和属性信息。对内核的 I3C 核心来说,这 3 个 cell 是有明确语义的,不能省,也不能为了省事把别人设备树里的<1>抄过来。
改完控制器节点后,再在板级 dts 里像&i2c0那样去引用它:
&i3c0 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&i3c0_scl>, &i3c0_sda>; /* 速率参数,名称以你的 BSP 内核版本为准 */ i3c-sdr-hz = <12000000>; i3c-ddr-hz = <24000000>; };i3c-sdr-hz和i3c-ddr-hz这类属性在个别内核版本里不一定是这个命名,所以我建议你拿到源码后先看一眼Documentation/devicetree/bindings/i3c/i3c.txt或者对应 vendor 的 yaml 绑定文档。重要的是理解逻辑:SDR 是基础模式,DDR 是增强模式,如果外设只支持 SDR,DDR 设再高也不会被用到。
3.2 reg 三 cell 的坑:动态地址、偏好静态地址和标志位
I3C 子设备节点的 reg 写法,是和我之前 I2C 经验差距最大的地方。I2C 子设备写reg = <0x48>,就是固定的 7bit 从地址;I3C 则允许控制器在 DAA 流程里给设备分配一个全新的动态地址。所以 reg 的三个 cell 分别表示:
- 第一个 cell:动态地址,如果为 0,表示由控制器在枚举时分配;
- 第二个 cell:偏好静态地址,设备上电后如果没被分配动态地址,会先尝试用这个地址通信;
- 第三个 cell:标志位,用来表达设备是否支持 IBI、是否要求加入时的特殊处理等。
一个典型子设备节点是这样写的:
&i3c0 { // ... tmp117: temperature-sensor@0 { compatible = "ti,tmp117-i3c"; reg = <0x0 0x48 0x0>; }; };写这段的时候,temperature-sensor@0只是一个方便阅读的标签,真正决定访问路径的是 reg 三 cell。如果第二个偏好静态地址 0x48 与总线上另外一个设备的静态地址冲突,DAA 会避开冲突并重新分配。所以千万别把这里的第二个 cell 当成 I2C 那样“写死地址”去理解,它只是一个偏好,不是最终动态地址。
如果你只是临时验证某颗 I3C 设备,也可以把三个 cell 都写成 0,完全走动态地址流程。但要注意,有些老老外设虽然物理上支持 I3C,驱动里却没实现 DAA 相关的回调,这时候强制动态分配会导致设备无法正常枚举。稳妥的做法是先用 0x0 动态地址跑一次,如果 dmesg 里出现 DAA 超时,再把偏好静态地址填上试试。
3.3 挂载不支持 I3C 的老 I2C 设备:I2C fallback 的正确姿势
很多项目并不是所有外设都换成了 I3C 版本。随便举几个:可能触摸屏已经是 I3C 原生设备,但旁边还有一颗只支持 I2C 的 eeprom 或老款环境传感器。此时不需要给这些老设备写什么特殊节点,你可以在&i3c0内直接挂一个普通 I2C 风格子节点:
&i3c0 { at24c02: eeprom@50 { compatible = "at24,24c02"; reg = <0x50>; pagesize = <8>; size = <256>; }; };内核的 I3C 总线驱动会检测到这个设备不支持 I3C DAA,自动把控制器切换到 I2C fallback 模式去访问它。这里有个细节很多人不知道:一条 I3C 总线上只要存在需要 fallback 的老设备,这部分通信速率就会明显低于 I3C 原生设备;Linux 的 i3c core 对 fallback 设备有独立的时序约束参数,你设置的i3c-sdr-hz不会作用到它身上。所以评估性能时,不能把所有设备都按 12.5MHz 的乐观预期去算。
另外,I2C fallback 模式下,总线上的动态地址分配、IBI 等 I3C 特性对该老设备完全失效。这类设备的中断如果之前是通过独立 GPIO 上报的,可以继续保留 GPIO 中断方案,不用为了“统一”而强行改造。
4. 速率之外的变化:CCC 命令、IBI 中断和 Linux 驱动模型
4.1 CCC 命令是总线的“管理层”,不是普通读写
I3C 相比 I2C 最本质的变化,不是频率,而是协议里多了一种叫 CCC(Common Command Code)的命令体系。CCC 有广播和定向两种类型,用来做动态地址分配、设备使能/禁用、模式切换、总线重置、热加入协商等管理操作。你写驱动时不会直接操作这些,但控制器固件和内核 I3C 子系统的交互大量依赖它。
举例来说,I3C 控制器上电后的第一个动作就是发送 RSTDAA 或 SETDASA,然后再进入 DAA 流程逐个枚举总线上的设备。这个角色等同于“总线的 DHCP 服务器”,和 I2C 里从设备地址焊死在芯片里的逻辑完全不同。如果你用逻辑分析仪抓 I3C 总线,看到的第一个有内容的时序往往不是某个传感器的数据帧,而是一连串 CCC 帧,这是在分配动态地址。
在 DTS 层面,CCC 相关的东西你不需要显式配置,但理解它很有意思:为什么 I3C 设备节点 reg 是三个 cell?就是因为这个设备最终地址是由 DAA 动态确定的,偏好静态地址只是“建议值”。控制器发送 SETDASA 时,会把建议地址写进设备,如果设备支持,就会采用;后续就不再依靠地址竞态了。
4.2 IBI 代替 GPIO 中断:省线,但要做好驱动层适配
I3C 的另一个核心特性是 In-Band Interrupt,即带内中断。I2C 时代,传感器产生中断只能拉一根 GPIO 去通知 SoC;I3C 时代,从设备可以直接在总线上发起一个 IBI Request,控制器在总线空闲时仲裁这个请求并响应。好处显而易见:省掉一根 GPIO,BOM 降低,原理图连线也干净。
但带内中断也有代价。第一,IBI 请求的时序优先权和普通数据传输不同,控制器内部的仲裁逻辑如果优先级配得不好,高频率的中断事件可能会让正常数据流变得卡顿。第二,驱动模型里多了一个i3c_ibis或等价的事件处理路径,你从设备的 driver 不能再简单依赖传统 IRQ 号触发中断回调,而是要注册一套和 I3C master 关联的事件处理接口。
RK3576 的控制器对 IBI 的支持在 BSP 里已经比较完整,但我建议项目前期不要把所有传感器都换成 IBI,先用一个设备把 IBI 链路验证通,确认中断频率和数据吞吐量的关系,再决定是否批量迁移。尤其是触摸屏这种中断频率不低的外设,如果 IBI 处理路径里还有额外的软件调度开销,性能不一定比独立 GPIO 中断好。
4.3 Linux 侧驱动模型:I3C 不是“I2C 的改名版”
在 Linux 内核里,I2C 和 I3C 各自有独立的子系统。I2C 是i2c-core,设备模型用struct i2c_client;I3C 是i3c-core,设备模型有struct i3c_device。你在写驱动时不能把i2c_driver直接拿去匹配 I3C 设备,必须用i3c_driver和i3c_device_id结构。老 I2C 设备走 fallback 模式则例外,它的驱动仍然是i2c_driver,匹配方式也更接近传统 I2C。
实际开发里,很多传感器厂商给的 Linux 驱动只写了 I2C 版本。想快速在 I3C 总线上用起来,有两条路:
- 稳妥路线:把该设备当作老 I2C 设备挂在
&i3c0下,驱动一点不用改,但享受不到高速和 IBI。 - 完整路线:自己基于
i3c_driver写一个小驱动,通常是把原 I2C 的读写函数改成i3c_device_do_priv_xfers或对应的 regmap 封装。
以我的经验,如果设备本身有明确的高吞吐需求(比如高分辨率触摸屏、多通道传感器聚合),值得走第二条路;如果只是普通温湿度传感器,I2C fallback 足够用了,过度设计没意义。
内核里还要记得开CONFIG_I3C、CONFIG_I3C_MASTER。你会得到类似/dev/i3c-0的总线设备节点。用户态工具i3c-tools(可从开源仓库拉取)可以用来枚举总线设备、查看动态地址和 PID,调试阶段非常有用,后面我还会聊到具体用法。
5. 实测踩坑记录:信号完整性、dmesg 报错与逻辑分析仪验证
5.1 信号完整性比 I2C 敏感得多:上拉电阻和板级寄生电容
I3C 的高速是建立在推挽驱动基础上的,但它仍有开漏窗口期,特别是 SDR 模式下做总线仲裁和 STOP/START 条件时,总线上拉能力依然要够。在 RK3576 实际板子上,如果沿用 I2C 时代的 4.7k 上拉电阻,12.5MHz 下很容易出现上升沿不达标,导致设备偶发失步。这个问题的典型现象是:系统刚启动几百次里正常,偶尔在某个设备的连续读取中报一次 CRC 错误。
解决方向不是简单粗暴地减小电阻——太小的上拉电阻会让 SDA 低电平抬不干净,反而更糟。正确的做法是参考目标设备的 I3C 时序要求,计算总线上拉电阻的上下限。一般 I3C 设备手册里会给最小上升时间和最大上升时间,实际板子可以用如下公式估算:
- 总线电容 Cbus = 设备引脚电容之和 + 走线寄生电容;
- 上拉电阻上限约等于 tR / (0.8473 × Cbus),其中 tR 是设备要求的最大上升时间;
- 上拉电阻下限取决于 VOL max 和推挽电流,公式是 (Vcc - VOL_max) / IOL_max。
这里的边界不需要算到完全精确,但至少要知道:不是电阻越小越好,也不是电阻越大越稳,它在一个窗口区间内。
RK BSP 里有些 I3C 节点会带上 read-puller 这类边沿控制参数,用来调整控制器接收端的采样点。调速率时如果硬件上拉电阻已经确定了,优先检查这个参数是否和实际板级匹配。我这边第一次把 SDR 提到 12.5MHz 后出现随机超时,后来就是拿逻辑分析仪测出边沿余量不足,最后一边微调上拉电阻、一边调整 read-puller 才稳定下来。
5.2 常见 dmesg 报错与排查顺序
| 症状 | 根因 | 排查方向 |
|---|---|---|
i3c0: timeout waiting for target response | 设备未进入 I3C 模式或上拉不足 | 用逻辑分析仪看总线是否有 ACK/NACK、测边沿 |
DAA failed | 子设备 reg 三 cell 配置错误,或设备不支持 DAA | 检查 reg 的第一个 cell,尝试偏好静态地址 |
| 设备注册成功但读写全返回错误 | SDR 速率设置过高,设备只能跑低速率 | 降低i3c-sdr-hz,确认设备手册最高速率 |
| I3C 和 I2C 节点同时使用后死锁 | 引脚复用冲突 | 全局搜索 dts 里同引脚的 I2C 节点并禁用 |
| 唤醒后总线卡住 | 电源域或时钟在 suspend 时被关掉 | 确认 power-domains 配置是否与供应商参考板一致 |
排查时不要一上来就怀疑硬件。第一步先用i3c dump或dmesg | grep i3c看控制器是否完成了枚举。如果枚举通过了,问题大概率出在时序或外设驱动上;如果枚举都没过,方向应该是 DTS、电源、引脚复用和信号完整性。
i3c-tools的典型操作是:
# 查看所有 I3C 总线 ls /dev/i3c-* # 导出总线信息 i3c dump /dev/i3c-0如果你用的是供应商深度改过的 BSP,工具不一定直接可用,但内核里那些i3c_*debugfs 节点多少也能顶一下。逻辑分析仪才是最终仲裁者。
5.3 逻辑分析仪怎么看 I3C:先分清 I2C 兼容帧和 SDR 帧
I2C 分析仪默认只能解 I2C 协议,拿来抓 I3C 信号会一脸懵。I3C 总线上有几种帧:带 ST 条件的 I2C 兼容帧、带 SDR 帧头的新帧、CCC 帧等。它们的起始条件、重复起始条件和 STOP 条件的定义与 I2C 有微妙的差异,普通逻辑分析仪很容易把 I3C 的 DAA 流程误判成“Bus error”。
我建议你用支持 I3C 解码的逻辑分析仪方案,或者至少手动把采样率调到 100MHz 以上,先抓一段只有琥珀色(?)总线的枚举片段,人工对比协议规范中的 I3C 帧结构,确认自己能分辨 Start、Repeated Start、STOP、CCC、Dynamic Address 这些关键时段后,再挂真实设备,否则排错效率会非常低。
上次我把 RK3576 的 I3C 节点改成支持两颗传感器和一个 fallback eeprom 后,抓到的波形里三种帧混在一起。用 I2C 模式分析仪看,几乎所有包都报 CRC 错;切换到 I3C 解码思路后,一眼就看出是 fallback I2C 设备的地址周期占用了总线,影响了后续 I3C 设备的一笔 SDR 传输。这类问题不抓波形,靠猜是猜不出来的。
5.4 什么时候别急着上 I3C:几个判断依据
I3C 优点多,但它不是万能药。我见过不少项目本来 I2C 用得挺好,看了“快 10 倍”的宣传就直接上 I3C,结果给自己添了一堆事。这里有几个我实际判断时的依据,供参考:
- 总线上都是老 I2C 设备,只有一两个网卡 EEPROM 之类的小容量存储设备:I2C fallback 模式下,I3C 原生设备数量太少,高速优势发挥不出来,不如维持原来的 I2C 总线。
- 板子走线特别长,总电容明显超标:I3C 高速工作对走线长度和 PCB 布局有更高要求,不适合随便拉飞线。
- Linux 内核版本太老:早期内核 I3C 子系统还不够完善,Rockchip 的 BSP 打补丁也不一定齐全。如果内核版本低于某个成熟度,整个 I3C 驱动栈可能处于实验状态,调试成本很高。
- 团队对 I3C 协议不熟,也没有支持 I3C 解码的逻辑分析仪:高速协议调试本质上就是“看波形、摸协议”,没有趁手工具会非常痛苦。
如果以上四条都不沾,那 I3C 确实值得上。RK3576 上 I3C 带来的是带宽红利和 GPIO 节省,在传感器数量一多、I2C 中断引脚吃紧的项目里,收益非常直接。
最后说个自己的体会。这次迁移我花了最多时间的地方,不是 DTS 的语法,而是从逻辑分析仪上分辨“这是 SDR 帧还是 I2C 兼容帧”。I3C 的起始条件、重复起始、STOP 条件跟 I2C 有细微差别,只看波形不看 CCC 命令结构,很容易把一段合法的 DAA 当成总线错误。你要是也准备上手,建议先把逻辑分析仪采样率提上去,先分析一条只有 CCC 命令的总线,再挂真实设备,会比对着寄存器一遍遍猜快很多。毕竟 RK3576 上 I3C 的潜力很大,但能不能发挥出来,就看最初这两步验证打得够不够扎实。