动态地址分配(DAA)原理
老规矩,我们先说清楚一件事:I3C 总线上挂了一大堆设备,出厂时大家都处在一种叫「未分配」的状态。想干活?可以,但得先有个地址。这个地址不是写在芯片 PIN 脚上的,也不是你拿烙铁焊出来的,而是由主设备动态发下去的。
嗯,这就是动态地址分配(Dynamic Address Assignment, DAA)的由来。
我早期做 I2C 的时候,地址冲突简直是家常便饭。两个器件焊错了地址,总线就废了。所以第一次接触 I3C 的 DAA 机制时,我心里是窃喜的——终于不用再为了抢地址而改 PCB 了。
核心概念:DAA 是 I3C 主设备在启动阶段或热加入事件后,为从设备分配唯一 7-bit 动态地址的过程。每个从设备在获得动态地址之前,通常需要通过其 48-bit 的临时 ID(在地址分配时由从设备提供)来被识别。
为什么要搞 DAA?
说白了,I3C 想解决两个根问题:
- 地址冲突: 传统 I2C 从设备地址是硬编码的,多主环境下极易撞车。DAA 让主设备按需分配,保证每个从设备在某个时刻只看得到自己的专属地址。
- 热插拔管理: 系统已经在跑了,突然有一个新从设备加入总线。主设备可以通过 DAA 流程,不重启、不停机,就把它收入麾下。
我记得有一次在调试一个多摄像头模组项目时,总线上一共挂了 4 个相同型号的传感器。如果用 I2C,每个传感器的地址是固定的 0x6C,根本没法同时用。但换了 I3C 后,每个传感器通过 DAA 拿到了 0x1A、0x3B、0x5C、0x7D 这四个不同的地址。嗯,一次成功。
DAA 的完整流程
流程看起来步骤多,但其实很清晰。我习惯把它拆成三个阶段:
阶段一:广播 ENTDAA
主设备在总线上广播一个 ENTDAA(Enter Dynamic Address Assignment)命令。这个命令告诉所有还「没有地址」的从设备:准备排队拿号。
- 命令格式:
0x07 (7-bit)+RnW = 0。 - 只有状态为
DAA_Rdy(未分配地址且可以接受分配)的从设备才会响应。 - 所有已分配过地址的从设备保持沉默,不参与本次流程。
一个小坑: 主设备在发出 ENTDAA 后,必须等待一个固定的 tCAS 时间(一般 100ns 到 1μs 不等,视总线速度而定)。这个等待时间是为了让从设备准备好回应。我最早踩过这个坑——没等够时间就准备收数据,结果全是毛刺。
阶段二:从设备应答与数据传送
ENTDAA 广播后,所有未分配地址的从设备会同时尝试应答。但 I3C 的设计很巧妙——它让每个从设备在应答位上发送自己的 48-bit 临时 ID 和一个 8-bit 的 CRC。
你想想看,如果两个设备试图同时发送不同的数据,总线会不会乱?答案是会,但 I3C 利用总线仲裁机制解决了这个问题。
| 字段 | 位宽 | 描述 |
|---|---|---|
| 临时 ID | 48 bits | 每个 I3C 从设备出厂时烧录的唯一标识(类似 MAC 地址) |
| CRC | 8 bits | 覆盖临时 ID 的循环冗余校验,由从设备计算并发送 |
| 尾位 | 1 bit | 用于终止本次传送并释放总线 |
这个过程是这样的:所有意图参与 DAA 的从设备,在 SDA 线上逐位输出自己的临时 ID。如果某个位是低电平,而另一个设备输出的是高电平,那么低电平会「覆盖」高电平——这就是仲裁。仲裁输了的设备会立即退出,准备下一轮。
注意! 主设备在收临时 ID 的过程中同时也在做仲裁。如果主设备检测到总线上出现无效序列(比如两个从设备地址完全相同,但这种情况概率极低),它会发出 NACK 并退出 DAA,重新尝试。
阶段三:主设备分配地址
当主设备成功接收到一个完整的临时 ID 和 CRC 后,它需要做两件事:
- 选择一个 7-bit 动态地址(不能是保留地址,如 0x7E 等)。
- 发回该地址给从设备,作为确认。
从设备收到地址后,会发一个 ACK 来确认。主设备再发一个 STOP 条件,结束本次分配。然后重复以上三个阶段,直到所有从设备都拿到地址。
我一般会在代码里用一个循环来处理:
// 伪代码:I3C DAA 主设备侧流程
while (1) {
ret = send_ENTDAA();
if (ret != OK) break; // 没有更多从设备了
tmp_id = read_48bit_ID_with_arbitration();
crc = read_8bit_CRC();
if (crc_verify(tmp_id, crc) != PASS) {
// 数据错误,重试或报错
send_NACK();
continue;
}
dynamic_addr = allocate_free_addr();
send_address_and_stop(dynamic_addr);
// 等待从设备 ACK
if (receive_ACK() == TIMEOUT) {
// 从设备没回应?释放地址,重新尝试
free_addr(dynamic_addr);
}
}
嗯,代码很直白。关键在于仲裁那一步,你必须在每 bit 上都做正确。我在实际项目里用 FPGA 实现过这个逻辑,说实话最头疼的不是代码,而是时序的收敛。
地址分配后的处理策略
分配完了并不代表万事大吉。有时候设备会意外掉线,或者我们想更换某个从设备的动态地址。I3C 提供了两种处理手段:
重新分配(Reassignment)
主设备可以随时对某个已经拥有动态地址的从设备发出 RSTDAA 命令。这个命令会让该从设备回到未分配状态,然后主设备就可以重新给它 DAA。这个操作不会影响总线上其他设备。
地址丢弃(Discard)
主设备也可以强制让所有从设备放弃当前动态地址,回到出厂状态。这个命令叫 SETDASA 的变体:SETNEWDA。但这很少用,一般都是重启总线更省事。我个人更喜欢直接发 RSTDAA,针对性强。
流程图:DAA 整体脉络
我把核心的 DAA 流程画成一张图,你一看就懂了:
个人习惯: 我在设计 DAA 重试逻辑时,总会在循环里加一个超时计数器。如果连续 3 次 CRC 校验失败,我就认为总线有问题,直接中止 DAA 并上报错误。别硬撑,I3C 的总线状态有时候不是靠重试能解决的。
关于重试你必须知道的
实际场景中 DAA 不可能百分百一次成功。总结几个我踩过的雷:
- 仲裁失败时的从设备吞没:仲裁输了的从设备不会发 ACK,它只是默默等待下一次 ENTDAA。所以主设备必须保证重试间隔不要过长,否则从设备会认为主机已放弃而进入空闲态。我一般设 50μs 以内。
- CRC 错的根源:大部分 CRC 错来自 PCB 走线过长或噪声。如果你在实验室发现 DAA 频繁失败,先查示波器看 SDA 和 SCL 的边沿质量。嗯,十有八九是信号完整性问题。
- 地址范围的选择:I3C 协议动态地址可以用 0x00 ~ 0x7F 中除了保留地址外的所有值。我建议你避开 0x00(通用呼叫地址)和 0x7E(I3C 保留地址),这样能少很多麻烦。
| 常见问题 | 根因 | 建议措施 |
|---|---|---|
| ENTDAA 无响应 | 总线无设备或所有设备已分配 | 检查设备状态位,或先发 RSTDAA |
| CRC 持续失败 | 电气干扰 / 时钟拉伸问题 | 增加 tCAS 延迟 / 降低总线速率 |
| 地址冲突 | 主设备分配了相同地址到两个设备 | 实现地址使用记录表,分配前检查 |
| 分配一半设备掉线 | 热移除未检测 | 启用热连接检测(HJ)流程 |
曾记否? 我曾经在一个量产测试板上看到过 DAA 分配地址后,设备返回了 NAK。查了三天才发现是板子上的去耦电容焊错了,导致从设备电源瞬间跌落。DAA 过程中电源必须稳定——这个教训花了我一个星期的加班。