4. MDIO 读操作时序详解归纳总结

MDIO 读操作,说穿了就是主设备(比如 CPU 或 MCU)通过两根线从 PHY 的寄存器里把数据“拉”出来。时序对了,数据就稳;时序错一点,读回来的值可能全是错的。我早年调一块板子时,就因为读时序里的空闲周期没留够,结果 PHY 的状态机一直没复位,读啥都是 0xFF……后来查了好久才找到原因。

4.1 帧结构速览

标准的 MDIO 读帧一共 64 个时钟周期,每个周期在 MDC 上升沿传输一位。咱们直接看结构:

字段位宽值说明
PREAMBLE32全 1前导码,让 PHY 同步
START201帧起始,不是 10!
OP210读操作码(写是 01)
PHYAD50–31目标 PHY 地址
REGAD50–31目标寄存器地址
TA2Z0转向位:第一拍高阻,第二拍 PHY 驱动 0
DATA16读回值PHY 在 MDC 上升沿输出数据
IDLE≥1高阻总线释放,准备下一帧

关键点:TA 位的第二拍必须为 0,这是 PHY 在告诉主控“我准备好输出数据了”。如果你读到 TA 的第二拍不是 0,说明 PHY 根本没响应——地址可能写错了,或者 PHY 没上电。

4.2 读操作 vs 写操作的核心差异

很多人觉得读写差不多,其实就两个地方不一样:

  • 操作码不同:读是 10,写是 01。千万别搞反。
  • 数据方向不同:写操作时主设备全程驱动 MDIO;读操作在 TA 位之后,主设备必须释放 MDIO(高阻),由 PHY 驱动数据线。很多新手在这块翻车——主设备没释放,两个输出打架,数据全乱。

我曾经踩过的坑:读帧的 TA 第一拍,主设备要主动释放总线,变成高阻输入。如果你用的 GPIO 模拟,一定要先把 GPIO 方向设为输入,等 2 个时钟后再切回输出。我有一版代码忘了切方向,结果 PHY 拉低 MDIO 时,主设备还在使劲输出 1,整个波形都扭曲了。

4.3 时序参数与容差

MDC 的最高频率标准是 2.5 MHz(有些 PHY 支持 12.5 MHz 快速模式),但实际项目中我一般跑 1–2 MHz,留足余量。几个关键时间:

参数最小值说明
MDC 周期400 ns2.5 MHz 对应周期
MDC 高/低电平宽度160 ns占空比接近 50%
MDIO 数据建立时间10 ns相对 MDC 上升沿
MDIO 数据保持时间10 ns相对 MDC 上升沿
帧间空闲时间≥1 MDC 周期实际我习惯留 2–3 个周期

为什么强调空闲时间?因为 PHY 内部状态机需要时间回到 IDLE。如果你连续发帧,空闲时间太短,PHY 会把下一帧的前导码当成上一帧的尾巴——我遇到过这种情况,读回来的数据总差一位。加两个空闲周期就解决了。

4.4 读时序步骤拆解

你自己实现读操作时,可以照着下面这几步走:

  1. 拉高 MDC,释放 MDIO(高阻),准备开始。
  2. 输出 32 个 1:前导码,每个 1 在 MDC 上升沿之前准备好。
  3. 输出 START(01):注意是 0 然后 1,不是 10。
  4. 输出 OP(10):告诉 PHY 接下来是读。
  5. 输出 PHYAD(5 位):高位在前,比如地址 0x03 就输出 00011。
  6. 输出 REGAD(5 位):要读的寄存器地址。
  7. 进入 TA 阶段:第一拍将 MDIO 设为输入(高阻);第二拍读取 MDIO 引脚,检查是否为 0。如果为 0,说明 PHY 已响应;否则报错。
  8. 读取 16 位数据:在每个 MDC 上升沿前 10 ns 采样 MDIO 引脚,从高位到低位。
  9. 释放总线:数据读完立刻把 MDIO 设为输出高阻,再走至少 1 个空闲时钟。

嗯,有一个小技巧:采样数据时最好在 MDC 上升沿之后等 10–20 ns 再读 GPIO,因为 PHY 的输出可能会有一些延迟。如果你用 FPGA 实现,考虑用下降沿采样——这样数据在上升沿输出,下降沿采样,刚好错开半个周期,容错性好很多。我在项目里一直这么干。

4.5 读操作全流程图

下面这张图把整个帧结构串起来了。你注意看 TA 位那里,主设备方向的变化——之前是输出,之后是输入。这是读操作最精华的地方。

MDIO 读操作帧结构(64 时钟周期) PREAMBLE (32个1) START 01 OP 10 PHYAD 5位 (0-31) REGAD 5位 (0-31) TA Z0 DATA (16位) 由 PHY 驱动 IDLE 主设备驱动 MDIO(输出模式) 主设备释放 MDIO(输入模式),PHY 驱动 DATA 和 TA 第二拍 时序要点 TA 第一拍必须释放总线;采样数据推荐在 MDC 下降沿(或上升沿后 10–20 ns)

4.6 避坑指南

  • 前导码不能少:虽然有些 PHY 支持“无前导码模式”,但默认大部分都要求 32 个 1。我建议永远老老实实发全,省得兼容性问题。
  • TA 位一定要读取确认:如果 TA 第二拍不是 0,立即停止后续操作,检查 PHY 地址和电源。强行读数据只会得到垃圾。
  • 空闲时间别吝啬:至少 1 个时钟,但我的习惯是 2–3 个时钟。连续读不同寄存器时,中间加一点空闲,PHY 状态机更稳定。
  • 注意 MDIO 上拉电阻:标准要求 MDIO 有 1.5kΩ 上拉到 1.8V/2.5V/3.3V。如果你用 FPGA 直连,记得外部加电阻。没上拉的话,高阻状态电平会飘,TA 第一拍就可能读到错误值。

曾经有个教训:我在一块 4 层板上布 MDIO 和 MDC 时,两根线走了差不多 8 cm,还过了一个连接器。结果信号反射严重,读数据总是丢低位。后来在终端加了个 33Ω 串联电阻,并减小驱动强度,问题才消失。高频信号设计,走线越短越好,实在长了就要做匹配。

读操作是整个 MDIO 通信里最常用的操作。你只要把帧结构记牢,TA 位的方向切换处理好,再注意一点空闲和采样时机,基本上就不会出大问题。嗯,多看几次示波器波形,心里就有底了。