MDIO 帧结构基础:帧的构成要素
好,咱们接着聊 MDIO 的帧结构。说白了,帧就是 MDIO 总线上传输的一个完整数据包。我刚接触这个协议的时候,觉得它就是一个简单的串行读写,但后来在调试一款交换芯片时,就因为对帧结构理解不深,浪费了整整两天。嗯,咱们现在就把这七个部分拆开来看。
MDIO 帧,一共 64 位。你仔细看,是严格按照顺序排下来的。我习惯把它们分成三块:同步头(前导码 + 起始码)、地址与命令(操作码 + PHY 地址 + 寄存器地址)、数据与结尾(数据 + 空闲位)。这样记起来就轻松多了。
1. 前导码(Preamble):32 个连续的 "1"
前导码是帧的开头,长度 32 位。说白了,就是让 PHY 芯片准备好接收数据。为什么是 32 个 1?因为 PHY 内部需要这段时间来同步时钟沿,建立稳定的采样点。我在一个项目里遇到过,某个 PHY 芯片对前导码要求特别严格,少了 2 个 bit 直接不响应。嗯,所以千万别偷工减料。
波形特征: 32 个连续的 MDC 时钟周期,MDIO 线一直保持高电平。没有跳变,纯粹是“预热”信号。
2. 起始码(Start of Frame):2 位的 "01"
前导码之后,就是起始码。固定为二进制 "01"。你想想看,前导码是 32 个 1,突然来一个 0,这就是一个明显的下降沿。PHY 检测到这个下降沿,就知道“好,数据要开始了”。我习惯把它叫做“起跑线”。
有些工程师会问:为什么不直接用 32 个 1 后的第一个 0 作为起点?其实也可以,但协议规定必须显式地送出 "01" 这 2 位,让 PHY 有一个明确的帧起始识别窗口。这是一种设计上的鲁棒性考虑。
3. 操作码(Opcode):2 位的 "10" 或 "01"
起始码之后,就是告诉 PHY 你要干什么。操作码就 2 位:
| 操作类型 | 操作码 | 方向 |
|---|---|---|
| 读操作(Read) | 10 | MAC → PHY(请求);PHY → MAC(响应) |
| 写操作(Write) | 01 | MAC → PHY(一直驱动) |
这里有个坑,我曾经踩过。 你注意看,读操作的操作码是 "10",写操作是 "01"。正好反过来。我记得有一次调试,错把读操作的 "10" 写反成了 "01",结果 PHY 老老实实把数据写进去了,我还奇怪怎么没读到数据。所以写代码的时候,建议加个宏定义,比如 MDIO_OP_READ 0x2 和 MDIO_OP_WRITE 0x1,不容易搞混。
4. PHY 地址(PHY Address):5 位
接下来是 PHY 地址,占 5 位。也就是说,一条 MDIO 总线上最多可以挂 32 个 PHY(地址 0 到 31)。实际项目中,PHY 的地址通常通过硬件引脚(比如 AD0-AD4)来配置,上电后即可确定。
为什么要 5 位? 你可以算一下:2^5 = 32。很多交换芯片内部集成了多个 PHY,地址范围从 0 开始顺延。比如一个 4 口的交换机,PHY 地址可能是 0、1、2、3。我建议在设计原理图时,给每个 PHY 的地址引脚留出上拉/下拉电阻的位置,方便板级调试时修改。
5. 寄存器地址(Register Address):5 位
寄存器地址也是 5 位,因此每个 PHY 有 32 个寄存器(0-31)。不过 IEEE 802.3 规范只定义了其中 0-15 为基本寄存器,16-31 留给厂商自定义。
这里我要分享一个经验:很多 PHY 芯片的寄存器地址 0 是控制寄存器(Control Register),地址 1 是状态寄存器(Status Register)。这两个寄存器最常用,比如软复位、自协商控制、链接状态检测,都在这两个寄存器里。你可以先读这两个摸清 PHY 的“脾气”。
6. 数据(Data):16 位
数据段是帧的核心。无论是读还是写,数据都是 16 位。
- 写操作: MAC 在 MDIO 线上依次送出 16 位数据,高位在前。PHY 在每个 MDC 上升沿采样。
- 读操作: 这里有个重要的时序转换。MAC 送出地址和操作码后,需要释放总线(把 MDIO 设为高阻)。然后在第 32 个 MDC 时钟之后,PHY 接替控制 MDIO 线,开始发送 16 位数据。所以读操作帧的前半段由 MAC 驱动,后半段由 PHY 驱动。
避坑指南: 读操作时,MAC 释放 MDIO 到 PHY 接管之间,有一个时钟周期的“转向(turn-around)”,也就是空闲状态。如果你用 GPIO 模拟 MDIO,一定要在这个阶段把 GPIO 方向从输出改为输入,否则数据会被直接拉到地或者电源,造成总线冲突。我曾经就因为这个原因,读回来的数据全是 0x0000,排查了好久才发现是方向没切换。
7. 空闲位(Idle Bits):2 位
数据段之后,总线进入空闲状态。MDIO 线由 MAC 或 PHY 拉至高阻,配合一个上拉电阻把总线拉到高电平。空闲位至少 2 位,但实际上可以更长,取决于下一帧什么时候开始。
为什么要空闲位? 说白了,就是给总线一个“休息”时间。连续高速读写时,如果没有空闲位,PHY 可能来不及处理上一笔数据。我个人的习惯是,在连续写寄存器时,每帧之间至少插入 5-10 个 MDC 周期的空闲位,给 PHY 留足处理时间。虽然协议只要求 2 位,但多留一点不会错。
小结:一个完整的读帧流程
咱们拿一个实际例子串起来。假设你要读 PHY 地址 0x03 的寄存器 0x0A:
// MDIO 写帧示例(读请求阶段,MAC 驱动前 32 位)
位序列:先发高位
前导码: 11111111 11111111 11111111 11111111 (32位)
起始码: 01 (2位)
操作码: 10 (2位,读)
PHY地址: 00011 (5位,0x03)
寄存器: 01010 (5位,0x0A)
// 之后 MAC 释放总线,转为输入
// 等待转向时间,PHY 开始驱动
// PHY 回复数据,假设返回 0xABCD
数据: 10101011 11001101 (16位,0xABCD)
空闲位: Z Z (高阻)