3. MDIO帧格式:前导码 · 起始码 · 操作码 · 地址 · 数据 · 空闲

要跟MDIO总线打交道,你得先看懂它说话的方式。

帧格式,说白了就是MDIO线上每一条完整消息的结构。我刚开始接触时,老觉得这东西跟串口协议差不多,结果真去抓波形才发现——坑不少。你想想看,一条总线上挂着十几个PHY,谁在说、说什么、说给谁听,都得靠这个帧格式来约定。

3.1 标准帧结构一览

MDIO帧一共32位(加上空闲位),按顺序分为6个字段。我个人习惯把它们记成「前起操地数空」,调设备时脑子里过一遍这个口诀,就不容易看丢位了。

字段 位数 缩写 说明
前导码 32 PREAMBLE 32个连续"1",用于同步总线
起始码 2 ST 固定为"01",表示帧开始
操作码 2 OP "01"=写,"10"=读
PHY地址 5 PHYAD 目标PHY编号(0~31)
器件地址 5 DEVAD 寄存器地址(0~31)
数据 16 DATA 读/写的数据内容
空闲 不定 IDLE 高阻态,由下拉电阻维持高电平
关键提醒:前导码虽然是32位,但有些PHY芯片也接受少于32位的前导码。不过——我建议你别省。我在项目里试过只发16个"1",结果有两款PHY丢帧了,查了一整天。

3.2 每个字段的细节

前导码(PREAMBLE)

前导码就是32个连续的"1"。它用来让总线上所有器件同步时钟边沿。MDIO是同步串行协议,时钟由MAC侧提供。如果MAC的时钟频率变了(比如从2.5MHz切到25MHz),前导码得保证所有从设备重新同步。

嗯,这里要注意:前导码并不是可选的。虽然IEEE 802.3里面提到有些管理帧可以省略前导码,但那属于非标用法。做产品时,老老实实发32个"1"最安全。

起始码(ST)

固定为"01"。它告诉PHY:"准备,正式内容要来了。"

你可能会问,为什么不是"00"或"10"?其实协议这么设计,是为了跟前导码的"1"区分开。前导码全是1,突然变成0,就是一个清晰的切换信号。我在调试中遇到过一种情况——如果空闲状态的下拉电阻失效了,总线电平飘忽不定,起始码就可能被误判断。后来我总会在板级验证时测一下空闲状态的电平。

操作码(OP)

两位,决定这次传输是读还是写。

  • 01 —— 写操作(MAC -> PHY)
  • 10 —— 读操作(MAC -> PHY 先发地址,再从PHY读数据)

操作码错了,整个帧就废了。我记得有一次在调试时,代码里操作码的bit顺序写反了——把"01"写成了"10"。总线上一通乱抓,PHY明明在线就是没响应。这种低级错误,往往是最容易踩的。

PHY地址(PHYAD)

5位,范围0~31。MDIO支持最多32个PHY器件挂在同一条总线上。地址一般通过PHY芯片的硬件引脚(如AD0~AD4)配置。

实际调试时有个坑:很多PHY芯片的默认地址是0,但你板子上可能好几个器件都用了默认。冲突了怎么办?我遇到过,一片PHY回复数据时总线上另一片也抢答,结果读到的是乱码。解决办法是上电前确认每片的地址配置,或者写一个地址轮询脚本扫描总线。

器件地址/寄存器地址(DEVAD)

还是5位,范围0~31。它指明要访问PHY内部的哪个寄存器。别忘了,每个PHY内部有32个16位寄存器(00~1F),标准定义了前16个的用途,后面是厂商自定义的。

很多新手搞混PHYAD和DEVAD。我习惯这么记:PHYAD是找芯片,DEVAD是找寄存器。两个都必须对,错一个就读回0xFFFF。

数据(DATA)

16位,不多不少。MDIO传输的就是这16位数据。写操作时,MAC在数据段驱动这16位;读操作时,PHY在数据段驱动总线。

这里有个细节:读操作的最后两个时钟周期,总线从MAC驱动切换到PHY驱动。切换时会有短暂的高阻态。如果你的示波器探头电容太大,可能会看到一个小台阶。我一直用无源探头加短地线,这样毛刺少。

空闲(IDLE)

IDLE是三态。MDIO总线外部通常要有上拉电阻(一般4.7kΩ到10kΩ),空闲时总线被拉到高电平。具体是MDIO线上有上拉,MDC线没有。

那为什么需要空闲?很简单——MDIO是半双工。MAC和PHY只有一根数据线,当一方说完,必须释放总线,另一方才能发言。空闲段就是给总线换向用的。

避坑指南:我曾经在一块板上忘记加MDIO上拉电阻,导致空闲时总线电平不稳。PHY有时能正常工作,有时回的数据全是0。后来加了4.7kΩ上拉,问题消失。所以——上拉电阻不是摆设。

3.3 帧传输时序

MDIO跟时钟MDC同步。数据在MDC的上升沿被采样,这是规矩。写操作时,MAC在每个上升沿前把数据准备好;读操作时,PHY在上升沿前驱动数据。

我们来看一个典型的写帧时序:

写操作示例(MAC写0x1234到PHY地址3,寄存器地址5):
MDC  : ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁...
MDIO : 11111111...11111111 01 01 00011 00101 0001001000110100 ZZZZ
       └───32个1──────┘ └ST┘└OP┘└PHYAD┘└DEVAD┘ └──DATA=0x1234──┘ └IDLE┘

读操作类似,只是数据段由PHY驱动。注意读操作时,MAC必须在数据段释放总线,让PHY来控制。

3.4 帧格式的变体:Clause 45 vs Clause 22

这里必须提一下,MDIO协议有两个版本:

特性 Clause 22(经典) Clause 45(增强)
帧结构 32位前导码 + 2位ST + 2位OP + 5位PHYAD + 5位DEVAD + 16位数据 + IDLE 前导码 + ST + OP + 端口地址 + 器件地址 + 寄存器地址 + 数据
寄存器宽度 16位 16位,但地址空间更大
速率 最高2.5MHz 最高25MHz(甚至更高)
主要用途 传统百兆/千兆PHY 万兆PHY、多通道器件

我们课程主要讲Clause 22。不过你不用担心,基本概念懂了,Clause 45不过是帧里多了些字段而已。

3.5 帧格式的SVG图示

下面这张图把整个帧结构画出来了。我建议你对着波形图一块一块地看,特别关注时钟沿和数据变化的关系。

MDIO帧结构(Clause 22) 前导码 (32 bit '1') ST
01 OP
01/10
PHYAD
(5 bit, 0~31)
DEVAD
(5 bit, 0~31)
数据 (16 bit) IDLE 写操作:MAC驱动所有数据 读操作:地址段MAC驱动 数据段PHY驱动 空闲状态 总线释放 = 高阻 靠上拉电阻维持高电平 时钟采样 MDIO在MDC上升沿采样 数据必须在边沿前稳定 总长度 32 + 2 + 2 + 5 + 5 + 16 = 62位 加上空闲位 MDIO帧通信核心逻辑 寻址 PHYAD + DEVAD 锁定目标 操作 OP码决定读/写方向 传输 16位数据串行交换 牢记帧结构的目的是什么?—— 让总线上的每一方都清楚: 「谁在说话,对谁说话,要做什么,数据是什么」
小技巧:拿到一块新PHY板子后,我习惯先在bus上发一个读ID寄存器的帧(地址0x00),看看能不能收到预期的值。这一步能快速确认MDIO链路是否通畅。如果回的是0xFFFF,多半是地址错了或上拉不对。

3.6 帧格式的拆解练习

讲再多理论,不如拆一个真实帧。假设你抓到下面这条MDIO波形(为了方便阅读,我把二进制和十六进制混写了):

MDIO线: 1111...1111 01 01 0 0011 0 0101 0001 0010 0011 0100
字段拆解:
前导码: 32个1
起始码: 01
操作码: 01 (写)
PHYAD : 00011 (十进制3)
DEVAD : 00101 (十进制5)
数据  : 0001 0010 0011 0100 (十六进制0x1234)
空闲  : Z

对应回你做的操作:你想把0x1234写到PHY地址3的寄存器5里。MAC先拉高总线发出前导码,然后一步一步把地址和数据送出去。最后释放总线,进入空闲。

读操作类似,但数据段要等PHY回答。如果你抓波形时看到数据段没有变化,就是总线没有按时切换方向——八成是代码里少了个三态控制。

注意:实际调试时,MDIO线上经常有毛刺。尤其是当MAC和PHY的驱动强度不匹配时,边沿会变缓,台示波器可能看到错误的采样点。我建议你写一个自检帧——先写一个已知值到PHY,再读回来,比对是否一致。转几圈都对了,才能说明帧格式是可靠的。

3.7 小结

这一章我们掰开了MDIO帧的每一个字段。你只要记住:前导码同步、起始码标识、操作码定方向、PHYAD选器件、DEVAD选寄存器、数据传内容、空闲给总线放个假。把这个顺序刻在脑子里,以后看任何MDIO波形,你都能一眼看出哪里出错了。