MDIO 读写协议:帧格式、操作码、地址

MDIO 这个协议,说白了就是 MAC 层和 PHY 芯片之间的一条“私聊通道”。你给它一条指令,它告诉你 PHY 当前的状态。这条指令长什么样?我带你拆开看看。

帧格式长什么样?

MDIO 的每一帧数据,都严格按照 64 个时钟周期来走。你想想看,64 个时钟就是一帧,不多不少。我最早调 MDIO 的时候,总以为少几个周期没关系,结果读回来的数据全是 0xff——嗯,那是我踩的第一个坑。

这 64 个时钟被分成下面这些字段:

字段 位数 描述
Preamble(前导码) 32 连续 32 个“1”,用来同步时钟
Start(起始码) 2 固定 “01”,表示帧开始
Opcode(操作码) 2 决定是读还是写
PHY Address 5 指定你要找哪个 PHY
Register Address 5 指定 PHY 内部的哪个寄存器
Turnaround(转向位) 2 总线切换的缓冲时间
Data(数据) 16 读回来的数据,或要写进去的数据
Idle(空闲位) — 高阻态,总线释放
我的习惯:前导码 32 个“1”不是摆设。有些 PHY 对这个前导时序很敏感,少一个“1”它就不认。我建议在代码里用循环严格产生 32 个时钟的高电平,别偷懒。

操作码:读还是写?

操作码就两位,但这两位的意义重大。它决定了你是要问 PHY 要数据,还是给 PHY 发指令。

Opcode 功能 说明
00 写操作 MAC 向 PHY 写入 16 位数据
01 保留 标准里没定义,不要用
10 读操作 PHY 向 MAC 返回 16 位数据
11 保留 同上

你看,读操作是 “10”,写操作是 “00”。这里有个细节,读和写的操作码故意留了位差异,就是为了防止误判。我在一个项目里就遇到过 MAC 层发错了操作码,把读写成写,结果把整个 PHY 的寄存器写乱了——设备直接断网。从那以后,我每次发帧前都会先打印操作码看一眼。

我曾经踩过的坑:有个厂家手册把操作码的顺序写反了。他们标的“读”其实是写。如果你发现读回来的数据和预期差很远,先怀疑操作码的顺序,别一股脑去查硬件。

地址:怎么找到目标 PHY?

地址分两层:PHY 地址和寄存器地址。5 位 PHY 地址,最多可以挂 32 个 PHY 设备。5 位寄存器地址,每个 PHY 内部有 32 个寄存器。

地址类型 位宽 范围 说明
PHY Address 5 位 0x00 – 0x1F 由硬件管脚决定,不能动态改
Register Address 5 位 0x00 – 0x1F 标准前 16 个有定义,后 16 个厂家自定义

PHY 地址通常由 PHY 芯片的 ADDR 管脚上的上下拉电阻决定。你想想看,如果板子上有 5 个 PHY,它们的地址分别是 0x01、0x02、0x04、0x08、0x10,这其实就是靠管脚的二进制编码实现的。我最早做这个实验时犯过一个很初级的错误——我把两个 PHY 的地址都设成了 0x01,结果读到的一直是 0xffff。

核心:一个完整的 MDIO 帧,通过“操作码 + PHY 地址 + 寄存器地址”这三个要素,就能唯一确定你要对哪个 PHY 的哪个寄存器做什么操作。说白了,就是“告诉总线:我想读/写 第 X 个 PHY 的第 Y 个寄存器”。

寄存器地址 0x00 到 0x0F 是 IEEE 802.3 标准规定的,所有 PHY 必须实现。比如 0x00 是控制寄存器(BMCR)、0x01 是状态寄存器(BMSR)。后面的 0x10 到 0x1F 则由 PHY 厂家自己定义,用来存放扩展能力或诊断信息。我记得见过一个 PHY,它的厂家自定义寄存器里存了温度值,简直离谱。

MDIO 帧格式图解 Preamble Start Opcode PHY Addr Reg Addr TA Data Idle 32 位 2 位 2 位 5 位 5 位 2 位 16 位 — 读操作时,TA=Z0;写操作时,TA=10。 空闲位强制高阻,释放总线控制权。 MDIO 帧传输方向 MAC → PHY(写)或 MAC → PHY → MAC(读)

你记住这个 64 时钟的帧结构,后面所有的 PHY_ID 读取都要靠它来发。我个人习惯是把帧的每个字段都定义成一个宏,调试的时候逐段检查,从 preamble 到 data,哪里断了都能快速定位。