寄存器读写基础: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位(写操作也类似)。我习惯把这个帧拆成几个字段来记:
| 字段 | 位宽 | 说明 |
|---|---|---|
| Preamble | 32位 | 全1,同步用 |
| Start of Frame | 2位 | 固定 01 |
| Operation Code | 2位 | 10读 / 01写 |
| PHY Address | 5位 | 目标PHY设备号(0-31) |
| Register Address | 5位 | 目标寄存器号(0-31) |
| Turnaround | 2位 | 读操作:MDIO切换方向;写操作:10 |
| Data | 16位 | 读写的数据内容 |
为什么Preamble要32个1?我理解是给PHY足够时间锁定时钟相位。实际测试中,有些PHY对这个字段不那么严格,但规范就是规范,不要偷懒。
小技巧:读操作时,Turnaround字段的第1位由MAC保持高阻(Z),PHY拉低第2位作为应答。如果你用GPIO模拟MDIO,务必在此时切换IO方向。我见过很多新手在这个地方卡住——方向没切,数据一直为0。
读写操作对比
读和写的帧结构基本一致,差别在OP码和Turnaround。看这张表就清楚了:
| 操作 | OP码 | Turnaround | 谁驱动Data |
|---|---|---|---|
| 读 | 10 | Z → 0(PHY应答) | PHY |
| 写 | 01 | 10(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字段完成读写。
SVG图使用指引:从顶层到底层,依次是"帧协议 → 设备寻址 → 寄存器功能"。你在调试时遇到问题,可以按这个顺序逐层排查:先看MDIO波形对不对,再看PHY地址是否匹配,最后确认寄存器地址和数据。这样效率最高,我屡试不爽。