第2章 MDIO/MDC接口协议:MII管理接口的读写时序与帧格式
MII管理接口,说白了就是PHY芯片的”配置通道“。你要调速率、改电平、查状态,全得靠这两根线——MDIO和MDC。我在项目里见过不少同事,配寄存器前根本不关心时序,结果读回来的数据全乱套。嗯,这一章咱们就把它彻底捋清楚。
2.1 什么是MDIO/MDC?
MDIO(Management Data Input/Output)是双向数据线,MDC是时钟线。它们组成了一个类似SPI的串行总线,但比SPI更“薄”——只有一主多从、固定帧长。我习惯叫它”小两线“。
- MDIO:数据信号,由主设备(MAC或CPU)驱动,PHY在特定相位应答。
- MDC:时钟信号,最高速率通常2.5MHz(某些支持4MHz),由主设备产生。
- 拓扑:MDC共享给所有PHY,MDIO需要上拉电阻(典型值1.5kΩ~4.7kΩ)。
2.2 帧格式详解
为什么说MDIO协议的帧结构特别”固执“?因为IEEE 802.3规定死了:一帧必须是32个时钟周期+数据段。我当年第一次写驱动时,少了1个时钟就吞了整个世界……
具体的帧由下面几个字段组成(以Clause 22为例):
| 字段名 | 位数 | 方向 | 说明 |
|---|---|---|---|
| PREAMBLE | 32 | 主→从 | 连续32个“1”,MDIO保持高阻前同步 |
| ST (Start of Frame) | 2 | 主→从 | 固定为 01 |
| OP (Op Code) | 2 | 主→从 | 10 读操作;01 写操作 |
| PHYAD | 5 | 主→从 | 5位PHY地址,范围0~31 |
| REGAD | 5 | 主→从 | 5位寄存器地址,范围0~31 |
| TA (Turn Around) | 2 | 高阻抗→主/从 | 读时:第1个时钟高阻, 第2个时钟PHY驱动0;写时:主设备驱动第2个时钟0 |
| DATA | 16 | 主/从 | 写:主设备输出;读:PHY输出 |
简单归纳:一帧要读或写一个16位寄存器。前32位preamble是“清场”,之后依次送地址、操作码、PHYAD、REGAD,最后是TA和DATA。我在项目里调试时,最喜欢用逻辑分析仪抓TA段——如果PHY没正确回应0,大概率是地址配错了。
// 读写操作对照表(Clause 22)
// OP码: 10 = 读, 01 = 写
// PREAMBLE: 32个1
// 示例:读PHY地址1,寄存器0x10
// 帧序列:
// [1]*32 + 01 + 10 + 00001 + 10000 + Z/0 + 16-bit data
// 写:同上,但TA位由主设备驱动
2.3 读写时序图
先看读操作时序。MDC上升沿采样MDIO。前32个1之后,ST、OP、PHYAD、REGAD都在MDC上升沿被PHY捕获。TA段特别有意思:第33个时钟MDIO进入高阻,第34个时钟PHY拉低MDIO作为响应。如果你用GPIO模拟,这个高阻必须靠三态门实现,否则总线冲突。
写操作类似,只不过TA段第2个时钟由主设备拉低,然后直接输出16位数据。嗯,这里要注意:写操作后PHY会立即把新值写入寄存器,所以连续读写同一个寄存器时,中间最好插一个空周期(或等待至少10个MDC)让PHY稳定。
2.4 SV图:MDIO/MDC句协议状态机
2.5 Clause 22 vs Clause 45
你可能已经听说过Clause 45——这是后来为了扩展寄存器地址(最多32个页面,每个页面最多32个寄存器)而设计的。我做千兆PHY时常用Clause 22,但遇到10GBase-T这类复杂PHY,Clause 45是必选项。
| 对比项 | Clause 22 | Clause 45 |
|---|---|---|
| 帧长度 | 64个MDC(PRE+ST+OP+ADDR+TA+DATA) | 64个MDC(PRE+ST+OP+PORT+DEV+TA+DATA) |
| ST字段 | 01 | 00 |
| OP字段 | 10读,01写 | 00读(递增),11读(固定),01写 |
| PHYAD vs DEVAD | PHYAD 5位 + REGAD 5位 | PORTAD 5位 + DEVAD 5位 + DATA/ADDR |
| 地址空间 | 32 PHY × 32 寄存器(1KB) | 32 PORT × 32 DEV × 64KB 地址空间 |
| 典型应用 | 百兆/千兆PHY | 万兆/多端口/MACsec PHY |
你想想看,如果你的PHY支持Clause 45,但MAC只实现了Clause 22,那怎么办?有些Phy可以通过strap引脚或shadow寄存器切换模式。我曾在某款国产PHY上踩过这个坑——寄存器手册写的是Clause 45,但读操作却按Clause 22也能得到部分数据,其实是内部做了映射,但地址重叠了……
2.6 软件模拟的注意事项
很多嵌入式系统用GPIO模拟MDIO/MDC。我个人的习惯是:先拉低MDC,然后设置MDIO,再拉高MDC,保持至少1μs高电平脉冲。为什么?因为PHY内部有采样窗口,如果MDC高电平太短,采样可能不稳。
- 初始化时GPIO配置为开漏输出,外部上拉电阻拉到3.3V。
- 每次发送位前,MDC先置低,延时至少100ns。
- MDIO设置电平后,MDC置高,延时至少100ns(如果CPU主频高,最好用NOP循环或定时器)。
- 读操作时,在MDC高电平期间采样MDIO电平(用读IO指令)。
- TA段的高阻态:通过GPIO方向寄存器输入模式实现。
// 伪代码:写一位到MDIO
void mdio_write_bit(int bit) {
gpio_set(MDC_PIN, 0); // 拉低MDC
delay_ns(200);
gpio_set(MDIO_PIN, bit); // 设置MDIO电平
delay_ns(200);
gpio_set(MDC_PIN, 1); // 拉高MDC
delay_ns(200);
}
// 读一位:在MDC高电平时采样
int mdio_read_bit(void) {
gpio_set(MDC_PIN, 0);
delay_ns(200);
gpio_set(MDC_PIN, 1);
delay_ns(100); // 等PHY输出稳定
int val = gpio_get(MDIO_PIN);
delay_ns(100);
return val;
}
volatile变量和__NOP解决。
好了,MDIO/MDC协议的基本内容就这些。时序和帧格式是后续寄存器配置的地基,理解得越透,后面踩坑越少。接下来你可以照着帧结构自己动手抓一次读写波形,再结合PHY的数据手册验证地址和数据的对应关系。嗯,看到波形和预期一致时,那种成就感——我当年第一次看到MDIO上回来的16位数据完全正确,高兴了整整一天。