MDIO帧结构:前导码、起始码、操作码、地址、数据、空闲
做MDIO读写,说白了就是往PHY的寄存器里写东西、读东西。但总线怎么知道你要读哪个寄存器?怎么知道你是读还是写?数据怎么对齐?这些都得靠一帧一帧的协议来约定。
我接触MDIO总线好多年了,第一次调PHY驱动时就被帧结构坑过——波形看着都对,读回来的数据全是乱的。后来老老实实把每一bit拆开看,才发现是地址位少算了两个。嗯,今天咱们就把这帧结构彻底讲透。
一帧MDIO的长度与组成
标准MDIO帧,一共 64个时钟周期(也就是64个bit)。时钟由主控(MAC/CPU)提供,PHY只在数据段和地址段回传数据时拉低读时序。整个帧分成6个阶段:
| 阶段 | 名称 | bit数 | 方向 | 描述 |
|---|---|---|---|---|
| 1 | 前导码 (Preamble) | 32 | 主控 → PHY | 连续32个1,用于同步 |
| 2 | 起始码 (Start) | 2 | 主控 → PHY | 固定01,标志帧开始 |
| 3 | 操作码 (Opcode) | 2 | 主控 → PHY | 读操作01,写操作10 |
| 4 | PHY地址 (PHY ADDR) | 5 | 主控 → PHY | 目标PHY编号,5bit,最大31 |
| 5 | 寄存器地址 (REG ADDR) | 5 | 主控 → PHY | 寄存器偏移,5bit,共32个 |
| 6 | 转态 (Turnaround) | 2 | 主控→PHY→主控 | 第一个bit高阻,第二个bit PHY驱动读数据 |
| 7 | 数据 (Data) | 16 | 主控/PHY | 16bit寄存器值,写时主控发,读时PHY发 |
| 8 | 空闲 (Idle) | ≥1 | 主控 | 输出高阻或拉高,总线释放 |
逐段拆解——每个bit都有讲究
1. 前导码 (Preamble) — 32个连续1
主控把MDIO线拉高,同时MDC时钟打32个周期。PHY看到这32个1就知道要准备收指令了。为什么是32个?协议从1994年定下来就没改过——足够长,让PHY内部状态机稳定。我自己调试时经常用示波器看MDIO波形,前导码齐不齐一眼就能看出问题:少了几个1,后面解析全错。
2. 起始码 (Start) — 固定01
前导码结束后,接着2个bit:先0后1。这2个bit是帧开始的标志。PHY检测到01后就锁定后续的bit顺序。注意:不能是11或10,更不能是00。有一次我写代码把起始码写成了10,贴片后100片里有3片PHY读不对,排查了一天。
3. 操作码 (Opcode) — 读01 / 写10
2个bit,决定了这一帧是读还是写:
- 读 (01):主控发01,之后PHY会在数据段把寄存器值放上MDIO线。
- 写 (10):主控发10,然后主控自己把要写的16bit数据发出去。
为什么读是01,写是10?我猜是为了让PHY解码时方便:最高位为0是读,为1是写。但协议就这么定的,记住就行。
4. PHY地址 (5bit) 和寄存器地址 (5bit)
两个各5bit,总共10bit。PHY地址范围0~31,寄存器地址也是0~31。注意:地址都是主控发出去的,PHY只监听与自己地址匹配的帧,不匹配的帧忽略。我调多PHY系统时,经常挂几个PHY在一条MDIO总线上,每个PHY地址不一样,靠这5bit区分。
哦对了,寄存器地址虽然是5bit,但实际标准规定前16个(0~15)是必须实现的MII寄存器,后面的16个(16~31)留给厂商自定义。但我见过有的PHY芯片只实现了前16个,你读地址16~31会返回0xFFFF或者固定值。
5. 转态 (Turnaround) — 最容易被忽略的2bit
这2bit严格来说不是“地址”也不是“数据”,而是总线控制权的交接。看协议定义:
- 第1个bit(TA1):主控必须输出高阻 (Z),MDIO线由上拉电阻拉高。
- 第2个bit(TA2):
- 读操作:PHY驱动力低,输出0(表示准备好送数据)。
- 写操作:主控驱动高,输出1(继续掌控总线)。
这个机制保证总线不会冲突。我在项目中遇到过:主控制器没进入高阻,TA1时刻还持续拉高,PHY也想拉高,结果两个驱动对着干,电流倒灌,波形变平顶。嗯,幸亏驱动能力有限没烧芯片。
6. 数据 (Data) — 16bit
前面所有准备工作都是为了这16bit。读操作时PHY在MDIO线上输出值;写操作时主控输出值。bit顺序:MSB先发,即bit15最先送出,bit0最后。PHY内部寄存器也是高15~低0排列,没问题。
7. 空闲 (Idle) — 至少1个周期
帧结束后,主控把MDIO置为高阻或直接拉高,释放总线。至少1个时钟周期的空闲时间,让PHY处理完上一帧。如果你连续发帧没有空闲,PHY可能处理不过来。我自己一般空闲2~3个周期,稳一点。
整体帧结构图
下面这张图把上面的拆解串到一起,方便你对照时序调试。
我总结的几个关键点
- 前导码不能省略:有些高速模式可以跳过前导码(Clause 45),但标准的Clause 22 MDIO必须有32个1。我建议即使你的PHY支持跳过,也先发完整帧,稳定再优化。
- 地址顺序:先发PHY地址(高5bit),再发寄存器地址(低5bit)。这两个字段紧挨着,总线上连续10个bit。我曾经混淆过:以为先寄存器后PHY,结果读到的值张冠李戴。
- 转态是调试的照妖镜:如果示波器看到TA1不是高电平,或者TA2读操作时不是0,那肯定有一方没配置正确。我有一回发现TA2读时PHY输出了1,查手册发现那个PHY的TA时序要求多等一个时钟——厂家自己改了协议。
- 空闲周期别省:连续读写至少留1个空闲周期。你想想看,PHY内部还在处理上一帧的数据,下一帧又来了,容易丢数据。
记忆口诀(我自己的):
32个1同步好,01起板不能少。
操作码定读写,PHY号5bit先到。
寄存器号紧跟,转态两bit交接好。
数据16位送出,空闲放松别抢道。
Clause 22 vs Clause 45的帧区别
这里简单提一句,后面章节会详细展开。Clause 22就是咱们上面讲的64bit帧。而新的Clause 45(支持更多寄存器和端口)帧结构变了:前导码变成32个0,操作码变成4bit,地址变成15bit等等。但初学者先把标准的Clause 22吃透,后面再学扩展就轻松了。
嗯,关于MDIO帧结构,核心就是这七个阶段。你只要能画出一帧波形,把每一段对应的bit数说清楚,写代码时照这个结构组织bit流就行了。下一段咱们直接看怎么用代码发一帧读操作。不过那是后面的事了,今天先把结构刻在脑子里。