第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 读写时序——动手写代码前必须搞懂

咱们直接看时序图可能更直观。不过我这里用文字描述一下:

写操作时序

  1. 主设备拉高MDC,输出32个1(前导码)。
  2. 输出起始码01。
  3. 输出操作码01(写)。
  4. 输出5位PHY地址。
  5. 输出5位寄存器地址。
  6. 输出2位转向时间11。
  7. 输出16位数据。
  8. MDC继续,但MDIO变为高阻(或者主设备停止驱动)。

读操作时序

  1. 前导码、起始码、操作码(10)都一样。
  2. 输出PHY地址和寄存器地址。
  3. 转向时间:主设备输出Z(高阻),PHY在第二个TA位开始驱动数据。
  4. 主设备在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时常用的方法:

  1. 用示波器抓波形:这是最直接的方法。看MDC的频率对不对,MDIO的时序对不对。我习惯抓写操作和读操作两个波形,对比标准时序。
  2. 读PHY ID寄存器:每个PHY芯片都有一个唯一的ID寄存器(地址2和3)。如果能正确读出PHY ID,说明MDIO通信是正常的。
  3. 写一个已知值,再读回来:比如,往控制寄存器(地址0)写0x1000(软复位),然后读回来确认。如果读写一致,那基本没问题。

好了,关于MDIO/MDC总线协议,咱们就聊到这儿。下一章,我会带大家手写一个MDIO驱动,从GPIO模拟开始,一步步实现读写函数。到时候,咱们再细聊代码里的那些“坑”。