第3章:MDIO数据帧结构 — 逐字节拆解
做MDIO调试,最基础的是什么?很多人以为是时序,其实不是。你得先认识它说话的方式——数据帧结构。说白了,就是PHY和MAC之间怎么约定“我要开始说话了”“我要读什么”“数据来了”这一套流程。
我以前带新人的时候,经常让他们先背帧结构。有人觉得枯燥,但后来在项目里吃了一次亏,就再也不嫌烦了。那次我们调试一块板子,PHY死活不通,波形看起来也正常,就是读不到数据。折腾半天,最后发现是前导码少了几拍。嗯,从那以后,我每次抓波形,第一件事就是先看前导码对不对。
MDIO帧结构,就是你在示波器上看到的"暗号"。
学会拆解它,你就能看懂PHY在"说"什么。
3.1 帧结构全景图
标准MDIO帧,一共是这样组成的:
- 前导码(Preamble) — 32个连续“1”
- 起始码(Start of Frame) — 2位,“01”
- 操作码(Op Code) — 2位,决定读写
- PHY地址(PHY Address) — 5位,指定哪个PHY
- 寄存器地址(Register Address) — 5位,指定哪个寄存器
- 数据(Data) — 16位,真正要读写的值
- 空闲位(Idle) — 至少1个三态周期
你想想看,这跟I2C有点像,但MDIO更简单。没有ACK/NACK,没有从机地址的读写位。纯粹的串行帧,一拍一个坑。
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波形,我习惯按这个顺序看:
- 先看空闲位:MDIO线是不是高电平?如果不是,可能有总线冲突。
- 数前导码:够32个1吗?少一个都不行。
- 定位起始码“01”:这是帧开始的精确标记。用示波器的光标卡这里。
- 读操作码:是10(读)还是01(写)?跟你的程序对应上。
- 读PHY地址和寄存器地址:逐位读取,跟预期值对比。比如PHY_ADDR=0x01,波形上应该是00001。
- 重点看数据段:读帧的话,注意切换周期那1位。插一句,如果切换周期不够,总线转换会有毛刺,数据就废了。
- 最后再看一次空闲位。
掌握这套检查流程,90%的MDIO问题都能在5分钟内定位。剩下的10%,可能是PHY内部状态机问题或者时钟抖动,那就要上更高级的调试手段了。但今天先把帧结构吃透,后面我们会讲怎么抓波形、怎么设触发条件。