第3章 PHY芯片驱动:从寄存器到链路状态
PHY芯片,说白了就是以太网的物理层收发器。它负责把MAC层送来的数字信号转成能在网线上跑的模拟信号,反过来也一样。很多刚入行的朋友觉得PHY驱动就是配几个寄存器,其实没那么简单。
我个人习惯把PHY驱动分成四个模块:寄存器映射、自动协商、链路检测、复位与配置。咱们一个一个来聊。
3.1 PHY寄存器映射——标准与扩展
IEEE 802.3定义了一套标准寄存器,地址从0到31,共32个。前16个(0-15)是基本寄存器,所有PHY芯片都得支持。后16个(16-31)是扩展的,各厂家自己玩。
我刚开始做PHY驱动时,以为所有芯片都按标准来。结果换了个型号,读出来的ID全不对。后来才明白——寄存器映射虽然标准,但不同芯片的扩展寄存器差异很大。
| 寄存器地址 | 名称 | 功能 |
|---|---|---|
| 0 | BMCR | 基本控制寄存器(复位、自协商、速度、双工) |
| 1 | BMSR | 基本状态寄存器(链路状态、自协商完成) |
| 2-3 | PHYIDR1/2 | PHY标识符(厂家+型号) |
| 4-5 | ANAR/ANLPAR | 自协商通告/对端能力 |
| 6-7 | ANER/ANNPTR | 自协商扩展/下一页 |
核心要点:读PHYIDR1和PHYIDR2是驱动初始化的第一步。如果读出来全是0或0xFFFF,说明MDIO通信有问题,或者PHY地址配错了。
3.2 自动协商机制——双方握手
自动协商,说白了就是两个PHY芯片互相喊话:「我能跑100M全双工,你呢?」「我也能,那就100M全双工吧。」
这个过程靠FLP(快速链路脉冲)完成。每个PHY把自己的能力写在ANAR寄存器里,对端收到后填到ANLPAR里。两边取交集,选最优模式。
优先级从高到低:
- 1000BASE-T全双工
- 1000BASE-T半双工
- 100BASE-TX全双工
- 100BASE-TX半双工
- 10BASE-T全双工
- 10BASE-T半双工
避坑指南:我曾经遇到一个项目,PHY死活协商不到千兆。查了两天,发现是PCB走线太长,FLP信号衰减太厉害。换了个短网线就好了。所以硬件设计时,MDI走线长度一定要控制好。
3.3 链路状态检测——轮询还是中断?
检测链路状态,通常有两种方式:
- 轮询:定时读BMSR寄存器的bit 2(链路状态位)。简单,但浪费CPU。
- 中断:PHY芯片支持中断引脚,链路变化时主动通知CPU。高效,但硬件要支持。
我个人习惯,在资源紧张的单片机上用轮询,周期100ms左右。在Linux等系统里用中断,配合phy_state_machine状态机。
为什么轮询周期不能太短?因为PHY寄存器读取本身就有延迟,而且链路状态变化没那么快。我曾经见过有人1ms轮询一次,结果CPU占用率飙到30%,完全没必要。
/* 轮询链路状态示例 */
int phy_check_link(struct phy_device *phydev)
{
uint16_t bmsr;
/* 读BMSR寄存器 */
bmsr = phy_read(phydev, PHY_BMSR);
/* 检查bit 2 */
if (bmsr & PHY_BMSR_LINK_STATUS)
return 1; /* 链路正常 */
else
return 0; /* 链路断开 */
}
3.4 PHY复位与配置——顺序很重要
PHY复位有两种:硬件复位(拉低RST引脚)和软件复位(写BMCR的bit 15)。
我建议的做法是:
- 先硬件复位,等待至少1ms
- 再软件复位,等待PHY自检完成(约50ms)
- 读PHYID确认通信正常
- 配置自协商或强制模式
- 等待自协商完成(约2-3秒)
注意:软件复位后,所有寄存器恢复默认值。如果你之前配过什么,复位后得重新配。我曾经犯过这个错——复位后忘了重新配置LED模式,结果指示灯全灭,还以为板子坏了。
3.5 常见PHY芯片驱动实现
3.5.1 LAN8720——低功耗小封装
LAN8720是Microchip的10/100M PHY,RMII接口,QFN封装,很适合空间受限的产品。
它的特殊寄存器:
| 寄存器 | 地址 | 说明 |
|---|---|---|
| MODE_CTRL | 0x1F | 模式控制,可配RMII时钟源 |
| INT_CTRL | 0x1B | 中断控制,使能链路变化中断 |
| LED_CFG | 0x1C | LED配置,可自定义指示灯行为 |
驱动初始化代码:
int lan8720_init(struct phy_device *phydev)
{
uint16_t id1, id2;
/* 读PHY ID */
id1 = phy_read(phydev, PHY_IDR1);
id2 = phy_read(phydev, PHY_IDR2);
if (id1 != 0x0007 || (id2 & 0xFFF0) != 0xC0F0) {
printf("LAN8720 not detected!\n");
return -1;
}
/* 软件复位 */
phy_write(phydev, PHY_BMCR, PHY_BMCR_RESET);
delay_ms(50);
/* 使能自协商 */
phy_write(phydev, PHY_BMCR, PHY_BMCR_ANENABLE);
/* 配置LED:Link/Activity */
phy_write(phydev, 0x1C, 0x0001);
return 0;
}
3.5.2 DP83848——工业级稳定
DP83848是TI的工业级PHY,支持-40°C到85°C,MII/RMII都支持。我在一个户外设备上用过,三年没出过问题。
它的特殊寄存器:
| 寄存器 | 地址 | 说明 |
|---|---|---|
| PHYSTS | 0x10 | 详细状态,含极性、信号质量 |
| PHYCR | 0x11 | PHY控制,可配BIST测试 |
| LEDCR | 0x18 | LED控制,支持三色LED |
DP83848有个很实用的功能——信号质量指示。读PHYSTS的bit 10-12,可以知道当前链路的信号质量。这在排查网线问题时特别有用。
/* DP83848信号质量检测 */
int dp83848_signal_quality(struct phy_device *phydev)
{
uint16_t physts;
physts = phy_read(phydev, 0x10);
/* bit 10-12: SQ[2:0] */
return (physts >> 10) & 0x07;
/* 返回值:0=差,7=好 */
}
经验之谈:DP83848的复位时序比LAN8720要长。硬件复位后,我建议等至少10ms再操作。另外,它的LED默认配置是100M绿色、Link黄色、Activity闪烁。如果客户要改,记得在初始化时配LEDCR。
3.6 PHY驱动核心流程
下面这张图,是我自己总结的PHY驱动初始化流程。每次写新驱动,我都照着这个框架来。
3.7 调试技巧与避坑
调试PHY驱动,我常用的三板斧:
- 读ID:如果读不到正确ID,先查MDIO时钟频率。很多PHY对MDIO时钟有要求,不能超过2.5MHz。
- 看状态:BMSR寄存器bit 2是链路状态,bit 5是自协商完成。这两个位能帮你快速定位问题。
- 抓波形:用示波器看MDIO和MDC的波形,确认时序是否正确。我曾经遇到一个案例,MDIO上拉电阻没焊,导致通信时好时坏。
重要提醒:PHY芯片的VDDIO电压必须和MAC侧匹配。比如STM32的GPIO是3.3V,那PHY的VDDIO也得接3.3V。接错了轻则通信异常,重则烧芯片。我见过不止一次这种低级错误。
嗯,PHY驱动这块,说白了就是读读写写那几个寄存器。但每个芯片都有自己的脾气,多看看数据手册,多动手试试,慢慢就熟了。