I3C 协议栈简介

做 I3C 开发和调试,第一件事就是搞清楚它的协议栈长什么样。说白了,协议栈就是一套分层规则,告诉你底下怎么传比特,中间怎么分帧,上面怎么发命令。我个人习惯把 I3C 协议栈分为四层:物理层、数据链路层、地址分配层、应用层。每一层都有自己该管的事,也都有坑。

核心要点:I3C 协议栈跟传统 I2C 最大的区别,就是它把地址分配提到了数据链路层之上,单独作为一个逻辑层次来处理。这意味着什么?意味着地址冲突不再靠硬件比对,而是靠协议协商。

物理层

物理层决定总线怎么走线、电平怎么摆、驱动谁来负责。I3C 物理层复用 I2C 的 SCL 和 SDA 两根线,但驱动模式改成了推挽(Push-Pull,简称 PP)结合开漏(Open-Drain,简称 OD)的混合模式。

我记得第一次调 I3C 波形时,示波器上看到上升沿一塌糊涂。后来发现是 OD 模式下没有配好上拉电阻,导致总线被拉死的假象。I3C 的物理层其实对 PCB 布局的要求比 I2C 高不少,尤其是高频通信时,推挽模式的瞬态电流很容易产生过冲。

模式 速度 驱动方式 典型用途
开漏 (OD) ~1 MHz 外部上拉电阻 仲裁、广播、旧设备兼容
推挽 (PP) 12.5 MHz 内部驱动 高速数据传输、动态地址分配

数据链路层

这一层负责把字节流打包成帧,处理起始条件(START)、重复起始(Sr)、停止条件(STOP),以及确认位(ACK/NACK)。I3C 的数据链路层继承了很多 I2C 的帧结构,但去掉了我一直觉得多余的 10 位地址模式——这在 I3C 里完全不再使用。

I3C 数据链路层跟 I2C 还有一个重大区别:多主支持。传统 I2C 多主靠仲裁,谁发低谁赢;I3C 则靠动态角色切换(Dynamic Role Switch),任何一个设备都能在适当的时候主动申请当主。

调试小技巧:抓 I3C 帧时,先看 SCL 频率。OD 模式下如果 SCL 超过 1 MHz,八成是设备不支持高速模式,会丢帧。我早期遇到过这种问题,排查了两天,最后发现是 FPGA 的 I/O 驱动能力没配够。

地址分配层

这是 I3C 协议栈最核心、也最容易出问题的一层。地址分配层做的事很简单:给总线上的每个设备分一个唯一的 7 位动态地址(Dynamic Address,简称 DA)。为什么要分动态地址?因为 I3C 保留了 I2C 的静态地址机制用于向后兼容,但静态地址容易冲突。我见过一个产品,两个不同厂商的传感器都用了 0x48,总线一拉起来就打架。

动态地址分配协议(Dynamic Address Assignment,简称 DAA)就派上了用场。DAA 过程完全是软件驱动的:主发一个广播 ENTDAA(Enter Dynamic Address Assignment),从机依次响应,主读取其临时 UID(唯一标识符),分配新 DA。

避坑指南:我曾经碰到过一个从机在 ENTDAA 过程中不响应的情况,抓波形发现它的 ACK 时间窗不对,差了一点点。I3C 协议对时序的容错比较严格,尤其是 ACK/NACK 的窗口,从机必须在 START 后的第 9 个 SCL 周期内放下 SDA,晚了就当没听见。

应用层

应用层负责解释命令和数据。I3C 定义了一组通用命令代码(Common Command Code,简称 CCC),比如 GETPID、GETBCR、SETDASA 等。这些 CCC 是标准化的,所有兼容设备都必须实现。我通常把 CCC 分成三类:

  • 广播命令:主发给所有从机,比如 ENTDAA、RSTDAA(重置动态地址)
  • 定向命令:主发给指定从机,比如 SETAASA(设置静态地址别名)、GETSTATUS
  • 私有命令:供应商自定义,不在标准 CCC 列表内

应用层里还有 I3C 的 IBI(带内中断)和 HDR(高数据速率)模式。IBI 允许从机在无时钟仲裁的情况下主动拉低 SDA 发起中断,这个机制在传感器场景下特别好用。

I3C 协议栈层次结构 物理层 (Physical Layer) SCL / SDA 总线、OD + PP 混合驱动、电平兼容 I2C / I3C 数据链路层 (Data Link Layer) START / STOP / Sr、ACK / NACK、字节帧、多主切换 地址分配层 (Address Assignment Layer) DAA / RSTDAA / SETAASA、UID 读取、动态地址协商 应用层 (Application Layer) CCC 命令、IBI、HDR 模式、私有协议 地址分配层是 I3C 区别于 I2C 的核心创新 它解耦了地址冲突与硬件绑定,让多主系统自发现、自配置

协议栈与地址分配的关系

你可能会问:为什么地址分配要单独拉一层?其实原因很简单——I3C 没有专用的地址线。传统 I2C 中,地址冲突是硬件级的:谁的地址线被拉低了,谁就赢。但 I3C 放弃了这种打法,它在数据链路层之上做了一个仲裁点,通过协议解决冲突。

我在项目里见过很多人把 I3C 的地址分配当成 I2C 那样硬编码,结果挂两个同型号传感器就触发冲突。嗯,这里要注意,I3C 的地址分配层是支持动态重分配的,你可以先用广播命令枚举所有从机,再根据 UID 依次分配 DA。这样即使两个从机的静态地址一样,上了总线也能正常工作。

经验之谈:做好地址分配层的关键是理解 DAA 的回退机制。当多个从机同时响应 ENTDAA 时,只有一个能成功,失败的会被要求等待下一轮广播。我早期做多主系统时,漏了这一步,导致总线上的从机永远只有 1/3 能拿到地址。

协议栈各层对地址分配的影响
协议层 与地址分配的关系 常见问题
物理层 推挽模式提供高速状态切换,支撑 ENTDAA 时序 OD 模式下速度不足导致 DAA 超时
数据链路层 帧结构承载地址命令(广播/定向) ACK/NACK 窗口不匹配导致地址分配失败
地址分配层 直接管理动态地址的申请、分配、释放 多主并发分配时的冲突检测不完善
应用层 通过 CCC 命令触发与检查地址分配状态 GETACCCRC 返回不匹配,说明地址写入时序有误

最后说一句,I3C 协议栈的设计思路,其实是一个“软件定义总线”的缩影。物理层拼命做减法(只留两根线),地址分配层则做加法(靠协议代替仲裁)。这种取舍听起来很美,但落地时对调试者的要求确实高了一个台阶。我个人觉得,搞懂协议栈,就是搞懂 I3C 的七寸。