寄存器基础知识:地址、位宽、属性与复位值
做PHY驱动这些年,我发现很多人一上来就盯着寄存器值改来改去,却连最基本的概念都没搞清楚。地址在哪?位宽多少?能读还是能写?复位后是什么值?这些不弄明白,后面调试就是瞎猜。今天这节我就把这四个核心要素掰开揉碎讲清楚。
寄存器地址:物理地址与偏移地址
每个寄存器在芯片内部都有一个唯一的位置,这就是它的地址。PHY芯片通常通过MDIO/MDC接口访问,地址分两层:PHY地址(器件地址)和寄存器地址(偏移地址)。
- 物理地址:芯片在总线上被分配的设备编号,比如PHY_ADDR=0x01,由硬件管脚决定。
- 偏移地址:寄存器在器件内部的位置编号,比如寄存器0x00、0x01……
记得我第一次调一个千兆PHY,怎么读写都不对。查了半天发现是MDIO帧里的PHY地址写错了——板子上PHY的ADDR管脚拉高拉低组合我没对。那之后我养成了习惯:上电先读一遍所有寄存器的复位值,确认地址通路通不通。
地址对齐也是个坑。有些PHY的寄存器是16位访问,但地址必须按2字节对齐。我见过一个同事把地址写成奇数,结果读回来的数据全是0xFFFF,还以为是芯片坏了。
数据位宽:8位、16位还是32位?
PHY寄存器最常用的是16位宽,但也有一些内部寄存器是8位或32位。位宽决定了你一次能读写多少数据,也决定了寄存器的排列方式。
| 位宽 | 典型场景 | 注意事项 |
|---|---|---|
| 8位 | 低端PHY内部状态寄存器 | 按字节操作,地址递增1 |
| 16位 | 标准IEEE 802.3 PHY寄存器 | MDIO规范默认16位,地址递增2 |
| 32位 | 扩展寄存器(如vendor specific) | 通常需要两次MDIO事务或MMD访问 |
我遇到过一个坑:芯片手册写着寄存器地址0x10~0x1F是16位,但实际测试发现某些地址读出高低字节颠倒。后来发现是因为总线字节序(Endianness)没对齐。所以你读任何寄存器前,先确认一下字节序是如何处理的。
核心要点:位宽不是随便定的。它决定了读写数据的长度、地址步长,以及跨边界时是否需要拆分访问。写代码时一定要用对应位宽的数据类型(比如uint16_t)来存储寄存器值。
读写属性:不只是R和W
寄存器不是你想读就能读、想写就能写的。不同位域有不同属性,搞错了轻则写不进去,重则让芯片死机。
常见的属性有:
- R/W(读写):最普通,随便读写。
- RO(只读):状态寄存器,写操作无效。
- WO(只写):比如触发寄存器,写1启动某个操作,读回无意义。
- R/C(读后清除):中断状态寄存器,读一次自动清零。你连续读两次,第二次就是0了。
- W1C(写1清除):向某位写1会清除该位,写0无影响。很多PHY的中断标志位就是这个属性。
还有一种叫「保留位」(Reserved),手册会告诉你必须写0,读回忽略。别不信邪——我见过有人往保留位写了1,结果芯片进入测试模式,整个网络都挂了。
复位值:芯片通电后的一切起点
复位值(Reset Value / Default Value)是芯片上电或复位后寄存器的初始状态。理解这一点很重要:PHY的初始化需要参考复位值来决定要改哪些位。
举个例子:某个寄存器默认是0x8000,最高位是软复位位。如果你在代码里直接写0x8100上去,可能会触发一次软复位,导致连锁反应。正确的做法是读-改-写,只改动你需要改的位。
| 场景 | 建议做法 | 原因 |
|---|---|---|
| 复位值全0 | 直接写目标值 | 不影响其他位 |
| 复位值非0 | 读-改-写 | 保留BIT_7~BIT_14这些你不想动的配置 |
| 复位值有保留位 | 写0 | 避免引发未知行为 |
我曾经接手一个项目,前任工程师把寄存器直接赋值了,没考虑复位值。结果下载到一批不同批次的PHY上,有些跑得好好的,有些直接不工作。查到最后发现是复位值的BIT_3在不同版本里变了,而他的写操作把那个位覆盖成了错误值。
val = read_reg(addr); val |= MASK; write_reg(addr, val);
这四要素是寄存器学习的基石。地址告诉你怎么找到它,位宽告诉你一次读多少字节,属性告诉你读写的规矩,复位值告诉你芯片的出厂状态。后面讲具体的PHY寄存器实例时,我们都会反复用到这套框架。记住,先看手册,再写代码,别凭感觉猜。