3. MDC MDIO 时序规范

时序规范这东西,很多人觉得背下来就行了。我以前也这么想,直到第一次在板子上看到 MDC 上升沿过缓,导致从机设备反复读错寄存器——才真正明白,纸上参数和示波器光标之间的差距,就是排查故障要死磕的地方。

这一章我把时钟频率、上升/下降时间、建立保持时间和帧格式揉在一起讲。你别当成分开的四个知识点,它们是一根信号完整性的绳子,哪一处松了,设备间通信就会出乱子。

3.1 时钟频率:最大 2.5 MHz

MDC 时钟频率规范明确写着最大 2.5 MHz。为什么是这个数?因为 MDIO 是半双工双向协议,从机设备需要在半个时钟周期内完成数据采样和驱动切换。频率再高,从机的时序余量就没了。

我调试过的 PHY 芯片里,有些声称支持 5 MHz,但一旦加上 PCB 走线寄生电容和温度漂移,实际可用的频率往往只有 1.5~2 MHz。所以我的习惯是:除非必须用高速轮询,否则尽量把频率降到 1 MHz 左右。稳定第一,速度往后排。

我踩过的坑: 曾经为了加快链路检测速度,把时钟设到 2.5 MHz,结果 3 片 PHY 里有 2 片间歇性无响应。降回 1.8 MHz 后问题消失。所以标称最大 2.5 MHz 不代表你必须跑到那个值。

3.2 上升/下降时间

规范要求 MDC 和 MDIO 信号的上升/下降时间在 10 ns 到 100 ns 之间(10%~90%)。太慢会让信号处于阈值区的时间过长,接收端可能误判逻辑电平;太快会产生过冲和振铃,同样影响采样窗口。

你想想看,如果你的总线长度超过 10 cm,又没有串联电阻做阻抗匹配,上升沿很可能变得像斜坡一样。我见过一块板子,MDC 上升时间超过 200 ns,示波器上直接看不到陡峭的边沿——那怎么调驱动强度?

下面这张时序图把主要参数都标出来了,你可以拿它当调试时的对照模板。

MDC/MDIO 关键时序参数示意 MDC T_period (≥ 400 ns for 2.5 MHz) Tr (10~100 ns) Tf (10~100 ns) MDIO Tsetup (≥ 10 ns) Thold (≥ 10 ns) 注意:MDIO 数据在 MDC 下降沿被主设备更新,从设备在上升沿采样。建立/保持时间以 MDC 上升沿为参考。

3.3 建立保持时间

建立时间是指 MDIO 数据必须在 MDC 上升沿到来前稳定,保持时间是上升沿之后数据需维持稳定的时长。规范规定两者都至少 ≥ 10 ns。

在实际项目中容易出问题的是保持时间。很多 MCU 的 MDIO 输出延迟很大,上升沿刚过数据就变了,造成从机采样到毛刺。我用逻辑分析仪抓过这样的案例:主机侧的保持时间只有 3 ns,但 PHY 要求至少 8 ns,结果每 3 个帧就错一次。

参数最小值典型值最大值单位
MDC 时钟周期400500—ns
MDC 频率—2.02.5MHz
上升/下降时间 (10%–90%)1030100ns
建立时间 (Tsetup)1020—ns
保持时间 (Thold)1015—ns

我曾经踩过的坑: 在 FPGA 里用状态机生成 MDC 时,没有考虑组合逻辑的延时。示波器上看 MDIO 输出变化正好踩在时钟上升沿上,建保持时间全为负。后来加了一级寄存器打拍,设好输出延迟约束才搞定。

3.4 协议帧格式

MDIO 帧分读帧和写帧,长度固定为 64 个 MDC 周期(不包括空闲状态)。格式如下:

  • Preamble(前导码):32 个连续的 '1',用于同步从机。别小看这 32 个 1,很多 PHY 对前导码的边沿质量很敏感,如果 MDC 抖动过大,前导码检测会失败。
  • Start of Frame(帧起始):固定 2'b01,标志帧开始。
  • Opcode(操作码):读为 2'b10,写为 2'b01。
  • PHY Address(设备地址):5 位,最常用的是 0x00–0x1F。
  • Register Address(寄存器地址):5 位。
  • Turnaround(转向周期):2 位。读帧时主机释放 MDIO(高阻),从机在第二个周期驱动数据;写帧时主机继续驱动。
  • Data(数据):16 位,MSB first。
  • Idle(空闲):MDIO 被驱动为高阻,由外部上拉拉高。

下面用一段伪代码来展示一个写寄存器的流程。实际调试时可以拿着这段逻辑去和示波器波形对比。

// MDIO 写帧示例 (PHY addr=0x03, reg=0x0E, data=0xA5B6)
void mdio_write(uint8_t phy_addr, uint8_t reg_addr, uint16_t data) {
    // 1. 前导码: 32 个 1,每个 MDC 周期送 1 位
    for (int i = 0; i < 32; i++) {
        set_mdio(1);
        toggle_mdc();
    }
    // 2. 帧起始: 2'b01
    set_mdio(0); toggle_mdc();
    set_mdio(1); toggle_mdc();
    // 3. Opcode: 写=2'b01
    set_mdio(0); toggle_mdc();
    set_mdio(1); toggle_mdc();
    // 4. PHY 地址: 0x03 (5 位)
    send_bits(phy_addr, 5);
    // 5. 寄存器地址: 0x0E (5 位)
    send_bits(reg_addr, 5);
    // 6. Turnaround: 2'b10 (写帧主机驱动)
    set_mdio(1); toggle_mdc();
    set_mdio(0); toggle_mdc();
    // 7. 数据: 0xA5B6 (16 位)
    send_bits(data, 16);
    // 8. 空闲: MDIO 置高阻
    set_mdio_highz();
}

3.5 关键参数汇总

为了方便排查时快速对照,我把前面提到的参数整理成一个清单。建议你打印一份贴在调试台旁边。

  1. MDC 频率:≤ 2.5 MHz,推荐 1~2 MHz。
  2. 上升/下降时间:10~100 ns(10%–90%)。
  3. 建立时间 ≥ 10 ns(数据在 MDC 上升沿前稳定)。
  4. 保持时间 ≥ 10 ns(数据在 MDC 上升沿后保持)。
  5. 帧长度固定 64 个时钟周期,不包含空闲。
  6. 前导码 32 个 '1',不可省略。
  7. Turnaround 读帧需切换 MDIO 方向。

我个人的排查顺序: 遇到 MDIO 通信失败,先用示波器看 MDC 频率对不对,然后看上升沿斜率,再看建保持时间是否满足。这三步能解决 80% 的时序问题。别一上来就怀疑从机地址写错了。

3.6 实际调试中的注意事项

最后聊几个我反复踩过的坑:

  • MDIO 上拉电阻不能省:空闲状态靠上拉保持高电平。电阻值推荐 2.2kΩ~4.7kΩ,太大会导致上升沿过缓,太小会增加功耗和驱动负担。
  • MDC 和 MDIO 走线尽量等长:如果两者长度差超过 5 cm,信号延迟差会吃掉建立时间余量。
  • 不要在 MDIO 上直接接电容滤波:我曾经在调试板上加了一个 100 pF 的去耦电容,导致 MDIO 信号变成三角波,建立时间直接变为负数。
  • 逻辑分析仪采样率要够:至少 4 倍于 MDC 频率,即 10 MHz 以上,否则看不出上升/下降时间的细节。

嗯,记住这些,你在示波器面前就能更有底气。时序规范不是背的,是拿来看波形、量窗口、找余量的。