寄存器读写基础:MDIO/MDC协议详解、设备地址与寄存器映射、典型寄存器功能

做PHY调试,寄存器读写是基本功。你得学会通过MDIO/MDC总线跟PHY芯片对话。说白了,这就像跟一个只懂数字的家伙打电话——你拨对号码,说对指令,他才能给你想要的答案。我早期做嵌入式网络设备时,就因为没搞懂这套协议,对着一个Link Down的问题折腾了两天……后来发现只是寄存器地址写错了。嗯,从那以后我都不敢跳过这个基础了。

本章核心地图:MDIO/MDC帧结构 → PHY设备寻址 → 寄存器空间映射 → 典型寄存器功能。把这四步走通了,任何PHY在你面前都没有秘密。

1. MDIO/MDC 协议解剖

MDIO(Management Data Input/Output)配上MDC(Management Data Clock),就是管理PHY的专用串行总线。MDC提供时钟,MDIO负责传数据。标准定义在IEEE 802.3的第22条款里,单条线就能完成读写,设计得很巧妙。

帧结构是重点。每次传输固定64位(读操作)或64位(写操作也类似)。我习惯把这个帧拆成几个字段来记:

字段位宽说明
Preamble32位全1,同步用
Start of Frame2位固定 01
Operation Code2位10读 / 01写
PHY Address5位目标PHY设备号(0-31)
Register Address5位目标寄存器号(0-31)
Turnaround2位读操作:MDIO切换方向;写操作:10
Data16位读写的数据内容

为什么Preamble要32个1?我理解是给PHY足够时间锁定时钟相位。实际测试中,有些PHY对这个字段不那么严格,但规范就是规范,不要偷懒。

小技巧:读操作时,Turnaround字段的第1位由MAC保持高阻(Z),PHY拉低第2位作为应答。如果你用GPIO模拟MDIO,务必在此时切换IO方向。我见过很多新手在这个地方卡住——方向没切,数据一直为0。

读写操作对比

读和写的帧结构基本一致,差别在OP码和Turnaround。看这张表就清楚了:

操作OP码Turnaround谁驱动Data
读10Z → 0(PHY应答)PHY
写0110(MAC驱动)MAC

你想想看,如果读操作时MAC没释放总线,PHY根本拉不动,结果永远是0xFF或者0x00。我在一个项目里遇到过这种情况——MDIO上拉电阻选太小,MAC释放后电平恢复太慢,导致Turnaround采样错误。

2. 设备地址与寄存器映射

每条MDIO总线最多挂32个PHY,地址从0到31。地址怎么来的?看硬件设计——PHY芯片的AD[0]~AD[4]引脚,要么拉高要么拉低。我记得有一次拿到的板子,原理图上PHY地址设的是0x05,结果实际焊的电阻把地址变成了0x07……这种硬件和软件对不上的情况,排查起来真的很耗时间。

每个PHY内部有32个16位寄存器(地址0-31)。IEEE 802.3强制定义了前6个(0-5),剩下的留给厂商自定义。这就像一栋楼,前6个房间功能是统一的,后面的房间每家自己布置。

特别注意:不是所有PHY都实现了完整的32个寄存器。读未实现的寄存器,行为不确定——有些返回0,有些返回上次读的值,有些甚至会挂死总线。如果你发现读某个寄存器时MDIO没反应,先确认一下这个地址在数据手册里是不是"Reserved"。

3. 典型寄存器功能详解

这里挑几个最常用的寄存器,讲讲它们能告诉你什么。我调试PHY时,90%的问题靠这几个寄存器就能定位。

寄存器0:BMCR(Basic Mode Control Register)

这是控制寄存器,你写什么,PHY就做什么。关键位段:

  • Bit 15:Reset——写1复位PHY,自清零。复位后所有寄存器恢复默认值。
  • Bit 13:Speed Selection——LSB,与Bit 6组合:00=10M,01=100M,10=1000M。
  • Bit 12:Auto-Negotiation Enable——写1开启自协商。我建议除非你明确知道链路对端配置,否则别关它。
  • Bit 9:Restart Auto-Negotiation——写1重启自协商,自清零。
  • Bit 8:Duplex Mode——1=全双工,0=半双工。
  • Bit 6:Speed Selection MSB——与Bit 13组合。

实操建议:写BMCR前,先读一次确认当前值。特别是Speed Selection位,有些PHY的默认值和标准不一样。我习惯用"读-修改-写"模式,避免误改其他位。

寄存器1:BMSR(Basic Mode Status Register)

这是状态寄存器,只读的。你读它,它告诉你PHY当前的状态。关键位段:

  • Bit 15:100BASE-T4能力——基本不用管了,T4早已淘汰。
  • Bit 14:100BASE-TX全双工能力——1表示支持。
  • Bit 13:100BASE-TX半双工能力
  • Bit 12:10BASE-T全双工能力
  • Bit 11:10BASE-T半双工能力
  • Bit 9:Auto-Negotiation完成——1表示自协商完成。调试时我第一个就看这个位。
  • Bit 8:Link Status——1表示链路正常。注意这是个"粘性位"——读一次后如果链路断过,它会保持0直到你再次读取。嗯,很多人被这个坑过。
  • Bit 5:Auto-Negotiation能力——1表示支持自协商。
  • Bit 2-0:扩展能力指示

避坑指南:我曾经在调试一块板子时,BMSR的Link Status一直为0,但示波器量RX差分对明明有信号。后来看了数据手册才发现,这个寄存器需要连续读两次才能拿到实时状态——第一次读清除"粘性"标志,第二次读才是当前值。

寄存器2-3:PHY Identifier

这两个寄存器存的是PHY的OUI(组织唯一标识符)和厂商型号。读出来你就知道你对面是谁——比如某颗瑞昱的PHY,ID通常是0x001C开头。我每次拿到新板子,第一件事就是读这两个寄存器,确认板子上实际贴的PHY型号和BOM一致。

寄存器4-5:Auto-Negotiation 相关

  • 寄存器4(ANAR):本地自协商能力通告。你告诉对端"我能跑哪些模式"。
  • 寄存器5(ANLPAR):对端自协商能力通告。对端告诉你"我能跑哪些模式"。

把这两个寄存器读出来,你就能推断出最终的链路协商结果——取双方能力的交集。比如本地支持100M全双工和10M全双工,对端只支持100M全双工,结果就是100M全双工。

4. 实操:用MDIO读一个寄存器

下面是我常用的一段MDIO读操作代码(用GPIO模拟)。注意时序——MDC高低电平的保持时间,标准要求不低于160ns,我实际写驱动时通常会放宽到1µs,兼容性更好。

// MDIO读操作(GPIO模拟)
// phy_addr: 0-31, reg_addr: 0-31
// 返回16位寄存器值
uint16_t mdio_read(uint8_t phy_addr, uint8_t reg_addr) {
    uint16_t data = 0;
    uint8_t i;

    // 1. Preamble: 32个1
    for (i = 0; i < 32; i++) {
        MDIO_WRITE(1);
        MDC_PULSE();
    }

    // 2. Start of Frame: 01
    MDIO_WRITE(0); MDC_PULSE();
    MDIO_WRITE(1); MDC_PULSE();

    // 3. Operation Code: 10 (读)
    MDIO_WRITE(1); MDC_PULSE();
    MDIO_WRITE(0); MDC_PULSE();

    // 4. PHY Address (5位)
    for (i = 5; i > 0; i--) {
        MDIO_WRITE((phy_addr >> (i-1)) & 0x01);
        MDC_PULSE();
    }

    // 5. Register Address (5位)
    for (i = 5; i > 0; i--) {
        MDIO_WRITE((reg_addr >> (i-1)) & 0x01);
        MDC_PULSE();
    }

    // 6. Turnaround: 释放总线,等待PHY应答
    MDIO_INPUT();            // 切换IO为输入
    MDC_PULSE();             // 第1个TA:PHY拉低
    MDC_PULSE();             // 第2个TA:PHY输出数据

    // 7. Read Data (16位)
    for (i = 16; i > 0; i--) {
        data <<= 1;
        if (MDIO_READ()) data |= 1;
        MDC_PULSE();
    }

    return data;
}

关于代码的提醒:上面的例子用GPIO模拟,适合MCU平台。如果你用的是带MAC的处理器(比如i.MX、STM32+ETH),直接用芯片自带的MDIO控制器会更省心——但要记得配置MDC时钟频率,不能超过2.5MHz(标准最大时钟)。

5. 本章知识体系总览

下面这张SVG图把MDIO/MDC协议、设备寻址和寄存器映射串在了一起。你可以把它当作一个快速索引:从协议帧结构出发,经过PHY地址选择芯片,再经过寄存器地址找到具体功能单元,最后通过Data字段完成读写。

MDIO/MDC 寄存器访问知识体系 ① 协议层 — MDIO/MDC 帧结构 Preamble 32位 SF 2位 OP 2位 PHY_ADDR 5位 REG_ADDR 5位 TA 2位 DATA 16位 ← 同步 → ← 帧标记 → ← 读/写 → ← 选哪个PHY → ← 选哪个寄存器 → ← 方向切换 → ← 数据内容 → ② 设备层 — PHY地址映射 MDIO总线 PHY 0 PHY 1 PHY 2 ...... PHY 31 硬件引脚 AD[0]~AD[4] 决定地址,最多 32 个设备挂在同一条 MDIO 总线上 ③ 寄存器层 — 寄存器地址与典型功能 R0 BMCR R1 BMSR R2-3 PHY ID R4 ANAR R5 ANLPAR R6-31 厂商自定义 前6个寄存器(0-5)由IEEE 802.3统一规范,后面的留给芯片厂商自由发挥。调试时先从R0和R1开始,定位速度/双工/链路状态。 核心路径:MDIO帧 → PHY_ADDR选中设备 → REG_ADDR选中寄存器 → DATA完成读写

SVG图使用指引:从顶层到底层,依次是"帧协议 → 设备寻址 → 寄存器功能"。你在调试时遇到问题,可以按这个顺序逐层排查:先看MDIO波形对不对,再看PHY地址是否匹配,最后确认寄存器地址和数据。这样效率最高,我屡试不爽。