MDIO协议时序规范:帧格式与数据解析

做MDIO调试,抓波形的人十有八九都卡在协议时序上。说白了,MDIO就是一根时钟线MDC加一根数据线MDIO,但数据传输的「规矩」非常严密。我个人习惯先把帧格式背下来,然后再去动手抓波形,否则看示波器就是一团乱麻。

一句话总结:MDIO帧由「前导码 + 起始码 + 操作码 + PHY地址 + 寄存器地址 + Turnaround + 数据」7个字段组成。读操作与写操作的区别,就在操作码和Turnaround上。

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 读/写的数据内容
我个人习惯:在协议分析仪里提前配好帧模板,把7个字段用不同颜色高亮,这样抓到波形一眼就能看出是哪一段出问题。

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找了半天才发现。

踩坑提醒:PHY地址和寄存器地址都是5位,但位序是MSB先行。如果你抓到的波形地址段是'00001',那是地址1,不是16。很多新手在这里栽跟头。

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线
一个调试技巧:抓读操作波形时,重点看TA的第2个位是否为'0'。如果PHY没有拉低,说明PHY没识别到地址或寄存器,或者PHY根本没准备好。我遇到过PHY还在复位中就开始发读命令,TA一直浮空。

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;
给你提个醒:三态切换不能太随意。从输出切到输入时,最好先等半个MDC周期,让总线「安静」一下。否则因为PCB寄生电容,总线电压还没稳定就开始采样,数据会出问题。

2.6 帧格式可视化

下面这张图把MDIO帧的完整结构画出来了。我建议你把它打印出来贴在调试台前,抓波形时对照着看效率会高很多。

MDIO协议帧结构图 MDIO帧结构(64个MDC时钟周期) 前导码 32个连续'1' 起始 '01' OP 2位 PHY地址 5位 寄存器地址 5位 TA 2位 数据 16位 时钟: 0 32 34 36 41 46 48 64 帧字段关键说明 • 前导码: 32位全'1',用于PHY同步,实际应用中可缩短为1个'1'(部分PHY支持) • 起始码: 固定'01',标识有效帧开始,非'01'则忽略 • 操作码: '01'=写操作, '10'=读操作 (注意: 不是二进制数值,而是位序列) • TA: 写操作'10'; 读操作第一个位高阻'Z', 第二个位PHY拉低'0' • 数据: 16位MSB先行,读操作由PHY驱动,写操作由MAC驱动

2.7 写操作与读操作对比

讲真,帧结构原理不难,难的是波形上实时区分读和写。我做了个简单对照表,你抓波形时直接套用就行。

对比项 写操作 读操作
操作码 '01' '10'
Turnaround '10'(MAC驱动) 'Z0'(第一拍高阻,第二拍PHY拉低)
数据方向 MAC → PHY PHY → MAC
帧长度 64个MDC周期 64个MDC周期
典型应用 配置PHY寄存器、复位、下电 读取PHY状态、链路协商结果
实测验证:写完寄存器后,一定要读回来确认。我有过惨痛教训,写寄存器时地址计算错误,寄存器写废了都不知道,整个链路协商异常调了两周才发现是写错了寄存器。

MDIO协议的时序规范就这些。嗯,这里要注意:所有数据都是在MDC的上升沿采样的。下降沿用来改变数据,上升沿用来捕获数据。记住这个原则,你再去看波形,就不会觉得乱了。