☰
RK3576 I3C实战:比I2C快10倍?设备树配置与迁移要点
2026/9/29 2:42:25 网站建设 项目流程

做 RK3576 平台适配的时候,我在 SDK 的设备树里第一次认真看到了i3c这个节点,一下就把我拉回到当年调 I2C 的老问题上。I3C 算得上近十年里嵌入式短距总线领域最重要的一次升级,MIPI 联盟把目光落在了传统 I2C 身上,想在保留两根线、兼容老设备的底子上,把速率、中断、地址分配这些痛点一起解决掉。这篇文章不打算复述协议原文,而是从 RK3576 这个具体平台出发,谈谈 I3C 到底比 I2C 快多少、快在哪、以及 DTS 里我们需要关注哪几个关键配置点,给准备从 I2C 往 I3C 迁移的朋友一个可以直接上手的参考。

1. “快 10 倍”不是一句空话,但要看和哪个 I2C 比

1.1 先聊聊 I2C 的速率天花板为什么这么难突破

I2C 从 1982 年诞生以来,速率一直在往上加,但正常产品里绝大多数还跑在 100k 或 400k。为什么不再跑快一点?根子出在物理层上。I2C 的 SCL 和 SDA 是开漏输出,简单说,就是器件只能把线拉低,想拉高必须靠外部上拉电阻。于是信号上升时间就成了一个 RC 充放电过程:

t_rise ≈ 0.847 × R_pullup × C_bus

总线电容一旦大起来,比如多挂几个从机、走线长一点,想要提高速率就必须把上拉电阻往下调。电阻调太低,总线上低电平时的灌电流变大,功耗和信号质量都会出问题。所以你翻很多板子的 I2C 设计,上拉电阻通常都在 2.2k 到 4.7k 之间,还得留余量给各种噪声和负载。这一步物理限制,让 I2C 想在高频阶段变得非常吃力。

于是 I2C 规范里虽然定义了 100k、400k、1M 甚至 3.4M,但实际用的时候,3.4M 的 high-speed 模式需要额外挂电流源上拉,电平标准和前几档还不完全一样,很多 MCU 的 I2C 控制器甚至根本不支持 3.4M 这个档位。结果是,很多开发者潜意识里已经把 I2C 默认为“低速但省线”的协议,一天只能传几百字节也无所谓。

1.2 I3C 的提速思路:把推挽输出搬进数据阶段

I3C 的物理层设计几乎就是针对这个问题来的。它保留了 SCL/SDA 两根线,但在 SDR(Single Data Rate)模式下,数据阶段不再使用开漏输出,而是推挽驱动,时钟也可以一路拉到 12.5MHz 左右。推挽输出的特点是输出级主动拉高和拉低,信号上升沿不再依赖外部电阻充放电,所以总线电容对速率的影响小得多。

HDR 模式就更夸张了。HDR-DDR 在 SDR 的基础上做了双沿采样,数据速率可以到 25Mbit/s 左右;HDR-TSL 和 HDR-TSP 用上了三进制符号编码,在同样时钟下塞进更多信息,速率能到 32Mbit/s 以上。要知道这还是两根线,而且正儿八经工作在嵌入式传感器这种低功耗场景下。

1.3 所以“快 10 倍”到底怎么算

很多人看到 i3c 比 i2c 快 10 倍,会觉得是夸张说法,但实际算一算就明白了。

模式速率典型用途
I2C Standard100 kbit/s低速 EEPROM、RTC
I2C Fast400 kbit/s大多数传感器
I2C Fast+1 Mbit/s高吞吐传感器
I2C High-speed3.4 Mbit/s少数专用场景
I3C SDR12.5 Mbit/sI3C 基本工作模式
I3C HDR-DDR25 Mbit/sDDR 采样,更高吞吐
I3C HDR-TSL/TSP32~33.33 Mbit/s三进制符号编码

拿大家最常用的 I2C Fast 400k 来算:I3C SDR 的 12.5M 是它的 30 倍。就算拿 I2C 规格里最高的 3.4M 来比,I3C 的 HDR-TSP 也能跑到它的 10 倍上下。那些“快 10 倍”的说法,虽然严格来说取决于对比基数,但方向上一点不虚。

如果放实际传输场景里更好理解:读一颗传感器 16 字节寄存器组,I2C Fast 400k 下光字节传输和 ACK/START/STOP 开销,差不多要到 500us 量级;在 I3C SDR 12.5M 下,同样 16 字节可能就是几十个微秒,差别一眼就能看出来。

2. 速率只是开胃菜:动态地址、带内中断才是 I3C 的护城河

2.1 动态地址分配,治好了 I2C 地址冲突的老毛病

做硬件的人对 I2C 地址冲突这件事应该都不陌生。同一个 I2C 总线上挂两颗同型号传感器,地址如果相同就非常尴尬,要么改芯片地址引脚的电平组合,要么用 TCA9548A 这类 I2C MUX 把总线拆开。这些方案不是不行,但占 PCB 面积、占用更多 GPIO,一到量产还要检查有没有贴错电阻。

I3C 的做法是动态地址分配(DAA)。总线上的 I3C 设备上电后,由主控通过 CCC 命令广播 ENTDAA,设备参加一个类似于 I2C 仲裁的机制,依次把静态地址、官方分配的 ID 等数据交出来,主控再给每个设备动态分配一个 7 位地址。设备多也好、同型号也好,靠的是 48 位 Provisioned ID(PID)区分身份,地址只是主控临时给它在总线上用的。这样一来,硬件上不再需要因为地址冲突改跳线,同一型号的传感器可以在同一条总线上批量接入。

这个特点对 RK3576 这种接口密度高的平台吸引力很大。多路 I2C 控制器的板子上,以前为了避开地址冲突可能要把传感器分散到不同总线上,而 I3C 总线上你可以放心大胆串一串设备。

2.2 IBI,带内中断让 GPIO 压力瞬间小了很多

传统 I2C 设备想做异步事件通知,几乎都要额外拉一根中断 GPIO。传感器数据就绪、触摸屏按下、温湿度阈值触发,全走 GPIO interrupt。设备一多,SoC 的 GPIO 资源和中断控制器资源都会吃紧。热词里能看到不少“i2c hid 该设备找不到足够资源可以使用”的说法,很多时候就是中断资源不够用或者 GPIO 申请失败。

I3C 的带内中断(IBI)直接把中断请求合入两根线的通信过程。设备有事件要上报时,在总线空闲阶段主动发起一个带内请求,主控 ACK 它,再根据实际需求处理后续事务。完全不需要为每个 I3C 设备单独配一个中断引脚。这意味着 RK3576 的 BSP 里,DTS 不用再为每个传感器节点写冗长的interrupts属性,也少了一堆 GPIO 复用冲突。

2.3 热接入、错误检测这些“小而美”特性

I3C 还支持热接入(Hot-Join),模块可以在系统运行过程中插到总线上,主控会定期检测并有能力为新设备动态分配地址。对于可插拔子卡的产品形态非常实用。

错误检测方面,I2C 是出了名的“裸奔”,没有 CRC,数据传错只能靠 ACK 和上层协议重试,很多驱动在读取传感器时偶尔读到跳变数据,排查到最后才发现是 I2C 传输出错。I3C 在 SDR 里有奇偶校验,HDR 模式里有 CRC,虽然不能保证百分百不变,但至少能在链路层发现错误并重启事务。

这些特性叠加起来,真正提升了总线的工程可用性。“比 I2C 快 10 倍”只是表象,背后的通信模型其实已经换代了。

3. RK3576 上的 I3C 外设:从芯片与 SDK 角度看配置前提

3.1 RK3576 总算把 I3C 控制器带出来了

熟悉 Rockchip 的朋友应该知道,之前 RK3568、RK3588 这些主力型号的外设列表里,基本见不到原生 I3C,很多板子想用 I3C 只能外扩一颗桥接芯片或者干脆继续用 I2C。到了 RK3576,I3C 控制器成了芯片外设的一部分。这也符合近两年传感器市场的趋势——越来越多的器件原生支持 I3C,SoC 不跟上就只能看着接口在那儿落灰。

我看到的 RK3576 SDK 里,rk3576.dtsi中已经预留了 i3c 节点,通常会有 i3c0、i3c1 这样的实例。它在芯片内部有独立的时钟、复位和中断资源,引脚也通过 iomux 复用出来。要注意,I3C 控制器和 I2C 控制器是两台独立硬件,不是同一个 IP 做模式切换,至少从 Linux 驱动和设备树呈现方式上,它们是各自独立的节点。

3.2 典型 dtsi 节点长这样

下面这段是从 SD 卡常见 SDK 中简化出来的示意结构,不同版本细节可能不一样,但整体框架八九不离十:

i3c0: i3c@feaa0000 { compatible = "rockchip,rk3576-i3c"; reg = <0x0 0xfeaa0000 0x0 0x10000>; interrupts = <GIC_SPI 336 IRQ_TYPE_LEVEL_HIGH>; clocks = <&cru CLK_I3C0>, <&cru PCLK_I3C0>; clock-names = "i3c", "pclk"; resets = <&cru SRST_I3C0>; reset-names = "i3c"; pinctrl-names = "default"; pinctrl-0 = <&i3c0_xfer>; #address-cells = <3>; #size-cells = <0>; status = "disabled"; };

有一点提醒大家,compatible在你拿到的 SDK 里可能是"snps,dw-i3c-master-1.00a"而不是"rockchip,rk3576-i3c",因为 Rockchip 在某些场景下用的就是 DesignWare 的 I3C IP,这并不奇怪。重点是别拿着别家平台的 dtsi 直接套,一定要以当前 SDK 的 rk3576.dtsi 为准。基址、中断号、时钟名这些,在 TRM 里都能查到,我是拿示意值举例。

3.3 时钟频率怎么配置:i3c-scl-hz 和 i2c-scl-hz

I3C 的 SCL 频率在 DT 里一般通过i3c-scl-hz属性配置,例如i3c-scl-hz = <12500000>表示 SDR 模式跑到 12.5MHz。如果你的 I3C 总线上还挂了传统 I2C 设备,主控访问它们时必须降速回到 I2C 时序,这时需要i2c-scl-hz这个属性来指定 I2C 兼容速率,一般写 400k 或 1M:

i2c-scl-hz = <400000>; i3c-scl-hz = <12500000>;

这里特别要解释一下:I3C 主控在不同阶段会动态调整时序参数。它跟纯 I2C 设备通信时,会按照i2c-scl-hz来生成时钟;跟支持 I3C 的设备通信时,切换到i3c-scl-hz的高频模式。这不是 DTS 里随便写一个 clock-frequency 就能搞定的,I3C 的 Linux 子系统和 I2C 子系统的处理逻辑完全不同。

3.4 硬件设计上别把 I3C 和 I2C 上拉混为一谈

有些人看到 I3C 用推挽就想当然认为不需要上拉电阻,这是不对的。I3C 在总线空闲、启动条件、兼容 I2C 阶段,仍然需要开漏和上拉来维持电平。而且刚上电还没有进入推挽数据阶段时,总线必须可靠呈高电平,否则主控一上来就误判。

经验做法是:如果这条总线只挂 I3C 设备,上拉电阻可以取得偏小一点,1k 到 2.2k 都可以试;如果总线上有老 I2C 设备共存,就按 I2C 的规范来,2.2k 到 4.7k 之间微调。总线电容大、走线长、器件多,就往下调;功耗敏感且设备少,可以往上调。这个调优过程没有什么捷径,具体值需要综合板子上的实测波形来定。

4. DTS 配置实战:I2C 设备与 I3C 设备怎么挂到同一条总线上

4.1 第一步要先理解 reg 的三个 cell

I3C 设备树 binding 和 I2C 有个非常明显的差异:I3C 主控制器的#address-cells = <3>,子节点的 reg 需要三个 cell。很多从 I2C 转过来的人第一反应还是写reg = <0x50>,然后#address-cells = <1>,结果内核解析时报错或者设备死活 probe 不上。

三个 cell 的含义大致是:

  • 第一个 cell:表示设备类型。0 代表 I3C 设备,1 代表传统 I2C 设备。
  • 第二个 cell:设备地址。I2C 设备直接填 I2C 从机地址;I3C 设备如果指定静态地址就填静态地址,如果决定用动态分配就写 0。
  • 第三个 cell:I3C 设备的 PID,I2C 设备固定填 0。

PID 是设备出厂固化的 48 位识别信息,里面包含了厂商 ID、设备类型等。它最大的价值是让 I3C 总线不依赖静态地址也能识别设备,这也是动态地址分配能成立的前提。

4.2 在 i3c0 节点中挂一个传统 I2C 设备

假设你的板子上有一颗经典的 AT24C02 EEPROM,挂在 RK3576 的 I3C0 总线上。DTS 可以写成:

&i3c0 { status = "okay"; i3c-scl-hz = <12500000>; i2c-scl-hz = <400000>; eeprom@50 { reg = <0x1 0x50 0x0>; compatible = "atmel,24c02"; pagesize = <16>; }; };

这里reg = <0x1 0x50 0x0>的 0x1 就明确告诉 I3C 主控:这是一个 legacy I2C 设备,地址 0x50。I3C 主控在访问它时,会退回到 I2C 兼容模式,时序上完全照着 I2C 快慢来。驱动侧也不用改,atmel,24c02走的标准 i2c 驱动路径。

这里有个好处是,你不用为了这颗 EEPROM 单独再开一路 I2C 控制器,省了一个 I2C bus 的资源。但也别高兴太早,传统 I2C 设备挂在 I3C 总线上时,它占用的速率是 I2C 的 400k,总线其他 I3C 设备访问时切回 12.5M 需要主控内部做模式切换,频繁切换也会带来一些额外损耗。

4.3 挂一颗真正的 I3C 设备,动态地址怎么处理

真正有意思的是原生 I3C 设备。假设某颗传感器支持 I3C 且我们没有给它预设静态地址,希望靠 DAA 动态分配,节点可以写成:

&i3c0 { status = "okay"; i3c-scl-hz = <12500000>; sensor@0 { reg = <0x0 0x0 0x0>; compatible = "vendor,sample-i3c-sensor"; }; };

reg 全 0 表示这是一个纯 I3C 设备,主控启动后会自动发起 DAA 流程,给设备分配动态地址。设备驱动匹配除了看 compatible,I3C 核心也可以通过设备返回的 PID 和 DCR 来匹配驱动。你会在/sys/bus/i3c/devices/下看到类似i3c-0-xxxx的设备节点,其中 xxxx 就是动态分配后确认的地址表示。

有些工程师会觉得既然能动态分配,那地址随便填也行。这里我建议反过来:如果你确切知道设备的静态地址,最好把第二个 cell 写上。比如某颗传感器出厂静态地址是 0x6E,就写:

reg = <0x0 0x6E 0x0>;

这样 I3C 主控可以先用静态地址和它建立起通信,再做 DAA 或校验,初始化路径更收敛,排查问题也少一个变量。动态地址机制是兜底方案,不是让你把信息全丢掉。

4.4 IBI 中断在 DTS 里需要单独配置吗

I3C 的带内中断,在设备树层面通常不需要像 I2C 设备那样配interrupt-parent和interrupts,因为中断请求是走 SDA 线的,不是走 SoC 的 GPIO 控制器。I3C 核心会在检测到 IBI 后回调对应驱动处理。

不过这不代表完全不用关心。驱动注册时,如果设备支持 IBI,通常需要调用 I3C 核心提供的接口去使能相关事件(比如 enable IBI、设置 IBI handler)。DTS 里写节点只是让驱动能被找到,I3C 设备是否真正能用 IBI 上报,还要看固件和驱动的配合。调试的时候如果发现 IBI 没触发,先别急着怀疑总线时序,检查一下设备驱动里有没有使能对应事件,这一步比调硬件更常见。

4.5 我再列一个 DTS 配置常见问题清单

症状最可能的原因
dmesg 报 reg 解析错误子节点 reg 三 cell 写错,或缺#address-cells = <3>
i3c 总线 probe 成功但总在 I2C 模式i3c-scl-hz没配,或主控没开 I3C 模式
I2C legacy 设备正常,I3C 设备找不到I3C 设备固件没实现 DAA,或节点 compatible/PID 不匹配
偶发数据错误或 CRC 错误总线电容偏大、上拉阻值不合适、i3c-scl-hz设置过高
访问传感器时系统卡顿I3C 主控频繁在 I2C/I3C 模式切换,或 IBI 风暴

5. 调试与验证:怎么知道 I3C 真正跑起来了

5.1 内核配置和 dmesg 是第一个证据

RK3576 这种 Linux 平台,I3C 支持在内核里是独立子系统的。配置内核时要把CONFIG_I3C、CONFIG_I3C_MASTER打开,如果是 Rockchip 自己的驱动,可能还需要对应CONFIG_I3C_MASTER_DW或平台相关的选项。板级 dts 里把对应 i3c 节点status = "okay"后重启,dmesg 应该能看到 I3C 主控初始化以及设备枚举信息。

如果设备没出现,第一件事不是抓波形,而是确认内核是否真的把 i3c 驱动编进去了。嵌入式开发里 90% 的“节点明明写了”最后查出来都是内核配置没开或者设备树没编进去,这种基础错误反而最容易忽略。

5.2 用 i3ctransfer 做总线级功能验证

Linux 用户空间有 i3c-tools 工具包,里面和 i2ctools 对应的命令是i3ctransfer。你可以像操作/dev/i2c-x一样操作/dev/i3c-0。

假设设备动态地址最终是 0x6E,想写命令再读 16 字节:

i3ctransfer -d /dev/i3c-0 -w 0x6e,0x10 -r 16

这比直接写驱动再验证要快得多。建议在写任何内核驱动之前,先用这个命令把 I3C 总线的读写打通。如果 i3ctransfer 都读不到东西,那问题大概率还停留在设备枚举和地址阶段,不用急着追驱动代码。

5.3 逻辑分析仪抓时序,注意采样率要够

I3C 的时序抓取和 I2C 很相似,但也有明显区别。I3C SDR 的数据阶段是推挽,波形边沿非常陡峭,如果你手里的逻辑分析仪采样率只有 12M 或者更低,抓 12.5M 的 I3C 波形基本是抓了个寂寞。我建议至少 50M 采样率起步,有条件直接上 100M。抓的时候重点看:

  • 启动条件、停止条件是否标准;
  • 地址阶段是 I3C 动态地址格式还是 I2C legacy 格式;
  • SDR 数据阶段边沿是否干净,有没有因为上拉过大导致的拖尾。

HDR 模式下波形会更复杂,三进制编码不是 SPI 那种一眼能看穿的结构,对新人来说,只抓 SDR 的启动过程和 CCC 广播就已经能验证很多问题了。

5.4 实际测试数据别只盯着毛速率

我在这类平台上测过同一颗传感器从 I2C 400k 切到 I3C SDR,单次读取的耗时大概缩短了 8 到 12 倍。这个数字看起来不错,但对不同应用场景意义完全不同。如果传感器只是 1kHz 采样率,每次也就传两三个字节,CPU 根本感觉不到差别;如果是一颗连续出数据的 IMU 或者高帧率图像数据流传感器,I3C 的带宽优势就非常明显了。

所以验证阶段建议做一个实用性评估:不仅测总线的空闲速率,还要测终端应用里单次事务的完整耗时、中断频率、CPU 占用。说白了,I3C 快 10 倍是物理层的上限,落到业务上能不能兑现,还得看设备模型和软件路径有没有拖后腿。

最后再聊两句经验

我个人在实际操作中的体会是,设备树层面的 I3C 配置并没有想象中复杂,最难的反而是“思维转换”。I2C 时代我们习惯了给每个设备分配静态地址、单独拉中断引脚、拿 GPIO 数量换功能;I3C 时代要接受动态地址、IBI 中断、以及一个更智能的控制器自动调度时序。如果你只是把 I3C 当一根更快的 I2C 来用,也能跑,但那些真正解决烦恼的特性就全浪费了。

还有个小建议:迁移初期不要把板子上所有 I2C 设备一股脑全换成 I3C。先挑一颗原生支持 I3C 的传感器,单独放到 i3c 总线上,配合 i3ctransfer 把总线调通,再逐步增加设备。老 I2C 设备挂 I3C 总线属于兼容玩法,适合省总线资源,但别指望它能享受到 I3C 的高速率和中断红利。先把原生 I3C 设备的流程走顺,后面再谈大规模迁移,才是这种新接口最稳妥的落地姿势。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询