3. I3C数据帧格式:起始条件、地址、读写位、数据字节、ACK/NACK

搞I3C的地址分配,你要是连帧格式都吃不准,那DMA描述符根本没法配。说白了,总线上的每一笔交易,都是靠这个帧结构来定义的。我刚开始接触I3C时,总觉得帧格式跟I2C差不多嘛,结果在地址分配环节踩了个大坑——后面再细说。

一个完整的I3C数据帧,包含以下7个要素:起始条件、7位地址、读写位、ACK、数据字节、ACK/NACK、停止条件。DMA要高效介入,就得在每个环节都清楚谁拉高谁拉低,什么时候触发传输完成。

核心记住一句话:I3C帧的每个字节后面都跟着一个应答位,DMA的计数逻辑必须把这个第9位算进去。我见过不少工程师配DMA时只配了8位,结果整个地址分配流程全乱套。

3.1 起始条件(START)

起始条件长什么样?SCL在高电平时,SDA从高切换到低。就这么一个下降沿,宣告一笔交易的开始。你想想看,总线原本是空闲的,SDA和SCL都被上拉电阻拉高,主机突然拉低SDA——所有从机都得竖起耳朵听。

I3C的起始条件和I2C基本一致,但有一个关键区别:I3C允许"重复起始"(Repeated START),也就是不发送停止条件就再发一次起始。在地址分配场景中,这个特性非常有用。我建议你在DMA描述符里把重复起始当作一个独立的传输段来处理,不要跟前面的数据混在一起。

时序参数方面,I3C要求START保持时间(tHD;STA)最小为0ns(是的,0!),这比I2C宽松多了。但SCL低电平时间有严格要求,一般是100ns到400ns不等,取决于总线模式。

调试小窍门:用逻辑分析仪抓START条件时,重点关注SDA下降沿到SCL下降沿的间隔。我在项目中遇到过,某个从机对START的建立时间特别敏感,差了20ns就死活不响应。调整DMA的时序配置后问题解决。

3.2 地址与读写位

起始条件之后,主机发送7位地址 + 1位读写位,总共8位,MSB优先。I3C的动态地址是7位的,范围从0x00到0x7F,但0x7E和0x7F被保留用作特殊用途。

地址值 用途 说明
0x00 – 0x7D 动态地址 主机通过CCC命令分配
0x7E 广播地址 所有从机必须应答
0x7F 虚拟目标地址 用于I3C到I2C的桥接

读写位跟在地址后面。0表示主机要写数据到从机,1表示主机要从从机读数据。在地址分配场景中,主机先发送广播地址0x7E + 写位,然后发送CCC命令码(比如ENTDAA),接着从机才会回应自己的地址。

我记得第一次调地址分配时,读写位搞反了。我以为主机要读从机的地址,配了个读操作,结果从机根本不搭理我。后来才意识到,地址分配时主机是先写CCC命令,从机再在数据阶段返回地址。这个流程必须用两次不同的传输段来完成。

3.3 数据字节传输

地址发完之后,就是数据字节。每个数据字节8位,同样MSB优先。主机或从机在SCL的驱动下,逐位把数据送到SDA线上。

这里有个重要的细节:数据字节的传输方向取决于之前的读写位。如果是写操作,主机驱动SDA;如果是读操作,从机驱动SDA。DMA设计时,必须根据方向切换数据流向控制。

// I3C数据帧的C结构体表示
typedef struct {
    uint8_t start_condition;    // 1: START, 0: no
    uint8_t address;            // 7-bit dynamic address
    uint8_t rw;                 // 0: write, 1: read
    uint8_t ack;                // ACK from receiver
    uint8_t data[MAX_DATA_LEN]; // data bytes
    uint16_t data_len;          // number of data bytes
    uint8_t ack_final;          // ACK/NACK for last byte
    uint8_t stop_condition;     // 1: STOP, 0: no
} i3c_frame_t;
注意:DMA在传输数据字节时,必须处理"时钟拉伸"(Clock Stretching)。I3C允许从机在数据字节之间拉低SCL来请求等待。如果DMA不监测SCL状态,可能会在从机还没准备好时就把数据发出去,导致总线崩溃。我在一个项目中就因为这个原因,地址分配的成功率只有60%。加了一个SCL轮询逻辑后,才稳定到100%。

3.4 ACK/NACK应答机制

每个字节(包括地址字节和数据字节)之后,接收方需要在第9个SCL时钟周期给出应答。SDA为低表示ACK,为高表示NACK。

在地址分配场景中,ACK/NACK有几个关键用途:

  • 地址阶段:从机收到自己的动态地址后,必须回复ACK。如果地址不匹配,从机必须保持SDA为高(NACK),让主机知道这个地址没人认领。
  • 数据阶段:接收方每收一个字节都要回复ACK。如果接收方想终止传输,可以在某个字节后回复NACK,主机收到NACK后通常会发起停止条件。
  • 地址分配的特殊流程:在ENTDAA过程中,从机在数据阶段返回自己的地址时,如果多个从机同时响应,会产生总线冲突。I3C通过"仲裁丢失"机制来处理——从机在发送地址时监听SDA,如果发现自己发送的位与总线状态不符,就退出竞争并回复NACK。

我曾经踩过一个坑:DMA在接收从机返回的地址时,把ACK位也当作数据收进来了。结果地址值直接错了一位。解决方案是在DMA描述符里把ACK位单独列出来,不参与数据校验。

避坑指南:DMA的传输计数器一定要配成"9位模式"——8位数据 + 1位ACK。很多DMA控制器默认是8位计数,你得手动改成9位。或者更常见的方法是,用两个DMA通道:一个管8位数据,一个管1位ACK,通过硬件触发串联起来。

3.5 完整帧格式与DMA协同

来,我们把所有东西串起来。一个完整的I3C数据传输帧,从START到STOP,长这样:

| START | 7-bit ADDR | R/W | ACK | 8-bit DATA | ACK | ... | 8-bit DATA | ACK/NACK | STOP |

在地址分配场景中,典型的帧序列是:

  1. 主机发送:START + 广播地址0x7E + 写位 + ACK
  2. 主机发送:CCC命令码(如ENTDAA 0x20)+ ACK
  3. 从机回复:从机发送自己的动态地址 + 主机回复ACK
  4. 主机发送:STOP

你看,三次独立的帧传输,才完成一次地址分配。DMA要做的就是在这三个帧之间无缝切换,不浪费CPU一个时钟周期。

我个人习惯把地址分配流程拆成3个DMA描述符链:

  • 描述符1:写广播地址 + CCC命令。方向:内存→I3C TX FIFO。长度:2字节(地址+命令)。
  • 描述符2:读从机地址。方向:I3C RX FIFO→内存。长度:1字节。注意这个描述符要等描述符1完成后自动触发。
  • 描述符3:发送STOP条件。没有数据,只配一个命令。

这样配下来,CPU只在三个描述符全部完成时收到一次中断,效率非常高。

关键指标:用链式DMA做一次地址分配,总线占用时间大约在5-10µs(取决于总线速度)。而纯软件轮询方式,同样的流程可能要50-100µs。在需要批量分配地址的场景(比如系统启动时给30个从机分配地址),DMA方案能节省近1.5ms的CPU时间。

下面这张图展示了一个完整I3C数据帧的结构,以及每个字段在地址分配中的角色:

I3C数据帧完整结构(地址分配场景) START 7位地址 R/W# ACK 数据字节 ACK/NACK STOP 1位 7位 1位 1位 8位 1位 1位 各字段在地址分配中的角色: • START:主机拉低SDA,宣告地址分配交易开始 • 7位地址 + R/W#:广播地址 0x7E + 写位(0),所有从机监听 • ACK:匹配的从机拉低SDA回应,表示"我在听" • 数据字节:CCC命令码(如 ENTDAA 0x20),从机返回自己的动态地址 • ACK/NACK:主机对从机地址的应答,NACK表示分配完成或出错

最后,用一个表格总结一下I3C帧格式的每个字段,方便你对照着配DMA描述符:

字段 位宽 驱动方 DMA注意事项
START 1位(时序) 主机 DMA不参与,由I3C控制器硬件生成
地址 7位 主机 放入TX描述符,与R/W位合并为一个字节
R/W# 1位 主机 决定DMA方向:0→TX,1→RX
ACK 1位 接收方 DMA计数必须包含此位,用9位模式
数据字节 8位 主机/从机 根据R/W#方向,放入TX或RX描述符
ACK/NACK 1位 接收方 NACK作为传输结束信号,触发DMA完成中断
STOP 1位(时序) 主机 由I3C控制器硬件生成,或通过命令描述符触发
再强调一遍:地址分配过程中,从机返回地址的阶段会用到"总线仲裁"。如果两个从机同时返回地址,它们的SDA驱动会冲突。胜出的从机继续发送,失败的从机退出。DMA必须能处理这种"部分传输"的情况——收到不完整的数据不报错,而是等下一个START重试。我的做法是在DMA完成回调里检查实际接收到的字节数,跟预期不符就重新调度一次地址分配。

搞清楚了帧格式,你才知道DMA描述符该怎么配、传输长度怎么算、方向怎么切。下一层我们要面对的是真正的地址分配协议——ENTDAA命令的完整流程。到时候你会发现,帧格式里每一个ACK/NACK都在关键时刻决定着分配的成败。