3. MDIO 读写时序分析

MDIO 总线虽然只有两根线——MDC(时钟)和 MDIO(数据),但时序细节稍不留神就会踩坑。我个人习惯在调 D-PHY 寄存器之前,先把时序图刻在脑子里,就像记 GPIO 模拟 I²C 那样,但 MDIO 比 I²C 更"慷慨"——它给足了建立保持时间,却也埋了一个小小的陷阱。

3.1 读操作波形

读操作的标准帧格式长这样:32 位 IDLE + 32 位 PREAMBLE + 2 位 ST + 2 位 OP + 5 位 PHY ADDR + 5 位 REG ADDR + 2 位 TA + 16 位 DATA。听起来一串,其实拆开看很简单。

前 32 个 MDC 周期,MDIO 线保持高阻(高电平)。这叫 IDLE。紧接着 32 个周期发连续 1(全 1),叫 PREAMBLE。然后 ST = 01,OP = 10(读)。之后 PHY 地址 5 位,寄存器地址 5 位。再是 2 个 TA 周期——第 1 个 TA 周期 MDIO 线变高阻(三态),第 2 个 TA 周期从机开始驱动数据。最后 16 个周期从机把寄存器数据放到 MDIO 上。

这里有一个隐藏问题:很多年轻工程师不知道 TA 周期实际是两个独立的时钟边沿。我曾经见过一个案例,有人用 GPIO 模拟 MDIO,在 TA 期间没有将 IO 切换为输入,导致总线冲突,读回来的值全为 0xFF。排查了两天才发现,调回来之后从此以后在 TA 开始前我都会加一个 GPIO 方向切换。

读操作关键时序窗口:

  • MDC 上升沿采样数据,MDIO 在 MDC 下降沿变化(对主机而言)
  • TA 第一个周期:主机释放 MDIO(三态),从机在第 2 个周期开始驱动
  • 从机输出的数据在 MDC 上升沿被主机采样,建立时间 ≥ 10 ns,保持时间 ≥ 10 ns(典型值)

3.2 写操作波形

写操作跟读操作的前半段几乎一样除了 OP 码变成 01。你们看:ST=01, OP=01, PHY ADDR, REG ADDR, 然后 TA 变成 2 个周期——但注意!写操作的 TA 两个周期中主机一直驱动 MDIO 线,输出 10(Z 和 0)。为什么不一样?因为写操作不需要让从机驱动数据,所以 TA 期间主机保持控制,发一个 Z(高阻)和一个 0。这里的 Z 是向总线插入高阻,让从机知道这是写操作的 TA,而不是数据。

然后主机继续驱动 16 位写入数据。整个写操作中主机从未释放 MDIO(除了 TA 第一个周期故意放高阻,但那个是协议规定的)。

字段长度读操作值写操作值备注
PREAMBLE3232 个 132 个 1有些 PHY 允许短前导码,但规范要求完整
ST20101起始码
OP21001操作码
PHY ADDR50x00~0x1F0x00~0x1FD-PHY 的地址通常在 0x00~0x04
REG ADDR50x00~0x1F0x00~0x1F寄存器地址
TA2Z, 0(从机驱动)Z, 0(主机驱动)关键差异
DATA16读回值写入值MSB 优先

嗯,写操作容易犯的错误是 TA 阶段忘记把 MDIO 置为 Z。我调过一款 FPGA 驱动 D-PHY,当时写操作总是失败,示波器抓波形看到 TA 阶段一直输出高电平,导致从机误判成非法序列。把这个 Z 加上去就对了。

3.3 最小周期

MDIO 总线的时钟频率由 MDC 决定,标准最高 2.5 MHz(周期 400 ns)。但很多 PHY 支持 10 MHz 甚至更高。我的建议是:除非你明确知道 D-PHY 器件手册允许,否则别超过 2.5 MHz。为什么?

  • MDIO 是电平驱动,没有上拉电阻快速上升的保证,频率越高信号边沿越差
  • 电缆或 PCB 走线长度影响反射,高速下容易误采样
  • D-PHY 的 MDIO 输入通常有施密特触发器,但建立保持时间在 2.5 MHz 下很宽松

我曾经在一个项目上,为了压榨性能把 MDC 调到 10 MHz,结果在 20 cm 排线上读回的数据每 100 次就错 1 bit。降回 2.5 MHz 后稳定运行。你说它不兼容吗?不,手册写的是支持 10 MHz,但实际工作环境干扰多了就容易翻车。所以很多时候保守一点能省很多排查时间。

时序参数速查(典型值):

MDC 最小高电平时间160 ns
MDC 最小低电平时间160 ns
MDC 周期最小值(2.5 MHz)400 ns
MDIO 建立时间(相对于 MDC 上升沿)≥ 10 ns
MDIO 保持时间(相对于 MDC 上升沿)≥ 10 ns
MDIO 输出延迟(从 MDC 下降沿到数据变化)≤ 300 ns

3.4 空闲状态

当没有 MDIO 操作时,总线处于空闲状态。MDC 可以停止(保持高或低都可以,但建议保持低以降低功耗),MDIO 必须被拉高到 VDD(通过上拉电阻)。为什么是高电平?因为 MDIO 总线是开漏/三态结构,空闲时所有设备都不驱动,靠上拉电阻把总线置为逻辑 1。这样才能在下一个操作开始前让 PREAMBLE 的连续 1 被正确识别。

有些同学会问:MDC 能一直跑吗?可以,但通常建议停止以减少 EMI。不过停止期间 MDIO 要保持高电平。如果你的 MCU 或 FPGA 内部有弱上拉,记得确认上拉电阻值不能太大(通常 1.5 kΩ~10 kΩ 之间),否则信号上升沿会变慢,影响时序。

重要: 空闲时 MDIO 不能是低电平或浮空。如果总线长期处于低电平,后续的 PREAMBLE 序列(32 个 1)会丢失前几个 1,导致帧同步失败。我曾经在调试时遇到过硬件上 MDIO 漏了上拉电阻,示波器看到恒低,查了半天才发现。

3.5 时序概览:一张图看懂

下面这张 SVG 图把读操作和写操作的关键帧结构放在一起对比。注意读操作的 TA 期间从机驱动(虚线代表主机释放总线),写操作的 TA 期间主机全程控制。颜色标识也让逻辑更清晰。

MDIO 读写时序概览 MDC … 32+ … ST OP PHY ADDR REG ADDR TA TA DATA (16 bit) 读操作 TA: 主机释放 → 从机驱动数据 OP = 10, 从机在 TA2 开始输出 16 位数据 写操作 TA: 主机一直控制 → 输出 Z 和 0 OP = 01, 主机在 TA 后发送 16 位写入值 空闲状态:MDIO 高电平 最小 MDC 周期:400 ns 操作对比 读操作 写操作 ST = 01 是公共起始码 空闲时 MDIO=1 注:TA 前一周期主机释放总线(Z),第二周期根据读写决定驱动方

好了,这张图把读写的帧结构和空闲状态都串起来了。你可能会发现 TA 阶段用的是红色,数据段是绿色,前导字段是蓝色——这种配色我用了好几年,一眼就能区分不同的职责。

3.6 实战提示

最后补充几个我在调试中总结的要点:

  • 看波形不要只盯着 MDIO 电平: 把 MDC 和 MDIO 同时抓,标注每个上升沿对应的值。我会用逻辑分析仪解码 MDIO,但写 FPGA 时习惯自己抓原始波形,因为解码器可能隐藏毛刺。
  • 上电后先发一次读操作验证空闲电平: 有些设计中 MDIO 的上拉电阻没焊或者虚焊,导致空闲状态异常。先读一个已知寄存器,如果返回全 1 可能是总线悬空,返回全 0 可能是总线被拉低。
  • 模拟 MDIO 时注意时钟极性和相位: 虽然 MDIO 规范规定数据在 MDC 下降沿变化,上升沿采样,但有些 FPGA 的 IOB 输出延迟会造成边沿偏移。我一般在 MDC 下降沿之前 10 ns 就更新 MDIO,留足余量。

我的个人调试步骤:

  1. 用示波器探头夹到 MDC 和 MDIO,触发在 MDC 上升沿
  2. 发送一个读操作(地址设为 0x00,多数 PHY 支持读 ID 寄存器)
  3. 测量 MDC 周期、高/低电平宽度、MDIO 建立/保持时间
  4. 如果读回全 F,检查 MDIO 空闲电平;如果全 0,检查 TA 释放时机

说白了,时序这东西,光看文档没用,必须上手抓波形。MDIO 不像高速串行协议那么苛刻,但它的“太简单”反而容易让人麻痹。当你把一个读操作波形完整抓下来,并且确认每一拍都符合预期的时候,再往下写 D-PHY 的配置,心里就有底了。