3. PHY 寄存器访问方式:MDIO/MDC、内存映射、SPI

咱们做硬件调试,最常打交道的就是怎么“够到”PHY芯片内部的寄存器。

说白了,PHY芯片就像一栋黑漆漆的大楼,寄存器就是楼里的房间。你得先找到大门,才能进去查东西。我这些年调过的板子,很多问题都卡在“门没找对”上——协议选错了,时序没对齐,结果读回来的数据全是乱码。

常见的访问门路有三种:MDIO/MDC、内存映射、以及SPI。这一章咱们就挨个拆开看看。

3.1 MDIO/MDC 管理接口

这是最正统、也最通用的方式。IEEE 802.3 标准里专门定义了它,用于管理 MAC 层和 PHY 层之间的交互。你几乎所有的千兆以太网 PHY 芯片都会支持它。

基本引脚与拓扑

  • MDC:管理时钟,由 MAC 或 CPU 侧主动提供。频率最高通常到 2.5 MHz,但现在很多芯片也能跑到 25 MHz 或更高,看具体支持。
  • MDIO:管理数据,双向传输。

我经常把它比作 I2C 的简化版:一条时钟线,一条数据线,分时读取写入。但它的帧结构比 I2C 要明确得多。

帧结构与时序

MDIO 的读写帧是固定的 64 位。我建议大家死记住这个结构,调试时抓波形能救命:

位域位宽描述
PREAMBLE3232 个连续的 1,表示帧起始
ST2启动码:固定 01
OP2操作码:10 表示读,01 表示写
PHYAD5PHY 地址,一般由芯片外围管脚决定
REGAD5寄存器地址,一次只访问一个寄存器
TA2转向时间:读操作时为 Z0,写操作时为 10
DATA16数据
我的一点经验:PHY 地址不要乱设。我曾经有一次量产时发现好几块板子 PHY 读不到,查下来是地址管脚的上拉电阻虚焊了。地址冲突时,总线上的设备会互相打架,数据完全不可靠。

实际读写流程

以读操作为例,我一般会按这几步走:

  1. 确保 MDC 时钟稳定输出。
  2. CPU 通过 GPIO 或 MAC 内置的 MDIO 控制器,发出 32 个 1 的 PREAMBLE。
  3. 发送 ST、OP、PHYAD、REGAD。
  4. 第 33-34 位是 TA 阶段:CPU 将 MDIO 设置为高阻,由 PHY 驱动拉低 Z。
  5. PHY 在 TA 的第二个周期将 MDIO 拉为 0,表示准备输出数据。
  6. 随后的 16 个周期,PHY 把寄存器的值送出来。

你想想看,如果 TA 阶段电平不对,多半是 PHY 地址或寄存器地址写错了。我早期用逻辑分析仪抓过一次,才发现原来自己 REGAD 写成了 6 位,多了一位就把后面的时序全搞偏了。

// 伪代码:MDIO 读操作
uint16_t mdio_read(uint8_t phy_addr, uint8_t reg_addr) {
    uint16_t data = 0;
    // 1. 发送 32 个 1 作为 PREAMBLE
    for (int i = 0; i < 32; i++) {
        mdio_clk_cycle(1);
    }
    // 2. 发送 ST=01, OP=10, PHYAD, REGAD
    send_bits(0x01, 2);  // ST
    send_bits(0x10, 2);  // OP
    send_bits(phy_addr, 5);
    send_bits(reg_addr, 5);
    // 3. TA 阶段:设置方向为输入,读 2 位(Z0)
    set_mdio_dir(INPUT);
    wait_ta();  // 等待 PHY 拉低
    // 4. 读 16 位数据
    for (int i = 0; i < 16; i++) {
        data = (data << 1) | mdio_read_bit();
    }
    return data;
}

3.2 内存映射访问方式

对于集成度更高的 SoC,MDIO 有时太慢了。你就会发现很多设计直接把 PHY 的寄存空间映射到 CPU 的内存地址空间里。

我遇到过一些高端交换芯片,内部集成了几十个 PHY。如果每个都用 MDIO 轮询,光是读一遍状态就要花不少时间。所以它们会用内存映射,CPU 直接读写某个内存地址就能访问寄存器。

与 MDIO 的对比

特性MDIO内存映射
速度慢(受限于 MDC 频率)快(直接内存总线速率)
实现复杂度简单,适用于独立 PHY依赖系统内存控制器
调试难度需要外部逻辑分析仪可以直接用 print 或调试器查看内存
典型场景外接独立 PHY 芯片集成 SoC 内部 PHY
注意:内存映射方式虽然快,但要注意 endianness 问题。我有一回在 ARM 核上读取数据,发现高低字节反了,原来 PHY 内部寄存器是 big-endian,而 CPU 是小端模式。如果你忘了转换,读出来的寄存器值全是乱的。

3.3 SPI 访问方式

某些 PHY 芯片也支持 SPI 接口来访问管理寄存器。这通常用在以太网供电(PoE)或一些工业级 PHY 上。SPI 的好处是速度快、接口简单,而且支持多个寄存器连续访问(burst 模式)。

一张 SPI 帧的典型格式:

  • CS 拉低:开始事务。
  • CPU 发送 8 位命令(高 1 位为 R/W,后面为地址或片段)。
  • 接着发送或者接收 16 位数据(有些芯片是 8 位,但标准 PHY 寄存器是 16 位)。
  • CS 拉高:结束事务。
我的习惯:如果板子上同时有 SPI 接口的 EEPROM 和 PHY,一定要确认 SPI 片选没有共用。我之前踩过坑:两个设备共用一根 CS 线,结果读 EEPROM 时顺便把 PHY 寄存器也写乱了。

3.4 三种方式的取舍

上面讲了三种方法,它们不是互相排斥的。在我做过的项目中,同一个 PHY 芯片可能同时提供 MDIO 和 SPI 两种接口,芯片内部会有一个模式选择管脚。我通常是:

  • 能用 MDIO 的地方优先用 MDIO,兼容性最好,调试工具也多。
  • 如果系统对延迟要求高、寄存器访问频繁,我会换成内存映射。
  • SPI 适合需要高速连续读写的场景,比如批量配置 PHY 的初始参数。

3.5 知识体系总览

下面这张 SVG 图把三种方式的知识要点都串起来了。你可以对着图,回忆每一种方式的入口、时序关键点和避坑事项。

PHY 寄存器访问方式知识体系 MDIO/MDC 接口 内存映射 SPI 总线 IEEE 802.3 标准帧 64 位固定帧结构 PHY 地址 + REG 地址 内存基址 + 偏移 CPU 直接 read/write 注意 endian 转换 CS + SCLK + MOSI + MISO 支持 burst 连续读 片选不可共用 调试要点总结 1. MDIO:检查 PREAMBLE 完整性、PHYAD 电平、TA 转向 2. 内存映射:确认地址映射范围、字节序、总线宽度 3. SPI:验证片选时序、模式极性(CPOL/CPHA)是否一致

3.6 避坑指南与调试建议

前面讲了不少,最后我把这几年踩过的坑列一列,给你提个醒:

  • PHY 地址要确认两遍:上电后先用逻辑分析仪抓一下 MDIO 总线,确认 PHY 返回的回应是不是你期望的地址。我曾经遇到过芯片的地址跟 datasheet 不一致,最后发现是某个保留管脚默认电平跟手册对不上。
  • MDC 频率兼容性:有些老 PHY 只能跑 2.5 MHz,你拿 25 MHz 去打他,它可能根本不响应。建议先从最低频率开始,稳定后再提高。
  • 跨页寄存器怎么办:很多 PHY 支持扩展页(page),在 MDIO 协议里通过写入特定的扩展页地址寄存器来切换。我建议你在调试时先读一次扩展页寄存器,确认当前页面,否则你读到的可能根本不是你想看的那些寄存器。
  • 内存映射的保护:如果 PHY 寄存器映射到了系统内存,小心你的代码不要意外写入了不该写的地方。有一次我调试 DMA 时,一个指针越界直接把 PHY 的速率配置寄存器踩成了 0,整条链路瞬间断了。从此我在映射区域加了内存保护。

掌握这三种访问方式,就等于拿到了打开 PHY 芯片大门的钥匙。你可以根据板子的具体资源、调试工具和性能要求灵活选用。接下来我们就要进到“房间”里面,看看那些寄存器到底是干嘛的了。