第1章:MDIO/MDC总线协议——PHY芯片的“神经”与“语言”
大家好,我是你们的嵌入式讲师。今天咱们来聊聊以太网PHY芯片驱动里最基础、也最绕不开的一个话题——MDIO/MDC总线协议。
说实话,我刚开始做网络驱动那会儿,觉得PHY芯片就是个“黑盒子”。给它写个寄存器,它就能自动协商、自动翻转,挺省心的。直到有一次,板子死活连不上千兆,我查了三天,最后发现是MDIO时序不对,读回来的寄存器值全是0xFF。嗯,从那以后,我再也不敢小看这条“两线总线”了。
1.1 MDIO/MDC是什么?
MDIO(Management Data Input/Output)和MDC(Management Data Clock)是IEEE 802.3标准定义的管理接口。说白了,就是用来让MAC层(或者CPU)去读写PHY芯片内部寄存器的两条线。
- MDC:时钟线,由主设备(通常是MAC或CPU)驱动。频率一般不超过2.5MHz,我习惯用2.0MHz,留点余量。
- MDIO:数据线,双向的。写操作时由主设备驱动,读操作时由PHY芯片驱动。
你想想看,PHY芯片内部有几十个寄存器,比如控制寄存器、状态寄存器、自动协商寄存器等等。没有MDIO/MDC,你怎么知道当前链路是100M还是1000M?怎么知道有没有翻线?所以,这条总线就是PHY芯片的“神经”,而MDIO帧格式就是它的“语言”。
1.2 MDIO帧格式——读懂PHY的“话”
MDIO的通信是以“帧”为单位的。每一帧包含一串比特,有固定的格式。我把它拆成几个部分来讲:
| 字段 | 位数 | 说明 |
|---|---|---|
| Preamble(前导码) | 32 | 连续32个1,用于同步。Clause 22和Clause 45都要求这个。 |
| Start(起始码) | 2 | Clause 22是01,Clause 45是00。这是区分两种协议的关键。 |
| OP Code(操作码) | 2 | 10表示读,01表示写。其他值保留。 |
| PHY Address(PHY地址) | 5 | 最多支持32个PHY地址(0~31)。 |
| Register Address(寄存器地址) | 5 | Clause 22只有5位,所以最多32个寄存器。Clause 45这里变成了DEVAD(设备地址)。 |
| Turnaround(转向时间) | 2 | 读操作时,主设备释放总线,PHY驱动。写操作时,主设备继续驱动。 |
| Data(数据) | 16 | 真正的寄存器值,16位。 |
这里有个细节:Turnaround字段。读操作时,主设备在第一个TA位输出高阻(Z),PHY在第二个TA位开始驱动数据。写操作时,主设备直接输出11。我见过有人读操作时忘了释放总线,结果数据一直冲突,读回来的值全是乱的。
1.3 读写时序——动手写代码前必须搞懂
咱们直接看时序图可能更直观。不过我这里用文字描述一下:
写操作时序
- 主设备拉高MDC,输出32个1(前导码)。
- 输出起始码01。
- 输出操作码01(写)。
- 输出5位PHY地址。
- 输出5位寄存器地址。
- 输出2位转向时间11。
- 输出16位数据。
- MDC继续,但MDIO变为高阻(或者主设备停止驱动)。
读操作时序
- 前导码、起始码、操作码(10)都一样。
- 输出PHY地址和寄存器地址。
- 转向时间:主设备输出Z(高阻),PHY在第二个TA位开始驱动数据。
- 主设备在MDC的上升沿采样MDIO,读取16位数据。
我个人习惯在写驱动时,把MDIO的时序用GPIO模拟。虽然很多MCU有内置的MDIO控制器,但模拟的方式更灵活,调试起来也方便。我曾经在一个项目里,因为硬件设计问题,MDC和MDIO的走线太长,导致信号反射。用模拟方式,我可以在每个时钟沿后加一点延时,轻松解决了问题。
核心要点:MDIO的数据是在MDC的上升沿采样的。所以,写数据时要在MDC低电平时准备好数据,然后拉高MDC。读数据时,在MDC低电平时PHY准备好数据,主设备在MDC上升沿读取。
1.4 Clause 22 vs Clause 45——新老协议的“恩怨”
Clause 22是IEEE 802.3u里定义的,1995年的标准了。Clause 45是IEEE 802.3ae里定义的,2002年为了支持万兆以太网而推出的。
它们最大的区别在哪?我总结三点:
| 对比项 | Clause 22 | Clause 45 |
|---|---|---|
| 起始码 | 01 | 00 |
| 地址空间 | 5位PHY地址 + 5位寄存器地址(共32个寄存器) | 5位PHY地址 + 5位DEVAD(设备地址) + 16位寄存器地址(共65536个寄存器) |
| 操作码 | 10读,01写 | 00写地址,01写数据,10读数据,11读地址增量 |
| 应用场景 | 百兆、千兆PHY | 万兆、25G、100G PHY,以及需要大量寄存器的复杂PHY |
你可能会问:“那我该用哪个?”
嗯,这得看你的PHY芯片。大部分千兆PHY(比如RTL8211、KSZ9031)都支持Clause 22。万兆PHY(比如BCM84891)则必须用Clause 45。有些PHY芯片同时支持两种协议,通过一个引脚或者寄存器来切换。
我的经验:如果你不确定PHY支持哪种协议,可以先试试Clause 22。因为Clause 22的帧格式更简单,兼容性也更好。如果读回来的寄存器值全是0xFFFF或者0x0000,那大概率是协议不匹配,再切换到Clause 45试试。
1.5 多PHY地址访问——一条总线挂多个PHY
MDIO总线是共享的。一条MDC和一条MDIO线上,可以挂多个PHY芯片。每个PHY芯片有一个唯一的5位地址(0~31)。
实际项目中,PHY地址通常通过硬件引脚(比如LED0、LED1、LED2、LED3、LED4)来配置。比如,把PHY_ADDR0引脚拉高,PHY_ADDR1引脚拉低,地址就是0b00001(1)。
我遇到过一个问题:板子上有两个PHY,地址分别是0和1。我写驱动时,只初始化了地址0的PHY,结果地址1的PHY一直没反应。后来发现,地址1的PHY的复位引脚和地址0的PHY是连在一起的,复位后两个PHY都启动了,但地址1的PHY的MDIO引脚被拉低了,导致它无法响应。嗯,这种硬件设计上的坑,有时候比软件还难排查。
注意事项:多PHY访问时,一定要确保每个PHY的地址是唯一的。如果两个PHY地址冲突,它们会同时响应MDIO帧,导致数据冲突。另外,MDIO总线上需要上拉电阻(通常4.7kΩ),否则高阻态时电平不确定。
1.6 实战小贴士——调试MDIO的“三板斧”
最后,分享几个我调试MDIO时常用的方法:
- 用示波器抓波形:这是最直接的方法。看MDC的频率对不对,MDIO的时序对不对。我习惯抓写操作和读操作两个波形,对比标准时序。
- 读PHY ID寄存器:每个PHY芯片都有一个唯一的ID寄存器(地址2和3)。如果能正确读出PHY ID,说明MDIO通信是正常的。
- 写一个已知值,再读回来:比如,往控制寄存器(地址0)写0x1000(软复位),然后读回来确认。如果读写一致,那基本没问题。
好了,关于MDIO/MDC总线协议,咱们就聊到这儿。下一章,我会带大家手写一个MDIO驱动,从GPIO模拟开始,一步步实现读写函数。到时候,咱们再细聊代码里的那些“坑”。