以太网 PHY 芯片 MDIO 管理接口深入分析寄存器读写、自协商状态机与 Link 检测完整实现一、PHY 不是透明管道自协商失败与 Link Flapping 的底层根因在嵌入式 Linux BSP 开发中以太网 PHY 芯片如 RTL8211F、KSZ9031、DP83848往往被视为 MAC 控制器和 RJ45 之间的透明管道。直到现场部署出现 Link 反复 Up/Down、对端交换机协商到 10Mbps 半双工、或特定网线长度下无法建链——开发才发现所有问题都指向 PHY 寄存器的配置与状态转换。PHY 芯片通过 MDIOManagement Data Input/Output串行总线与 MAC 控制器通信。IEEE 802.3 标准第 22 条定义了 16 个基本寄存器的地址空间第 45 条扩展了更多寄存器。MCU/SOC 通过 MDC时钟和 MDIO数据两根引脚以类似 I2C 的串行协议读写 PHY 内部寄存器完成速率配置、自协商控制、Link 状态检测和错误统计读取等关键操作。本文深入解析 MDIO 的寄存器操作时序、自协商状态机的全状态遍历并给出在裸机 RTOS 环境下的完整驱动实现。二、MDIO 帧格式与 PHY 自协商状态机MDIO 协议定义了两种帧格式Clause 22基本5bit PHY 地址 5bit 寄存器地址和 Clause 45扩展支持更多寄存器和间接寻址。最基本的 Clause 22 读/写帧格式如下读操作: PRE(32×1) | ST(01) | OP(10) | PHYAD(5bit) | REGAD(5bit) | TA(ZZ) | DATA(16bit) 写操作: PRE(32×1) | ST(01) | OP(01) | PHYAD(5bit) | REGAD(5bit) | TA(10) | DATA(16bit)自协商Auto-Negotiation是 PHY 最重要的完整状态机。IEEE 802.3 定义的 AN 状态机包含以下关键状态自协商的关键寄存器寄存器地址描述BMCR (Basic Mode Control)0x00Bit[12]Auto-Neg Enable, Bit[9]Restart ANBMSR (Basic Mode Status)0x01Bit[2]Link Status, Bit[5]AN CompletePHYID1/PHYID20x02/0x03PHY 芯片 OUI 标识ANAR (Advertisement)0x04本端能力通告ANLPAR (Link Partner Ability)0x05对端能力寄存器自协商故障的核心诊断路径读取 ANAR(0x04) → 确认本端通告能力完整 → 读取 ANLPAR(0x05) → 确认对端能力匹配 读取 BMSR[5] → 确认 AN 完成标志 读取 BMSR[2] → 确认 Link 状态。三、MDIO 驱动的裸机实现与自协商状态监控以下代码实现 MDIO 总线的底层 bit-bang 操作和 PHY 寄存器读写。适用于 STM32 或任何有 GPIO 控制能力的 MCU。/* * mdio_driver.c — MDIO 总线 bit-bang 驱动 PHY 自协商控制 * * 硬件连接: * MCU GPIO_A → MDC (时钟, 最大 2.5MHz) * MCU GPIO_B → MDIO (数据, 开漏, 需上拉电阻 1.5KΩ) * * 时序遵从 IEEE 802.3 Clause 22 * * 设计要点: * 1. MDIO 为开漏信号读操作时需将 GPIO 设为输入模式 (Hi-Z) * 2. MDC 时钟频率上限 2.5MHz本实现使用 1MHz 确保兼容性 * 3. 读取操作中 TA 阶段需在第一个时钟周期释放总线Hi-Z * PHY 在第二个时钟周期驱动数据线 */ /* * 平台相关的 GPIO 操作 —— 用户需根据实际平台实现 * 以下为 STM32 HAL 风格的伪接口 */ #define MDC_PIN GPIO_PIN_0 #define MDIO_PIN GPIO_PIN_1 #define MDIO_PORT GPIOA static void mdc_high(void) { /* GPIO_SetBits(MDIO_PORT, MDC_PIN) */ } static void mdc_low(void) { /* GPIO_ResetBits(MDIO_PORT, MDC_PIN) */ } static void mdio_high(void) { /* 开漏输出高 (释放总线) */ } static void mdio_low(void) { /* 开漏输出低 */ } static int mdio_read(void) { /* 返回 GPIO 输入电平 (0 或 1) */ } static void mdio_out(void) { /* 设置 GPIO 为开漏输出模式 */ } static void mdio_in(void) { /* 设置 GPIO 为输入 (浮空) 模式 */ } static void delay_us(int us) { /* 微秒级延时 */ } #define MDC_DELAY_US 1 /* 1MHz 时钟: 高低各 1us 周期 2us */ /* MDIO Clause 22 帧结构常量 */ #define MDIO_PREAMBLE_LEN 32 /* 前导码: 32 个连续 1 */ #define MDIO_START_BITS 0x01 /* ST 01 */ #define MDIO_OP_READ 0x02 /* 读操作: OP 10 */ #define MDIO_OP_WRITE 0x01 /* 写操作: OP 01 */ #define MDIO_TA_READ 0x02 /* TA 读: ZZ → PHY 驱动 → 0 */ /* * 发送指定数量的 bit * 数据从 MSB 开始逐位发送 */ static void mdio_send_bits(uint32_t data, int num_bits) { mdio_out(); /* 确保 GPIO 为输出模式 */ for (int i num_bits - 1; i 0; i--) { mdc_low(); if (data (1u i)) { mdio_high(); } else { mdio_low(); } delay_us(MDC_DELAY_US); mdc_high(); delay_us(MDC_DELAY_US); } } /* * 发送前导码: 32 个连续的 1 * 用于同步 PHY 的 MDIO 状态机 */ static void mdio_send_preamble(void) { mdio_out(); for (int i 0; i MDIO_PREAMBLE_LEN; i) { mdc_low(); mdio_high(); /* 数据 1 */ delay_us(MDC_DELAY_US); mdc_high(); delay_us(MDC_DELAY_US); } } /* * 读取 16 bit 数据 (MSB first) * * 读操作中master 在 MDC 上升沿采样数据 * PHY 在 MDC 下降沿后驱动数据 */ static uint16_t mdio_recv_bits(int num_bits) { uint16_t data 0; mdio_in(); /* GPIO 切换为输入 (Hi-Z) */ delay_us(MDC_DELAY_US); for (int i 0; i num_bits; i) { mdc_low(); delay_us(MDC_DELAY_US); mdc_high(); delay_us(MDC_DELAY_US / 2); /* 等待数据稳定 */ if (mdio_read()) { data | (1u (num_bits - 1 - i)); } delay_us(MDC_DELAY_US / 2); } return data; } /* * Clause 22 MDIO 读操作 * * 帧格式: PRE(32) | ST(01) | OP(10) | PHYAD(5) | REGAD(5) | TA(ZZ) | DATA(16) * * 注意 TA (Turnaround) 阶段: * - 第 1 个 bit: master 释放总线 (Hi-Z), PHY 尚未驱动总线为高上拉电阻 * - 第 2 个 bit: PHY 驱动总线为 0表示正常响应 * * 返回值: 16-bit 寄存器值读取失败返回 0xFFFF */ uint16_t mdio_read_register(uint8_t phy_addr, uint8_t reg_addr) { /* 前导码 */ mdio_send_preamble(); /* ST (01) OP (10) PHYAD(5) REGAD(5) 14 bits */ uint32_t frame ((uint32_t)MDIO_START_BITS 12) | ((uint32_t)MDIO_OP_READ 10) | ((uint32_t)(phy_addr 0x1F) 5) | ((uint32_t)(reg_addr 0x1F)); mdio_send_bits(frame, 14); /* TA (Turnaround) 阶段 */ mdio_in(); /* master 释放总线 */ mdc_low(); delay_us(MDC_DELAY_US); mdc_high(); delay_us(MDC_DELAY_US); /* 接收 16-bit 数据 */ uint16_t data mdio_recv_bits(16); /* 总线恢复为输出 (驱动为高释放) */ mdio_out(); mdio_high(); return data; } /* * Clause 22 MDIO 写操作 * * 帧格式: PRE(32) | ST(01) | OP(01) | PHYAD(5) | REGAD(5) | TA(10) | DATA(16) */ void mdio_write_register(uint8_t phy_addr, uint8_t reg_addr, uint16_t data) { /* 前导码 */ mdio_send_preamble(); /* ST (01) OP (01) PHYAD(5) REGAD(5) 14 bits */ uint32_t frame ((uint32_t)MDIO_START_BITS 12) | ((uint32_t)MDIO_OP_WRITE 10) | ((uint32_t)(phy_addr 0x1F) 5) | ((uint32_t)(reg_addr 0x1F)); mdio_send_bits(frame, 14); /* TA (Turnaround) 阶段: master 发送 10 */ mdio_send_bits(0x02, 2); /* 发送 16-bit 数据 */ mdio_send_bits(data, 16); /* 总线恢复空闲 (高电平) */ mdio_out(); mdio_high(); } /* * PHY 寄存器定义 (IEEE 802.3 Clause 22 标准寄存器) * */ #define PHY_REG_BMCR 0x00 /* 基本模式控制 */ #define PHY_REG_BMSR 0x01 /* 基本模式状态 */ #define PHY_REG_PHYID1 0x02 /* PHY ID 高16位 */ #define PHY_REG_PHYID2 0x03 /* PHY ID 低16位 */ #define PHY_REG_ANAR 0x04 /* 自协商通告 */ #define PHY_REG_ANLPAR 0x05 /* 对端能力 */ #define PHY_REG_ANER 0x06 /* 自协商扩展 */ /* BMCR 位定义 */ #define BMCR_RESET (1 15) /* 软件复位 */ #define BMCR_ANE_ENABLE (1 12) /* 自协商使能 */ #define BMCR_RESTART_AN (1 9) /* 重启自协商 */ #define BMCR_DUPLEX_MODE (1 8) /* 全双工 */ #define BMCR_SPEED_100 (1 13) /* 100Mbps */ /* BMSR 位定义 */ #define BMSR_AUTO_NEG_COMPLETE (1 5) /* 自协商完成 */ #define BMSR_LINK_STATUS (1 2) /* Link 状态 */ /* * PHY 初始化与自协商控制 * * 返回值: 0 成功, -1 PHY 无响应, -2 自协商超时 */ int phy_init_and_autoneg(uint8_t phy_addr) { /* 第一步: 读取 PHY ID验证 MDIO 通信 */ uint16_t id1 mdio_read_register(phy_addr, PHY_REG_PHYID1); uint16_t id2 mdio_read_register(phy_addr, PHY_REG_PHYID2); if (id1 0xFFFF || id2 0xFFFF) { printf([ERROR] PHY 无响应 (addr%d, ID10x%04X, ID20x%04X)\n, phy_addr, id1, id2); return -1; } printf([INFO] PHY 检测成功: OUI0x%04X%04X\n, id1, id2); /* 第二步: 软件复位 PHY */ mdio_write_register(phy_addr, PHY_REG_BMCR, BMCR_RESET); /* 等待复位完成 (轮询 BMCR[15] 直到清零最多 500ms) */ int retry 500; while (retry-- 0) { uint16_t bmcr mdio_read_register(phy_addr, PHY_REG_BMCR); if (!(bmcr BMCR_RESET)) { break; } delay_us(1000); /* 1ms */ } if (retry 0) { printf([ERROR] PHY 复位超时\n); return -2; } /* * 第三步: 配置自协商能力通告 * 通告: 100Base-TX Full/Half, 10Base-T Full/Half * ANAR[8:5] 100Base-TX FD/HD, 10Base-T FD/HD */ uint16_t anar (1 8) /* 100Base-TX Full Duplex */ | (1 7) /* 100Base-TX Half Duplex */ | (1 6) /* 10Base-T Full Duplex */ | (1 5) /* 10Base-T Half Duplex */ | (1 0); /* 802.3 选择器字段 */ mdio_write_register(phy_addr, PHY_REG_ANAR, anar); /* 第四步: 使能并重启自协商 */ mdio_write_register(phy_addr, PHY_REG_BMCR, BMCR_ANE_ENABLE | BMCR_RESTART_AN); printf([INFO] 自协商已启动 (ANAR0x%04X)\n, anar); /* 第五步: 等待自协商完成 (最多 5 秒) */ retry 5000; while (retry-- 0) { uint16_t bmsr mdio_read_register(phy_addr, PHY_REG_BMSR); if (bmsr BMSR_AUTO_NEG_COMPLETE) { /* 读取协商结果 */ uint16_t anlpar mdio_read_register(phy_addr, PHY_REG_ANLPAR); printf([INFO] 自协商完成! 对端能力: 0x%04X\n, anlpar); printf([INFO] Link 状态: %s\n, (bmsr BMSR_LINK_STATUS) ? UP : DOWN); /* 解析协商的最高速率 */ if (anlpar (1 8)) { printf([INFO] 协商速率: 100Mbps 全双工\n); } else if (anlpar (1 7)) { printf([INFO] 协商速率: 100Mbps 半双工\n); } else if (anlpar (1 6)) { printf([INFO] 协商速率: 10Mbps 全双工\n); } else if (anlpar (1 5)) { printf([INFO] 协商速率: 10Mbps 半双工\n); } return 0; } delay_us(1000); /* 1ms */ } printf([ERROR] 自协商超时 (5 秒)\n); return -2; } /* * Link 状态轮询任务 —— 建议在 RTOS 中以 100ms 周期调用 * 检测 Link 变化并上报事件 */ int phy_link_poll(uint8_t phy_addr, int *prev_link_up) { uint16_t bmsr mdio_read_register(phy_addr, PHY_REG_BMSR); if (bmsr 0xFFFF) { /* PHY 无响应 —— 可能处于复位状态 */ return -1; } int link_up (bmsr BMSR_LINK_STATUS) ? 1 : 0; if (link_up ! *prev_link_up) { printf([EVENT] Link %s → %s\n, *prev_link_up ? UP : DOWN, link_up ? UP : DOWN); *prev_link_up link_up; /* Link UP 后重新确认协商速率 */ if (link_up) { uint16_t anlpar mdio_read_register(phy_addr, PHY_REG_ANLPAR); printf([INFO] 协商速率能力: 0x%04X\n, anlpar); } } return link_up; }四、MDIO 的通信可靠性与硬件层面的边界条件MDIO 总线的电气特性MDIO 采用开漏输出 上拉电阻典型值 1.5KΩ to 3.3V的电气规范。最大容性负载为 470pF超过此值会导致上升沿缓慢、时序违规。在一主多从的多 PHY 拓扑中每个 PHY 的引脚电容叠加需控制在 32 个器件以内。布线长度超过 20cm 时建议在 MDC 上串联 22Ω 终端电阻。PHY 地址冲突MDIO 协议支持 5 位 PHY 地址0~31。多数 PHY 芯片通过硬件 strap pin 配置地址如 RTL8211F 的 PHYAD[4:0] pins。当 PCB 上存在多个 PHY 时需确保地址不冲突。地址 0 有特殊含义广播地址部分 PHY 会将地址 0 的写入转发到所有 PHY——这是调试中容易忽略的陷阱。寄存器写入的读回验证IEEE 802.3 要求 MDIO 写入操作不提供 ACK 机制。为保证写入确实生效必须对关键寄存器如 BMCR 的 Auto-Neg 使能位进行读回验证。示例代码中未包含此步骤但在生产代码中对于 BMCR、ANAR 等控制寄存器的写入后必须读回确认。禁用场景场景原因使用 RMII 内建 MDIO 控制器的平台使用硬件 MDIO 控制器比 bit-bang 更可靠需要访问 Clause 45 扩展寄存器bit-bang 实现需扩展为 Clause 45 帧PHY 间距 30cm 的多板互联信号完整性不可保证需要原子性的寄存器批量更新MDIO 不支持事务需 read-modify-write五、总结MDIO 是连接 MAC 和 PHY 的唯一管理通道对其深入理解直接影响以太网链路的稳定性和可调试性寄存器是诊断的入口Link flapping 的根因 90% 可通过 BMSR、ANLPAR 和 PHY 厂商的扩展状态寄存器定位。建议在 BSP 中内置寄存器 dump 命令。自协商状态机完整遍历 AN_ENABLE → ABILITY_DETECT → ACKNOWLEDGE_DETECT → COMPLETE_ACK → LINK_OK。任何阶段的超时或失败都能从 BMSR 中读取。bit-bang 的可靠性MDIO 的 bit-bang 实现需严格遵循 IEEE 802.3 的 32-bit 前导码和 TATurnaround时序。1MHz MDC 频率是兼容性的安全选择。寄存器读回验证MDIO 写入操作无 ACK关键控制寄存器写入后必须读回验证。这是生产代码和调试代码之间的关键质量差异。