4. MDIO/MDC 总线协议详解:信号定义与电气特性
MDIO 跟 MDC,这俩名字听起来挺唬人。说白了,就是管理以太网 PHY 芯片的一条串口。我刚开始接触的时候也觉得这东西挺玄乎,两根线就能搞定配置?后来做项目多了,发现就这么两根线,坑还不少。咱们今天把它彻底搞明白。
4.1 信号定义:就是两根线的事儿
MDIO 总线其实就两路信号:
- MDC (Management Data Clock):管理数据时钟。由 MAC 侧或者控制器主动产生,是个方波。
- MDIO (Management Data Input/Output):管理数据输入输出。这是一根双向数据线,既用来发命令,也用来收数据。
你可以把 MDC 想象成节拍器,MDIO 就是跟着节拍跳舞的人。谁打拍子?打拍子的人永远是主设备(一般是 CPU 或者 MAC 控制器)。PHY 芯片只管跟着拍子回应数据。这个关系搞清楚,后面的时序就自然明白了。
4.2 电气特性:电压域匹配与布线
嗯,这里要注意。MDIO/MDC 的电压通常跟相连的 MAC 或控制器 IO 电压一致。常见的有 3.3V 和 1.8V 两种。我个人习惯在搞定原理图之前,一定要先确认两边的电压域是否匹配。
| 参数 | 典型值 | 说明 |
|---|---|---|
| MDC 最高频率 | 2.5 MHz (标准) / 12.5 MHz (高速) | 标准模式对齐 802.3 规范 |
| MDIO 空闲状态 | 高阻 / 上拉高电平 | 待机时避免总线冲突 |
| 上拉电阻 | 1.5kΩ ~ 10kΩ | 取决于总线负载和走线长度 |
| 负载电容 | 不超过 50pF 为宜 | 线太长或者挂的 PHY 太多会影响信号质量 |
布线方面,能短尽量短。MDC 虽然是时钟,但速度不高,一般不用走差分或者阻抗控制。不过,如果你想把它拉到排针或者连接器上,我建议加个小电阻(比如 22Ω)靠近发送端串一下,可以减少过冲。我曾经在一个项目里就因为 MDC 线走得太长还没加串阻,导致读回来的寄存器值老是跳变,查了整整一个下午。
4.3 协议时序:读和写,就是串行帧
MDIO 的协议帧结构,在 IEEE 802.3 规范里定义得很清楚。我把它拆成两部分来讲:写帧和读帧。你想想看,它俩的区别其实就两个 bit 的事儿。
仔细看看这张图的帧结构。每一帧都包含以下几个字段:
- 前导码 (PREAMBLE):32 个连续的 '1'。这是同步用的,告诉 PHY 准备接收命令。有的控制器支持跳过前导码,但我建议除非你明确知道 PHY 支持,否则别省。
- 帧起始 (ST):固定为 '01'。标识一个有效管理帧的开始。
- 操作码 (OP):'01' 代表写,'10' 代表读。就这两 bit,决定了数据流向。
- PHY 地址 (PHYAD):5 bit,支持 0~31。板子上每个 PHY 芯片的地址必须唯一。
- 寄存器地址 (REGAD):5 bit,支持 0~31。这就是我们常说的 MMD 或者 Clause 22 的直接寄存器。
- 周转状态 (TA):这是个两时钟周期的空档。写操作时,MAC 驱动 '10';读操作时,MAC 释放总线,PHY 先回应一个 '0'(高阻转低),再拉高,代表准备好数据。
- 数据 (DATA):16 bit。写操作由 MAC 发出,读操作由 PHY 回复。
4.4 地址映射与 Clause 22 / Clause 45
讲到这里,不得不提 IEEE 802.3 的两个标准子句。
| 标准 | PHY 地址 | 寄存器地址 | 特点 |
|---|---|---|---|
| Clause 22 | 5 bit (0-31) | 5 bit (0-31) | 基础、简单、普遍 |
| Clause 45 | 5 bit (端口地址) + 21 bit (设备类型/地址) | 16 bit | 支持更复杂、更多扩展寄存器 |
大多数通用 PHY 都支持 Clause 22。如果你在做千兆网或者更复杂的 PHY,可能就需要用到 Clause 45 了。Clause 45 的帧格式略有不同,它把地址空间扩展得更大。但我个人的建议是:前期先搞定 Clause 22 的基本读写功能,把控制通路跑通之后再去看 Clause 45 的扩展访问,这样调试起来不会乱。
4.5 寄存器读写流程示例
下面我写一段伪代码,帮你捋一下实际的底层操作流程。这在嵌入式驱动开发里非常典型。
// 伪代码:写 PHY 寄存器
void mdio_write(phy_addr, reg_addr, data) {
// 1. 构造帧字段:前导码 (32个1) + ST(01) + OP(01) + PHYAD + REGAD + TA(10) + DATA
// 2. 驱动 MDIO 引脚为输出(写操作全程由 MAC 驱动 MDIO)
// 3. 对于 MDC 的每个上升沿,按位送出 MDIO 数据
// 4. 发送完后,维持 MDIO 为高阻,等待下一帧
mdio_set_dir(OUTPUT);
// 发送前导码 32 个 '1'
for (int i = 0; i < 32; i++) {
mdio_set_bit(1);
mdio_toggle_mdc();
}
// 发送 ST + OP + PHYAD + REGAD + TA + DATA
// 具体按位发送省略...
mdio_set_dir(INPUT); // 释放总线
}
// 伪代码:读 PHY 寄存器
uint16_t mdio_read(phy_addr, reg_addr) {
uint16_t data = 0;
// 1~4 步同上,但 OP 码为 '10'
// TA 阶段:MAC 先释放 MDIO,转为输入
// PHY 在 TA 的第二位开始驱动 MDIO
mdio_set_dir(OUTPUT);
// 发送前导码 32 个 '1'
// 发送 ST + OP + PHYAD + REGAD
mdio_set_dir(INPUT); // TA 阶段前切换方向
// 等待 TA 完成(PHY 拉低再拉高)
// 读取 16 bit 数据
for (int i = 0; i < 16; i++) {
mdio_toggle_mdc();
data <<= 1;
data |= mdio_read_bit(); // MDC 上升沿读取
}
return data;
}
4.6 总线拓扑与常见电气问题
MDIO 总线通常是开漏加外部上拉的结构。由于是双向传输,时序上对上升沿和下降沿有要求。你想想看,一根线上既有主设备驱动,又有从设备驱动,信号质量处理不好就容易出问题。
- 总线竞争: 如果多个主设备在同一总线上,或者 PHY 未正确释放 MDIO,会导致电平冲突。我建议在设计时就明确:总线只允许一个主设备驱动。
- 信号斜率: MDC 频率提高后,信号边沿变缓可能会导致建立时间不够。可以使用施密特触发器输入的引脚,或者在 PCB 上加小电容滤波。
- 电平转换: MAC 和 PHY 电压不一致时,需要加电平转换芯片。我通常选用 BSS138 搭建的简单双向电平转换电路,便宜又好用。
嗯,关于 MDIO/MDC 总线的核心内容,就讲到这里。这部分理解了,后面讲寄存器具体配置时,你就知道怎么下命令、怎么收数据了。