第3章:MDIO数据帧结构 — 逐字节拆解

做MDIO调试,最基础的是什么?很多人以为是时序,其实不是。你得先认识它说话的方式——数据帧结构。说白了,就是PHY和MAC之间怎么约定“我要开始说话了”“我要读什么”“数据来了”这一套流程。

我以前带新人的时候,经常让他们先背帧结构。有人觉得枯燥,但后来在项目里吃了一次亏,就再也不嫌烦了。那次我们调试一块板子,PHY死活不通,波形看起来也正常,就是读不到数据。折腾半天,最后发现是前导码少了几拍。嗯,从那以后,我每次抓波形,第一件事就是先看前导码对不对。

MDIO帧结构,就是你在示波器上看到的"暗号"。
学会拆解它,你就能看懂PHY在"说"什么。

3.1 帧结构全景图

标准MDIO帧,一共是这样组成的:

  1. 前导码(Preamble) — 32个连续“1”
  2. 起始码(Start of Frame) — 2位,“01”
  3. 操作码(Op Code) — 2位,决定读写
  4. PHY地址(PHY Address) — 5位,指定哪个PHY
  5. 寄存器地址(Register Address) — 5位,指定哪个寄存器
  6. 数据(Data) — 16位,真正要读写的值
  7. 空闲位(Idle) — 至少1个三态周期

你想想看,这跟I2C有点像,但MDIO更简单。没有ACK/NACK,没有从机地址的读写位。纯粹的串行帧,一拍一个坑。

MDIO 数据帧结构 前导码 32位:全1 起始码 2位:01 操作码 2位 PHY地址 5位 寄存器地址 5位 数据 16位 空闲 向左看:帧头 → 地址 → 数据 → 空闲 写帧:MAC驱动MDIO线;读帧:PHY在数据段驱动MDIO

3.2 前导码:用32个"1"打招呼

前导码是32个连续的“1”。

它的作用很单纯:给PHY一个准备时间。MDC时钟一开始,PHY不知道什么时候来数据,前导码就是告诉它“我要说话了”。我先说32个1,你听着,数够了我们开始正式通信。

我记得有一次,用逻辑分析仪抓波形,看到前导码只有16个1。嗯,那台MAC控制器在设计时直接忽略了前导码检测,但PHY不买账。最后换了个支持“无前导码模式”的PHY才解决。

小技巧:很多PHY支持“跳过前导码”模式(如BCM5461的Register 0x09)。这时候你不需要发32个1,直接发起始码就行。但为了兼容性,我个人习惯:所有项目都发完整的前导码。

3.3 起始码与操作码:说话的分界线

起始码(Start of Frame):固定2位,顺序是“01”。

你可能会问,为什么不是“10”或“00”?标准就是这么定的。有些PHY也支持“00”作为起始码,但那是非标用法。我劝你别碰。用标准“01”,最省事。

操作码(Op Code):同样是2位,区分读写:

操作码 操作类型 说明
01 写帧 MAC写入PHY寄存器
10 读帧 MAC读取PHY寄存器
00 读帧(地址自动递增) 标准中保留,部分PHY支持
11 未定义 不要使用

这里有个坑:读帧操作码是“10”,写帧是“01”。我一开始老是记反。直到有一次,写代码时把操作码写反了——写帧变成了读帧,PHY根本不响应。排查了半天,才发现是自己把位序搞反了。你记住:读是10,写是01。二进制里,读比写大,就这样记。

警告:读帧操作码“10”中,2位顺序是MSB先发“1”,再发“0”。不要跟SPI的MSB/LSB混淆。MDIO是MSB first,但操作码比特顺序在数据帧中必须严格遵守。
我曾经抓过一份波形,操作码在驱动代码里被写反了,波形上看是“01”(写帧),但程序是读操作,结果PHY一直返回全1。自查了3天才发现。

3.4 PHY地址与寄存器地址:精准定位

PHY地址:5位,从0到31。

一个MDC/MDIO总线可以挂多个PHY。每个PHY的地址由硬件引脚决定。比如,RTL8211的PHYAD0~PHYAD4引脚,电阻上拉或下拉,对应二进制地址。你在板子上看到的丝印“PHY_ADDR=0x01”,那就是5位二进制“00001”。

寄存器地址:也是5位,从0到31。

IEEE 802.3规定了一些标准寄存器地址:

  • 0x00:控制寄存器(BMCR)
  • 0x01:状态寄存器(BMSR)
  • 0x02:PHY标识符高16位
  • 0x03:PHY标识符低16位
  • ……

但注意:很多PHY自定义了0x10~0x1F范围的寄存器,用于厂商特定功能。比如LED配置、TX/RX幅度调整、MDI反转等。这些不在标准里,要看数据手册。

3.5 数据字段:16位的真相

数据段是16位,这是MDIO最核心的部分。

写帧:MAC在这16位里驱动数据,PHY采样。

读帧:前面所有段(前导码、起始码、操作码、地址)都是MAC在驱动MDIO线。到了数据段,MAC会释放MDIO线(变为高阻),PHY接管并驱动16位数据回来。

这就涉及一个关键点:三态门(Turn-Around)。在读帧中,数据段的第一个MDC周期,其实是个“切换周期”。MAC释放总线,PHY准备驱动。标准要求这个周期内,MDIO线必须保持高阻状态,由上下拉电阻维持高电平。

我踩过一个坑:某个板子的MDIO上拉电阻值太大(10kΩ),三态切换时电压被干扰拉低,PHY驱动的数据全错。后来换成了2.2kΩ,问题消失。你调试的时候,如果发现读回来的数据全是0x0000或0xFFFF,先检查这个切换周期。

关键指标:读帧数据段的第一个MDC时钟边沿,MDIO线应该看到从高阻变为PHY驱动。波形上,会有一小段“毛刺”或者“阶梯”,那就是三态切换。不要怕,那是正常的。

3.6 空闲位:帧间的沉默

空闲位,至少1个MDC周期。这段时间里,MDIO线处于高阻态,由外部上拉电阻拉高。

为什么要有空闲位?说白了,就是个帧间分隔符。告诉PHY:上一帧结束了,你该准备处理了。如果连续发帧,没有空闲位,有些PHY会混淆帧边界。

我的习惯是,在连续读写时,至少插入2个空闲周期。不是为了规范,是为了让逻辑分析仪看得清楚。软件里加个NOP循环,成本可以忽略,但调试时省大劲了。

3.7 完整帧传输流程总结

步骤 字段 位宽 驱动方 值示例(读帧)
1 空闲态 至少1 高阻(上拉) 三态高
2 前导码 32 MAC 1111...(32个1)
3 起始码 2 MAC 01
4 操作码 2 MAC 10(读帧)
5 PHY地址 5 MAC 00001(地址1)
6 寄存器地址 5 MAC 00000(寄存器0)
7 数据段(切换周期) 1 高阻→PHY 三态→PHY驱动
8 数据段(数据) 16 PHY 0x3100(BMSR典型值)
9 空闲位 至少1 高阻(上拉) 三态高

关于数据端序:MDIO数据段是MSB first。比如PHY返回0x3100,MDIO线上先看到的是bit15(0),然后是bit14(0),依次到bit0。如果你用字节模式写驱动,记得用大端序处理。

3.8 我的调试步骤建议

每次拿到MDIO波形,我习惯按这个顺序看:

  1. 先看空闲位:MDIO线是不是高电平?如果不是,可能有总线冲突。
  2. 数前导码:够32个1吗?少一个都不行。
  3. 定位起始码“01”:这是帧开始的精确标记。用示波器的光标卡这里。
  4. 读操作码:是10(读)还是01(写)?跟你的程序对应上。
  5. 读PHY地址和寄存器地址:逐位读取,跟预期值对比。比如PHY_ADDR=0x01,波形上应该是00001。
  6. 重点看数据段:读帧的话,注意切换周期那1位。插一句,如果切换周期不够,总线转换会有毛刺,数据就废了。
  7. 最后再看一次空闲位。

掌握这套检查流程,90%的MDIO问题都能在5分钟内定位。剩下的10%,可能是PHY内部状态机问题或者时钟抖动,那就要上更高级的调试手段了。但今天先把帧结构吃透,后面我们会讲怎么抓波形、怎么设触发条件。