4. MDIO 帧格式——读懂每一比特的“暗语”

MDIO 协议其实不复杂。它的整个通信过程,说白了就是一根时钟线(MDC)加上一根数据线(MDIO),通过固定格式的帧来交换信息。

我刚开始接触 MDIO 时,总觉得帧格式很枯燥。但后来调试 PHY 芯片时,发现很多问题都出在帧格式上——要么前导码长度不够,要么操作码写反了。从那以后,我就把这套帧结构背得滚瓜烂熟。

咱们今天就把 MDIO 的帧格式拆开看。它一共包含 8 个部分。我习惯把它们分成“头、身、尾”三块来记。

MDIO 帧格式结构图 前导码 32 个“1” 起始码 01 操作码 10 / 01 PHY 地址 5 比特 寄存器地址 5 比特 转向 Z / 0 数据 16 比特 空闲 Hi-Z 帧头(固定格式) 帧身(地址信息) 帧尾(数据 + 控制) MDC 时钟 数据方向 ← 控制器 → PHY(写操作) ← PHY → 控制器(读操作)

4.1 前导码(Preamble)——同步的“敲门砖”

前导码由 32 个连续的“1”组成。它的作用很简单:让 PHY 芯片的接收电路锁定到正确的时钟相位上。

你想想看,MDIO 总线是多设备共享的。如果时钟同步没建立好,后面传什么都是白搭。我曾经见过一块板子,控制器和 PHY 之间走线太长,导致前导码的最后一个“1”被误判为起始码。问题查了三天,最后发现是前导码少发了 2 个时钟周期。

⚠️ 避坑指南: IEEE 802.3 明确规定前导码必须是 32 个连续的“1”,一个都不能少。有些控制器支持配置前导码长度,但强烈建议不要擅自修改。

4.2 起始码(Start of Frame)——帧开始的“口令”

起始码固定为 01(2 比特)。这是个标志性组合,告诉 PHY:“注意了,真正的帧要来了。”

为什么是 01 而不是 00 或 11?我个人的理解是:前导码全是“1”,起始码用“0”跳变一下,形成清晰的上升沿/下降沿,便于接收端做比特对齐。

4.3 操作码(Opcode)——读还是写?

操作码只有 2 比特,但意义重大。它决定了这次 MDIO 交互是读操作还是写操作:

操作码 含义 数据方向
01写操作控制器 → PHY
10读操作控制器 ← PHY

我刚开始做驱动时,经常把这两个码记反。后来总结了一个窍门:“1”在前的读,“0”在前的写。你试试看,是不是好记一点?

4.4 PHY 地址(PHY Address)——点名时间

MDIO 总线上可以挂多个 PHY。每个 PHY 都有一个唯一的地址,范围 0~31(5 比特)。这一段的职责就是告诉总线:“我找的是第 X 号 PHY”。

PHY 地址通常由芯片的硬件管脚(如 AD[4:0])决定。我在项目里见过两次地址冲突的情况——两块开发板共用一个总线时,两个 PHY 都设成了地址 1。结果读回来的数据永远是混乱的。

💡 实战技巧: 如果总线上有多个 PHY,建议错开它们的地址。如果你通过 MDIO 扫描到的 PHY 地址全是 0xFF,很可能总线信号存在问题,或者 PHY 根本没上电。

4.5 寄存器地址(Register Address)——我要读哪个寄存器

每个 PHY 内部有 32 个基本寄存器(0~31),这也是 5 比特。地址空间虽然不大,但基本覆盖了 IEEE 802.3 定义的必选寄存器:控制寄存器(地址 0)、状态寄存器(地址 1)、PHY ID 寄存器(地址 2、3)等。

你可能会问:“数据就那么点,够用吗?”其实很多 PHY 厂商还扩展了 Clause 45 寻址方式,能访问更多寄存器。但那属于进阶内容,咱们后面章节会讲到。

4.6 转向位(Turnaround)——交接控制权

转向位是 2 比特。它的作用很特殊——切换总线的驱动方向。

  • 写操作: 转向位为 10。控制器一直占着总线,不给 PHY 说话的机会。
  • 读操作: 转向位为 Z0。第一个比特是 Hi-Z(高阻态),控制器释放总线;第二个比特是 0,由 PHY 接管驱动。

说白了就是:写操作时,控制器从头讲到尾;读操作时,控制器先把麦克风放下,让 PHY 接过话筒。

我调试过一块 PHY 芯片,发现读回来的数据总少了一个比特。后来用示波器抓波形才看到——转向位的 Hi-Z 时间太长了,导致 PHY 没能及时接管。这就是典型的时序配合问题。

4.7 数据位(Data)——核心负载

数据字段共 16 比特。无论是读还是写,数据都是从 MSB(最高位)到 LSB(最低位)顺序传输。写操作时数据由控制器提供;读操作时数据由 PHY 提供。

注意事项:

  • 有些 PHY 寄存器的高位是保留位。写操作时,保留位一般写入 0。
  • 读操作时,保留位会返回 0,但不要依赖它。

4.8 空闲位(Idle)——总线休息时间

一帧结束后,总线进入空闲状态。此时 MDIO 线处于高阻态(Hi-Z),由外部上拉电阻拉高。

为什么要 Hi-Z?因为 MDIO 是双向数据线。如果控制器在空闲时还强拉电平,别的设备想发数据就没法发。上拉电阻确保总线在没人驱动时保持确定的逻辑“1”。

✨ 总结: 一个完整的 MDIO 帧总共是 64 个 MDC 时钟周期(写操作)或 64 个时钟周期(读操作)。
前导码(32)+ 起始码(2)+ 操作码(2)+ PHY 地址(5)+ 寄存器地址(5)+ 转向位(2)+ 数据(16)+ 空闲(0 周期,但引脚要浮空)= 64 比特。

嗯,MDIO 帧格式的内容就讲到这里。记住:帧头负责同步,帧身负责寻址,帧尾决定方向并传输数据。理解了这个结构,后面编写驱动代码就轻松多了。