第2章 I3C总线物理层:电压域、时序要求、开漏与推挽工作模式

做I3C开发,第一关就是搞懂它的物理层。你想想看,不管上层协议多花哨,最终都得靠总线上的电平变化来传数据。电压不对、时序乱了、模式搞错了,地址分配根本跑不起来。我之前带过一个项目,就是因为电压域没匹配好,折腾了两天才找到原因。今天我把这几个核心概念掰开揉碎了讲清楚。

2.1 电压域

I3C的一个大优势,就是支持宽电压范围。不像某些总线死死卡在3.3V或1.8V,I3C可以在1.2V、1.8V、3.3V下正常工作。当然,这不是说同一时刻什么电压都行,而是说系统设计时可以根据器件选择最合适的电压域。

  • 1.2V:低功耗场景的最爱。物联网传感器、可穿戴设备里很常见。但要注意,这个电压下信号摆幅小,对噪声更敏感。
  • 1.8V:我个人最常用的电压域。功耗和抗噪性之间平衡得不错,很多主控和传感器都支持这个电平。
  • 3.3V:传统I2C器件常用的电压,兼容老设备时绕不开。如果你板子上既有3.3V的I2C从机又有1.8V的I3C从机,那就得加电平转换了。

关键点:I3C总线上的所有器件必须在同一个电压域下工作——或者说,它们的SCL/SDA引脚电平必须互相匹配。主控的电压域决定了总线电压。

我曾经踩过的一个坑:有次我手头有一个1.8V的I3C传感器和一个3.3V的老款I2C从机,想挂在同一条总线上。结果通信时断时续,最后发现是电平转换芯片的上升沿太慢,导致时序违例。后来换了一颗高速电平转换器才搞定。

2.2 时序要求

时序这东西,说白了就是「什么时候做什么事」。I3C的时序比I2C灵活不少,但核心参数你心里得有数。

参数 开漏模式 推挽模式 说明
SCL 最大频率 12.5 MHz 12.5 MHz 推挽模式下实际可达,开漏模式受上拉电阻限制
SCL 最小频率 无严格下限 无严格下限 可以极慢,甚至停在高电平
数据建立时间 (tSU;DAT) ≥ 3 ns ≥ 3 ns SCL高电平前数据必须稳定
数据保持时间 (tHD;DAT) ≥ 0 ns ≥ 0 ns SCL下降沿后数据保持
SCL 上升/下降时间 由RC决定,通常较慢 由驱动强度决定,快很多 推挽模式下上升沿陡峭,时序更容易满足

你可能会问:「这些时序参数我该怎么配?」答案是:看主控的数据手册,里面一般会有一个时序寄存器结构体。我习惯先把SCL目标频率定下来,再算tHIGH和tLOW,最后调数据窗口。

下面是我经常在初始化代码里用到的时序配置结构体——当然,不同芯片的寄存器名不一样,但思路是通用的:

/* I3C 时序配置示例 —— 目标SCL = 10 MHz, 推挽模式 */
typedef struct {
    uint8_t  scl_half_period;   /* SCL半周期, 单位: 10 ns */
    uint8_t  data_setup_time;   /* 数据建立时间, 单位: ns */
    uint8_t  data_hold_time;    /* 数据保持时间, 单位: ns */
    uint8_t  rise_time;         /* 预期上升时间, 单位: ns */
    uint8_t  fall_time;         /* 预期下降时间, 单位: ns */
} i3c_timing_t;

/* 10 MHz => 周期100 ns, 半周期50 ns */
i3c_timing_t timing_cfg = {
    .scl_half_period = 5,    /* 50 ns / 10 ns = 5 */
    .data_setup_time = 5,    /* 5 ns, 略大于最小值3 ns */
    .data_hold_time  = 2,    /* 2 ns, 大于0即可 */
    .rise_time       = 2,    /* 2 ns, 推挽模式很快 */
    .fall_time       = 2     /* 2 ns, 推挽模式很快 */
};

我的小技巧:实际调时序时,别只看理论值。我曾经用示波器抓过波形,发现PCB走线的寄生电容会把上升沿拖慢不少。如果你发现SCL波形「圆滚滚」的,那就把驱动强度调高一档,或者降低目标频率。

2.3 开漏与推挽工作模式

这是I3C物理层的灵魂所在。I2C只有开漏模式,所以速度上不去。I3C在大部分时间用推挽模式跑高速,需要的时候才切回开漏模式兼容老器件。

开漏模式

输出级只有下拉管,上拉靠外部电阻。SCL和SDA的高电平是被电阻「拖」上去的,所以上升沿慢。好处是多个器件可以「线与」——谁拉低谁说了算。仲裁和时钟拉伸都靠这个机制。

推挽模式

输出级既有上拉管又有下拉管,高低电平都靠管子主动驱动。上升沿非常陡,所以可以跑很高的频率。但「线与」能力没了——两个器件同时驱动总线,可能打架。因此推挽模式只在主控独占总线时使用,而且通信前必须确保没有仲裁冲突。

模式切换

I3C可以在两种模式之间动态切换。切换由主控发起,通过特定的总线序列来完成。我举个例子:主控想跟一个老I2C从机通信,就得先切回开漏模式,等传输完再切回推挽模式。

/* 模式切换伪代码 —— 从推挽切到开漏 */
void i3c_switch_to_open_drain(void) {
    /* 1. 确保总线空闲: SCL和SDA都是高电平 */
    /* 2. 发送「进入开漏模式」的特定序列 */
    /*    一般是主控拉低SDA超过 tLOW 时间 */
    /* 3. 等待从机响应ACK */
    /* 4. 之后所有数据都以开漏模式传输 */
    
    /* 实际实现依赖具体MCU的I3C控制器寄存器 */
    I3C_CTRL->MODE = I3C_MODE_OPEN_DRAIN;
    while (!(I3C_STAT & I3C_STAT_MODE_ACK));
}

void i3c_switch_to_push_pull(void) {
    /* 类似地,切回推挽模式 */
    I3C_CTRL->MODE = I3C_MODE_PUSH_PULL;
    while (!(I3C_STAT & I3C_STAT_MODE_ACK));
}

注意:模式切换不是随便就能做的。我遇到过一种情况:总线上有一个从机正在拉伸时钟,主控却强行切模式,结果总线直接卡死。正确的做法是先确认所有从机都释放了总线,再发起切换序列。

2.4 两种模式对比

对比项 开漏模式 推挽模式
输出驱动 仅下拉管,上拉靠电阻 上下拉管都有,主动驱动
上升沿速度 慢(RC决定) 快(管子驱动)
最大频率 受限于上升时间,通常 ≤ 1 MHz 可达 12.5 MHz
线与能力 支持(仲裁、时钟拉伸) 不支持(不能多驱动)
功耗 静态功耗低,动态功耗中等 动态功耗较高,尤其高频时
兼容I2C 完全兼容 不兼容(I2C器件无法理解)

择模式的原则——我是这么看的:跟老I2C器件通信、或者总线上可能有多个主控需要仲裁时,用开漏模式。跟高速I3C从机通信、且你确定总线只被一个主控控制时,果断用推挽模式。

I3C 总线物理层核心概念 I3C 总线物理层 电压域 时序要求 工作模式 1.2 V 1.8 V 3.3 V SCL 频率 建立时间 保持时间 开漏模式 推挽模式 动态切换 选择电压域 → 满足时序 → 切换工作模式 三个环节环环相扣,任何一个出问题,动态地址分配都跑不起来

上面这张图把本章的三个核心知识点串在了一起。你看,电压域决定了信号电平,时序要求决定了通信窗口,工作模式决定了驱动方式——三个环节环环相扣。任何一个地方出问题,动态地址分配都跑不起来。

一句话总结:先把电压域定好,再把时序配准,最后搞清什么时候该用开漏、什么时候该用推挽。I3C物理层的这点事,你就拿捏住了。