4. MDIO 读写时序:读操作时序、写操作时序、时序参数要求

好,咱们这一节开始啃 MDIO 的时序。

说实话,协议这个东西,光看文字描述可能会觉得有点虚。你想想看,时序图摆在那,高低电平来回跳,到底哪一段是地址,哪一段是数据,什么时候该采样,什么时候可以放松一点,我刚开始玩 PHY 的时候也被绕晕过。后来硬着头皮拿着示波器去抓波形,一个一个比特对照规格书,心里才踏实下来。

这一节,我就把读操作、写操作,还有那些容易踩坑的时序参数要求,掰开了跟你讲。

4.1 读操作时序:从发起请求到拿到数据

MDIO 的读操作不是一次就完成的,它分两个阶段:先写地址,后读数据。协议本身是利用同一个帧结构来承载的。

一个完整的读操作帧长 64 个时钟周期(这里指的是 MDC 时钟的上升沿),具体我们拆开来看:

  1. Preamble(前导码):32 个连续的 '1'。用来让 PHY 同步到 MDC 时钟上。我个人习惯用一段循环去发,别偷懒少发,有的老 PHY 对前导码数量有要求。
  2. Start(起始位):2 个比特 '01'。这是帧开始的标志。
  3. OP Code(操作码):2 个比特 '10'。表示这是一个读操作。
  4. PHYAD(PHY 地址):5 个比特。选择你要跟哪个 PHY 芯片通信。
  5. REGAD(寄存器地址):5 个比特。选择 PHY 内部的哪个寄存器。
  6. TA(转向时间):2 个比特 'Z0'。这里是个特殊的地方。第一个比特是高阻态(由 MAC 释放总线),第二个比特是 PHY 把数据线拉低,表示准备输出数据。
  7. Data(数据):16 个比特。PHY 在这个时间段内把寄存器的值放到总线上,由 MAC 端读取。

重点理解 TA 状态

TA 的第一个比特是总线控制权的交接点。MAC 在发完 REGAD 后,必须把 MDIO 输出置为高阻态,然后 PHY 会在下一个时钟周期把驱动权拿过来。如果 MAC 没有释放总线,数据线就会打架,你读回来的数据铁定是错的。我以前遇到过一次,查了半天才发现是 FPGA 代码里输出使能没拉低。

4.2 写操作时序:把数据配进寄存器

写操作相比读操作要简单一点,因为全程都是 MAC 在驱动总线,没有控制权交接的问题。它的帧结构也是 64 个时钟周期:

  1. Preamble(前导码):32 个连续的 '1'。跟读操作一样,不能少。
  2. Start(起始位):2 个比特 '01'。
  3. OP Code(操作码):2 个比特 '01'。注意,这里是 '01',不是 '10',这是区分读和写的关键。
  4. PHYAD(PHY 地址):5 个比特。
  5. REGAD(寄存器地址):5 个比特。
  6. TA(转向时间):2 个比特 '10'。写操作的 TA 没有高阻态,MAC 直接驱动为 '1' 和 '0'。
  7. Data(数据):16 个比特。MAC 把要写的数据发出去,PHY 在此时采样并更新内部寄存器。

一个小技巧:

写操作的时候,有些 PHY 会要求你在写完数据后,再发一个“空读”操作(读同一个地址),来确保数据已经被正确地 latch 进去了。我一般会在写操作后面加一个读操作做回读校验,用代码来验证写值。

4.3 时序参数要求

空谈时序没意义,我们必须看具体的数字。MDIO 标准(IEEE 802.3)给了一些推荐参数,但实际项目中,由于 PCB 走线、温度、电压的差异,你最好留点余量。

下面这个表格是我从多份 PHY 数据手册里整理出来的典型值,你可以收藏一下:

参数名称 符号 最小值 典型值 最大值 单位
MDC 时钟频率 fMDC - 2.5 15 MHz
MDC 高电平时间 tHIGH 30 200 - ns
MDC 低电平时间 tLOW 30 200 - ns
MDIO 数据建立时间(对 MDC 上升沿) tSU 10 - - ns
MDIO 数据保持时间(对 MDC 上升沿) tHOLD 10 - - ns

坑点一:时钟频率别跑满

有些 PHY 宣称支持 15 MHz MDC,但当你真跑到 15 MHz 的时候,如果 PCB 走线长了或者芯片温度高了,时序余量会变得很小。我曾经在一个产品上发现,跑到 12.5 MHz 以上时,偶尔会有寄存器读出来是 0xFFFF 的异常现象。后来降频到 8 MHz,问题就消失了。所以,除非你确认布线非常好,否则我建议把 MDC 控制在 2.5 MHz 到 10 MHz 之间。

坑点二:数据建立和保持时间

tSU 和 tHOLD 是相对 MDC 上升沿而言的。MAC 在 MDC 上升沿之前,必须把 MDIO 的数据稳定下来;PHY 在上升沿采样。对于读操作,PHY 会保证在 MDC 下降沿之后输出数据,然后在下一个上升沿之前数据已经稳定。所以,你的硬件设计一定要保证 MDC 和 MDIO 两根线在板级上的时延差尽量小。如果有条件,可以让 MDC 走线比 MDIO 短一些(或者用蛇形线让两者等长),减少 skew。

4.4 MDIO 读写时序流程图

下面这张图,我把读和写的帧结构放在一起对比,方便你直观理解:

MDIO 读写帧结构对比 读操作帧结构 (64 个时钟周期) Preamble (32 '1') OP '10' PHYAD REGAD TA Data (16 bit) Idle 写操作帧结构 (64 个时钟周期) Preamble (32 '1') OP '01' PHYAD REGAD TA Data (16 bit) Idle 关键时序参数说明 • MDC 时钟频率:建议控制在 2.5 MHz - 10 MHz 之间,同时满足 PHY 的 tHIGH 和 tLOW 要求 • 采样方式:MAC/PHY 均在 MDC 上升沿采样 MDIO 数据 • 输出方式:读操作 PHY 在 MDC 下降沿更新数据;写操作 MAC 在 MDC 下降沿更新数据

4.5 实际调试中的几个建议

嗯,理论讲完了,说点实战中我总结的想法:

  • 读操作一定要做超时处理:万一 PHY 挂死或者没有应答,你的 MDC 时钟不能一直卡在那。我习惯在代码里加一个计数器,如果 64 个时钟后还没收到有效的 TA 就判超时,然后重新发前导码去复位总线状态。
  • 写操作可以适当放宽时钟占空比:有些 PHY 对高电平时间要求不那么严,只要别低于 30 ns 就好。我一般让高电平和低电平各占一半,但在某些低功耗场景下,我会把高电平拉长一点(比如 40% 占空比),让 PHY 有更充裕的采样窗口。
  • 尽量避免连续背靠背操作:我遇到过一种情况,连续发读操作,帧之间的 Idle 位太少,PHY 的内部状态机没复位过来,导致第二帧的地址被当成数据解析了。所以,我通常在两次操作之间插入一个至少 2 个 MDC 周期的 Idle(让 MDIO 保持高电平),让总线回归空闲状态。

一个小工具:

如果你手头有逻辑分析仪,可以把 MDC 和 MDIO 两根线夹上去,然后用协议解析功能直接看。很多分析仪默认就支持 MDIO 协议解析,一眼就能看出 OP code 是不是 '10' 还是 '01',再也不用手动数比特了。