ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

I3C比I2C快10倍?RK3576设备树配置与实战避坑指南

I3C比I2C快10倍?RK3576设备树配置与实战避坑指南 做 RK3576 方案 bring-up 的时候我拿到一版原理图上面标着 SDA、SCL第一反应是“这又是 I2C 口”。结果翻了一遍传感器 datasheet发现它同时支持 I3C最高能跑 12.5MHz。我在 DTS 里把 i2c 节点改成 i3c 节点顺手调低采样周期然后看波形和数据读取时间差距是肉眼可见的。这篇文章就以 RK3576 为例把 I3C 和 I2C 的差异、所谓“快 10 倍”是怎么来的、DTS 设备树具体怎么配置以及我踩过的坑一次说清楚。适合做嵌入式驱动、硬件选型或板级 bring-up 的工程师参考新手也能跟着跑一遍。1. I3C 到底强在哪先搞懂它凭什么比 I2C 快 10 倍1.1 I2C 的瓶颈不只是速度I2C 从 1982 年出现到现在本质没变过两根线SCL、SDA开漏输出外部靠上拉电阻把电平拉高。开漏的好处是引脚可以容忍不同电压域、做线与仲裁代价是上拉电阻给总线电容充电时需要时间频率越高上升沿越跟不上信号就越容易失真。所以 I2C 的标准速率从 100kHzStandard、400kHzFast到 1MHzFast-mode Plus、3.4MHzHS 模式听起来一路在涨但现实中真正把 I2C 跑上 3.4MHz 的传感器/SoC 组合少得可怜。绝大多数产品也就是 400kHz 打底少数能到 1MHz。规格表上的“支持高速模式”和“能在量产板上稳定跑高速”是两码事这个做硬件的人体会最深。除了速率I2C 还有一个老毛病地址。一条总线上想多挂两个同型号传感器就得靠硬件跳线、地址引脚或者换型号来错开地址。I2C 的 7 位地址空间本来就只有 128 个实际去除保留地址、重复地址能用的可能不到 110 个对于现在的穿戴设备、AI 相机这种动不动一条总线挂 5~6 颗器件的场景规划和维护成本很高。再加上 I2C 从设备没有主动上报机制传感器 FIFO 满了、按键被按下、电源异常了全靠外部中断引脚把信号拉到主控的 GPIO 上。板子管脚资源紧张的时候每多加一颗 I2C 从设备就要多占一个中断脚、多拉一根线丢给系统工程师的麻烦是实打实的。1.2 “快 10 倍”的账我是这么算的I3C 的全称是 Improved Inter Integrated Circuit由 MIPI 联盟在 2017 年前后发布从命名就能看出它是 I2C 的“改进续作”而不是另起炉灶。它保留了 I2C 的两线制思想但把输出结构从开漏改成了推挽为主SDRSingle Data Rate模式下时钟可以跑到 12.5MHzHDR-DDR、HDR-TSL、HDR-TSP 这些高级模式还能再往上走。说“比 I2C 快 10 倍”其实是个保守说法。我习惯拿实际读操作算一笔账读 32 字节数据块I2C 的帧要经过起始位、从地址读写位、ACK、寄存器地址字节、重复起始、从地址读写位、ACK、32 字节数据、停止位总共大约 320 个 bit。按 400kHz 算要 0.8ms按 1MHz 算要 0.32ms。I3C 在 SDR 模式下用动态短地址、每字节也带 ACK 位但基础速率是 12.5MHz同样的读操作只要几十微秒HDR 模式下还能再翻一翻。对比下来和实际常用的 400kHz I2C 比I3C 不止快 10 倍二十倍、三十倍都有可能。需要注意的是这个对比没有把 I3C 总线初始化时的 DAA动态地址分配和 CCC 命令开销算进去但那是上电后的一次性流程不体现在连续读写上。实际工程里把一条 400kHz 的总线迁到 I3C SDR 模式通常能感受到 10~30 倍的提升这已经够让整个系统的采样频率和响应速度上一个台阶了。1.3 除了快I3C 还解决了几个老问题I3C 的卖点要是只有速度那它不配叫“改进版 I2C”。真正让我觉得它值得从 I2C 迁过去的是下面这几条第一个是动态地址分配。I3C 总线启动时主控制器会发起 ENTDAA 公共命令每个从设备用 48 位的 Provisional ID 来应答主控制器给它们分配 7 位动态地址。也就是说同一型号的传感器可以挂很多颗在同一条 I3C 总线上互相之间不需要跳线、不需要地址引脚地址冲突从协议层面被解决了。如果设备支持静态地址还可以通过 SETDASA 命令让动态地址和出厂静态地址一致减少上层软件适配工作量。第二个是带内中断 IBI。I3C 从设备想主动上报事件时不需要一根独立 GPIO 中断线而是直接在总线上发出一个中断请求In-Band Interrupt主控制器收到后可以回应。对穿戴设备这种“管脚比黄金还贵”的场景来说省下一根中断线、一个电源域、一段 PCB 走线收益很明显。第三个是热加入和总线复位机制。热加入允许设备在总线运行过程中动态接入主控制器可以再走一遍 DAA 流程而总线异常卡死时I3C 协议提供了比“手动翻转 SCL 9 个时钟”更优雅的复位手段。协议层的改进最终都是为了降低开发者的运维成本。我把 I2C 和 I3C 的关键差异整理成了一张表方便快速对比维度I2CI3C输出结构开漏外部上拉推挽为主兼容模式开漏常用速率400kHz少数 1MHzSDR 12.5MHzHDR 25MHz 以上地址7/10 位静态易冲突7 位动态分配 静态兼容从设备主动上报需独立中断引脚IBI 带内中断设备热加入不支持支持 Hot Join错误检测基本没有带 CRC/奇偶校验多主支持有仲裁但很勉强改进的多主模型2. RK3576 上的 I3C 资源与硬件排布2.1 RK3576 的 I3C 控制器不是摆设RK3576 是瑞芯微面向 AIoT 场景的一颗 8 核处理器带着 6 TOPS 级别的 NPU外设接口很齐全其中就有原生 I3C 控制器。在 SDK 的 dtsi 文件里搜i3c关键词能看到类似i3c0: i3c...的节点说明这颗芯片的 I3C 不是某个 IP 的私有实现而是跑在 Linux I3C 子系统下的标准外设。不过有一点要提醒RK3576 的 I3C 控制器和某些 I2C 控制器可能是同一个引脚组的“复用”关系。举个例子某个 I3C 控制器的两组可选引脚m0/m1里有一组和 I2C0 的引脚重叠。如果你原本 I2C0 上已经挂了设备又想把 I3C 也用起来就得先查 TRM 确认复用关系否则两个控制器抢同一组引脚DTS 编译不报错硬件上却根本没有信号。具体到你手上的 SDKi3c 节点的索引和 pinctrl 命名可能会跟我的描述有差异这不奇怪。做板级 bring-up 的第一件事就是在 dtsi 和 pinctrl 头文件里把 i3c 相关的节点找全看清楚控制器编号、引脚复用组、默认时钟配置再谈后续。2.2 引脚复用与上拉电阻怎么选I3C 的推挽结构对上升沿不敏感但并不能完全忽略上拉电阻。原因很简单总线上可能还挂着 I2C legacy 设备老设备工作在开漏模式下没有合适的上拉它们根本起不来。另外 I3C 控制器在兼容 I2C 设备时也会切回开漏时序所以上拉电阻还是要留只是可以比纯 I2C 时选得稍微“随意”一点。我的习惯是这么定的总线上如果有 I2C 老设备就按这条总线上最高的 I2C 兼容速率选上拉如果只有原生 I3C 设备上拉选 2.2k~4.7k 都行主要为了兼容后续设备。下面是常用参考值真正的取值还要结合总线电容、VCC 电压和主控 IO 驱动强度来算不能照抄总线工作速率上拉参考阻值说明I2C 100kHz4.7k~10k低速、电容大也能用I2C 400kHz2.2k~4.7k最常见的中速组合I2C 1MHzFM1k~2.2k需要更快的上升沿I3C SDR 12.5MHz总线上有 I2C legacy1k~2.2k兼顾 I3C 信号质量和老设备上拉I3C 纯从设备无 legacy2.2k~4.7k推挽驱动为主上拉只是兜底另一个容易踩的坑是 IO 驱动强度。RK3576 的 pinctrl 里通常可以配置 drive-strength默认值不一定适合 12.5MHz 的快速翻转。信号上升沿太缓、过冲太大、振铃明显都会导致高速通信随机报错。我一般先用默认值跑再用逻辑分析仪看波形如果边沿不干净就在板级 DTS 的 pinctrl 节点里调驱动强度从小往大加直到眼图最好。注意驱动强度调大会增加过冲风险不是越大越好。2.3 一个值得参考的总线规划思路做 RK3576 这种 AIoT 平台总线上挂什么设备、走 I3C 还是 I2C建议提前规划而不是拿到原理图再改。我自己的思路是高频采样或需要低延迟的器件优先放 I3C比如陀螺仪、加速度计、气压计这类持续输出数据的传感器低速、启动时序复杂或只支持 I2C 的老器件比如 EEPROM、老触摸屏控制芯片、音频 codec 的寄存器配置口留在 I2C 总线上就行。这样做的好处是I3C 总线上尽量少挂 legacy 设备避免控制器频繁切换时序模式拖慢整体吞吐I2C 总线上也不需要为了照顾某个高速传感器而把整条总线提到 1MHz所有老设备都在舒适区间跑。举个实际规划总线挂载设备工作模式原因I3C0传感器 A、传感器 BI3C SDR / HDR高频采样且支持 IBI 中断I2C3EEPROM、触摸 IC、Audio codecI2C 400kHz低速从设备地址固定没有高速需求硬件设计上I3C 的 SCL/SDA 两根线要尽量短、少打过孔总线上每挂一个设备都会引入输入电容设备多了信号质量就会变差。布局时还要注意把 I3C 设备和主控放在同一面不要在中间穿过连接器否则 12.5MHz 的信号很容易变成一场灾难。3. 设备树实战DTS 里 I3C 节点怎么写3.1 先看传统 I2C 节点在 RK3576 上的样子在写 I3C 之前得先对 RK3576 的 I2C 设备树有个印象。老司机都知道RK 平台的外设节点默认在公共 dtsi 里是 disabled 的板级 dts 里再置为 okay 并配好 pinctrl。一个典型的 I2C 节点长这样i2c6 { status okay; pinctrl-names default; pinctrl-0 i2c6m0_xfer; clock-frequency 400000; sensor76 { compatible vendor,barometer; reg 0x76; interrupt-parent gpio3; interrupts RK_PA4 IRQ_TYPE_LEVEL_LOW; }; };这段代码里i2c6m0_xfer是引脚复用配置clock-frequency是总线速率0x76是传感器从地址一切都很直观。很多人迁到 I3C 之后还按这个思路写节点结果发现有些属性没了、有些属性变了其实没那么复杂I3C 节点的骨架和 I2C 是同一套逻辑只是多了几个 I3C 特有的属性。有一类器件比较特殊比如我之前调过的 GT911 触摸芯片地址由 INT 引脚的电平在上电时决定而且需要严格的 RST/INT 时序。这类老设备本身不支持 I3C 动态地址迁到 I3C 总线上也只能按 legacy I2C 模式访问所以它原有的启动时序、地址选择逻辑、中断接线全都不能省。协议升级不会改变器件行为硬件时序该做还是得做。3.2 从 I2C 迁移到 I3C完整 DTS 示例假设我原来把一颗气压传感器挂在 i2c6 上地址是 0x76现在换了颗支持 I3C 的新型号想把它移到 I3C0 上同时保留那棵老 EEPROM 挂在同一条 I3C 总线上。DTS 可以这么写i3c0 { status okay; pinctrl-names default; pinctrl-0 i3c0m0_xfer; clock-frequency 12500000; #address-cells 1; #size-cells 0; /* 原生 I3C 设备 */ barometer0 { compatible vendor,barometer_i3c; reg 0x0; assigned-address 0x76; interrupt-parent gpio3; interrupts RK_PA4 IRQ_TYPE_LEVEL_LOW; }; /* I2C legacy 设备挂在同一条总线上 */ eeprom50 { compatible atmel,24c02; reg 0x50; pagesize 8; }; };这段代码里最需要理解的是barometer0这个子节点。原生 I3C 设备的地址是 DAA 流程中动态分配的所以reg 0x0表示“当前还没有确定地址”而assigned-address 0x76是告诉控制器希望 DAA 之后给这个设备分配 0x76 这个动态地址。设备如果支持 SETDASA就会在枚举时被设置成这个固定地址上层应用访问起来反而稳定。eeprom50这种老设备就直接把reg写成真实静态地址 0x50I3C 主控制器发现它的 compatible 或地址特征后会把它当作 I2C legacy 设备注册到内核子系统驱动 probe 流程几乎不变。有一点要特别提醒设备树里reg的用法在不同内核分支和 SDK 里可能会有细节差异。动手之前先看一眼你内核里的Documentation/devicetree/bindings/i3c/i3c.txt或者 SDK 自带的 RK 平台示例 dts确认属性名和语义不要直接拿网上的模板硬套。3.3 内核开启 I3C 与驱动注册DTS 写完之后还要保证内核把 I3C 相关的子系统编进去。菜单路径一般在 Device Drivers - I3C support常见选项包括 I3C subsystem、I3C master controller driver比如CONFIG_DW_I3C_MASTERRK 平台可能会有自己的名称。如果没开这些选项设备树节点写了也只会被忽略i3c0根本不会出现在系统里。这里有个验证小技巧开机后在 /sys/bus 下面看一眼有没有i3c总线有的话说明 I3C 子系统起来了再用dmesg | grep i3c看主控制器的 probe 日志一般能看到 controller 注册成功的打印。如果这里什么都没有基本就是内核配置没打开或者 DTS 的 status 没置为 okay。设备驱动的写法和 I2C 驱动也很像差别在于总线类型和读写 API。I3C 驱动要注册成struct i3c_driver读写用i3c_device_do_priv_xfers()它比 I2C 的i2c_transfer()更接近 raw 时序能表达 I3C 特有的私有传输。示意图如下#include linux/i3c/device.h #include linux/i3c/master.h static int barometer_i3c_probe(struct i3c_device *i3cdev) { struct i3c_priv_xfer xfers[2]; u8 reg 0x00; u8 val 0; xfers[0].rnw 0; /* 写寄存器地址 */ xfers[0].len 1; xfers[0].data reg; xfers[1].rnw 1; /* 读数据 */ xfers[1].len 1; xfers[1].data val; return i3c_device_do_priv_xfers(i3cdev, xfers, 2); } static struct i3c_driver barometer_i3c_driver { .driver { .name barometer_i3c, .of_match_table barometer_of_match, }, .probe barometer_i3c_probe, /* id_table 的具体字段以内核头文件为准这里不展开 */ }; module_i3c_driver(barometer_i3c_driver);很多 I2C 驱动开发者第一次见到 I3C 驱动时会觉得陌生其实核心只是把i2c_client换成i3c_device。如果驱动层还没有面向某种传感器的现成支持直接照这个框架写一个比在 I2C 层反复折腾地址和中断要省事得多。4. 实际使用中的坑与排查心得4.1 刚切到 I3C 时最容易踩的三个坑第一个坑是只改 DTS 不查内核配置。很多同事把i2c6改成i3c0后发现设备消失第一反应是检查引脚、上拉、焊接最后才发现CONFIG_I3C根本没开。这不算稀奇因为 I3C 在不少项目里是“新东西”默认 defconfig 不一定包含迁移之前先确认内核配置是最基础的常识。第二个坑是 pinctrl 写错或没写。I3C 的引脚必须由 pinctrl 切到 I3C 功能如果复用了 I2C 的 pinctrl 节点或者引脚组名打错总线上可能根本没有时钟。用逻辑分析仪抓 SCL 就能看出来有波形但频率不对可能是时钟配置问题完全没有波形优先查 pinctrl 和控制器是否 probe。第三个坑是把 legacy I2C 设备挂上后整个总线被拖慢。I3C 主控制器为了和 I2C 老设备通信必须周期性地切换到兼容模式老设备如果只支持 400kHz它会带着整条总线的 I3C 通信节奏变慢。这不是 bug是协议兼容的代价。设计上尽量把 legacy 设备集中到单独一条 I2C 总线上别让几颗老设备拖累原本可以跑满速的 I3C 总线。还有一类问题是从设备本身的上电时序引起的。开启总线的瞬间I3C 主控制器会做 DAA 枚举如果某些 legacy I2C 设备复位还没完成、还没准备好响应地址通信就会失败表现为 probe 有概率失败、偶尔读不到数据。解决办法是在硬件上给这类设备加 EN 或 RST 引脚控制确保枚举开始前它们已经稳定或者在内核允许的范围内加延时等设备 ready 再访问。4.2 总线上混挂老 I2C 设备的注意事项关于 I2C 老设备能不能挂在 I3C 总线上这个问题结论是可以但有几个前提。主控制器必须支持 legacy 模式设备树里老设备的节点要写得像普通 I2C 子设备一样地址填写真实静态地址同时老设备本身不能参与 I3C 特有的热加入和 IBI 中断机制。混挂后最常见的问题是地址冲突。I3C 的动态地址是协议自动分配的但老 I2C 设备的静态地址是固定的万一 I3C 主控制器给某个原生设备分配了一个和老设备一样的地址就需要在 DTS 中用assigned-address把原生设备的地址固定到安全区。换句话说原来 I2C 下“两个设备撞地址”的噩梦在 I3C 混挂场景下需要你自己通过规划避开。另外要注意I2C legacy 设备和原生 I3C 设备在同一根总线上的通信优先级不同。I3C 主控制器访问 legacy 设备时会降速、切时序访问原生 I3C 设备时又切回高速模式切换本身有额外命令开销。如果总线上 legacy 设备过多I3C 的性能优势会被稀释得很厉害。我实测过一条总线上挂 1 颗原生 I3C 传感器和 3 颗 I2C 老设备整体吞吐只有纯 I3C 场景的七成左右虽然还是比 400kHz 快但已经远不如“理论快 10 倍”那么让人惊喜。4.3 调试与验证手段逻辑分析仪、工具链和心态调 I3C 和调 I2C 最大的不同是你要用对工具。普通 24MHz 的逻辑分析仪看 400kHz 的 I2C 是轻轻松松但看 12.5MHz 的 I3C SDR 信号一个周期只能采到一两个点除非只是确认有没有波形否则根本没法分析时序细节。我建议至少用 100MHz 采样率以上的逻辑分析仪有条件直接用带协议解码的能省很多时间。总线工具方面i3c-tools 的用法和 i2c-tools 很像i3cdetect -l能列出系统里的 I3C 总线还有类似 i2ctransfer 的传输命令可以快速验证某颗设备能不能响应。调试过程中如果出现“开始好好的跑一段时间就通信失败”的随机问题别急着怀疑软件先降速跑一遍把clock-frequency改成 1000000看问题是否复现。如果降速后稳定大概率是信号完整性问题优先级应该是走线电容、驱动强度、上拉电阻最后才回头查软件状态机。这中间还有一个我个人的体会迁移 I3C 不是简单改个 dts 就够了。I3C 的动态地址、带内中断、总线复位这些新特性如果驱动层和业务层还是按 I2C 的老思路去设计那“快 10 倍”最多就是数字上好看系统整体架构并不会有实质性提升。反过来一旦把设备状态上报改成 IBI、把总线复位机制用起来你会在 RK3576 这样的平台上明显感受到I3C 真正带来的不是快而是整个控制链路变“聪明”了。自己动手迁的时候我建议不要一次把所有设备都迁到 I3C先拿一条 I3C 总线、一颗原生 I3C 传感器试水逻辑分析仪抓一帧完整时序确认 DAA、读写、中断都符合预期之后再铺开到其他总线和设备。把 I3C 的脾气摸透之后再看那句“比 I2C 快 10 倍”你会发现它只是最不值一提的优点。
返回列表