4、基本控制寄存器(BCR):复位、环路、速度选择、自动协商使能等位域详解
基本控制寄存器(BCR)是PHY芯片里你最先碰到的寄存器,没有之一。它的地址通常是0x00,每个bit都有自己的脾气。我个人习惯在调试链路时,第一件事就是读BCR,看它当前的状态——是复位了还是自协商跑完了。
BCR一共16位,IEEE 802.3规范把它安排得明明白白。我挑几个关键位域拆开讲,这些你在项目里几乎天天都要打交道。
4.1 第15位:复位(Reset)
写1到这个位,PHY会执行一次硬件复位。所有寄存器回到默认值,内部状态机从头跑。我在项目中遇到过一种坑:复位后立刻写其他寄存器,结果写入不生效。为什么?因为复位操作需要一点时间(典型值50μs到100μs),你写得太快,PHY还没准备好。我曾经排查过一整天,最后发现是复位后等待不够——从那以后我习惯复位后至少等200μs再操作。
4.2 第14位:回环模式(Loopback)
写1让PHY进入内部回环。发送的数据直接环回到接收路径,不经过外部线缆。这个位我在调试板级互联时特别喜欢用——先把环回打开,发已知报文,看能不能收回来。如果能,说明MAC到PHY的串行通路没问题;如果不行,问题大概率在本地。
注意:不同PHY对回环的实现细节有差异。有的PHY只环回数据路径,有的连MDI/MDIX状态也一起环。我建议在产品文档里确认一下具体行为。
4.3 第13位:速度选择(Speed Selection)
这个位需要和第6位(Speed Selection MSB)配合使用,因为PHY通常至少支持10M/100M/1000M三档速度。组合方式:
| 位13 (LSB) | 位6 (MSB) | 速度 |
|---|---|---|
| 0 | 0 | 10 Mbps |
| 1 | 0 | 100 Mbps |
| 0 | 1 | 1000 Mbps |
| 1 | 1 | 保留(通常不用) |
手动配置速度时,记得同时设置自动协商使能位为0。否则你可能发现PHY根本不听你的,因为它还在自动协商。
4.4 第12位:自动协商使能(Auto-Negotiation Enable)
这是BCR里最重要的位之一。写1启动自协商,PHY会通过FLP Burst和对端交换能力信息。协商完成后,双方自动选择最佳速度和工作模式。
为什么要关掉它?有些场景下你必须关:
- 对端设备不支持自协商
- 链路固定,不需要协商过程(省时间)
- 调试时想固定强制模式
我踩过的坑是:有些PHY在关掉自协商之后,速度选择位的优先级会变化。比如,你设了100M全双工,但PHY的实际行为可能还是按上次协商结果跑。解决方法是先复位,再设强制参数,最后才写BCR关协商。顺序很重要,你想想看。
4.5 第9位:全双工(Full Duplex)
只有在自协商关闭、手动配置模式下才有效。写1表示全双工,0表示半双工。这哥们儿和速度选择一起决定了最终的链路参数。
我见过有人只改了速度,忘了改全双工位,结果链路协商成半双工,吞吐量直接腰斩。排查时一定要把BCR整个读回来,别只盯着某一个位。
4.6 第8位:重启自协商(Restart Auto-Negotiation)
写1触发重新协商。注意这是单脉冲触发,不是保持位。写入后PHY会中断当前链路,重新开始FLP交换。我通常在修改了自协商能力寄存器(比如ANAR)后,必须写这个位让新能力生效。
实战经验: 有一次在客户现场,网口死活不通。抓包发现PHY在反复重启自协商。后来读BCR发现第8位被驱动误写成了1,导致它不断重启。解决方法是:驱动里每次写该位后要确保后续清零(硬件会自动清,但软件不要重复写1)。
4.7 其他位(第7、10、11位)
第7位:无意义(保留)。第10位:隔离模式(Isolate),极少用。第11位:电源下状态(Power Down),掉电模式能省电但会把链路断开。
BCR寄存器整体结构可以用下面这张图看清关系:
4.8 编程实战:如何正确读/写BCR
下面是一段伪代码,展示配置100M全双工关自协商的流程:
// 第一步:读BCR
u16 bcr = phy_read(phy_addr, 0x00);
// 第二步:清掉自协商使能位和速度位,再设置新值
bcr &= ~(1 << 12); // 关自协商
bcr &= ~(3 << 6); // 清速度两位(Bit13和Bit6)
bcr | = (1 << 13); // 速度LSB=1 → 100M
bcr &= ~(1 << 6); // 速度MSB=0
bcr | = (1 << 9); // 全双工
// 第三步:写入BCR
phy_write(phy_addr, 0x00, bcr);
// 第四步:等待链路稳定
mdelay(300);
// 读BCR确认
bcr = phy_read(phy_addr, 0x00);
if (bcr & (1 << 12)) {
printf("WARNING: 自协商被意外打开!\n");
}
你想想看,这段代码里最容易被忽略的是第二步——很多人关自协商时没同时清掉速度位,结果PHY可能混用了上次协商残留的数值。
我个人保留一个习惯:每次写完BCR后,马上再读一遍,做一次回读校验。这么做虽然慢一点,但能立刻发现总线上的毛刺或写入错误。曾经在一次PCB改版后,MDIO走线过长导致写0x00变成了写0x1000,幸亏回读校验抓住了,不然板子焊上去三天都查不出问题。