第2章:MDIO帧格式详解
咱们搞硬件的,天天跟各种协议打交道。MDIO的帧格式,说白了就是一条指令的结构。你得告诉从设备:“我要干嘛、找谁、查什么、结果放哪”。这章我就把它的帧格式掰开了讲清楚。
一个标准的MDIO帧,总共64个时钟周期。每个周期传1个比特。我刚开始接触这个协议时,总觉得64个周期太长。后来调试时才发现,每个字段都有它的作用,少一个都不行。
2.1 前导码(Preamble)- 32个1的“热身”
前导码占前32个时钟周期,全部发送逻辑“1”。它的作用只有一个:让MDIO从设备同步时钟。
为什么需要这个?你想想看,主设备刚上电时,时钟可能还不稳定。从设备需要一段时间来锁定时钟边沿。我个人习惯在设计PHY时,把前导码检测做得严一些——少于32个1就直接丢弃这一帧。
2.2 起始码(Start of Frame)- 固定为“01”
紧跟在前导码后面的是2位的起始码,固定为“01”。这是帧开始的标志。
你可能会想:为什么不是“00”或者“11”?其实这是为了增加可靠性。“01”这种跳变,从设备很容易检测到。我当时也好奇过这个问题,后来查了IEEE 802.3的规范才知道,设计者是有意选的这个模式。
// C代码例子:检测起始码
uint8_t sof_detected = 0;
if ((mdi_rx_bits[0] == 0) && (mdi_rx_bits[1] == 1)) {
sof_detected = 1; // 检测到起始码
}
// 注意:索引0对应第33个bit(前导码之后的第一个bit)
2.3 操作码(Operation Code)- 读还是写?
操作码也是2位。它决定了这次访问是读操作还是写操作。
| 操作码 | 操作类型 | 说明 |
|---|---|---|
| 10 | 读操作 | 主设备读取PHY的寄存器 |
| 01 | 写操作 | 主设备写入数据到PHY的寄存器 |
这里有个容易搞错的地方。很多初学者会把读操作码记成“01”,其实是反的。我之前在一个项目里就写反过,结果读回来的数据全是错的。排查了两天才发现是操作码搞反了。说白了,就是没认真看手册。
2.4 地址域(Address Field)- 找对设备,找对寄存器
地址域分两部分,各占5位。
- PHY地址(5位):总共可以寻址32个PHY设备。
- 寄存器地址(5位):每个PHY内部最多有32个寄存器。
5位地址能覆盖32个设备,这在大多数应用场景下是够用的。但有些复杂的交换机,可能会用到多个MDIO总线来管理更多PHY。
地址域的范围:PHY地址 0~31,寄存器地址 0~31。你需要通过硬件配置引脚给每个PHY分配一个唯一的地址。
2.5 转向符(Turnaround)- 切换数据方向的关键
转向符占2位,是帧格式里最容易被忽视的地方。
- 读操作:TA为“Z0”。第一个周期是高阻态,第二个周期是0。
- 写操作:TA为“10”。第一个周期是1,第二个周期是0。
为什么读操作要有个高阻态?我解释一下:因为MDIO是双向总线,主设备发送完地址后,需要把总线控制权让给从设备。高阻态这段时间,就是给从设备一个“接管”的机会。我曾经调试一块板子,发现读回来的数据始终不对,后来用示波器一看,转向符的时序完全乱掉了。
2.6 数据域(Data Field)- 真正的“干货”
数据域是16位,也就是2个字节。读操作时,由从设备驱动;写操作时,由主设备驱动。
数据的传输顺序是MSB优先,也就是高位在前。举个例子:如果我要写入0xABCD,先传0xAB的高8位,再传0xCD的低8位。
// 写操作:主设备发送16位数据
// 假设要写入的数据是 data_16
for (int i = 15; i >= 0; i--) {
MDIO = (data_16 >> i) & 0x01;
MDC ^= 1; // 产生时钟上升沿
MDC ^= 1; // 产生时钟下降沿
}
2.7 帧格式总结
我整理了一张表,把各字段的细节全部列出来,方便你查阅:
| 字段 | 位数 | 内容 | 驱动方 |
|---|---|---|---|
| 前导码 | 32 | 全部为1 | 主设备 |
| 起始码 | 2 | 01 | 主设备 |
| 操作码 | 2 | 10(读)/ 01(写) | 主设备 |
| PHY地址 | 5 | 0~31 | 主设备 |
| 寄存器地址 | 5 | 0~31 | 主设备 |
| 转向符 | 2 | Z0(读)/ 10(写) | 读:从设备 / 写:主设备 |
| 数据域 | 16 | 2字节数据(MSB优先) | 读:从设备 / 写:主设备 |
嗯,内容基本就是这样。你把这些字段记熟,之后再分析MDIO时序,心里就有谱了。