MDIO帧结构:前导码、起始码、操作码、地址、数据、空闲

做MDIO读写,说白了就是往PHY的寄存器里写东西、读东西。但总线怎么知道你要读哪个寄存器?怎么知道你是读还是写?数据怎么对齐?这些都得靠一帧一帧的协议来约定。

我接触MDIO总线好多年了,第一次调PHY驱动时就被帧结构坑过——波形看着都对,读回来的数据全是乱的。后来老老实实把每一bit拆开看,才发现是地址位少算了两个。嗯,今天咱们就把这帧结构彻底讲透。

一帧MDIO的长度与组成

标准MDIO帧,一共 64个时钟周期(也就是64个bit)。时钟由主控(MAC/CPU)提供,PHY只在数据段和地址段回传数据时拉低读时序。整个帧分成6个阶段:

阶段 名称 bit数 方向 描述
1 前导码 (Preamble) 32 主控 → PHY 连续32个1,用于同步
2 起始码 (Start) 2 主控 → PHY 固定01,标志帧开始
3 操作码 (Opcode) 2 主控 → PHY 读操作01,写操作10
4 PHY地址 (PHY ADDR) 5 主控 → PHY 目标PHY编号,5bit,最大31
5 寄存器地址 (REG ADDR) 5 主控 → PHY 寄存器偏移,5bit,共32个
6 转态 (Turnaround) 2 主控→PHY→主控 第一个bit高阻,第二个bit PHY驱动读数据
7 数据 (Data) 16 主控/PHY 16bit寄存器值,写时主控发,读时PHY发
8 空闲 (Idle) ≥1 主控 输出高阻或拉高,总线释放
经验谈: 前导码32个1其实够奢侈的。我见过有些简化实现只发31个1,PHY也能工作,但不符合协议规范。如果你要在产品里过认证,老老实实发32个。

逐段拆解——每个bit都有讲究

1. 前导码 (Preamble) — 32个连续1

主控把MDIO线拉高,同时MDC时钟打32个周期。PHY看到这32个1就知道要准备收指令了。为什么是32个?协议从1994年定下来就没改过——足够长,让PHY内部状态机稳定。我自己调试时经常用示波器看MDIO波形,前导码齐不齐一眼就能看出问题:少了几个1,后面解析全错。

2. 起始码 (Start) — 固定01

前导码结束后,接着2个bit:先0后1。这2个bit是帧开始的标志。PHY检测到01后就锁定后续的bit顺序。注意:不能是11或10,更不能是00。有一次我写代码把起始码写成了10,贴片后100片里有3片PHY读不对,排查了一天。

避坑指南: 起始码的两bit必须紧跟前导码最后一个1。有些代码在32个1后多插入一个空闲周期,PHY会错过帧起始,丢帧。

3. 操作码 (Opcode) — 读01 / 写10

2个bit,决定了这一帧是读还是写:

  • 读 (01):主控发01,之后PHY会在数据段把寄存器值放上MDIO线。
  • 写 (10):主控发10,然后主控自己把要写的16bit数据发出去。

为什么读是01,写是10?我猜是为了让PHY解码时方便:最高位为0是读,为1是写。但协议就这么定的,记住就行。

4. PHY地址 (5bit) 和寄存器地址 (5bit)

两个各5bit,总共10bit。PHY地址范围0~31,寄存器地址也是0~31。注意:地址都是主控发出去的,PHY只监听与自己地址匹配的帧,不匹配的帧忽略。我调多PHY系统时,经常挂几个PHY在一条MDIO总线上,每个PHY地址不一样,靠这5bit区分。

哦对了,寄存器地址虽然是5bit,但实际标准规定前16个(0~15)是必须实现的MII寄存器,后面的16个(16~31)留给厂商自定义。但我见过有的PHY芯片只实现了前16个,你读地址16~31会返回0xFFFF或者固定值。

5. 转态 (Turnaround) — 最容易被忽略的2bit

这2bit严格来说不是“地址”也不是“数据”,而是总线控制权的交接。看协议定义:

  • 第1个bit(TA1):主控必须输出高阻 (Z),MDIO线由上拉电阻拉高。
  • 第2个bit(TA2):
    - 读操作:PHY驱动力低,输出0(表示准备好送数据)。
    - 写操作:主控驱动高,输出1(继续掌控总线)。

这个机制保证总线不会冲突。我在项目中遇到过:主控制器没进入高阻,TA1时刻还持续拉高,PHY也想拉高,结果两个驱动对着干,电流倒灌,波形变平顶。嗯,幸亏驱动能力有限没烧芯片。

6. 数据 (Data) — 16bit

前面所有准备工作都是为了这16bit。读操作时PHY在MDIO线上输出值;写操作时主控输出值。bit顺序:MSB先发,即bit15最先送出,bit0最后。PHY内部寄存器也是高15~低0排列,没问题。

7. 空闲 (Idle) — 至少1个周期

帧结束后,主控把MDIO置为高阻或直接拉高,释放总线。至少1个时钟周期的空闲时间,让PHY处理完上一帧。如果你连续发帧没有空闲,PHY可能处理不过来。我自己一般空闲2~3个周期,稳一点。

整体帧结构图

下面这张图把上面的拆解串到一起,方便你对照时序调试。

MDIO帧结构(64时钟周期) 前导码 (32个1) 01 读/写 PHY ADDR (5b) REG ADDR (5b) TA 数据 (16bit) 空闲 32bit 2 2 5 5 2 16 ≥1 说明: 读操作TA: PHY驱动第2个bit为0;写操作TA: 主控驱动第2个bit为1。空闲周期MDIO线必须释放。 MDIO数据线电平示意(读操作) 前导码 (32个1高电平) 01 01 (读) PHY+REG地址 TA: Z-0 16bit数据 (PHY发) 空闲

我总结的几个关键点

  • 前导码不能省略:有些高速模式可以跳过前导码(Clause 45),但标准的Clause 22 MDIO必须有32个1。我建议即使你的PHY支持跳过,也先发完整帧,稳定再优化。
  • 地址顺序:先发PHY地址(高5bit),再发寄存器地址(低5bit)。这两个字段紧挨着,总线上连续10个bit。我曾经混淆过:以为先寄存器后PHY,结果读到的值张冠李戴。
  • 转态是调试的照妖镜:如果示波器看到TA1不是高电平,或者TA2读操作时不是0,那肯定有一方没配置正确。我有一回发现TA2读时PHY输出了1,查手册发现那个PHY的TA时序要求多等一个时钟——厂家自己改了协议。
  • 空闲周期别省:连续读写至少留1个空闲周期。你想想看,PHY内部还在处理上一帧的数据,下一帧又来了,容易丢数据。

记忆口诀(我自己的):

32个1同步好,01起板不能少。
操作码定读写,PHY号5bit先到。
寄存器号紧跟,转态两bit交接好。
数据16位送出,空闲放松别抢道。

Clause 22 vs Clause 45的帧区别

这里简单提一句,后面章节会详细展开。Clause 22就是咱们上面讲的64bit帧。而新的Clause 45(支持更多寄存器和端口)帧结构变了:前导码变成32个0,操作码变成4bit,地址变成15bit等等。但初学者先把标准的Clause 22吃透,后面再学扩展就轻松了。

嗯,关于MDIO帧结构,核心就是这七个阶段。你只要能画出一帧波形,把每一段对应的bit数说清楚,写代码时照这个结构组织bit流就行了。下一段咱们直接看怎么用代码发一帧读操作。不过那是后面的事了,今天先把结构刻在脑子里。