2、PHY ID 的概念:为什么需要 PHY ID、如何定义
各位好,我是做嵌入式网络开发的老工程师了。今天聊一个新手容易忽略的点——PHY ID。你们可能已经习惯调 MDIO 去读寄存器,但有个问题:总线上要是挂了 3 个 PHY,你读的是哪个?怎么确认它是你要的那个芯片?嗯,PHY ID 就是干这个的。
为什么需要 PHY ID
我个人刚接触 Ethernet 驱动时,以为每个 PHY 只要配好地址就行。后来在项目里碰到一批混乱的 PCBA,PHY 地址冲突、型号混贴,调试时根本分不清哪个是哪个。说白了,PHY ID 就是 PHY 芯片的身份证,让你在 MDIO/MDC 总线上唯一识别出具体设备。
归纳下来,需要 PHY ID 的原因很直接:
- 多设备区分——MDIO 总线可以挂最多 32 个 PHY,地址相同只能靠 ID 确认。
- 型号/厂商验证——同一地址可能用不同厂商的 PHY,驱动初始化流程完全不一样。
- 复位后自检——读回 ID 的预期值可以判断链路是否松动、芯片是否正常。
- 热插拔/动态发现——工业网关里 PHY 可能带电接入,主机需要枚举所有 ID 来建立设备树。
- 固件兼容判断——有些 PHY 的版本号会变,ID 中的版本域能帮助你决定是否更新驱动补丁。
大家想想看,如果你连手里这颗 PHY 是什么都不知道,后面所有的寄存器操作都是瞎蒙。所以我一直强调,读 PHY 之前,先把 ID 读完、校验完,再做其他配置。
PHY ID 的定义
标准定义来自 IEEE 802.3 第 22 章。PHY 的 ID 被固化在寄存器 2(PHY ID Register 1)和寄存器 3(PHY ID Register 2)里,这两个寄存器是只读的。整个 ID 共 32 位,分成三块:OUI(Organizationally Unique Identifier)、型号、版本。
| 寄存器 | 地址 (十进制) | 位域 | 宽度 | 含义 |
|---|---|---|---|---|
| PHY_ID1 | 2 | 15:10 | 6 bit | OUI 的前 6 位(位 23:18 of OUI) |
| PHY_ID1 | 2 | 9:4 | 6 bit | OUI 的中间 6 位(位 17:12 of OUI) |
| PHY_ID1 | 2 | 3:0 | 4 bit | OUI 的最后 4 位(位 11:8 of OUI) |
| PHY_ID2 | 3 | 15:10 | 6 bit | OUI 的剩余 6 位(位 7:2 of OUI) |
| PHY_ID2 | 3 | 9:4 | 6 bit | 厂商定义的型号编号 |
| PHY_ID2 | 3 | 3:0 | 4 bit | 版本号 (Revision) |
OUI 是 IEEE 分配给每家厂商的 24 位唯一码,比如 Realtek、Broadcom、Marvell 都有自己的 OUI。OUI 的低 2 位(bit 1:0)被 IEEE 保留不用,所以 PHY ID 只取了其中的 22 位。这部分我刚开始看标准时也迷惑过,后来翻到了 IEEE 802.3 的勘误表才搞明白。
实际用起来很简单:把寄存器 2 左移 16 位,再与寄存器 3 的值合并成 32 位数。然后将这个数右移 2 位(因为 OUI 原始数据少了最低 2 位),就能还原出完整的 OUI。厂商的型号字段直接查表就知道是哪个子型号。
怎么读取和验证 PHY ID
我习惯用一个简单的状态机来枚举所有可能的 PHY 地址(0~31),每读到非零且非全 F 的值就认为该地址有 PHY。下面是一个典型的读取流程(伪代码):
// 假想 MDIO 读写函数
uint16_t mdio_read(uint8_t phy_addr, uint8_t reg_addr);
void read_phy_id(uint8_t phy_addr) {
uint16_t id1 = mdio_read(phy_addr, 2);
uint16_t id2 = mdio_read(phy_addr, 3);
if (id1 == 0x0000 || id1 == 0xFFFF) {
// 设备不存在或链路异常
return;
}
uint32_t oui_full = ((uint32_t)id1 << 16) | id2;
oui_full >>= 2; // 还原 OUI
uint8_t model = (id2 >> 4) & 0x3F;
uint8_t revision = id2 & 0x0F;
printf("PHY[%d] OUI=0x%06X Model=%d Rev=%d\n",
phy_addr, oui_full, model, revision);
}
这段代码我在好几个项目里直接用过。注意读 PHY_ID1 时必须先确认 MDIO 总线已稳定,否则可能读到全 0 或全 F。另外,有些老 PHY(比如 10M 时代的)可能不遵循这个标准,读回来的 ID 是厂商自定义的格式,这种情况下我建议直接查对应芯片的 datasheet 确认。
这张图你大概能看清楚整体逻辑:左边是 MDIO 主机通过总线访问 PHY 设备,右边展示了寄存器 2 和寄存器 3 里 OUI、型号、版本的存放位置。最后所有信息落地成唯一标识。
实际项目中的避坑点
最后,分享几点我个人趟过的坑:
- 地址重复——某些 PHY 芯片上电默认地址相同(比如 0x00),如果板子上焊了两颗且没做地址引脚拉电阻,结果就是 ID 读回来都是同一颗的。我建议在硬件设计时就给每个 PHY 分配不同地址,万一无法改硬件,就靠 PHY ID 做二次校验。
- 读取时机——PHY 复位后需要等待至少 2 ms 才能稳定读取 ID。我见过有人在复位后立即读,结果读到 0x0000,然后误判为设备不存在。嗯,记得加延时。
- OUI 还原的坑——标准里 OUI 的 24 位是“倒序”放置的,你在合并 32 位后的右移 2 位操作不可省略。我之前不做这个移位,拿着 OUI 查厂商表怎么都对不上,后来翻标准才发现问题。