MDIO 总线协议详解:帧格式、读写操作指令、起始/停止条件

做嵌入式这些年,跟 MDIO 打交道的次数多得数不清。说实话,MDIO 协议不算复杂,但坑不少。我记得刚入行那会儿,第一次调 D-PHY 链路,光看 MDIO 的时序就把我折腾够呛。后来摸透了帧格式和指令细节,才真正体会到——MDIO 就是管理层的串行握手。

这一节,我把 MDIO 协议的细节拆开来讲。从帧结构到读写指令,再到起始/停止条件。我会结合自己踩过的坑,尽量让你一次搞明白。

2.1 MDIO 帧结构——到底长什么样?

MDIO 总线上每帧数据固定是 64 位(或者说 32 个 MDC 时钟周期,每个周期传输 2 位?别急,后面说)。
嗯,实际上 MDIO 是串行同步协议,每个 MDC 时钟沿采样 1 位数据。一帧共 64 个 MDC 周期,传输 64 位数据。这 64 位有固定的分段:

字段 位宽 描述
PREAMBLE 32 位 前导码,全 1,用于同步
ST (Start) 2 位 帧起始标志
OP (Operation) 2 位 读 / 写操作码
PHYAD 5 位 PHY 设备地址 (0~31)
DEVAD (REGAD) 5 位 寄存器地址 (0~31)
TA (Turn Around) 2 位 地址/数据切换周期
DATA 16 位 读回或写入的数据

记得:前导码的 32 个 1 不是摆设。我遇到过一些 PHY 芯片要求前导码严格 32 位,少了或者多了都不认。尤其某些国产 PHY,前导码的时序余量特别紧。我曾因为 MCU 的 MDC 时钟抖动导致前导码少了 1 位,结果怎么读都是 0xFFFF。查了三天才发现是前导码问题。

2.2 起始条件(ST)与停止条件

MDIO 的起始条件通过 ST 字段来标识。ST 固定为 2 位:

  • ST = 01:表示一帧新数据的起始(标准帧)
  • ST = 00:保留,不能用于数据帧(有些 PHY 用它标识管理帧,但非标准)

停止条件呢?MDIO 没有单独的总线释放信号。一帧结束后,MDIO 数据线变成高阻态(三态),由 PHY 或 MAC 的上下拉电阻维持高电平。下次传输前,主设备先发出前导码,再拉低数据线形成真正的“起始”。
说白了,MDIO 的停止就是帧结束后的空闲高电平。

避坑:我曾经在切换主从设备时,忘记让总线空闲至少一个 MDC 周期。结果连续帧之间没有足够的 Turn Around,导致下一帧的起始被误判。我的经验是:每帧结束后,主设备必须释放 MDIO 总线(输出高阻),然后等待至少 2 个 MDC 高电平周期再发起下一帧。

2.3 读写操作指令(OP 字段)

OP 码告诉 PHY 你要干嘛:

  • OP = 01:写操作(Write)
  • OP = 10:读操作(Read)
  • OP = 00 或 11:保留或用于地址扩展

写操作流程:主设备在 DATA 段输出 16 位数据,TA 段由主设备驱动 2 位(Z0),然后 PHY 在接下来的 DATA 段采样。
读操作流程:主设备在 TA 段释放总线(Z态),由 PHY 驱动 2 位(Z0),然后 PHY 在 DATA 段输出 16 位数据。

这里有个关键点——TA 段的第一个位是“Z”高阻,第二个位是“0”。为什么?我猜是为了确认从设备已准备好。实际上,很多 PHY 对 TA 段要求很严格:主设备必须在第一个 MDC 上升沿前释放总线,否则 PHY 不会输出数据。

个人习惯:我写 MDIO 驱动时,会在 TA 段插入一个空操作(NOP),确保总线切换有足够的余量。尤其当 MDC 频率超过 10MHz 时,信号反射和边沿抖动可能在 TA 段造成毛刺。我曾经在 25MHz 的 MDC 下做实验,发现 TA 段如果太短,PHY 会误将主设备的释放动作当成数据,导致读回的结果错位。

2.4 读写完整时序示例

下面是一个读寄存器的完整帧(假设 PHY 地址 0x1E,寄存器 0x0A):

MDC 时钟周期:  0..31  32-33 34-35 36-40 41-45 46-47 48-63
MDIO 内容:     32个1   01    10    11110  01010   Z0    (16位读数据)

写操作类似,只是 TA 段变成主设备驱动 Z0,然后 DATA 段由主设备输出数据。

2.5 用 SVG 梳理帧结构

下面这张图把 64 位 MDIO 帧的完整结构画了出来。我建议你仔细看节点间的衔接——尤其 TA 段和 DATA 段的方向变化。

MDIO 帧结构示意图 MDIO 帧结构(64 个 MDC 周期) PREAMBLE (32位) ST (2位) OP (2位) PHYAD (5位) DEVAD (5位) TA (2位) DATA (16位) 关键说明: • 读写操作由 OP 决定(01=写, 10=读),TA 段切换数据方向。 • 读操作:主设备在 TA 段释放总线,PHY 输出 DATA;写操作:主设备一直占用总线。 • 前导码的 32 个 1 用于同步,不能省略。

2.6 常见问题与我的调试建议

写到这里,回忆一下我以前在 MDIO 上栽过的跟头:

  • 前导码长度不对:有的 PHY 对前导码要求 32 位,有的能识别更少。保险做法:固定 32 位。
  • TA 段方向切换失败:主设备释放总线后,PHY 可能延迟 1~2 个 MDC 才接管。我的建议:TA 段的 Z0 之后,再插入一个 dummy 时钟让总线稳定。
  • 地址匹配问题:PHYAD 和 DEVAD 的 bit 顺序?MDIO 采用 MSB 优先(高位在前)。比如地址 0x1E 的二进制是 11110,发送时先发 MSB(1)。我曾经搞反过,结果一直读不到数据。
  • 时钟频率过高:MDC 理论上最高 2.5MHz(802.3 规范),但现代 PHY 通常支持更高。不过我建议不要超过 12.5MHz,否则从机可能采样不稳。

经验分享:调试 MDIO 时,一定要用逻辑分析仪或示波器抓 MDC 和 MDIO 两根线。我曾经仅靠打印日志调试,完全看不出问题。后来发现是 MCU 的 GPIO 配置成开漏输出,上升沿太慢导致采样错位。嗯,从那以后,我调任何串行协议都先看波形。

2.7 本章小结

MDIO 协议其实不复杂——64 位一帧,分 7 个段。只要抓住起始标志(ST=01)、操作码(OP)、地址段、TA 切换、数据段这五个核心,基本就掌握了。实际项目中遇到的大部分问题都在 TA 段和时钟匹配上。记住:调试先看波形,不要靠猜。