3. MDIO 读写时序分析
MDIO 总线虽然只有两根线——MDC(时钟)和 MDIO(数据),但时序细节稍不留神就会踩坑。我个人习惯在调 D-PHY 寄存器之前,先把时序图刻在脑子里,就像记 GPIO 模拟 I²C 那样,但 MDIO 比 I²C 更"慷慨"——它给足了建立保持时间,却也埋了一个小小的陷阱。
3.1 读操作波形
读操作的标准帧格式长这样:32 位 IDLE + 32 位 PREAMBLE + 2 位 ST + 2 位 OP + 5 位 PHY ADDR + 5 位 REG ADDR + 2 位 TA + 16 位 DATA。听起来一串,其实拆开看很简单。
前 32 个 MDC 周期,MDIO 线保持高阻(高电平)。这叫 IDLE。紧接着 32 个周期发连续 1(全 1),叫 PREAMBLE。然后 ST = 01,OP = 10(读)。之后 PHY 地址 5 位,寄存器地址 5 位。再是 2 个 TA 周期——第 1 个 TA 周期 MDIO 线变高阻(三态),第 2 个 TA 周期从机开始驱动数据。最后 16 个周期从机把寄存器数据放到 MDIO 上。
这里有一个隐藏问题:很多年轻工程师不知道 TA 周期实际是两个独立的时钟边沿。我曾经见过一个案例,有人用 GPIO 模拟 MDIO,在 TA 期间没有将 IO 切换为输入,导致总线冲突,读回来的值全为 0xFF。排查了两天才发现,调回来之后从此以后在 TA 开始前我都会加一个 GPIO 方向切换。
读操作关键时序窗口:
- MDC 上升沿采样数据,MDIO 在 MDC 下降沿变化(对主机而言)
- TA 第一个周期:主机释放 MDIO(三态),从机在第 2 个周期开始驱动
- 从机输出的数据在 MDC 上升沿被主机采样,建立时间 ≥ 10 ns,保持时间 ≥ 10 ns(典型值)
3.2 写操作波形
写操作跟读操作的前半段几乎一样除了 OP 码变成 01。你们看:ST=01, OP=01, PHY ADDR, REG ADDR, 然后 TA 变成 2 个周期——但注意!写操作的 TA 两个周期中主机一直驱动 MDIO 线,输出 10(Z 和 0)。为什么不一样?因为写操作不需要让从机驱动数据,所以 TA 期间主机保持控制,发一个 Z(高阻)和一个 0。这里的 Z 是向总线插入高阻,让从机知道这是写操作的 TA,而不是数据。
然后主机继续驱动 16 位写入数据。整个写操作中主机从未释放 MDIO(除了 TA 第一个周期故意放高阻,但那个是协议规定的)。
| 字段 | 长度 | 读操作值 | 写操作值 | 备注 |
|---|---|---|---|---|
| PREAMBLE | 32 | 32 个 1 | 32 个 1 | 有些 PHY 允许短前导码,但规范要求完整 |
| ST | 2 | 01 | 01 | 起始码 |
| OP | 2 | 10 | 01 | 操作码 |
| PHY ADDR | 5 | 0x00~0x1F | 0x00~0x1F | D-PHY 的地址通常在 0x00~0x04 |
| REG ADDR | 5 | 0x00~0x1F | 0x00~0x1F | 寄存器地址 |
| TA | 2 | Z, 0(从机驱动) | Z, 0(主机驱动) | 关键差异 |
| DATA | 16 | 读回值 | 写入值 | MSB 优先 |
嗯,写操作容易犯的错误是 TA 阶段忘记把 MDIO 置为 Z。我调过一款 FPGA 驱动 D-PHY,当时写操作总是失败,示波器抓波形看到 TA 阶段一直输出高电平,导致从机误判成非法序列。把这个 Z 加上去就对了。
3.3 最小周期
MDIO 总线的时钟频率由 MDC 决定,标准最高 2.5 MHz(周期 400 ns)。但很多 PHY 支持 10 MHz 甚至更高。我的建议是:除非你明确知道 D-PHY 器件手册允许,否则别超过 2.5 MHz。为什么?
- MDIO 是电平驱动,没有上拉电阻快速上升的保证,频率越高信号边沿越差
- 电缆或 PCB 走线长度影响反射,高速下容易误采样
- D-PHY 的 MDIO 输入通常有施密特触发器,但建立保持时间在 2.5 MHz 下很宽松
我曾经在一个项目上,为了压榨性能把 MDC 调到 10 MHz,结果在 20 cm 排线上读回的数据每 100 次就错 1 bit。降回 2.5 MHz 后稳定运行。你说它不兼容吗?不,手册写的是支持 10 MHz,但实际工作环境干扰多了就容易翻车。所以很多时候保守一点能省很多排查时间。
时序参数速查(典型值):
| MDC 最小高电平时间 | 160 ns |
| MDC 最小低电平时间 | 160 ns |
| MDC 周期最小值(2.5 MHz) | 400 ns |
| MDIO 建立时间(相对于 MDC 上升沿) | ≥ 10 ns |
| MDIO 保持时间(相对于 MDC 上升沿) | ≥ 10 ns |
| MDIO 输出延迟(从 MDC 下降沿到数据变化) | ≤ 300 ns |
3.4 空闲状态
当没有 MDIO 操作时,总线处于空闲状态。MDC 可以停止(保持高或低都可以,但建议保持低以降低功耗),MDIO 必须被拉高到 VDD(通过上拉电阻)。为什么是高电平?因为 MDIO 总线是开漏/三态结构,空闲时所有设备都不驱动,靠上拉电阻把总线置为逻辑 1。这样才能在下一个操作开始前让 PREAMBLE 的连续 1 被正确识别。
有些同学会问:MDC 能一直跑吗?可以,但通常建议停止以减少 EMI。不过停止期间 MDIO 要保持高电平。如果你的 MCU 或 FPGA 内部有弱上拉,记得确认上拉电阻值不能太大(通常 1.5 kΩ~10 kΩ 之间),否则信号上升沿会变慢,影响时序。
重要: 空闲时 MDIO 不能是低电平或浮空。如果总线长期处于低电平,后续的 PREAMBLE 序列(32 个 1)会丢失前几个 1,导致帧同步失败。我曾经在调试时遇到过硬件上 MDIO 漏了上拉电阻,示波器看到恒低,查了半天才发现。
3.5 时序概览:一张图看懂
下面这张 SVG 图把读操作和写操作的关键帧结构放在一起对比。注意读操作的 TA 期间从机驱动(虚线代表主机释放总线),写操作的 TA 期间主机全程控制。颜色标识也让逻辑更清晰。
好了,这张图把读写的帧结构和空闲状态都串起来了。你可能会发现 TA 阶段用的是红色,数据段是绿色,前导字段是蓝色——这种配色我用了好几年,一眼就能区分不同的职责。
3.6 实战提示
最后补充几个我在调试中总结的要点:
- 看波形不要只盯着 MDIO 电平: 把 MDC 和 MDIO 同时抓,标注每个上升沿对应的值。我会用逻辑分析仪解码 MDIO,但写 FPGA 时习惯自己抓原始波形,因为解码器可能隐藏毛刺。
- 上电后先发一次读操作验证空闲电平: 有些设计中 MDIO 的上拉电阻没焊或者虚焊,导致空闲状态异常。先读一个已知寄存器,如果返回全 1 可能是总线悬空,返回全 0 可能是总线被拉低。
- 模拟 MDIO 时注意时钟极性和相位: 虽然 MDIO 规范规定数据在 MDC 下降沿变化,上升沿采样,但有些 FPGA 的 IOB 输出延迟会造成边沿偏移。我一般在 MDC 下降沿之前 10 ns 就更新 MDIO,留足余量。
我的个人调试步骤:
- 用示波器探头夹到 MDC 和 MDIO,触发在 MDC 上升沿
- 发送一个读操作(地址设为 0x00,多数 PHY 支持读 ID 寄存器)
- 测量 MDC 周期、高/低电平宽度、MDIO 建立/保持时间
- 如果读回全 F,检查 MDIO 空闲电平;如果全 0,检查 TA 释放时机
说白了,时序这东西,光看文档没用,必须上手抓波形。MDIO 不像高速串行协议那么苛刻,但它的“太简单”反而容易让人麻痹。当你把一个读操作波形完整抓下来,并且确认每一拍都符合预期的时候,再往下写 D-PHY 的配置,心里就有底了。