4. 分配过程中的超时问题
动态地址分配这块,超时是我见过的最高频故障。说白了,I3C 主机发出了 ENTDAA 命令,结果左等右等,就是收不到目标设备的 ACK,或者收到一半就断了。这时候控制器通常会报一个超时错误位,可具体原因在哪,你得自己挖。
个人习惯,遇到超时先别急着改代码。先分清楚是主机侧超时还是从机侧超时。主机侧超时多数是配置问题或总线拖拽;从机侧超时往往是设备初始化没完成,或内部状态机卡死了。
4.1 超时的常见根因
我整理了一份快速排查清单,项目里碰到的超时案例,十有八九能在这找到对应项。
| 故障类型 | 典型表现 | 最可能的原因 |
|---|---|---|
| 总线空闲超时 | ENTDAA 发出后无任何响应 | 目标设备没就绪、地址槽被占用、SCL/SDA 被拉死 |
| 响应超时 | 收到部分 bit 后停止响应 | 设备内部时钟未稳定、Tcas 窗口过短 |
| 广播超时 | 主机连续发 ENTDAA 都失败 | 总线电容过大、上拉电阻不对、SDR 速率太高 |
| 地址冲突超时 | 地址分配完成后总线异常 | 两台设备输出了相同地址,导致总线争用 |
一个小技巧: 我习惯先在 1MHz 的 SDR 速率下做地址分配。速率低了,时序裕量就大,能帮你排除掉绝大多数信号完整性问题。等设备都能分配到地址了,再慢慢提频。
4.2 主机侧排查:先看时序和配置
你想想看,主机发出广播地址 0x7E 之后,如果 SDA 在规定的 9 个 SCL 周期内没有被从机拉低,那就是典型的广播超时。
检查下面几个参数,我一般按这个顺序操作:
- ENTDAA 命令时序: 用示波器抓 START + 0x7E + RnW + ACK 段。确认 SCL 高电平时间满足 >= 要求的最小值。I3C 标准里 SCL 高电平时间不能低于 tHIGH_MIN(通常 40ns@12.5MHz)。
- Tcas 寄存器配置: 扫描目标设备支持的 Tcas 值。主机选择的 Tcas 过长或过短,都会导致从机在响应时超时。我在项目中碰到过,某款 sensor 的 Tcas 只能是 16ns,而主机默认配了 32ns,结果一直超时。
- 动态地址槽数量: 主机轮询的地址槽次数,必须 ≥ 总线上存在的设备数量。如果总线上挂了 6 个设备,主机只轮询 4 槽,后面两个就一直没法分配。
- PPB 与 payload 长度: 有些从机要求主机在 ENTDAA 之前先清除它们的 PPB 位。否则从机不会参与分配,主机就会超时。
关键判断: 如果主机发出的 ENTDAA 被从机 ACK 了,但在读设备 PID 的过程中超时,那问题大概率在从机侧。如果连 ACK 都没有,先查主机的 SCL 时钟和 SDA 上拉。
4.3 从机侧排查:设备内部状态与外部负载
从机不响应,最常见的原因是:
- 设备没完成上电初始化: 内部 PLL 没锁定、PMU 没稳定。我遇到过一款 Motion 传感器,从 VCC 上电到能够参与 ENTDAA 需要 20ms。如果主机 5ms 就发命令,它肯定装死。
- 内部状态机不在 I3C 模式: 有些设备支持 I2C 和 I3C 双模,上电后默认进入 I2C 模式,需要先接收到 Hot-Join 或 CCC 命令才能切换到 I3C 模式。如果主机跳过了这个步骤,设备对 ENTDAA 根本无动于衷。
- 总线负载太大: SCL/SDA 电容超过 50pF,会拉长信号上升沿。从机可能在 SCL 边沿采样时发生位错误,从而忽略 ENTDAA。我曾在一条 4 设备总线上,因为 FPC 线缆太长增加 30pF,导致分配超时。最后换了一根短接法才解决。
避坑提醒: 我曾经遇到过一批芯片,内部寄存器映射有 bug,地址分配时如果主机发出的 Tcas 不是它期望的那个精确值,就会直接不响应。后来我们只能遍历所有 Tcas 值才稳定。建议你固件里做个 retry 机制,Tcas 超时后换下一个值重试两三次。
4.4 用示波器定位超时点
示波器是排查超时最趁手的工具。我一般设置触发条件为 SCL 上的第九个时钟下降沿,抓 ACK 位。如果你看到:
- SCL 第九个时钟后 SDA 仍然是高电平 ➜ 从机没拉低(缺 ACK)
- SCL 提前停止了(比如只走了 7 个 clock)➜ 主机内部超时计数器提前溢出
- SDA 低电平宽度明显不足 ➜ 总线竞争或上拉太弱
下面这张流程图,总结了我自己的排查路线:
4.5 代码级超时参数调整
很多 SoC 的 I3C 控制器都有可配置的超时寄存器。我一般根据总线速率和最长设备响应时间来计算,然后留 50% 的余量。
// 示例:某款 MCU 的 ENTDAA 超时配置
#define I3C_TIMEOUT_DEFAULT (500) // 默认 500us
#define I3C_TIMEOUT_MAX (1000) // 安全上限 1000us
void i3c_set_entdaa_timeout(uint8_t bus_speed_mhz) {
uint32_t timeout_us;
// 2 个字节的 PID 响应,加 ACK/NACK,至少需要 18 个 SCL 周期
// 加上安全余量 50%
timeout_us = (18 * 1000000 / bus_speed_mhz) * 1.5;
if (timeout_us > I3C_TIMEOUT_MAX) timeout_us = I3C_TIMEOUT_MAX;
if (timeout_us < 10) timeout_us = 10;
HAL_I3C_SetTimeout(timeout_us);
printf("[I3C] ENTDAA timeout set to %lu us\n", timeout_us);
}
现场经验: 有一次我在调试某个供应商的 PMU 芯片,发现它必须在 ENTDAA 之前收到一个定制 CCC 命令来解除电源放电状态。把这条命令加在枚举流程前端后,超时再也没出现过。所以说,多看看那些供应商的 errata 文档,别只顾着写代码。