MDIO帧格式详解

做D-PHY调试,MDIO是绕不开的看家本事。说白了,它就是一根时钟线加一根数据线的串行协议,用来读或写PHY芯片里的寄存器。我刚开始接触时,满脑子都是“这玩意怎么跟SPI这么像?” 结果坑就出在这“像”字上——帧结构完全不同。咱们把它掰开揉碎了讲。

标准帧结构

MDIO的每一帧固定32位,加上2个空闲位。你想想看,这32个比特按顺序排成下面这张表:

字段 位宽 说明
前导码(Preamble) 32位 全部为1,用来同步时钟
起始码(Start) 2位 固定值 01
操作码(Op Code) 2位 10=读,01=写
PHY地址 5位 目标PHY编号,范围0~31
寄存器地址 5位 寄存器编号,范围0~31
转向位(TA) 2位 Z0(读时为高阻+0),10(写时直接驱0)
数据位 16位 读返回值或写数据
空闲位(Idle) 2位 高阻,结束标志

关键记忆法:整帧 32+2 位,注意转向位有“Z”态——三态门没处理好,读操作会一直返回 0x0000。

我遇到过最离谱的案例:前导码少了2个1,PHY直接无视。手册上说32个1,实际用示波器数到30个就被打断。后来我老实做了个计数器,不够32个就补。

寄存器访问流程(读/写)

读和写的流程听着简单,但时序上稍有差别。先列个步骤对比:

  • 写操作:主机发完32位帧 + 2空闲,PHY收到后立即更新内部寄存器。不需要应答。
  • 读操作:主机先发前32位(包含读命令),转向位第一个时钟高阻,第二个时钟驱0;之后PHY接过数据线控制,把16位结果送回来。主机再拉高阻。

我个人的习惯:读操作时在转向位尾部故意多等半个时钟,防止PHY还没准备好就开拉数据线——很多入门级PHY芯片的时序余量只有几个纳秒。

时序参数雷区

写代码最头疼的是时钟速度。MDIO标准上允许最高2.5MHz,但别盲目冲满。我踩过坑:某国产PHY在2.0MHz下读写正常,换到2.5MHz读返回全0。查了老半天,不是帧结构不对,是PHY端的IO驱动能力不够。看我列个实战宽容度表:

参数 标准最小 推荐值 我的经验值
MDC频率 DC ~ 2.5 MHz 1.0 MHz 800 kHz(留余量)
时钟低/高电平时间 ≥ 200 ns 500 ns 625 ns
数据建立时间 ≥ 10 ns 50 ns 100 ns
数据保持时间 ≥ 10 ns 50 ns 50 ns

注意:上面表格里的“推荐值”是基于多数PHY在市面上表现。如果你的PCB走线超过10cm,建立时间建议再加50ns。不然跑1MHz也会丢数据。

代码实现要点

我习惯用状态机来驱动MDIO,因为原子操作好调试。下面是一个简化版读写函数框架(伪代码):

// 写寄存器
void mdio_write(uint8_t phy_id, uint8_t reg, uint16_t data) {
    mdio_start();                // 前导码+起始码
    mdio_send_bits(0b01, 2);    // 操作码:写
    mdio_send_bits(phy_id, 5);
    mdio_send_bits(reg, 5);
    mdio_send_ta_write();       // 转向位 (10)
    mdio_send_bits(data, 16);
    mdio_idle();
}

// 读寄存器
uint16_t mdio_read(uint8_t phy_id, uint8_t reg) {
    mdio_start();
    mdio_send_bits(0b10, 2);    // 操作码:读
    mdio_send_bits(phy_id, 5);
    mdio_send_bits(reg, 5);
    mdio_turnaround_read();     // 转向位 Z0
    uint16_t val = mdio_recv_bits(16); // PHY驱动数据
    mdio_idle();
    return val;
}

我曾经掉过的坑:转向位第二个时钟沿,主机必须把MDIO设成输入模式再去采样,否则会跟PHY的输出冲突。我最初用GPIO读前忘了切换方向,示波器上看到漂亮的信号冲突尖峰——那个教训值五百块打样费。

帧结构与流程的图形化

下面我把整个帧的布局画成一张图,你看一眼就能记住每个字段的位置和长度。绿色块代表主机驱动,橙色块代表PHY驱动(读时数据线切换)。

MDIO帧结构图(读操作示例) MDIO 帧结构(读操作) 前导码(32个1) 01 10 PHY地址(5bit) 寄存器地址(5bit) 转向 (Z0) 数据(16bit) (方向:PHY→主机) 空闲 时间方向 → 主机驱动 PHY驱动 结束标志 此处数据线控制权切换

这张图里我特别标了“数据线切换”的位置——转向位(Z0)的第二个时钟沿前,主机必须释放MDIO总线,改由PHY驱动。如果你用示波器抓,会看到一截“高阻”电平(一般是三态门输出悬空),然后变成PHY送来的数据。那个Z态持续时间只有半个时钟周期,但丢分分钟出事。

一句话心得:访问MDIO寄存器,最稳妥的做法是每次读或写之间留至少2个空闲位,让总线彻底释放。别信“连续读写”可以省时间——碰到弱上拉的PHY你丢数据丢到哭。