做嵌入式以太网调试,你大概率经历过这种场面:代码在开发板上跑得好好的,换成自己画的板子,ping 不通,PHY ID 读出来全是0xFFFF,查了一圈,最后用示波器怼到 REF_CLK 引脚上——一片安静。RMII Reference Clock,这个看起来只是数据手册里一个引脚的信号,其实是整个以太网链路里最先要亮起来的东西。这篇文章就把 STM32 的 RMII 参考时钟彻底讲透:为什么必须有它、谁来产生、怎么设计、怎么配置、踩过的坑怎么排。
内容面向正在用 STM32(F1/F4/H7)做以太网板卡的工程师和学生。无论你用的是 LAN8720A、RTL8211F 还是 YT8512,只要接口是 RMII,这篇文章里的思路都能直接用上。
1. RMII 与参考时钟:50MHz 到底从哪里来
1.1 为什么 RMII 必须带一个外部时钟
先把 RMII 和更早的 MII 放在一起看。MII(Media Independent Interface)有 16 根信号线:TXD[3:0]、RXD[3:0]、TX_EN、RX_DV、TX_CLK、RX_CLK、CRS、COL,外加 MDC/MDIO 管理接口。它的收发时钟是 PHY 给的,100Mbps 时跑 25MHz,10Mbps 时跑 2.5MHz。MAC 侧并不自己产生时钟,属于“PHY 说了算”。
RMII(Reduced MII)把数据线砍到 2 根发送、2 根接收,TX_EN 和 RX_DV 合并成一个 CRS_DV,总数只剩 9 根左右。省引脚是有代价的:收发双方必须共用一个 50MHz 参考时钟,所有数据都在这个时钟的上升沿同步。100Mbps 时每个时钟周期传 1 bit,10Mbps 时相当于每 10 个周期取 1 bit。这个“公共参考时钟”就是 RMII Reference Clock,业界简称 REF_CLK。
| 对比项 | MII | RMII |
|---|---|---|
| TXD 数据线 | 4 | 2 |
| RXD 数据线 | 4 | 2 |
| 发送使能 | TX_EN | TX_EN |
| 接收有效 | RX_DV | CRS_DV(合并) |
| 收发时钟 | TX_CLK/RX_CLK,由 PHY 提供 | REF_CLK,50MHz,外部公共时钟 |
| 信号总数 | 约 16 | 约 9 |
所以 RMII 这种接口天生就假设有一个稳定的 50MHz 参考时钟源存在。没有这个时钟,MAC 的接收状态机根本不跑,发送侧也找不到同步基准,后续所有初始化行为都是空转。
这里有一个新手特别容易混淆的概念:STM32 内部的 MAC 时钟,比如 AHB 或 AXI 域的主频,跟 RMII REF_CLK 没有直接关系。MAC 的寄存器访问和 DMA 搬运靠系统总线时钟,而数据收发同步靠 REF_CLK。就算你的 F407 主频跑到 168MHz,如果 REF_CLK 引脚没有 50MHz 信号进来,以太网照样不通。
1.2 50MHz 时钟的三种供给架构
从系统层面看,RMII 的 50MHz 参考时钟可以从三个位置产生:PHY 内部倍频输出、MAC(也就是 STM32)侧输出、外部独立时钟源。了解这三种来源,后面做选型和布线才有判断依据。
第一种是 PHY 自己产生。最典型的做法是在 PHY 旁边放一个 25MHz 无源晶振,PHY 内部 PLL 倍频到 50MHz,然后通过 REF_CLK 引脚输出给 STM32。LAN8720A、KSZ8081、YT8512 这些 PHY 都支持这种模式。这是目前 STM32 方案里最省事的做法,因为 MCU 不需要额外输出时钟,只要老老实实接收一个干净的外部时钟就行。
第二种是 STM32 通过 MCO 引脚输出 50MHz 给 PHY。F4 系列有 MCO1/MCO2 引脚,理论上可以输出主 PLL 的时钟。这个方案看着很香,因为它省掉一颗晶振,也省掉了 PHY 输出时钟的成本。实际做起来限制很多:MCO 输出频率来源于 PLL 配置,想要 50MHz 就得让系统主频配合;更麻烦的是,STM32 自己的 RMII_REF_CLK 引脚也需要时钟。很多参考设计的做法是 MCO 先把 50MHz 送给 PHY,PHY 再把自己的 REF_CLK 回送给 STM32,形成一条“MCU 产出 → PHY 消费 → PHY 产出 → MCU 消费”的链路。绕一圈,问题反而更多,所以这个方案我建议谨慎用。
第三种是外部有源晶振。50MHz 有源振荡器同时输出给 PHY 的时钟输入引脚和 STM32 的 RMII_REF_CLK 引脚。这个方案信号质量最好,源端驱动能力强,缺点是成本高、占 PCB 面积。对 H7 这种只能输入 RMII 参考时钟的系列来说,这是最稳的选择之一。
三种方案的优劣我用表格直接摊开:
| 供给方案 | 时钟源 | 对 MCU 的要求 | 优点 | 缺点 |
|---|---|---|---|---|
| PHY 自出 | 25MHz 晶振 + PHY PLL | REF_CLK 引脚输入 | 电路简单、成本低、STM32 负担小 | 依赖 PHY 的 REF_CLK 输出能力 |
| MCU MCO 输出 | STM32 PLL | MCO 引脚输出 50MHz | 少一颗晶振 | 主频配置受限,链路绕,容易踩坑 |
| 外部有源晶振 | 50MHz 振铃器 | REF_CLK 引脚输入 | 信号稳定、调试方便 | 成本高,多一颗有源器件 |
1.3 谁产生、谁消费:先分清身份再动手
做 RMII 时钟设计前,建议先画一张非常简单的草图:50MHz 信号从谁出发,经过谁的引脚,最后到达谁的引脚。不要觉得这个动作多余,我见过太多人直接照抄别人的原理图,结果 PHY 型号变了,REF_CLK 方向没跟着变,板子回来全废。
这里要说清楚一个“身份”问题:STM32 的 RMII_REF_CLK 引脚永远是输入方向,MAC 是消费者。PHY 在多数场景下是生产者。如果 STM32 用 MCO 做时钟源,那 STM32 同时扮演“给 PHY 供时钟”和“消费 RMII 时钟”两个角色,但 RMII_REF_CLK 这个引脚本身依然是输入。也就是说,你可以让 MCO 输出 50MHz,但你还得想办法让这个信号或者 PHY 回送的信号,最终进到 RMII_REF_CLK 引脚里。
判断一个电路设计是否合理,最简单的方法就是看数据通路:把 PHY 的 REF_CLK 输出引脚和 STM32 的 RMII_REF_CLK 引脚用一个虚拟箭头连起来,如果中间断了,那这个设计就有问题。
2. 不同 STM32 系列对 RMII 时钟的要求
2.1 F1/F4 系列:既能输入,也能用 MCO 输出
先明确哪些型号带以太网 MAC。F1 系列只有 STM32F107 带,F4 系列是 STM32F405、F407、F427、F437 等带,F401/F411 这类不带。不要看到“STM32F4”就觉得有以太网,选型时一定查数据手册里的外设列表。
F4 系列的 RMII 参考时钟支持两种用法:外部时钟输入,或者由内部 PLL 经过 MCO 引脚输出。大多数人用前者,因为后者有隐藏约束。我之前专门验证过 MCO 输出 50MHz 的路径:F407 的 MCO1 可选源是 HSI、HSE、LSE、PLLCLK,PLLCLK 实际是主 PLL 的 P 分频输出,也就是系统主频那条线。如果你把系统主频配成 168MHz,MCO1 的预分频只有 1~5 档,出来的频率是 168、84、56、42、33.6MHz,没有一档是 50MHz。想要在 MCO1 上得到 50MHz,得把系统主频配成 100MHz,比如外部 25MHz 晶振、PLLM=25、PLLN=200、PLLP=2,这样 SYSCLK=100MHz,MCO1 再除 2 得到 50MHz。
这个“为了给 PHY 供时钟而改主频”的做法,在高性能应用里很难接受。很多项目主频跑 168MHz 是为了算力,不可能为了一颗 PHY 降频。所以 F4 平台上我最终几乎全用“PHY 自出 50MHz”方案,省心又稳定。
F107 的情况更特殊一点。它本身没有 MCO 到 RMII 的成熟便捷路径,参考设计基本是让 PHY 侧提供 50MHz 参考时钟。你如果还在维护 F107 的老项目,直接按 PHY 出时钟去查电路就对了。
2.2 H7 系列:ETHREF_CLK 只能输入,别想着让 MCU 输出
STM32H7(H743、H750、H723 等带以太网的型号)在 RMII 参考时钟这个问题上有一个非常明确的限制:ETHREF_CLK 引脚只能作为输入,MCU 内部没有路径可以把这个 50MHz 信号输出到 PHY。参考手册原意就是要求外部时钟源提供 RMII 参考时钟,外部源可以是 PHY 的 REF_CLK 输出,也可以是独立的 50MHz 有源晶振。
这个限制直接让“MCO 输出给 PHY”的方案在 H7 上失效。我最初做 H7 板子时,以为可以像 F4 那样用 MCO 绕一下,结果仔细读手册才发现 H7 的 RMII 时钟路径里根本没有内部输出选项。后来老老实实把 PHY 的 CLK_OUT 引脚接到 STM32 的 ETHREF_CLK,问题才解决。所以如果你的主控是 H7,请直接放弃“由 MCU 给 PHY 供时钟”这个念头,老老实实选一颗带时钟输出能力的 PHY,或者加一颗 50MHz 有源晶振。
这里还想多说一句:H7 上还有一种常见组合是搭配 RTL8211F。这颗 PHY 本身支持 RMII 模式,但需要认真读它的寄存器,把 CLK_OUT 相关位配好,否则 REF_CLK 引脚上什么都没有。
2.3 选型判断:时钟方案和 PHY 怎么搭配
很多人一开始就被“RMII 需要一个 50MHz 参考时钟”这句话带偏,以为只要找个能出 50MHz 的器件就行。实际上,不同 PHY 对时钟输入的要求差异很大,选 PHY 之前先想清楚你的时钟方案。
如果你的方案是“PHY 自出 50MHz 给 MCU”,那就得选支持“25MHz 晶振输入 + 内部倍频 + REF_CLK 输出”的 PHY。LAN8720A、KSZ8081、YT8512 都是这一类,但注意它们输出 REF_CLK 的引脚可能和中断引脚复用,比如 LAN8720A 的 nINT/REFCLK0 引脚就是两用脚,上电默认是中断功能,需要配置寄存器切换到 REFCLK 输出,或者用硬件 Strapping 让它默认输出时钟。PCB 回来后发现 REF_CLK 引脚上是乱七八糟的中断脉冲而不是 50MHz 方波,多半就是这里没处理好。
如果你的方案是“外部 50MHz 有源晶振”,那 PHY 只需支持外部时钟直接输入即可,LAN8720A 也支持,把 50MHz 接到 XI 脚,XT2 悬空。这个方案对 PHY 的 REF_CLK 输出能力要求不高,因为 MCU 可以直接从有源晶振取同一个时钟。
如果你的方案是“MCU MCO 输出”,那 PHY 需要接受外部 50MHz 时钟输入,同时最好支持把输入时钟回传给 MCU,或者你在 PCB 上把 MCO 网络同时连到 MCU 的 RMII_REF_CLK 和 PHY 的时钟输入。后者等于用一根线拖两个负载,对 MCO 驱动能力是考验,而且信号反射问题也更突出。能用,但没必要。
3. 硬件设计实操:以 F407 + LAN8720A 为例
3.1 25MHz 晶振方案:让 PHY 自己倍频
这套组合是 STM32F407 平台上最普及的:LAN8720A 旁边放一颗 25MHz 无源晶振,晶振接 XI 和 XO 引脚,PHY 内部 PLL 倍频成 50MHz,然后从 nINT/REFCLK0 引脚输出给 F407 的 PA1(RMII_REF_CLK)。
无源晶振的匹配电容很关键。LAN8720A 的典型应用里,XI/XO 对地各接一个 33pF 左右,但实际上要根据晶振的 CL 负载电容换算。我看到很多人直接抄模块原理图,结果晶振不起振,REF_CLK 完全没有输出。一个简单规则是:如果晶振规格书的 CL 是 18pF,那么两颗匹配电容各取约 18~22pF;如果 CL 是 12pF,就取 12~15pF。走线时,晶振要尽量靠近 XI/XO 引脚,两个负载电容的地要短,最好直接打过孔到地平面。
这里还有一个硬件 Strapping 的问题:LAN8720A 的 nINT/REFCLK0 引脚在空载时默认是中断输出,要让它在 RMII 模式下直接输出 50MHz,通常需要配置 PHY 的寄存器。有的模块在硬件上通过 Strapping 电阻强制成 REFCLK0 输出,这样 MCU 上电就能直接收到 50MHz 时钟,不用额外写寄存器。买模块前先问清楚,自己画板则要仔细看数据手册的 Strapping 说明。
3.2 外部 50MHz 方案:有源晶振怎么接
如果追求调试方便、信号稳定,直接在板子上放一颗 50MHz 有源振荡器,输出脚通过一个 33Ω 串阻分成两路:一路给 PHY 的时钟输入,一路给 STM32 的 RMII_REF_CLK。这里注意两个点。
第一,有源晶振输出电平要和 PHY 的输入要求匹配。大多数 PHY 是 3.3V 供电,直接接 3.3V LVCMOS 输出没问题。如果 PHY 是 2.5V 或者 1.8V I/O,最好选对应电平的有源晶振,或者加电平转换,不要硬接。
第二,分路点要靠近负载,尽量减少 stub 长度。理想情况是有源晶振放在两个负载中间位置,输出线先到最近的引脚,再到另一个引脚。不要从晶振拉一根很长的线到板子另一端,然后又分叉回来。50MHz 不算特别高,但反射和振铃足以让 PHY 的输入时钟不满足电平阈值,表现为 PHY ID 偶发读不到、丢包率异常。
LAN8720A 在这种模式下,XI 接 50MHz,XO 悬空即可。注意有些 PHY 要求外部时钟输入直接接到 XI,而不是 XO,接反了一律不通。
3.3 PCB 布线和上拉电阻细节
RMII_REF_CLK 这条线在 PCB 上的优先级应该排所有以太网信号里的第一位。我的经验是:线走内层或表层都可以,但一定不要和开关电源、电感、DC-DC 反馈走线并行走长距离;不要打太多过孔,最好两个以内;源端串 22~33Ω 电阻,放在时钟输出端。这个串阻不是摆设,它吸收反射,能让示波器看到的波形上升沿更干净。
再回应一个热词里出现频率很高的疑问:RMII 接口的 MDC 需不需要接上拉电阻?答案是分开看。MDC 是 MAC 输出给 PHY 的时钟信号,推挽驱动,一般不用上拉。MDIO 是双向开漏信号,必须接上拉电阻,典型值 2.2kΩ 到 3.3V。如果 MDIO 漏接上拉,SMI 总线通信会非常不稳定,PHY ID 可能读出来一次、读不出来一次,但表现往往不是“完全不通”,而是“时通时不通”。这和 REF_CLK 无关,但很多人在排查 RMII 失败时把两件事混在一起,所以特意提一句。
还有一个容易忽略的点:PHY 的复位引脚。很多 LAN8720A 模块把 nRST 直接拉高,这样做能跑,但不规范。建议至少用一个 RC 复位电路,或者用一个 GPIO 控制复位,复位释放时间要在电源稳定之后。如果复位时序不对,PHY 内部寄存器可能处于随机状态,REF_CLK 输出配置、Strapping 检测都可能出错,问题看起来会非常像时钟故障。
4. CubeMX + HAL 的软件配置全流程
4.1 引脚模式:选 RMII,不是选 MII
在 STM32CubeMX 里使能 Ethernet,第一步要选择媒体接口模式。F407 的 ETH 外设界面里会让你选 MII 或 RMII,这里不能只看“功能”,选 RMII 后引脚分配会完全不同。
F407 选 RMII 后,CubeMX 自动分配的引脚大致是:
| 信号 | 引脚 |
|---|---|
| ETH_RMII_REF_CLK | PA1 |
| ETH_RMII_CRS_DV | PA7 |
| ETH_RMII_RXD0 | PC4 |
| ETH_RMII_RXD1 | PC5 |
| ETH_RMII_TX_EN | PB11 |
| ETH_RMII_TXD0 | PB12 |
| ETH_RMII_TXD1 | PB13 |
| ETH_MDIO | PA2 |
| ETH_MDC | PC1 |
如果你用的板子引脚和这个表不一致,先检查是否选错了型号或者模式。这里最容易出的问题是用 F407VG 的板子抄了 F407ZE 的引脚分配,RXD/TXD 接错位,REF_CLK 却接到了对的引脚,最后表现就是时钟正常、PHY ID 能读,但 ping 不通,或者数据全错。排查这种问题比排查缺时钟更费时间,因为你会先去怀疑软件。
还有一个细节:如果 PHY 的复位引脚接了 GPIO,CubeMX 里要把它配成 GPIO_Output,初始电平设为低,然后在初始化代码里先拉低再拉高,完成一次硬件复位。不要忘了这一步。
4.2 时钟树配置:要不要动 PLL
先说结论:如果使用 PHY 自出时钟方案,CubeMX 时钟树里不需要专门为 RMII 配置任何 50MHz 路径。RMII_REF_CLK 引脚上的时钟来自外部,和内部的 SYSCLK、HCLK 没有关系。CubeMX 里开着 ETH 外设,只要求 AHB 时钟(也就是 MAC 和 DMA 的时钟)正确即可,F407 默认 168MHz 没问题。
如果使用 MCO 输出方案,这里的时钟树就要单独算。比如外部 25MHz 晶振,想从 MCO1(PA8)输出 50MHz,需要让 PLL 分频链条正好落到 100MHz 主频,再除 2。PLLM=25,PLLN=200,PLLP=2,VCO=200MHz,PLLCLK=100MHz,MCO1 选 PLLCLK 并 div2,输出 50MHz。这样 SYSCLK 是 100MHz,不是常规的 168MHz。
CubeMX 的 Clock Configuration 页里,如果你选 MCO1 输出,它会实时显示频率,但你要注意它计算出来的频率是基于当前 PLL 配置的。一旦你调整 PLL 去迁就 50MHz,整个系统主频都会变,USB、定时器、串口波特率都可能受影响。这也是我不推荐 MCO 方案的重要原因。
H7 平台更简单:CubeMX 时钟树里根本没有“给 ETH 输出 RMII 时钟”的选项,你只需要确认 PHY 侧的时钟源硬件OK,软件配置里没有额外步骤。
4.3 初始化代码:从 GPIO 到读 PHY ID
CubeMX 生成的 HAL 初始化代码核心是MX_ETH_Init。它内部设置 MAC 地址、DMA 描述符、接收/发送缓冲区和媒体接口选择。媒体接口选择对应寄存器是ETH_MACCR里的ECLK相关位,生成的代码会通过宏ETH_MII_RMII_SELECTION区分,你不必手动改。
实际项目里,HAL_ETH_Init成功返回后,还要做几件事才能让链路真正跑起来。第一步是读 PHY ID,验证 MDC/MDIO 通信是否正常。以 LAN8720A 为例,它的 PHY ID 寄存器是地址 2 和地址 3,通常读到0x0007C0F1这样的值。如果读出来是0xFFFF,基本可以断定 MDIO 时序或者物理层供电有问题。第二步是配置 PHY 的基本寄存器,比如自动协商、广播速率等。第三步是轮询 PHY 的 Basic Status Register,等待 Link Up。
下面是一段常用的 PHY ID 读取和 Link 判断代码结构:
uint32_t id1 = 0, id2 = 0; HAL_ETH_ReadPHYRegister(&heth, PHY_ID1_REG, &id1); HAL_ETH_ReadPHYRegister(&heth, PHY_ID2_REG, &id2); printf("PHY ID: 0x%04X 0x%04X\r\n", id1, id2); // 等待 Link Up do { HAL_ETH_ReadPHYRegister(&heth, PHY_BSR_REG, &bsr); } while ((bsr & PHY_LINKED_STATUS) == 0);不同 PHY 的寄存器地址不一样,但思路通用。重点在于:如果 PHY ID 读不出来,就别继续往下调协议栈,先把 50MHz 参考时钟搞定。反过来说,如果 PHY ID 能读出来、Link 也能起来,REF_CLK 基本就是好的,剩下的问题在数据路径和协议栈配置。
5. 调试实录:常见问题和排查方法
5.1 排查顺序:先波形、再配置、后布线
我自己调试 RMII 以太网时,有一套固定的排查顺序,可以帮你少走很多弯路。
第一步永远是量波形。用示波器探头直接量 STM32 的 RMII_REF_CLK 引脚,确认有没有一个稳定、干净的 50MHz 方波,幅度接近 3.3V,占空比接近 50%。注意要用示波器的短地线弹簧,不要用那个长的鳄鱼夹地线,否则 50MHz 波形看起来全是振铃,根本看不出真实情况。如果 REF_CLK 引脚上没有波形,再去量 PHY 的 REF_CLK 输出脚;如果 PHY 也没有,去量晶振两端,看看是不是晶振本身没起振。
如果波形是有的,但 PHY ID 还是读不到,第二步查复位和 MDIO 上拉。确认 PHY nRST 是否已经释放,MDIO 上拉是不是接好了,MDC 引脚有没有正确连接到 PHY。有时候 PHY 的地址不是 0,比如 LAN8720A 的 PHYAD[0] 引脚拉高后地址就是 1,程序里用 0 去读当然读不到。
如果 PHY ID 能读到,但 Link 起不来,第三步查 RMII 信号线的映射。用万用表量 PHY 的 TXD0/TXD1/RXD0/RXD1 和 MCU 对应引脚是不是一一对应,有没有交叉。这一步很蠢但很有效,我至少三次在别人板子上发现 RXD0 和 RXD1 接反了,波形全对,就是不通。
最后才轮到查布线质量和协议栈参数。REF_CLK 的走线太长、过孔太多,会产生相位噪声和振铃,轻则偶尔丢包,重则完全不通。
5.2 三个实测案例
第一个案例:F407 板子用 MCO 输出 50MHz,示波器量 MCO 引脚发现是 25MHz 而不是 50MHz。查配置发现 CubeMX 里 PLL 设置是按 168MHz 主频来的,MCO1 选了 PLLCLK 分频 4,得到 42MHz,跟 50MHz 完全不搭边。后来重新按 100MHz 主频配置 PLL 才输出 50MHz。但代价是整个系统的 USB 时钟跟着跑偏,最后我放弃了 MCO,改成 LAN8720A 自出时钟。这个案例教训很直接:不要为了 MCO 方案牺牲主频,除非你从一开始就按这个频率设计整个系统。
第二个案例:H743 板子,PHY 是 RTL8211F。上电后 ETHREF_CLK 引脚完全没波形,读 PHY ID 超时。我当时以为是 PHY 没配置好,反复改寄存器,后来才发现 RTL8211F 在 RMII 模式下默认不输出 REF_CLK,需要把它的 CLK_OUT_EN 相关寄存器打开,并且正确配置时钟源选择。这个问题的本质不是芯片坏了,而是 PHY 的时钟输出功能默认关闭。遇到“REF_CLK 没波形”的故障,除了检查晶振,一定别忘了查 PHY 的寄存器配置。
第三个案例:一块自绘板,REF_CLK 波形量出来有 50MHz,但是上升沿很缓,幅度只有 2V 左右,PHY ID 时好时坏。检查发现是 25MHz 晶振的匹配电容用得不对,晶振振荡幅度偏小,导致 PHY 内部倍频出来的 50MHz 信号质量差。换了和晶振 CL 匹配的电容后,波形改善,问题消失。这类问题不量波形根本看不出来,你以为软件配置错了,实际是模拟域的问题。
5.3 排查清单速查表
| 现象 | 可能原因 | 处理办法 |
|---|---|---|
| REF_CLK 引脚无波形 | PHY 晶振未起振 | 检查晶振匹配电容,换晶振 |
| REF_CLK 引脚无波形 | PHY 的 REF_CLK 输出未使能 | 配置 PHY 寄存器,使能 CLK_OUT |
| REF_CLK 引脚 |