4. 常见PHY寄存器:控制寄存器、状态寄存器、厂商ID寄存器
拿到一颗PHY芯片,第一件事是什么?我的习惯是先把它的核心寄存器摸清楚。说白了,这就像开车前要看仪表盘——你得知道油门、刹车在哪,水温、转速怎么看。
MDIO能访问的PHY寄存器很多,但最常用的就那十几个。今天咱们重点讲三个:控制寄存器、状态寄存器、厂商ID寄存器。这三个摸透了,80%的调试场景你都能应付。
4.1 控制寄存器(Reg 0)——PHY的"油门"
控制寄存器的地址是0x00。它的核心功能就是你让PHY干什么活。你可能要问了:一个16位的寄存器,能干多少事?
其实不多,但每一件都很关键。我遇到过不少工程师,PHY不通了就去怀疑硬件布线,结果一看控制寄存器——自协商根本没打开。这就像你踩了油门才发现忘了松手刹。
控制寄存器关键的几个位:
| Bit位 | 名称 | 说明 |
|---|---|---|
| 15 | Reset | 软件复位PHY。写1触发复位,复位后自动清零 |
| 14 | Loopback | 环回模式。调试用的,数据发出去绕回来 |
| 13 | Speed Selection (MSB) | 速度选择高位,与Bit6配合:00=10M, 01=100M, 10=1000M |
| 12 | Auto-Negotiation Enable | 自协商使能。我建议你平时都开着 |
| 11 | Power Down | 掉电模式。写1进低功耗,写0唤醒 |
| 10 | Isolate | 隔离模式,断开MII/RMII接口 |
| 9 | Restart Auto-Negotiation | 重启自协商。如果你想强制重新握手,就置1 |
| 8 | Duplex Mode | 双工模式:0=半双工,1=全双工 |
| 6 | Speed Selection (LSB) | 速度选择低位,与Bit13配合 |
控制寄存器典型的操作场景:
- 复位PHY:写0x8000到Reg 0,等100ms再读,如果Bit 15变回0表示复位完成。
- 强制设定速度:比如强制100M全双工,写0x2100(自协商关闭 + 100M + 全双工)。
- 环回测试:写0x4000,数据发出去在内部绕回,不用连网线就能测。
我做项目时踩过的坑:
记得有一次调试交换机的PHY,死活连不通。反复检查硬件、时钟、复位,都没问题。后来我读了Reg 0,发现Bit 10 (Isolate)被置1了——原来是上电时序不对,导致PHY进入了隔离模式。写0清掉,立刻通了。
4.2 状态寄存器(Reg 1)——PHY的"仪表盘"
状态寄存器地址是0x01。它能告诉你PHY当前的状态——类似、仪表盘上的转速表和故障灯。你不需要去猜链路到底好没好,直接读这个寄存器就清楚了。
状态寄存器关键的几个位:
| Bit位 | 名称 | 说明 |
|---|---|---|
| 5 | Auto-Negotiation Complete | 自协商是否完成。1表示协商成功,0表示还在协商或失败 |
| 3 | Remote Fault | 远端错误。对面出问题了,这位置1 |
| 2 | Link Status | 链接状态。1=链路已建立,0=断开 |
| 1 | Jabber Detect | 超长帧检测。不太常用,但出现时说明链路异常 |
| 0 | Extended Capability | 扩展能力。1表示支持扩展寄存器,一般都为1 |
特别注意: 状态寄存器Bit 2 (Link Status)是一个锁存位。一旦链路断开,它就会变成0并且保持——即使链路后来恢复了,你也要重新读一次才会更新。我见过有人调试时看到Link Status为0就以为网线坏了,其实是因为没做二次读取。
我曾经踩过的坑: 有一次现场调试,客户说设备经常掉线。我远程读了Reg 1的Bit 2,一直显示1——链路正常。但客户说设备确实会断。后来我做了个脚本连续读Reg 1,才发现Bit 2其实偶尔会跳0然后又恢复。再一看Bit 3 (Remote Fault),竟然也是1。最后查出是对端的PHY电源不稳,导致间歇性复位。所以你看,单读一次状态寄存器是不够的,要做持续监控。
状态寄存器怎么用才到位?
- 链路不通? 先读Reg 1,看Bit 2是否为1。如果为0,检查网线和对端。
- 自协商没结果? 看Bit 5。如果长时间为0,可能是对端不支持自协商,需要手动设置速度。
- 偶尔断链? 写一个循环,每秒读一次Reg 1,连续读100次。看看Bit 2有没有抖动。
4.3 厂商ID寄存器(Reg 2 & Reg 3)——PHY的"身份证"
厂商ID寄存器占两个地址:Reg 2是厂商ID的高16位,Reg 3是厂商ID的低16位。合起来是32位。
这里面包含三个信息:
- OUI (Organizationally Unique Identifier):24位,但实际只用了22位。IEEE分配给厂商的唯一标识。
- 型号编号:6位,厂商定义的具体芯片型号。
- 版本号:4位,芯片的修订版本。
32位ID的结构:
| 寄存器 | Bit位 | 内容 | 说明 |
|---|---|---|---|
| Reg 2 | 15:0 | OUI 高16位 | 厂商唯一标识的高16位 |
| Reg 3 | 15:10 | OUI 低6位 | OUI的22位中的后6位 |
| Reg 3 | 9:4 | 型号编号 | 6位,比如RTL8211、LAN8720各有编号 |
| Reg 3 | 3:0 | 版本号 | 4位,用来区分同型号的不同批次 |
这个寄存器有什么实际用途?
假设你要换一款PHY芯片,但不确定驱动支不支持。先读Reg 2和Reg 3,拿到厂商ID。然后去网上查一下,就知道这颗芯片的兼容性了。
举个例子: 我调试过一款工控板,读到的Reg 2=0x001C, Reg 3=0xC915。查出来是博通BCM5461的ID,于是直接套用了已知的驱动配置,半小时就调通了。
常用的厂商ID速查表(我整理过,分享给你):
| 厂商 | OUI高16位 (Reg 2) | 常见型号 | 低16位 (Reg 3) 包含型号 |
|---|---|---|---|
| Broadcom (博通) | 0x001c | BCM5461 | 0xC915 (编号0x24, 版本1) |
| Micrel (现Microchip) | 0x0022 | KSZ9031 | 0x1620 (编号0x05, 版本0) |
| Realtek (瑞昱) | 0x001c | RTL8211 | 0xC832 (编号0x20, 版本2) |
| National Semiconductor | 0x0800 | DP83848 | 0x1781 (编号0x05, 版本1) |
| Marvell | 0x0141 | 88E1512 | 0x0DD0 (编号0x37, 版本0) |
我个人的做法: 拿到一块新板子,我会写一个小脚本,循环扫描PHY地址0~31,每个地址读Reg 2和Reg 3。然后打印出所有能找到的PHY及其厂商ID。这样一次就能知道板子上有哪些PHY、各是什么型号——再也不需要去翻原理图了。
读取厂商ID的伪代码:
// 假设我们有读MDIO寄存器的函数
// mdio_read(phy_addr, reg_addr) 返回16位值
uint16_t reg2 = mdio_read(phy_addr, 2);
uint16_t reg3 = mdio_read(phy_addr, 3);
// 拼装32位ID
uint32_t phy_id = ((uint32_t)reg2 << 16) | reg3;
// 提取OUI (22位)
uint32_t oui = (reg2 << 6) | (reg3 >> 10);
// 提取型号编号 (6位)
uint8_t model = (reg3 >> 4) & 0x3F;
// 提取版本号 (4位)
uint8_t revision = reg3 & 0x0F;
printf("PHY ID: 0x%08X\n", phy_id);
printf("OUI: 0x%06X, Model: 0x%02X, Rev: 0x%01X\n", oui, model, revision);
你看,其实三个寄存器加起来也就48位。但它们涵盖了PHY的基本控制、状态反馈、身份识别——就像一个人的姓名、健康状况、身份证号。调试的时候,先读这三个寄存器,就能对PHY的状况有个基本判断。
我记得有句话说得好:调试不是靠猜,是靠读数。控制寄存器告诉你该做什么,状态寄存器告诉你现在怎么样,厂商ID寄存器告诉你面前的是谁。三者配合,调试效率至少提高一倍。