3. MDIO 帧结构:拆开每个比特
MDIO 的通信,说白了就是靠一帧一帧的数据在 MDC 时钟的节拍下搬运。你想想看,几十个比特排成一串,哪个位代表什么含义,协议是定死的。要是移位错了一位,整个帧就废了。我最早调 MDIO 的时候,就吃过「数错比特位」的亏,逻辑分析仪抓出来看着都对,但 PHY 就是不理我……后来才发现,是我把前导码的长度数错了。废话不多说,咱们把这一帧拆开,一个一个字段讲清楚。
3.1 帧的完整结构
标准 MDIO 帧一共 64 个比特。为什么是 64?因为协议规定死了,多一个少一个都不行。下面这张图把整个帧的布局画了出来,你一眼就能看到每个字段的排布顺序。
3.2 前导码(Preamble)—— 32 个连续的“1”
前导码是帧的开头,作用是让 PHY 同步上 MDC 时钟,并准备好接收后面的有效数据。协议规定:前导码必须是 连续的 32 个高电平(逻辑 1)。少一个、多一个,PHY 可能就不认了。
我遇到过一种情况:MCU 的 GPIO 模拟 MDIO 时,因为中断抢占了时序,前导码只发了 31 个 bit,结果 PHY 直接忽略后续帧。当时抓波形看傻了,后来数时钟边沿才发现少了一个。所以你要是用 GPIO 模拟,建议先发 40 个前导码,反正多了也没事,PHY 只会等前导码结束后的起始位。少发了?一定不行。
3.3 起始位(ST, Start of Frame)—— 固定 01
紧接着前导码的是 2 bit 起始位,固定为 “01”(先 0 后 1)。PHY 检测到“01”就知道:前导码结束了,下面要开始正经信息了。
你想想看,如果前导码末尾连续两个“1”,PHY 会继续等;如果出现“00”,PHY 会认为无效。只有“01”才是合法的帧起始。我记得有一次调试,逻辑分析仪上 ST 位显示“11”,我检查代码才发现是移位写错了……整个帧被 PHY 丢弃,读寄存器永远返回 0xFFFF。
3.4 操作码(OP Code)—— 写 10 / 读 01
OP Code 也是 2 bit,它告诉 PHY 这一帧是读还是写:
- 写操作:OP = 10(先 1 后 0)
- 读操作:OP = 01(先 0 后 1)
这两个编码容易混淆?我个人习惯死记:写操作是“10”——拿个笔写下来就是 1 笔下去然后提起来,所以是 1 0;读操作是“01”——先看看书(0),然后再读(1)。嗯,你可能有自己的记法,反正别搞反就行。
3.5 PHY 地址(PHY Address)—— 5 bit,范围 0~31
PHY 地址决定了你要跟哪个 PHY 芯片通信。一般 PHY 芯片都会有一个地址引脚(比如 AD0~AD4),通过上下拉电阻设置地址。你在板子上看到 PHY 地址通常是 0x01、0x04 之类的,而不是 0x1F 这种。
我建议:调试时先确认 PHY 的实际地址。用万用表量一下地址引脚的电平,或者读 PHY ID 寄存器(寄存器 0x02、0x03)来反推。别想当然认为 PHY 地址就是 0x00——很多参考设计把地址设为 0x01 或 0x04。
| PHY 地址位 | 对应引脚 | 常见上拉/下拉 |
|---|---|---|
| bit4 (最高位) | AD4 | 通常下拉(0) |
| bit3 | AD3 | 可配置 |
| bit2 | AD2 | 可配置 |
| bit1 | AD1 | 可配置 |
| bit0 (最低位) | AD0 | 通常下拉(0) |
3.6 寄存器地址(Register Address)—— 5 bit,范围 0~31
每个 PHY 内部有 32 个基本寄存器(Clause 22 标准定义了前 16 个,后 16 个留给厂商自定义)。你需要通过帧中的 5 bit 寄存器地址来指定读/写哪个寄存器。
易错点:很多工程师写读寄存器时,直接填 PHY 地址和寄存器地址,以为这样就行了。但别忘了,帧格式里 PHY 地址在前,寄存器地址在后。我曾经见过有人把这两个地址填反了,结果读到的永远是寄存器 0x00(控制寄存器)的数据,排查了半天才发现是地址移位错了。
#define REG_ADDR_MASK 0x001F,然后强制检查寄存器地址不超过 31。如果有厂商扩展寄存器需要用 Clause 45 协议,那就不是本课范围了。
3.7 转交位(TA, Turnaround)—— 2 bit,方向切换的关键
TA 位是帧中最特殊的一段。它负责把总线控制权从 Master 移交给 PHY(读操作时),或者保持 Master 控制(写操作时)。具体规则:
- 写操作: Master 在 TA 位驱动 “10”(先 1 后 0),之后继续驱动数据。
- 读操作: Master 输出第一个 TA 位为高阻“Z”(释放总线),第二个 TA 位由 PHY 驱动为 “0”。然后 PHY 开始驱动接下来的 16 bit 数据。
这里有个大坑:如果你用 GPIO 模拟 MDIO,读操作时一定要把 GPIO 方向从输出切换为输入(或者开漏输出配上拉)。我遇到过 GPIO 没切换方向,Master 一直强输出,和 PHY 输出的数据打架,读回来的数据全是乱的。嗯,调了整整半天才想起来方向寄存器没改。
3.8 数据段(Data)—— 16 bit,真正的读写内容
数据段就是你要写入或读取的 16 位值。注意:MDIO 只支持 16 位数据宽度,没有 8 位模式。如果你要读一个 8 位寄存器……嗯,不好意思,你得自己把另外 8 位忽略或者扩展成 16 位。大部分 PHY 寄存器本身就是 16 位的,够用。
数据段传输时,高位先传(MSB first)。也就是说,数据 bit15 最先出现在 MDIO 线上,bit0 最后。抓波形时记得对照 datasheet 确认 bit 顺序,别把高低位搞反了。我一开始抓波形时没注意,把 0xABCD 看成了 0xD5C3(因为 bit 反向),幸好事后用 PHY ID 寄存器验证才纠正过来。
3.9 一个完整的帧示例(写操作)
假设我们要向 PHY 地址 0x04 的寄存器 0x0A 写入 0x1234。完整的帧如下:
- 前导码:32 个“1”
- ST:01
- OP:10(写)
- PHY 地址:00100 (0x04)
- 寄存器地址:01010 (0x0A)
- TA:10
- 数据:0001001000110100 (0x1234)
总共 32+2+2+5+5+2+16 = 64 bit。你看,数得清清楚楚。