MDIO/MDC接口概述:IEEE 802.3 Clause 22与Clause 45的区别

我们做硬件设计的,几乎天天要和MDIO/MDC打交道。但说实话,很多人对Clause 22和Clause 45的区别,也就是个模糊印象。我刚开始接触这接口时,也踩过坑,所以今天咱们好好捋一捋。

从两根线说起

MDIO(Management Data Input/Output)和MDC(Management Data Clock),说白了就是两根线。一个时钟,一个数据。用来干嘛?管理物理层(PHY)芯片。你想想看,以太网的PHY芯片内部有几十上百个寄存器,靠什么读写?就靠这两根线。

但IEEE 802.3标准里,对这个接口的定义有两个版本:

  • Clause 22:1998年就定下来的老标准
  • Clause 45:2000年之后才引入的新规范

这两者的差异,远比很多人想象的更大。我建议你在做新设计时,一定要先搞清楚该用哪个。

Clause 22:经典传承

Clause 22是最原始的MDIO规范。它的帧结构是这样的:

| 前导(32bit) | 起始(2bit) | 操作码(2bit) | PHY地址(5bit) | 寄存器地址(5bit) | 转向(2bit) | 数据(16bit) |

总共需要64个MDC时钟周期(32位前导 + 32位帧)。嗯,这里要注意,现在很多PHY支持“前导省略”,可以减少到32个时钟。

关键参数:
  • PHY地址空间:5位,最多32个设备
  • 寄存器地址空间:5位,每个设备最多32个寄存器
  • 标准MDC频率:2.5 MHz(最大8 MHz)
  • 支持设备类型:仅PHY

我在项目中遇到过,有些老设备也支持Clause 22的设备码扩展。但那不是标准做法。一般情况下,Clause 22就是简单、经典、够用——前提是你只做10/100/1000M的设计。

Clause 45:更广阔的世界

Clause 45是随着万兆以太网发展起来的。它解决了一个大问题:Clause 22的地址空间太小了。你想想看,一个万兆PHY内部可能有PMA、PCS、WIS等多个功能模块,每个模块都有自己的寄存器组。Clause 22那5位寄存器地址,完全不够用。

地址帧(数据帧不是同步的):
| 前导(32bit) | 起始(2bit) | 操作码(2bit) | 端口地址(5bit) | 设备类型(5bit) | 转向(2bit) | 寄存器地址(16bit) |

数据帧(读/写):
| 前导(32bit) | 起始(2bit) | 操作码(2bit) | 端口地址(5bit) | 设备类型(5bit) | 转向(2bit) | 数据(16bit) |

注意,Clause 45需要两个帧才能完成一次读写:先发地址帧设置寄存器地址,再发数据帧进行读写。所以操作时间比Clause 22翻了一倍。这是个重要的取舍。

设备类型(DEVAD)快查表:
DEVAD值设备类型
0PHY
1PMA
2PCS
3WIS
4DTE XS
5TCI
6Auto-Neg
7-31保留

快速对比表格

我把它们的核心区别整理成了一张表,方便你快速查阅:

属性Clause 22Clause 45
标准引入1998年2000年后
帧结构单帧32位双帧(地址+数据)
寄存器地址宽度5位(32个)16位(65536个)
设备地址宽度无(仅PHY)5位(32种设备类型)
支持设备仅PHYPHY/PMA/PCS/WIS等
MDC最高频率8 MHz12 MHz(典型)
兼容性基本所有PHY都支持万兆以上PHY必备

避坑指南:我的血泪教训

  • 地址冲突:我曾经遇到过板上两个PHY用Clause 22通信,结果PHY地址都设成了0x1B,直接导致读写混乱。设计时一定要确保每个PHY地址唯一。
  • 帧格式混淆:很多工程师写驱动时,Clause 45的“地址帧”和“数据帧”没区分开。我建议你在代码里加一个状态机,明确区分当前是地址阶段还是数据阶段。
  • 时钟频率过高:有些国产PHY对MDC频率敏感。我用8MHz去读某款芯片就遇到过数据丢失。最后老老实实降到2.5MHz才稳定。

代码示例:读取PHY寄存器

这里我分别给一个Clause 22和Clause 45的读取示例。注意看帧结构的差异。

// Clause 22 读取PHY寄存器 0x10
// PHY地址 = 0x03, 寄存器地址 = 0x10
uint32_t clause22_read(uint8_t phy_addr, uint8_t reg_addr) {
    uint32_t frame = 0;
    // 前导 (32个1) — 很多PHY可以省略
    // 起始码: 01
    frame |= (0x01 << 30);
    // 操作码: 10 (读)
    frame |= (0x02 << 28);
    // PHY地址: 5位
    frame |= ((phy_addr & 0x1F) << 23);
    // 寄存器地址: 5位
    frame |= ((reg_addr & 0x1F) << 18);
    // 转向: 2位 (Z+0)
    frame |= (0x02 << 16);
    // 发送32位帧...
    // 接收16位数据...
}

// Clause 45 读取设备类型2 (PCS) 的寄存器0x1000
// 端口地址 = 0x03, 设备类型 = 0x02
void clause45_read(uint8_t port_addr, uint8_t dev_type) {
    // 步骤1: 发送地址帧 (设置寄存器地址为0x1000)
    uint32_t addr_frame = 0;
    // 起始码: 00
    // 操作码: 00 (地址)
    addr_frame |= (0x00 << 28);
    // 端口地址: 5位
    addr_frame |= ((port_addr & 0x1F) << 23);
    // 设备类型: 5位
    addr_frame |= ((dev_type & 0x1F) << 18);
    // 转向: 00
    addr_frame |= (0x00 << 16);
    // 寄存器地址: 16位
    addr_frame |= (0x1000 & 0xFFFF);
    // 发送地址帧...

    // 步骤2: 发送数据帧 (读取)
    uint32_t data_frame = 0;
    // 起始码: 00
    // 操作码: 11 (读, 帧结束后数据返回)
    data_frame |= (0x03 << 28);
    //... 其余字段类似
    // 发送数据帧...
    // 返回接收到的16位数据...
}

知识体系:Clause 22 vs Clause 45

MDIO/MDC接口:Clause 22 vs Clause 45 Clause 22(经典) 单帧:32位+前导 完整操作需64个MDC时钟 PHY地址:5位(32个) 寄存器地址:5位(32个) 支持设备:仅PHY 无设备类型区分 MDC频率:≤8 MHz 典型值2.5 MHz 局限:地址空间小 Clause 45(扩展) 双帧:地址帧+数据帧 完整操作需96个MDC时钟 端口地址:5位 设备类型:5位(32种) 寄存器地址:16位(65536个) 支持PMA/PCS/WIS等 MDC频率:≤12 MHz 典型值5~8 MHz 优势:大地址、多设备 选择建议:10G以下用Clause 22,10G及以上用Clause 45

这张图很直观:左边是Clause 22,右边是Clause 45。核心差异就两点:地址空间和设备类型支持。你在做系统设计时,根据PHY的规格书选择就行。

实战建议:
  • 如果PHY规格书只写了“MDIO”,默认用Clause 22
  • 如果规格书明确写“Clause 45”或“C45”,就用Clause 45
  • 有些PHY同时支持两个标准,通过寄存器切换。我习惯在初始化时先读版本寄存器来判断