MDIO协议时序规范:帧格式与数据解析
做MDIO调试,抓波形的人十有八九都卡在协议时序上。说白了,MDIO就是一根时钟线MDC加一根数据线MDIO,但数据传输的「规矩」非常严密。我个人习惯先把帧格式背下来,然后再去动手抓波形,否则看示波器就是一团乱麻。
2.1 帧结构概览
你想想看,16位寄存器数据怎么在单根数据线上跑?答案是靠固定格式的帧,逐位传输。标准MDIO帧共64个时钟周期,其中前导码占了32个周期,剩下的32个周期承载有效信息。
| 字段 | 位数 | 说明 |
|---|---|---|
| 前导码 (Preamble) | 32 | 连续32个'1',用于同步 |
| 起始码 (Start) | 2 | 固定为'01',标识帧开始 |
| 操作码 (OP) | 2 | '01'=写,'10'=读(注意顺序!) |
| PHY地址 (PHY ADDR) | 5 | 目标PHY设备编号,0-31 |
| 寄存器地址 (REG ADDR) | 5 | 目标寄存器索引,0-31 |
| Turnaround (TA) | 2 | 写操作为'10',读操作为'Z0' |
| 数据 (Data) | 16 | 读/写的数据内容 |
2.2 前导码与起始码
前导码32个连续的'1',说白了就是「喂,协议来了,准备接收」。我在项目中遇到过一位同事,抓了半天波形发现前导码只有31个'1',结果PHY死活不响应——就是差这一位。
起始码是固定'01'。注意不是'10'也不是'11'。它紧跟前导码的最后一个'1'之后,在MDC的上升沿采样。如果起始码不对,帧会被直接丢弃。
2.3 操作码与PHY地址
操作码虽然只有2位,但千万别搞反。写操作是'01',读操作是'10'。很奇怪吧?其实反过来读帧就明白了:'01'代表主机要输出数据(写),'10'代表主机要接收数据(读)。
PHY地址5位,支持32个地址。我建议你留个心眼:很多PHY芯片默认地址都是0或1,但有些奇葩芯片默认地址是31或随机。我曾经被一块国产PHY坑过,它的默认地址是0x1F,对着Datasheet找了半天才发现。
2.4 寄存器地址与Turnaround
寄存器地址同样5位。IEEE 802.3标准规定前16个寄存器(0-15)是强制实现的,后16个(16-31)是可选的。调试时我一般先读寄存器0(基本控制寄存器)和寄存器1(基本状态寄存器),验证通信是否正常。
Turnaround是帧里最「玄学」的部分。写操作时,TA固定为'10',由主机驱动。读操作时,TA第一个位是高阻态'Z',第二个位是PHY驱动的'0'。换句话说,读操作时总线有段「切换所有权」的空隙。
// 伪代码:读操作时序
// 前导码+起始码+操作码+PHY地址+寄存器地址 由MAC驱动
// TA阶段:第一个位MAC释放总线(高阻),第二个位PHY拉低确认
// 数据阶段:PHY驱动16位数据到MDIO线
2.5 数据段与三态控制
数据段16位,MSB先行。写操作把数据放进MDIO帧,读操作从MDIO帧里取数据。但这里有个细节:写操作的数据由MAC驱动,读操作的数据由PHY驱动。所以MDIO是双向线,必须用三态缓冲器控制。
我曾经在FPGA里实现MDIO控制器时,忘记在TA阶段切换数据方向,结果读回来的数据全是0。查了三天波形才发现,PHY输出数据时,我的FPGA还在强驱动MDIO线,两边打架,数据全被拉低。
// Verilog示例:MDIO三态控制
assign mdio_oe = (state == WRITE_DATA ||
state == PREAMBLE ||
state == START ||
state == OP_PHY_REG) ? 1'b1 : 1'b0;
assign mdio = mdio_oe ? mdio_out : 1'bz;
2.6 帧格式可视化
下面这张图把MDIO帧的完整结构画出来了。我建议你把它打印出来贴在调试台前,抓波形时对照着看效率会高很多。
2.7 写操作与读操作对比
讲真,帧结构原理不难,难的是波形上实时区分读和写。我做了个简单对照表,你抓波形时直接套用就行。
| 对比项 | 写操作 | 读操作 |
|---|---|---|
| 操作码 | '01' | '10' |
| Turnaround | '10'(MAC驱动) | 'Z0'(第一拍高阻,第二拍PHY拉低) |
| 数据方向 | MAC → PHY | PHY → MAC |
| 帧长度 | 64个MDC周期 | 64个MDC周期 |
| 典型应用 | 配置PHY寄存器、复位、下电 | 读取PHY状态、链路协商结果 |
MDIO协议的时序规范就这些。嗯,这里要注意:所有数据都是在MDC的上升沿采样的。下降沿用来改变数据,上升沿用来捕获数据。记住这个原则,你再去看波形,就不会觉得乱了。