ARTICLE DETAIL

资讯详情

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

I3C与I2C差异解析:RK3576设备树配置与驱动实战

I3C与I2C差异解析:RK3576设备树配置与驱动实战 1. I3C和I2C的本质差异快只是一个结果1.1 I2C接口的“天花板”到底在哪里做嵌入式这几年I2C一直是外设接口里的“万金油”。挂一块EEPROM、一个温湿度传感器、一个触摸屏基本都是两线搞定。但你真把I2C逼到极限时会发现它的慢不是慢在协议设计而是慢在物理层的取舍上。I2C标准模式是100kHz快速模式400kHz高速模式虽然标称能做到3.4MHz但真正量产时很少人敢用。原因很简单I2C采用开漏输出加外部上拉电阻信号拉低靠器件主动驱动信号拉高靠上拉电阻充放电。上拉电阻越大电平爬升越慢上拉电阻越小静态功耗越大还会把边沿搞得很难看。再加上总线上挂的设备越多总线等效电容越大400kHz下能走多长的线、挂多少个设备大家心里基本都有数。所以I2C真正的问题不是“频率参数不够高”而是开漏结构决定了带宽上限。高速模式3.4MHz看着数字漂亮但对板级走线、上拉电阻、器件io的输入阈值都很挑剔很多MCU和SoC虽然写了支持高速模式实际芯片上的IO驱动能力并不匹配。更麻烦的是I2C在多主场景下靠仲裁识别总线冲突速率上去之后仲裁窗口变得极窄稍有时序偏差就翻车。1.2 为什么说I3C“快10倍”是个笼统的说法网上到处说I3C比I2C快10倍这种说法在传播层面没问题但做工程的人心里要有杆秤。I3C的SDR单数据率模式最高跑到12.5MHz比I2C常用快速模式400kHz快了约31倍拿I2C的高速模式3.4MHz来比则是3.7倍左右。所谓10倍更像是取了一个有画面感的中间值。I3C之所以敢跑高频率核心是把开漏输出换成了推挽输出。推挽结构下拉和上拉都由MOS管主动驱动信号边沿可以做得非常陡不再受上拉电阻充电时间拖累。总线物理层的变化才是“快”的真正来源而不是单纯把时钟配置调高。I3C也不是只靠推挽在硬撑协议层还做了很多补强。比如动态地址分配、带内中断、热连接、CRC校验这些功能在I2C时代想都不敢想。I2C的地址是靠硬件引脚电平组合出来的多块同型号板子放同一个系统里地址撞车的概率非常高。I3C上电后由主控统一分配动态地址从设备不用再靠拨码开关去躲冲突这在多传感器模组场景里非常实用。1.3 I3C协议层补齐了哪些I2C的老问题I3C保留了I2C的“两线制、多目标、带地址寻址”的基本交互方式所以工程师看到I3C时不会有陌生感。但它和I2C之间的差异不是“I2C Plus”而是一套新协议规范。一个明显改进是带内中断IBI。传统I2C设备要通知主控通常得额外拉一根中断脚主控这边也得配一个GPIO中断。I3C设备直接可以在总线上发起带内中断主控在完成当前事务后响应即可。这个功能对引脚紧张的模组板来说特别有价值省掉一路中断线还能减少因为中断信号抖动带来的误触发。另一个改进是动态地址分配DAA。I3C主控上电后会发起地址分配流程给所有支持I3C的从设备分配地址老式I2C设备则被保留在静态地址段。这意味着同一条总线可以混合挂I3C设备和I2C设备I2C设备继续用原来的静态地址访问I3C设备用动态分配的地址访问。这种向后兼容是I3C推广时很重要的加分项。I3C还新增了热连接功能设备可以在系统运行过程中接入或拔出总线由主控重新执行地址分配或设备发现流程。这对模块化硬件尤其友好以前I2C设备热插拔基本是在赌人品I3C至少从协议层面提供了机制剩下的就看主控驱动和物理连接怎么样。2. RK3576的I3C控制器与选型思路2.1 RK3576这颗SoC上的I3C外设长什么样RK3576是瑞芯微面向AIoT、智能网关、机器人和车载后装市场推出的6nm平台CPU侧挂的是4个A72加4个A53算力方面带6TOPS NPU外设资源非常齐全。最让我在意的是它内部集成了I3C控制器而不是像以前那样只能用I2C硬扛高速传感器。从硬件资源上看RK3576的I3C控制器沿用了瑞芯微外设的接入方式寄存器控制、中断、DMA都和I2C控制器类似。控制器内部有收发FIFO也支持DMA搬运这对批量读取高帧率传感器数据很有帮助。I3C和I2C的引脚通常是复用的具体复用关系需要查芯片TRM的Pin Mux表格做成设备树时要把对应引脚切到I3C功能上。RK3576的IO电压域配置也需要特别留意。I3C总线电平一般是1.8V或更低的IO域如果板子上VCCIO3供电给到3.3V就可能出现电平不匹配的问题。实际调试时我遇到过逻辑分析仪抓到波形的确有I3C启动序列但设备就是不回应最后查下来是IO域电压超出器件规格。2.2 什么时候值得从I2C切到I3CI3C听起来很美好但不是所有项目都该无脑上。判断标准要看总线上的设备类型和流量需求。如果你的系统里挂的是高刷触摸屏、高频率IMU、多主轴编码器、多路PMBus电源管理芯片这类对实时性和吞吐量有要求的设备I2C的400k带宽很容易成为瓶颈。比如一个输出频率1kHz、每次上报10字节姿态数据的IMU不算协议开销就需要8Mbps左右的线速率I2C快速模式完全吃不消I3C的SDR 12.5MHz才能轻松接住。反过来如果设备就是一颗EEPROM或者一天报几次温湿度I2C的100kHz都够用强行换I3C反而会把硬件设计复杂度拉高。I3C要求主控侧有对应控制器从设备也要专门支持I3C协议市面上支持I3C的传感器种类还在增长阶段远没有I2C设备那么丰富。还有一类场景是“总线资源不够”。I2C地址只有7位总线上同型号设备多了要加地址扩展器或者多路复用器I3C的动态地址机制可以从根上缓解这个问题。做机器人关节模组时一条总线上会挂多块同样的编码器板用I2C每次都要为地址冲突头疼换成I3C后主控自动分配地址软件逻辑清爽了很多。2.3 控制器选型前需要看哪几个关键参数选芯片时我会先看三点是否内置I3C控制器、控制器主模式支持哪些速率档位、是否能同时兼容I2C设备。RK3576这类平台的好处是控制器可以配置为I2C模式或I3C模式过渡阶段即使买不到I3C外设也能先当普通I2C控制器用等器件成熟了再切换。还要关注核电压和DMA通道。I3C高吞吐场景下DMA几乎是必须的如果控制器没有DMA能力高速传输时CPU中断负载会高得离谱应用层延迟也会被拉大。FIFO深度同样重要FIFO越大突发传输时越不容易被响应延迟打断。最后是电气特性。I3C推挽输出对板级信号完整性要求比I2C高走线过长或者链接器接触不良时高速边沿会产生过冲和振铃。设计阶段就要考虑阻抗连续性不能照抄I2C的标准布局。3. DTS配置实战RK3576下的I3C节点3.1 最小可用I3C设备树节点模板在RK3576的Linux SDK里配置I3C大思路和配置I2C非常接近。底层节点已经写好在SoC的dtsi里我们需要做的是打开对应控制器、选对引脚复用、配置时钟频率、挂上子设备。先放一个我在RK3576上调通的最小模板i3c0 { status okay; pinctrl-names default; pinctrl-0 i3c0_xfer_group; i3c-scl-hz 12500000; i2c-scl-hz 400000; /* 某些SDK版本使用clock-frequency */ clock-frequency 12500000; /* I3C从设备节点采用动态地址 */ some_sensor5c { compatible vendor,some-i3c-device; reg 0x5c 0x0 0; }; /* I2C传统设备老式EEPROM直接挂在I3C总线上 */ eeprom50 { compatible atmel,24c02; reg 0x50; pagesize 8; }; };这个模板里需要注意几个地方。首先是pinctrl-0要指向I3C复用而不是I2C复用很多第一次调试的人在这里翻车。第二是频率属性名在不同内核版本里不统一有的SDK用i3c-scl-hz有的用clock-frequency还有两个都识别具体要查SoC绑定的文档。关于子节点的reg属性I3C设备节点和I2C设备节点的格式并不一样。I3C设备需要携带静态地址、动态地址和器件ID等信息常见格式是三个cell但不同内核版本解析规则有差异。我建议拿到SDK后先到/kernel/Documentation/devicetree/bindings/i3c/目录下找对应的binding文档以官方文档为准不要凭经验乱写。3.2 时钟、电源域和引脚复用一个都不能少设备树配置最容易翻车的三个点就是时钟、电源域和引脚复用。时钟方面I3C控制器作为外设它的模块时钟由CRU控制。节点里通常会带clocks和clock-names属性指向控制器所需的aclk和pclk。配置完成后不要只盯着设备树还要到系统里实际确认时钟有没有开、频率对不对。调试时我喜欢看这几路径cat /sys/kernel/debug/clk/clk_summary | grep i3c如果时钟显示为0或unknown说明节点没有被正确使能或者状态机没有走到时钟门控打开那一步。另一个常见问题是GPIO子系统把引脚复用抢占掉。RK3576的引脚功能选择走pinctrl框架如果你的自定义板级dts里把同一组引脚配成了GPIOI3C控制器即使写statusokay也发不出波形。电源域方面I3C控制器通常归属在某个PD电源域内比如RK3576的PD_NOC或PD_BUS域。dtsi里已经配置好了正常不需要改但如果你自定义休眠逻辑要确保电源域在唤醒后能让I3C控制器重新初始化否则会出现休眠唤醒后I3C总线直接不工作的情况。3.3 I3C子设备与老式I2C设备混合挂载的注意事项I3C总线的一大亮点是能兼容传统I2C设备让项目过渡期可以把新旧器件混在一条总线上。但混挂不是简单把设备节点叠加在一起就行有几个细节要提前想清楚。当总线上有老式I2C设备时它们不理解I3C的CCC命令。I3C规范定义了总线初始化和设备发现流程这个流程走的是I3C协议老I2C设备只会把它当成一次不可思议的I2C通信忽略掉。大多数情况下没问题但有个别挑信号的老芯片会在DAA流程中被误识别导致总线初始化卡住。遇到这种情况可以在设备树里给I3C控制器配一个I3C设备掩码明确告诉主控哪些地址段留给老I2C设备。另外要注意I3C向I2C设备发数据时事务格式还是兼容I2C的START、地址、读写标志这一套但地址解析和停止条件处理和纯I2C驱动不完全一样。驱动层不能用模拟I2C那套直接套必须走内核的I3C子系统。还有一个被反复问到的点I3C控制器能不能像普通I2C那样用来扫描设备地址我建议别简单照抄i2cdetect。I3C的地址发现会涉及动态地址分配直接扫描可能把不该访问的I3C设备搞出异常。要枚举设备优先看内核I3C子系统提供的sysfs节点或者接逻辑分析仪观察总线行为。4. 从I2C迁移到I3C驱动与用户空间的差异4.1 Linux I3C子系统的框架思路Linux内核从5.0左右开始加入I3C子系统目标是复用I2C驱动的开发经验同时为I3C新增特性提供独立框架。整个框架分三层控制器驱动、核心层、设备驱动。RK3576的BSP里已经带好了控制器驱动我们普通工程师接触最多的其实是设备驱动要怎么写。I3C设备驱动的注册方式和I2C驱动很像都有一个类似i2c_driver的结构体但要用i3c_driver。设备端同样有探测、移除、 suspend/resume 回调也有类似于i2c_transfer的传输接口。最大的区别是驱动里要支持I3C特有的动态地址分配、带内中断、CCC命令等功能。如果你要写的设备本身有成熟的I2C驱动先别急着照搬。I2C驱动的i2c_client和I3C的i3c_device不是同一个对象probe函数拿到的设备指针类型都不同不能直接替换。比较省力的办法是写一个薄的I3C驱动层内部复用原I2C驱动的读写逻辑只在登记设备时换成I3C子系统接口。4.2 现有I2C驱动如何改造为I3C驱动改造的难点不在读写命令而在设备模型的匹配。I2C驱动靠compatible加addr匹配I3C驱动则多了一个PID匹配流程也就是MIPI定义的IEEE标准标识符。假如器件手册写的是“支持I3C”那它一定有一个MIPI分配的PID这个PID要写进i3c_device_id表里。看一段伪代码static const struct i3c_device_id my_sensor_i3c_ids[] { { .match_flags I3C_MATCH_PID, .pid MIPI_PID(V, N, D, 0x1234), }, { } }; static struct i3c_driver my_sensor_driver { .driver.name my_sensor, .id_table my_sensor_i3c_ids, .probe my_sensor_probe, .remove my_sensor_remove, };如果器件没有完整的PID也可以退而求其次用静态地址匹配但这种方式建议只用在纯I2C设备转接场景。真正想要的动态地址分配能力只有PID到位才能充分发挥。要提醒的是I3C驱动里读写数据和I2C完全不一样。I3C有Private Transfer的概念也就是主控和某个特定从设备之间的私有通信。这个私有传输可以带多种数据模式有的是SDR有的是HDR选择权在通信双方能力协商结果里。代码上不能默认时序要先用CCC查询设备能力再决定使用哪种传输速率。4.3 用户空间访问I3C工具生态还没完全成熟I2C时代大家习惯用i2c-tools一条i2cdetect -r直接在命令行扫设备。I3C时代这个工具链还在追赶如果想让用户态直接读I3C传感器目前大半还是靠写一个小的字符设备驱动来桥接。内核I3C子系统对外暴露的sysfs接口已经有一部分设备发现信息在板子上能看到/sys/bus/i3c/devices/下出现设备目录里面包含动态地址、PID等信息。但完整的用户态读写API不同内核版本差异很大还没有像i2c-dev那样稳定的i3c-dev接口。实际项目中我的建议是用户态应用不要直接碰I3C而是在内核驱动里封装好读写接口通过ioctl或者简单的字符设备传给用户态。这样即使内核I3C框架API调整也只是改驱动不会把应用层拖着一起改。5. 实测记录I3C到底能跑多快怎么量化调优5.1 用逻辑分析仪先看SDR波形我不太建议一上来就看“理论上多少兆”。真正的工程调优第一步是用逻辑分析仪抓I3C启动序列和SDR传输波形。RK3576的I3C控制器跑起来之后SCL线上如果能清楚看到12.5MHz的方波说明物理层配置基本OK。接着要确认总线启动序列里的设备发现流程有没有正常完成。I3C主控上电后会尝试 DAA如果逻辑分析仪上只看到重复的广播命令却没有任何设备回复大概率是总线上设备地址设置不对或者是总线上混挂了行为不规范的I2C设备干扰了CCC命令。我实测过同类设备在I2C 400kHz和I3C SDR 12.5MHz下的差距。读一个128字节的数据块I2C算上地址、应答、停止位大概耗时3.3ms左右I3C SDR模式大概只需要0.11ms左右提升接近30倍。如果你项目里的数据帧足够大这个差异会非常明显。5.2 判断“快10倍”背后的真实吞吐约束理论速率看着高实际吞吐要打折扣。I3C每次传输有很多协议开销包括启动序列、地址、CRC、停止条件再加上动态地址刷新和硬件初始化纯数据速率不会等于线速率。更重要的是总线上一旦挂了一个只支持400kHz的老I2C设备I3C主控为了兼容它会自动降速或切到I2C时序段这时候整条总线的有效吞吐会被拖到一个中间值。所以调吞吐前先盘点总线上所有设备的速率能力。如果只有一两个I3C设备剩余都是I2C设备建议在板级设计时把高速I3C设备放到独立的I3C控制器上老I2C设备留在I2C控制器上物理上分开别靠DTS协议兼容硬凑。5.3 DMA、FIFO和中断设置对性能的影响高速率只是I3C能力强的前提真正把能力落地靠的是DMA。RK3576的I3C控制器支持DMA搬运设备树里已经带了dma属性。实测中只要DMA通道配置正确批量读传感器数据的CPU占用会大幅下降这比单纯把时钟调高更有意义。如果发现数据吞吐还是上不去先看看FIFO深度和DMA burst大小是否匹配。有一次我遇到数据明明没读错但吞吐起不来最后发现是DMA burst配置太小每次只搬4字节中断频率高得吓人。把burst调到16字节后整体效率基本是成倍提升。I3C带内中断也要设置得合理。IBI本身是为了减少设备和主控之间的GPIO交互但如果每条IBI都触发一个高频中断CPU依然会被整疯。建议在驱动里把IBI事件做去重和合并只在状态变化时上报而不是把每个I3C中断原样往上丢。5.4 CRC校验和可靠性开销I3C相对I2C多了一个很大的优势CRC校验。I2C的可靠性几乎靠电气设计和运气I3C则可以在数据帧后面带CRC接收端校验失败直接重传。这个特性在电机编码器、车载传感器这类噪声恶劣的环境里非常有用。但CRC不是免费午餐。开启CRC后每个数据帧都多了校验字段有效吞吐会下降再加上重传逻辑总线有效带宽会比理论值低不少。普通消费类产品可以关闭CRC换性能工业场景建议开着。我自己的原则是能开则开除非性能实测确实不够。6. 常见问题排查实录从设备找不到到总线卡死6.1 设备树配完I3C设备却发现不了这是最常遇到的坑排查顺序我建议固定化第一dmesg | grep i3c看看I3C控制器有没有注册成功。如果控制器都注册失败问题多半在时钟或中断号。第二确认设备树里I3C子设备的compatible和驱动里i3c_device_id匹配了吗。很多人把I2C驱动节点直接改成I3C驱动没有转换probe自然进不来。第三用逻辑分析仪看总线有没有DAA流程。如果没看到启动序列说明控制器没真正工作如果看到启动序列但设备不回包重点排查设备地址和PID。第四检查总线上是否已经有一个设备占用了相同的静态地址。虽然I3C有动态地址但老I2C设备还是用固定静态地址如果和I3C设备保留地址冲突I3C主控会直接跳过部分设备。6.2 混挂GT911这类传统I2C触摸屏时的特殊处理GT911是现在很多安卓板上的标准触摸方案本身只支持I2C。如果你想在RK3576上省一个控制器把GT911挂到I3C总线上不能直接把它的设备树节点放进I3C总线节点就完事。GT911初始化依赖比较严格的复位时序和中断脚它的I2C行为也相对老派。当I3C控制器整部启动流程时GT911可能因为收到不认识的I3C广播信号而进入异常状态。我的解决方法是给I3C控制器配置好静态I2C地址掩码确保DAA流程不会把GT911的地址段当成I3C设备去处理。同时GT911的中断脚和复位脚必须按数据手册严格控制不能依赖I3C总线的热连接机制来做初始化。如果你的系统启动时偶尔出现触摸屏探测失败但复位一遍又好了七成就是上述DAA影响到了I2C设备。可以先把I3C控制器换成纯I2C模式验证GT911本身有没有问题再切回I3C模式排查总线干扰。6.3 高速传输时的偶发数据错误I3C跑12.5MHz时板级信号完整性会开始找你麻烦。常见现象是设备能探测到但读取数据偶发多bit错误。排查时先用示波器看SCL和SDA的过冲幅度如果波形振铃超过IO电平的30%先降低频率测试确认是信号质量问题。解决手段按优先级排降低I3C总线速率到10MHz甚至8MHz看是否稳定调整RK3576对应引脚的驱动电流强度检查I3C总线上是否挂了不必要的大电容比如保护电容、滤波电容。I3C推挽输出的边沿很陡如果走线寄生电感偏大过冲会很厉害这时候更大的可能是需要优化PCB走线而不是无脑降速。驱动侧也可以开启CRC校验来兜底。偶发错误如果不能从根源消除CRC重传机制是最后一层保险。不过重传要配合超时管理否则I3C控制器会在错误的设备状态上一直等表现成系统偶发hang住。6.4 休眠唤醒后I3C总线不恢复怎么排查RK3576这类平台进入低功耗模式后外设电源域可能被关断I3C控制器也会掉配置。唤醒后如果驱动没有重新初始化寄存器总线就一直处于半死状态。这个问题在I2C时代就有I3C因为初始化流程更复杂更容易踩中。排查方式是看唤醒后dmesg里I3C控制器有没有执行重新初始化流程。如果没有一般是驱动resume回调里没有做完整的控制器重置。可以手动在resume之后调用一次控制器初始化同时把I3C总线的DAA流程重跑一遍因为部分I3C设备在掉电后动态地址已经丢失不重跑DAA就无法访问。如果I3C控制器本身没有掉电只是从设备掉电主控侧可能需要主动发RSTDAA来重新分配地址。设备树属性里有些SoC会配置自动重置但RK3576的BSP默认不一定开需要确认核心驱动支持情况。6.5 从示波器波形判断I3C到底在工作吗最后说一个通用技巧没有逻辑分析仪的时候用示波器也能快速判断I3C总线状态。I3C正常工作时SCL上会有持续的时钟翻转停止状态只发生在空闲时。如果SCL在大部分时间处于低电平而SDA稳定在一个稍高的电位可能是主控在准备启动序列或者等待设备时钟拉伸。I2C时代设备可以通过拉低SCL做时钟拉伸来延缓传输I3C也保留了一部分握手能力但机制上和I2C不完全一致。看到SCL被持续拉低时不要直接怀疑I3C控制器坏了先排除从设备上电时序没有完成、从设备处于低功耗模式这类软件问题。如果你用的是逻辑分析仪直接抓总线启动之后的第一个DAA命令。I2C模式下常见的START 7-bit addr R/W在这里看起来会长很多因为I3C启动序列里包含动态地址分配和模式切换波形特征和I2C明显不同。抓几帧之后你就能一眼看出当前总线到底跑在哪种协议模式里。我自己在RK3576上调完这一圈I3C之后最大的体会是I3C带来的不只是带宽数字的提升它把总线的可管理性拉高了一个级别。动态地址、带内中断、CRC这些能力才是真正让项目从“能用”走向“好用”的东西。不过真要在量产项目里铺开还得看外围器件生态能不能跟上。现阶段我的建议是保持一个务实心态好钢用在刀刃上高速设备上I3C老I2C设备也别急着逼它退役各走各的总线比强行混在一起省心得多。
返回列表