寄存器基础:PHY寄存器访问方式、MDIO/MDC协议、寄存器地址映射

好,我们接着往下走。上一章聊了眼图的基本概念,今天我们来补一个基础——寄存器访问。你可能会想:“这些底层协议有啥好讲的?” 嗯,我刚开始做PHY调试时也是这么想的。直到有一天,我在项目里死活读不到某个寄存器的正确值,查了三天,最后发现是MDIO时序差了半个周期——从那以后,我再也不敢小看这玩意儿了。

说白了,PHY芯片就是一堆寄存器。你要调均衡、调摆幅、调预加重,全得通过寄存器读写来完成。所以,搞明白怎么访问这些寄存器,是眼图优化的基本功。

为什么要通过MDIO来访问?

PHY芯片本身没有键盘、没有屏幕,它需要和MAC(媒体访问控制器)通信。通信的桥梁就是MDIO接口。MDIO的全称是Management Data Input/Output,直译就是管理数据输入输出。它只有两根线:

  • MDC:管理时钟,由MAC提供。
  • MDIO:管理数据,双向传输。

我个人习惯把这两根线看作“串行总线的精简版”。不需要复杂的地址选通,一根时钟、一根数据,就能把PHY所有的寄存器给管起来。

核心要点: MDIO/MDC是IEEE 802.3标准定义的PHY管理接口。所有符合标准的PHY芯片,都通过这个接口暴露寄存器。你这辈子调PHY,基本就靠这两根线吃饭。

MDIO帧格式——读写的数据骨架

MDIO的通信是帧驱动的。每一帧包含64个时钟周期(在Clause 22模式下)。我刚开始记这个帧结构时觉得好麻烦,后来画了个图,一眼就明白了。你们看这个帧结构表:

字段 位宽 说明
PREAMBLE 32位 同步头,32个连续的"1"
ST 2位 帧起始,固定"01"
OP 2位 操作码:"10"为读,"01"为写
PHYAD 5位 PHY地址,最多32个
REGAD 5位 寄存器地址,最多32个
TA 2位 转向时间,读操作由MAC驱动"Z"后PHY驱动"0"
DATA 16位 读写数据

为什么要有这2位的TA?这里有个小坑。读操作时,MDIO线在TA周期需要切换驱动方向——MAC先释放总线,PHY再接管驱动数据。这个切换需要时间,TA就是用来做缓冲的。我曾经碰到过一款PHY,它的TA时序特别严格,MAC的GPIO模拟时序稍微慢了一点,读回来的数据全是乱的。你想想看,多冤枉。

寄存器地址映射——怎么找到你要的寄存器

每个PHY芯片内部寄存器按地址编址。标准Clause 22定义了前16个寄存器(地址0x00~0x0F)为基本寄存器,包括控制寄存器、状态寄存器、速度指示等。剩下的16个地址(0x10~0x1F)给厂商扩展用。地址映射结构大致是这样:

个人经验: 别以为标准寄存器就能通用。不同厂商对状态寄存器的位定义偶尔会有差异。我踩过最深的坑是在调试一款台湾厂商的PHY时,它的速度指示位和标准是反的——标准是"0"表示100M,它家是"1"表示100M。那一次我对着手册对了半天。

来看寄存器地址的分配:

  • 0x00: 控制寄存器 — 复位、环回、速度选择、使能
  • 0x01: 状态寄存器 — 链接状态、速度能力、自动协商完成
  • 0x02~0x04: PHY标识符 — 厂商ID和型号
  • 0x05~0x0F: 扩展状态/能力寄存器
  • 0x10~0x1F: 厂商自定义 — 这才是调眼图的主战场(均衡器、摆幅、预加重)

避坑指南: 我曾经在访问0x10~0x1F的扩展寄存器时,发现每次读写的延迟必须大于一定阈值(比如1微秒)。有些MAC的MDIO控制器默认没有加这个延迟,连续读写就会丢数据。解决方案是在每次读写之间加一个微小的延迟。嗯,这个我踩过,所以特意提一下。

读写时序——怎么让数据正确传输

MDIO的时序并不复杂。MAC控制MDC时钟,数据在MDC的上升沿采样,在下降沿改变。看一个简单的写操作流程:

// 伪代码示例:模拟MDIO写操作
void mdio_write(uint8_t phy_addr, uint8_t reg_addr, uint16_t data) {
    // 1. 发送32位前导码
    for (int i = 0; i < 32; i++) {
        mdc_toggle();  // 每个MDC周期发送1位"1"
        mdio_out(1);
    }
    // 2. 发送帧起始和操作码
    send_bits(0x01, 2);  // ST = "01"
    send_bits(0x01, 2);  // OP = "01" 表示写
    // 3. 发送PHY地址和寄存器地址
    send_bits(phy_addr, 5);
    send_bits(reg_addr, 5);
    // 4. 转向时间(写操作是MAC持续驱动)
    send_bits(0x10, 2);  // TA = "10" 写操作
    // 5. 发送数据
    send_bits(data, 16);
}

读操作和写操作的差别主要在TA阶段:写操作MAC一直驱动,读操作在TA的前半周期MAC释放总线,后半周期PHY开始驱动。如果你是用CPU的GPIO模拟MDIO,一定要处理好这个切换。有一次在调试25G光口时,我发现读回来的寄存器值每隔几次就跳变一次,就是TA阶段的GPIO方向切换慢了半拍。

本章知识结构

我把本章的核心逻辑梳理成了一张图。你看,从MAC到PHY,通过MDIO/MDC协议桥接,最终映射到寄存器地址空间。而寄存器地址从标准寄存器区跨越到厂商自定义区,后者才是眼图优化的真正宝藏。

MDIO/MDC协议栈与寄存器地址映射框架 MAC(主控) MDIO/MDC协议 PHY(从设备) 寄存器地址0x00-0x1F 0x00-0x0F 标准寄存器区 0x10-0x1F 厂商自定义区 控制寄存器 复位/环回/速度 状态寄存器 链接/自动协商 标识符寄存器 厂商ID/型号 均衡/摆幅 预加重(眼图调优) 说明:眼图优化主要操作厂商自定义区(0x10-0x1F),通过调整均衡、摆幅、预加重等参数改变信号质量。 寄存器访问流程:MAC —MDIO/MDC→ PHY(地址映射)→ 具体寄存器读写

从这张图你可以看到:MAC通过MDIO/MDC协议访问PHY,PHY内部按地址映射展开寄存器。我们调试时,大部分时间泡在厂商自定义区(0x10-0x1F)。

嗯,再补充一点。有些新的PHY支持Clause 45协议(主要是10G以上的速率)。Clause 45的帧结构更长,支持更大的寄存器地址空间。但基本原理一样,只是帧格式更复杂。如果你的项目应用在10G/25G甚至更高速率,建议直接翻Clause 45。不过当前讲的基础是通用的,把Clause 22搞明白,Clause 45上手就快了。