MDIO帧格式详解
做D-PHY调试,MDIO是绕不开的看家本事。说白了,它就是一根时钟线加一根数据线的串行协议,用来读或写PHY芯片里的寄存器。我刚开始接触时,满脑子都是“这玩意怎么跟SPI这么像?” 结果坑就出在这“像”字上——帧结构完全不同。咱们把它掰开揉碎了讲。
标准帧结构
MDIO的每一帧固定32位,加上2个空闲位。你想想看,这32个比特按顺序排成下面这张表:
| 字段 | 位宽 | 说明 |
|---|---|---|
| 前导码(Preamble) | 32位 | 全部为1,用来同步时钟 |
| 起始码(Start) | 2位 | 固定值 01 |
| 操作码(Op Code) | 2位 | 10=读,01=写 |
| PHY地址 | 5位 | 目标PHY编号,范围0~31 |
| 寄存器地址 | 5位 | 寄存器编号,范围0~31 |
| 转向位(TA) | 2位 | Z0(读时为高阻+0),10(写时直接驱0) |
| 数据位 | 16位 | 读返回值或写数据 |
| 空闲位(Idle) | 2位 | 高阻,结束标志 |
关键记忆法:整帧 32+2 位,注意转向位有“Z”态——三态门没处理好,读操作会一直返回 0x0000。
我遇到过最离谱的案例:前导码少了2个1,PHY直接无视。手册上说32个1,实际用示波器数到30个就被打断。后来我老实做了个计数器,不够32个就补。
寄存器访问流程(读/写)
读和写的流程听着简单,但时序上稍有差别。先列个步骤对比:
- 写操作:主机发完32位帧 + 2空闲,PHY收到后立即更新内部寄存器。不需要应答。
- 读操作:主机先发前32位(包含读命令),转向位第一个时钟高阻,第二个时钟驱0;之后PHY接过数据线控制,把16位结果送回来。主机再拉高阻。
我个人的习惯:读操作时在转向位尾部故意多等半个时钟,防止PHY还没准备好就开拉数据线——很多入门级PHY芯片的时序余量只有几个纳秒。
时序参数雷区
写代码最头疼的是时钟速度。MDIO标准上允许最高2.5MHz,但别盲目冲满。我踩过坑:某国产PHY在2.0MHz下读写正常,换到2.5MHz读返回全0。查了老半天,不是帧结构不对,是PHY端的IO驱动能力不够。看我列个实战宽容度表:
| 参数 | 标准最小 | 推荐值 | 我的经验值 |
|---|---|---|---|
| MDC频率 | DC ~ 2.5 MHz | 1.0 MHz | 800 kHz(留余量) |
| 时钟低/高电平时间 | ≥ 200 ns | 500 ns | 625 ns |
| 数据建立时间 | ≥ 10 ns | 50 ns | 100 ns |
| 数据保持时间 | ≥ 10 ns | 50 ns | 50 ns |
注意:上面表格里的“推荐值”是基于多数PHY在市面上表现。如果你的PCB走线超过10cm,建立时间建议再加50ns。不然跑1MHz也会丢数据。
代码实现要点
我习惯用状态机来驱动MDIO,因为原子操作好调试。下面是一个简化版读写函数框架(伪代码):
// 写寄存器
void mdio_write(uint8_t phy_id, uint8_t reg, uint16_t data) {
mdio_start(); // 前导码+起始码
mdio_send_bits(0b01, 2); // 操作码:写
mdio_send_bits(phy_id, 5);
mdio_send_bits(reg, 5);
mdio_send_ta_write(); // 转向位 (10)
mdio_send_bits(data, 16);
mdio_idle();
}
// 读寄存器
uint16_t mdio_read(uint8_t phy_id, uint8_t reg) {
mdio_start();
mdio_send_bits(0b10, 2); // 操作码:读
mdio_send_bits(phy_id, 5);
mdio_send_bits(reg, 5);
mdio_turnaround_read(); // 转向位 Z0
uint16_t val = mdio_recv_bits(16); // PHY驱动数据
mdio_idle();
return val;
}
我曾经掉过的坑:转向位第二个时钟沿,主机必须把MDIO设成输入模式再去采样,否则会跟PHY的输出冲突。我最初用GPIO读前忘了切换方向,示波器上看到漂亮的信号冲突尖峰——那个教训值五百块打样费。
帧结构与流程的图形化
下面我把整个帧的布局画成一张图,你看一眼就能记住每个字段的位置和长度。绿色块代表主机驱动,橙色块代表PHY驱动(读时数据线切换)。
这张图里我特别标了“数据线切换”的位置——转向位(Z0)的第二个时钟沿前,主机必须释放MDIO总线,改由PHY驱动。如果你用示波器抓,会看到一截“高阻”电平(一般是三态门输出悬空),然后变成PHY送来的数据。那个Z态持续时间只有半个时钟周期,但丢分分钟出事。
一句话心得:访问MDIO寄存器,最稳妥的做法是每次读或写之间留至少2个空闲位,让总线彻底释放。别信“连续读写”可以省时间——碰到弱上拉的PHY你丢数据丢到哭。