4. MDIO 帧格式——读懂每一比特的“暗语”
MDIO 协议其实不复杂。它的整个通信过程,说白了就是一根时钟线(MDC)加上一根数据线(MDIO),通过固定格式的帧来交换信息。
我刚开始接触 MDIO 时,总觉得帧格式很枯燥。但后来调试 PHY 芯片时,发现很多问题都出在帧格式上——要么前导码长度不够,要么操作码写反了。从那以后,我就把这套帧结构背得滚瓜烂熟。
咱们今天就把 MDIO 的帧格式拆开看。它一共包含 8 个部分。我习惯把它们分成“头、身、尾”三块来记。
4.1 前导码(Preamble)——同步的“敲门砖”
前导码由 32 个连续的“1”组成。它的作用很简单:让 PHY 芯片的接收电路锁定到正确的时钟相位上。
你想想看,MDIO 总线是多设备共享的。如果时钟同步没建立好,后面传什么都是白搭。我曾经见过一块板子,控制器和 PHY 之间走线太长,导致前导码的最后一个“1”被误判为起始码。问题查了三天,最后发现是前导码少发了 2 个时钟周期。
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。结果读回来的数据永远是混乱的。
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”。
前导码(32)+ 起始码(2)+ 操作码(2)+ PHY 地址(5)+ 寄存器地址(5)+ 转向位(2)+ 数据(16)+ 空闲(0 周期,但引脚要浮空)= 64 比特。
嗯,MDIO 帧格式的内容就讲到这里。记住:帧头负责同步,帧身负责寻址,帧尾决定方向并传输数据。理解了这个结构,后面编写驱动代码就轻松多了。