1. 项目缘起与整体设计思路
嵌入式以太网开发这件事,说简单也简单,说坑多那也是真的多。我前后做过十几个带网口的STM32项目,从F107到F407再到H743,PHY芯片从LAN8720A换到YT8512C,中间踩过的坑足够写一本小册子。这篇文章就把这些经验一次性倒出来,重点讲清楚STM32配合LAN8720A和YT8512C这两款常见PHY芯片时,怎么把以太网连接做稳,以及那些文档里不会写、但实际调试中一定会遇到的问题。
先明确一下这个项目的定位:主控用STM32F407系列(F107、F429同样适用),通过RMII接口连接PHY芯片,PHY再通过网络变压器接到RJ45座子,最终实现一个稳定的以太网通信链路。LAN8720A和YT8512C都是10/100M自适应的PHY,前者是SMSC(现Microchip)的老牌经典型号,后者是裕太微的国产替代方案,引脚基本兼容,但在时钟方案和配置细节上有差异。适合谁看?正在做STM32以太网项目、被PHY初始化卡住、ping不通、丢包严重、或者想从LAN8720A切换到YT8512C的兄弟,这篇内容应该能帮你省下不少调试时间。
整体设计上,我倾向于把硬件和软件分开讲,因为以太网不通,八成问题出在硬件上,剩下两成里又有一大半是时钟配置的问题。所以思路是:先把硬件链路理清楚,再讲软件初始化流程,最后集中火力讲排查。这个顺序也是我实际调试时遵循的顺序,从物理层往上查,比一上来就盯着代码看效率高得多。
2. 硬件链路设计与PHY选型考量
2.1 RMII接口的引脚连接与信号含义
RMII(Reduced Media Independent Interface)是MAC和PHY之间的精简接口,相比MII少了将近一半的引脚。STM32F407的RMII接口需要以下信号:
- REF_CLK:50MHz参考时钟,这是RMII的命脉,后面单独讲
- TXD0、TXD1:发送数据,2位宽
- RXD0、RXD1:接收数据,2位宽
- TX_EN:发送使能
- CRS_DV:载波侦听/数据有效,复用信号
- MDIO、MDC:管理接口,用来读写PHY寄存器
这里有个容易搞混的点:RMII的REF_CLK是50MHz,而MII是25MHz。很多人从MII参考电路抄过来,时钟就错了。另外CRS_DV这个信号在RMII里是复用引脚,既表示载波侦听又表示接收数据有效,接错或者上拉电阻没处理好,会出现能发送但收不到数据的情况。
STM32F407这边,RMII引脚是固定的,不能随便映射。以F407为例:PA1是REF_CLK,PA2是MDIO,PA7是CRS_DV,PC1是MDC,PC4是RXD0,PC5是RXD1,PB11是TX_EN,PB12是TXD0,PB13是TXD1。这些引脚在配置时要设成复用推挽输出或者浮空输入,具体看方向。我见过有人把MDIO配成普通GPIO,结果PHY寄存器读写全返回0xFFFF,查了半天才发现是复用功能没开。
2.2 LAN8720A与YT8512C的关键差异
这两款芯片虽然引脚兼容,但内部设计和配置方式有区别,直接替换不一定能跑起来。
| 对比项 | LAN8720A | YT8512C |
|---|---|---|
| 厂商 | Microchip | 裕太微 |
| 时钟方案 | 需外部25MHz晶振或STM32提供50MHz | 内置PLL,可外接25MHz晶振 |
| PHY地址 | 默认0,可通过引脚配置 | 默认0,配置方式略有不同 |
| 寄存器兼容性 | 标准IEEE 802.3 | 基本兼容,部分扩展寄存器不同 |
| 自协商 | 支持 | 支持 |
| 功耗 | 较低 | 更低 |
| 价格 | 较高 | 有优势 |
最大的差异在时钟方案。LAN8720A需要一个25MHz的晶振,然后内部PLL倍频到50MHz输出给STM32,或者反过来由STM32提供50MHz时钟。而YT8512C内置了PLL,可以自己产生时钟,对外部时钟的依赖更灵活。这个差异直接影响到REF_CLK的走线方向,后面细说。
2.3 网络变压器与RJ45的选型要点
PHY出来之后不能直接接RJ45,中间必须加网络变压器,作用是隔离、滤波和阻抗匹配。选变压器时注意几个参数:匝数比一般是1:1,共模电感要够,支持10/100M。常见的型号有HR911105A(带变压器的RJ45座子),用起来省事,但要注意它的内部变压器质量参差不齐,便宜的批次可能丢包。
如果分开选变压器和RJ45座子,变压器到RJ45的走线要尽量短,差分对要等长,阻抗控制在100欧姆。我试过用飞线接变压器,结果丢包率飙升,后来改成PCB走线就稳了。这个细节在调试阶段容易被忽略,但影响很大。
3. 时钟方案:以太网稳定的命门
3.1 REF_CLK的两种产生方式
RMII的50MHz参考时钟有两种来源:一种是PHY提供,一种是STM32提供。这两种方式对应不同的硬件设计,选错了就调不通。
方式一:PHY提供时钟。LAN8720A外接25MHz晶振,内部PLL倍频到50MHz,通过REF_CLK引脚输出给STM32的PA1。这种方式下,STM32的PA1要配置成输入模式,接收外部时钟。YT8512C也支持这种方式,但要注意它的PLL配置寄存器可能需要初始化。
方式二:STM32提供时钟。STM32通过MCO引脚(比如PA8)输出50MHz给PHY的REF_CLK。这种方式下,PHY不需要外部晶振,但STM32的MCO配置要正确,而且时钟精度要够。我实测下来,STM32的MCO输出50MHz时抖动比专用晶振大,长时间跑可能出现丢包,所以更推荐方式一。
3.2 时钟配置的实操步骤
以LAN8720A提供时钟为例,硬件上要确保:
- LAN8720A的XTAL1和XTAL2接25MHz晶振,负载电容按晶振规格选,一般是12pF到22pF
- REF_CLK引脚接到STM32的PA1
- STM32的PA1配置为浮空输入或者复用输入,不要配成输出
软件上,STM32的ETH外设要配置成RMII模式,并且选择外部时钟源。在CubeMX里,ETH的Clock source选External,然后PA1会自动配置成输入。如果你用的是标准库,要手动配置GPIO_Mode_IN和ETH_InitStructure.ETH_ClockSource = ETH_ClockSource_External。
注意:有些开发板的原理图把REF_CLK标成PA1,但实际连的是PA8的MCO输出,这种板子要改硬件或者改软件配置,别照着原理图死磕。
3.3 时钟不稳导致的典型现象
时钟问题最直接的表现就是ping不通或者丢包严重。具体来说:
- 完全ping不通,PHY的link灯不亮:可能是时钟根本没起来,用示波器量REF_CLK,看有没有50MHz
- link灯亮但ping不通:时钟频率不对,比如是25MHz而不是50MHz
- 能ping通但丢包率高:时钟抖动大,或者走线太长引入了干扰
我遇到过一次,REF_CLK的走线在PCB上绕了一大圈,结果时钟信号边沿变缓,PHY识别不稳定,时好时坏。后来把走线改短,问题消失。所以REF_CLK的走线要尽量短,最好包地处理。
4. 软件初始化流程与关键寄存器配置
4.1 STM32 ETH外设初始化
STM32的ETH外设初始化分几步:GPIO配置、时钟使能、DMA描述符配置、MAC配置、PHY配置。这里重点讲容易出问题的部分。
GPIO配置前面说过了,关键是复用功能和模式要配对。时钟使能要注意,ETH的时钟来自AHB总线,使能ETH时钟之前要先使能SYSCFG时钟,否则复用功能配不了。
DMA描述符要放在内存里,注意对齐。ETH的DMA描述符要求4字节对齐,如果放在结构体里没对齐,会出现DMA传输错误。我一般用__attribute__((aligned(4)))来强制对齐。
MAC配置里,最重要的是MII/RMII模式选择、速度设置、双工模式。RMII模式下,ETH_InitStructure.ETH_Mode = ETH_Mode_RMII,速度先设成100M,双工设成全双工,后面PHY自协商成功后再根据结果调整。
4.2 PHY寄存器读写与自协商
PHY的配置通过MDIO/MDC接口读写寄存器。基本流程是:先读PHY的ID寄存器(地址2和3),确认PHY型号和地址,然后配置控制寄存器(地址0)启动自协商,等待自协商完成,读状态寄存器(地址1)确认link状态和速度。
LAN8720A的PHY地址默认是0,但可以通过PHYAD0引脚配置。如果板子上有多个PHY,地址不能冲突。YT8512C的地址配置类似,但具体引脚定义要看数据手册。
自协商的过程需要时间,一般几百毫秒到几秒。代码里要加超时判断,不能死等。我见过有人写了个while循环等自协商完成,结果PHY没接好,程序卡死在那里,看门狗都救不回来。
// PHY自协商等待示例 uint32_t timeout = 0; while (!(ETH_ReadPHYRegister(PHY_ADDRESS, PHY_BSR) & PHY_Linked_Status)) { if (timeout++ > 10000) { // 超时处理 break; } HAL_Delay(1); }4.3 LAN8720A的特殊配置
LAN8720A有几个特殊寄存器需要配置。比如模式控制寄存器(地址17),要设置成RMII模式,并且使能REF_CLK输出。如果这个寄存器没配,PHY可能不输出50MHz时钟,STM32就收不到时钟信号。
另外,LAN8720A的LED配置寄存器(地址16)可以控制link灯和速度灯的行为,调试时可以把link灯配成常亮,方便观察。
YT8512C的寄存器基本兼容,但有些扩展寄存器的地址不同。比如它的时钟配置寄存器可能不在标准地址上,需要查数据手册。我建议在代码里把PHY型号检测做进去,根据读到的ID选择不同的配置流程。
5. 常见问题排查与避坑指南
5.1 问题速查表
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| PHY link灯不亮 | 时钟没起来、电源不对、复位没释放 | 量REF_CLK、量PHY电源、检查复位引脚 |
| link灯亮但ping不通 | 时钟频率不对、MAC配置错误、IP冲突 | 量时钟频率、检查MAC寄存器、换IP试 |
| 能ping通但丢包 | 时钟抖动、走线干扰、变压器质量差 | 换晶振、改走线、换变压器 |
| PHY寄存器读回0xFFFF | MDIO/MDC没配好、PHY地址错 | 检查GPIO复用、扫描PHY地址 |
| 发送正常接收异常 | CRS_DV接错、RXD引脚配错 | 检查CRS_DV上拉、检查RXD复用 |
| 长时间运行后断连 | 时钟漂移、电源纹波大、散热问题 | 换晶振、加滤波电容、改善散热 |
5.2 几个典型的坑
坑一:REF_CLK走线太长。前面提过,这个坑我踩过。PCB布局时REF_CLK要优先走线,尽量短,最好包地。如果实在走不开,可以考虑用STM32提供时钟,把时钟源放在STM32旁边。
坑二:PHY复位时间不够。LAN8720A的复位引脚需要至少100微秒的低电平,有些板子的RC复位电路时间常数不够,导致PHY没复位干净。可以在代码里加个延时,或者改小复位电容。
坑三:MDIO上拉电阻缺失。MDIO是开漏输出,需要上拉电阻,一般是1.5k到10k。有些参考电路图省了这个电阻,结果PHY寄存器读写不稳定。我一般用4.7k,实测比较稳。
坑四:YT8512C替换LAN8720A时没改配置。虽然引脚兼容,但YT8512C的PLL配置和LAN8720A不同,直接替换可能不输出时钟。要查YT8512C的数据手册,配置对应的寄存器。
坑五:网络变压器中心抽头没接对。变压器的中心抽头要接电源或者地,具体看PHY的要求。LAN8720A的TX中心抽头接3.3V,RX中心抽头接3.3V。YT8512C可能不同,要查手册。接错了会导致信号幅度不对,丢包严重。
5.3 调试工具与技巧
调试以太网,示波器是必备的。量REF_CLK看频率和幅值,量MDC看有没有时钟输出,量TX_EN看发送时有没有使能。如果没有示波器,可以用逻辑分析仪,便宜的那种也能看个大概。
软件层面,可以写个PHY寄存器扫描程序,把所有可能的PHY地址都读一遍,看哪个地址能读到有效的ID。这个方法在不确定PHY地址时特别有用。
另外,STM32的ETH外设有中断和状态寄存器,可以读DMA的状态看有没有传输错误。比如ETH_DMASR寄存器的错误位,能告诉你DMA传输有没有出问题。
6. 从LAN8720A切换到YT8512C的实操记录
6.1 硬件改动的注意事项
YT8512C和LAN8720A引脚兼容,但外围电路有差异。YT8512C内置PLL,可以不需要外部25MHz晶振,直接用STM32提供的50MHz时钟。如果要用YT8512C的内部时钟,要把它的时钟配置引脚拉对,具体看数据手册。
我最近一个项目就是从LAN8720A切到YT8512C,硬件上把25MHz晶振拆了,REF_CLK改成STM32的MCO输出。软件上改了PHY初始化部分,YT8512C的自协商流程和LAN8720A基本一样,但扩展寄存器要重新配。
6.2 软件配置的差异点
YT8512C的PHY ID和LAN8720A不同,读ID寄存器能区分。初始化时先读ID,如果是YT8512C,走YT8512C的配置流程。主要差异在时钟配置和LED配置,其他寄存器基本兼容。
自协商完成后,读状态寄存器确认link速度和双工模式。YT8512C的状态寄存器和LAN8720A的位定义基本一致,可以直接用。
6.3 切换后的实测结果
切换后实测,ping延迟和LAN8720A差不多,丢包率在正常范围内。功耗方面,YT8512C确实低一些,发热也小。但要注意,YT8512C的时钟配置如果不对,会出现link灯亮但ping不通的情况,这时候要重点查时钟。
7. 一些实操心得与建议
以太网调试这件事,硬件和软件各占一半。我的经验是,先确保硬件没问题,再调软件。硬件上重点查时钟、电源、复位、MDIO上拉这几个点。软件上重点查GPIO复用、MAC配置、PHY自协商。
另外,不要迷信参考电路。我见过很多开发板的参考电路有问题,比如REF_CLK走线太长、MDIO上拉电阻缺失、变压器中心抽头接错。照着抄不一定能跑,要理解每个元件的作用。
最后分享一个小技巧:如果ping不通,先别急着改代码,用示波器量一下REF_CLK和MDC。这两个信号正常,问题大概率在软件配置;这两个信号不正常,问题一定在硬件。这个判断方法帮我省了很多时间。