4. MDIO 帧格式详解:前导码、起始码、操作码、地址、数据

MDIO 通信说到底就是一套约定好的“暗号”。

主控(MAC 或 CPU)往总线上发一串电平变化,PHY 芯片听到后,就知道你要干嘛——读哪个寄存器、写什么值。这套“暗号”就是 MDIO 帧格式。

我最早看手册里那个时序图时,说实话,有点懵。一堆箭头、一堆延迟参数。后来啃了几个实际项目,回头再看才发现,帧结构其实很清晰,就是五个固定部分拼起来。

4.1 帧格式总览

一个标准的 MDIO 帧,无论读还是写,长度都是 64 个时钟周期。你想想看,64 个比特,正好对应一个完整的事务。

我把帧结构拆成一张表格,这样看更直观:

字段 比特数 读操作值 写操作值 说明
前导码 (Preamble) 32 全 1 全 1 同步总线,让 PHY 准备好接收命令
起始码 (Start) 2 0 1 0 1 标志一个有效帧的开始
操作码 (OP) 2 1 0 0 1 区分是读还是写
PHY 地址 (PHY ADDR) 5 AAAAA AAAAA 选择与哪个 PHY 通信(0x00 - 0x1F)
寄存器地址 (REG ADDR) 5 RRRRR RRRRR 选择 PHY 内部的哪个寄存器(0x00 - 0x1F)
转态位 (TA) 2 Z0 1 0 总线方向切换控制,读操作有高阻态
数据 (Data) 16 读回数据 写入数据 实际要读或写的寄存器值
总计 64 - - 固定 64 个 MDC 时钟周期

注意看,读和写的区别主要在于操作码和转态位。这个转态位是很多初期调试容易翻车的地方,我后面会细讲。

MDIO 帧格式结构(读操作示例) 前导码 (32'b1...1) 01 10 PHY ADDR REG ADDR Z0 Data 字段说明: • 前导码:32个连续的"1",用于同步总线时钟,所有PHY都监听这个信号 • 起始码:固定"01",表示一个真实有效的MDIO事务即将开始 • 操作码:"10"=读,"01"=写(注意读写的比特顺序是相反的) • 转态位(TA):读操作时驱动端先释放总线(高阻),再由PHY驱动数据 • 数据:16位数据,MSB先行,读操作在此处获取寄存器值 总长度固定:32 + 2 + 2 + 5 + 5 + 2 + 16 = 64 个MDC周期

4.2 前导码和起始码——总线的“起手式”

前导码 (Preamble)

说白了,前导码就是 32 个连续的“1”。主控在 MDIO 线上连续拉高 32 个时钟周期。

为什么要这么多?我个人的理解是:PHY 芯片上电后内部状态不确定,32 个连续的 1 能让它进入一个已知的“等待命令”状态。尤其是对于多端口交换芯片,前导码长了反而更可靠。

我记得有一次调试一款国产 PHY,前导码长度写死了 32 位,但那个 PHY 要求至少 48 位才能唤醒。嗯,这里要注意:虽然 IEEE 802.3 标准规定是 32 位,但个别芯片会有特殊要求,最好看下数据手册里的“Timing Specification”章节。

起始码 (Start)

前导码结束后,紧跟着就是起始码“01”。这是个固定序列,告诉 PHY:“我要开始发正经命令了,注意听。”

你可能会问,前导码不也是“1”吗,为什么不会混淆?

关键就在这个“0”到“1”的跳变。前导码全是 1,起始码先拉低再拉高,这个下降沿就是 PHY 用来对齐后续数据采样的参考点。

4.3 操作码——你是读还是写?

操作码就 2 个比特,决定了这个帧是读还是写:

  • 读操作:10(先发 1,再发 0)
  • 写操作:01(先发 0,再发 1)

这个顺序我记了很久才记牢。一开始总以为读是 01、写是 10。后来我自己总结了一个小技巧:

读的时候,主控要先“发话”(1),再“松口”(0)——因为后面 PHY 要开始回数据了。写的时候,主控先“闭嘴”(0),再“开讲”(1)——因为全程都是主控在驱动。

当然这只是我自己的记忆法,不一定适合每个人。

技巧提示: 在写 MDIO 驱动时,操作码最好用宏定义。比如:

#define MDIO_OP_WRITE   0x02  // 二进制 01,左对齐到高两位
#define MDIO_OP_READ    0x01  // 二进制 10,左对齐到高两位

这样代码可读性会好很多,也便于后期维护。

4.4 地址域——选哪个 PHY、哪个寄存器?

PHY 地址 (5 位)

MDIO 可以挂多个 PHY 在同一条总线上。每个 PHY 有一个物理地址,范围 0x00 到 0x1F,一共 32 个。

地址怎么来的?一般是硬件管脚决定的。PHY 芯片会有一组地址配置引脚(比如 AD0~AD4),上电或复位时硬件采样电平决定地址。

我在项目中遇到过一个问题:板子上两个 PHY 的地址配置都默认是 0x01,结果通信时两个 PHY 同时回应,数据完全乱掉。后来加了个上拉电阻改掉其中一个的地址才解决。你想想看,总线竞争时读到的东西有多离谱就明白了。

寄存器地址 (5 位)

每个 PHY 内部有 32 个寄存器(0x00 ~ 0x1F)。其中前 16 个是 IEEE 802.3 强制规定的基本寄存器,后 16 个由芯片厂商自定义。

常用的基本寄存器包括:

地址 名称 说明
0x00 BMCR (Basic Mode Control Register) 控制复位、Loopback、速度、全/半双工等
0x01 BMSR (Basic Mode Status Register) 链接状态、能力协商结果等
0x02 PHY ID 寄存器 (高 16 位) 芯片厂商和型号标识
0x03 PHY ID 寄存器 (低 16 位) 芯片厂商和型号标识
0x04 ANAR (Auto-Negotiation Advertisement) 宣告本端支持的能力
0x05 ANLPAR (Auto-Negotiation Link Partner) 对端宣告的能力

4.5 转态位和数据——最关键也最容易出问题的地方

转态位 (TA, Turnaround)

这是 2 个时钟周期,用于总线方向切换。读和写在这里行为完全不同。

  • 写操作:TA = 10。主控一直驱动总线,没有方向变化。第一个周期发 1,第二个周期发 0。
  • 读操作:TA = Z0。第一个周期主控释放总线(输出高阻),让给 PHY 驱动;第二个周期 PHY 把数据线拉低(发 0),作为数据开始的标志。

为什么读操作需要高阻态?因为只有这样才能让 MDIO 变成一个双向总线。主控不发数据了,PHY 才能接过去。

我在调试一块 FPGA 加 PHY 的板卡时,驱动里读操作的 TA 第一个时钟没有设成高阻,而是强行拉低了。结果 PHY 永远收不到正确的读回复。查了两天才发现是这里出了问题。所以,读操作千万要记住:第一个 TA 时钟必须释放总线。

数据位 (Data, 16 位)

数据部分固定 16 位,MSB 先传输。不管是读还是写,格式都一样。

  • 写操作:主控在 TA 之后立刻输出 16 个比特的数据。
  • 读操作:PHY 在 TA 之后把 16 个比特的数据逐位放到总线上,主控在每个 MDC 上升沿采样。

写数据示例:

// 写 PHY 地址 0x03、寄存器 0x0A,写入值 0x1234
// MDIO 帧比特序列 (MSB first):
// 前导码(32个1): 1111...1111
// 起始码:        01
// 操作码(写):    01
// PHY地址(0x03): 00011
// 寄存器(0x0A):  01010
// TA:            10
// 数据(0x1234):  0001 0010 0011 0100
// 总比特: 32+2+2+5+5+2+16 = 64

读数据示例:

// 读 PHY 地址 0x03、寄存器 0x0A,读回值假设为 0x5678
// MDIO 帧比特序列 (主控发送部分):
// 前导码(32个1): 1111...1111
// 起始码:        01
// 操作码(读):    10
// PHY地址(0x03): 00011
// 寄存器(0x0A):  01010
// TA(主控释放):  Z (高阻)

// MDIO 帧比特序列 (PHY 回复部分):
// TA(继续):      0
// 数据(0x5678):  0101 0110 0111 1000

警告: 读操作时,如果主控没有正确释放总线,PHY 会检测到总线被占用而放弃回应。常见的表现就是读回的数据全为 0 或全为 1。遇到这种情况,先检查 TA 阶段的总线控制逻辑——我吃了这个亏不少次。

4.6 时序参数(写在前面的话)

帧结构讲清楚了,但硬件上还有几个关键时序要求:

  • MDC 最高频率: 规范上写 2.5 MHz,但很多现代 PHY 可以跑到 25 MHz 甚至更高。保险起见,前期按 2.5 MHz 跑。
  • MDIO 数据建立时间: 在 MDC 上升沿之前,数据需要至少保持 10 ns 稳定。
  • MDIO 数据保持时间: 在 MDC 上升沿之后,数据至少再保持 10 ns。

这些时序参数不是固定的,不同厂家的 PHY 会略有差异。我个人习惯是在驱动里把 MDC 频率配得保守一些,等所有功能都调通了,再慢慢把频率往上提。

4.7 本章小结

MDIO 帧格式其实就是一套固定长度的串行协议:前导码唤醒 -> 起始码对齐 -> 操作码选模式 -> 地址选目标 -> 转态位切换方向 -> 数据传输。读和写的差异主要在操作码和 TA 阶段。

我建议你在看波形的时候,先把 64 个时钟周期数清楚,然后一帧一帧对。这样排查问题会快很多。