2. 时钟、数据线与帧格式——物理层细节
做嵌入式这么多年,我遇到过不少工程师以为MDIO就是随便拉两根线就能通信。其实不然。物理层的时序和帧格式一旦出错,PHY芯片根本不搭理你。这一章我们把时钟、数据线和帧结构掰开揉碎,讲清楚。
2.1 MDC时钟:不能快,也不能乱
MDC是时钟信号,由主控(MAC或调试器)产生。标准规定最高频率是2.5MHz。嗯,你没看错,慢得很。但为什么这么慢?因为MDIO协议本身设计得简单可靠,不需要跑高速。我曾经试过用3MHz的时钟去读PHY寄存器,结果返回的数据全是错的。老老实实降到2MHz,一切正常。
| 参数 | 最小值 | 典型值 | 最大值 | 备注 |
|---|---|---|---|---|
| MDC频率 | — | 2.0 MHz | 2.5 MHz | 超过2.5MHz可能不满足时序 |
| MDC高电平宽度 | 160 ns | 250 ns | — | 占空比50%左右 |
| MDC低电平宽度 | 160 ns | 250 ns | — | 同上 |
| MDIO建立时间 | 10 ns | — | — | 相对于MDC上升沿 |
| MDIO保持时间 | 10 ns | — | — | 相对于MDC上升沿 |
注意:MDC通常是空闲时为低电平。有些调试器会默认输出高电平,这样第一个沿就错了。我自己吃过这个亏——查了三天,才发现MDC极性反了。
2.2 MDIO数据线:一根线,双方向
MDIO是双向数据线,半双工。主控发命令时自己驱动,PHY回复时切给PHY驱动。说白了,你讲完了,就闭嘴,让PHY说。
实现上最简单的方法是用一个三态门。当主控发送时,输出使能拉高;发送完转向位(T)后,输出使能拉低,把线让出来。我在项目中经常看到有人忘记释放数据线,导致PHY无法拉低或拉高,读回来的数据全是0xFFFF。嗯,这个坑很常见。
经验:调试时可以把MDIO线接一个上拉电阻(1.5kΩ~4.7kΩ),这样空闲时线被拉高。如果读到全1,至少证明PHY没把线拉低,可能是地址或命令不对。
2.3 帧格式:一个完美的32位+16位结构
每一帧包含32位命令(主控发出)和16位数据(PHY回复),但有两个特殊情况:读操作PHY会在转向位后驱动16位数据;写操作则是主控继续驱动16位数据。
帧结构如下(以读操作为例):
| 前导码(32个1) | 起始码(01) | 操作码(10) | PHY地址(5b) | 寄存器地址(5b) | 转向位(Z) | 数据(16b) | 空闲位 |
注意前导码是32个连续的1。有些简化的实现会省略前导码,但标准强制要有。我记得有一次调试一块国产PHY,它非要32个前导码才肯响应,少一个都不行。浪费了三个小时。
2.4 各个字段详解
| 字段 | 长度 | 发送方 | 取值/说明 |
|---|---|---|---|
| 前导码 (Preamble) | 32位 | 主控 | 32个连续的1,用于同步。调试器在空闲时钟数过多时会先发前导码唤醒PHY。 |
| 起始码 (Start) | 2位 | 主控 | 01,固定。代表帧开始。 |
| 操作码 (Op Code) | 2位 | 主控 | 01=写,10=读,00或11保留。 |
| PHY地址 (PHYAD) | 5位 | 主控 | 0~31,对应PHY芯片的地址引脚。我习惯用0x1F(31)做广播地址。 |
| 寄存器地址 (REGAD) | 5位 | 主控 | 0~31。标准寄存器0~15,各厂商自定义16~31。 |
| 转向位 (Turnaround) | 2位 | 先主控,后PHY | 读时主控发Z(高阻),PHY发0作为应答;写时主控直接发10。 |
| 数据 (Data) | 16位 | 看操作 | 写操作由主控驱动,读操作由PHY驱动。 |
| 空闲位 (Idle) | 至少1位 | — | MDIO高阻,上拉保持高电平。 |
关键点:转向位是读操作最容易出错的地方。主控必须在操作码结束后,把MDIO线设为高阻,然后等一个时钟周期让PHY接管。如果主控释放晚了,PHY没法正常驱动,读到的数据会错位。
2.5 时序与帧结构——一张图看懂
下面这张SVG图把时钟、数据线和帧格式整合到一起。你可以对着图理一遍:前导码发完后,时钟一直运行,数据线按字段顺序切换。注意读操作时转向位那里的灰色区域代表主控释放,PHY开始驱动。
2.6 动手调试时的避坑指南
- 前导码不能偷懒:我见过有些软件库为了省时间,只发16个1。结果PHY完全不识别。严格按32个1来,宁多勿少。
- 转向位的高阻时间:读操作时,主控必须在操作码最后一位发出后立刻释放MDIO。如果你用GPIO模拟,要等一个完整的时钟周期才能再读。可以用示波器观察MDIO线上是否有短暂的高阻(浮空)状态。
- 时钟空闲时保持低电平:有些PHY在MDC高电平时会误以为有数据。所以调试器在帧与帧之间最好把MDC拉低。我习惯在每帧结束后加8个空闲时钟,MDC保持低,MDIO高阻。
- 地址确认:PHYAD引脚一般有上拉/下拉,实际地址和原理图上可能不一样。我曾经遇到一个板子,原理图标的PHYAD[2:0]=001,但实际测量是011,多亏用扫描函数把所有地址读了一遍才发现。
小技巧:你可以先发写操作把PHY寄存器0(控制寄存器)的值读出来,看是否得到标准回复。如果回复全是0xFFFF,大概率是地址或时钟问题;如果全0,可能是PHY没工作(复位或电源问题)。