
前阵子给一套基于 STM32F407 的数据采集板加网络功能目标是通过网口把采集到的数据送到上位机。板子上预留了以太网接口位芯片迟迟没定。对比了一圈最后选了 STM32 LAN8720 这个组合。调试过程不算顺但走完一遍发现只要协议栈原理和硬件连线理清楚它其实比想象中简单。这篇就把从选型、原理图到上电 Ping 通的完整过程整理出来包括我踩过的 PHY 地址坑、时钟配置坑以及最终的稳定性优化给正要入坑的人一条能直接抄的路线。1. 选型那点事STM32自带MACPHY芯片只需要干好两件事很多人第一次看到 STM32F407 参考手册都会注意到它内置了一个以太网 MAC 控制器。但光有 MAC 是接不了网线的还需要一颗 PHY 芯片把数字信号变成差分模拟信号送到网线上去。这个信号转换不是随便一个电路模块就能胜任里面涉及物理编码、自动协商、时钟恢复所以 PHY 芯片是绕不开的一环。拿生活中打比方MAC 像是负责写信和封装信的人它知道信封格式、收件地址怎么写PHY 则是快递员负责把信实际送到路上。快递员不需要知道信的内容但他必须认识路、会开车而且要保证路面畅通时能第一时间出发。你在 TCP/IP 协议栈里写的那些 socket 代码最终就是通过 MAC 组装成帧再由 PHY 变换成网线上的电信号发出去。市面上常见的以太网接入方案有好几种我给一个比较直观的对比方案芯片形态接口带宽协议栈负担典型场景W5500硬协议栈 PHYSPI10/100M芯片内部完成 TCP/IPMCU 几乎不耗资源物联网网关、简单 TCP 上报ENC28J60MAC PHYSPI只有 10M需要 lwIP 或 uIP但带宽受限低速率传感器采集STM32F4 内置 MAC LAN8720外部 PHYRMII10/100M需要 lwIPMCU 跑协议栈数据量较大的采集、文件传输、视频流如果你只是控制一个继电器、读取温湿度然后上报W5500 确实更省事因为 TCP/IP 协议栈在芯片内部主控写几个寄存器就能发 socket 数据。但如果设备有图片、日志文件、大量传感器数据要传W5500 的 SPI 接口反而是瓶颈而且价格不便宜。ENC28J60 虽然便宜但 10M 带宽在现在的局域网环境里实在有点不够看加上它驱动写得不好的时候中断容易丢包用起来比较折腾。LAN8720 这颗 PHY 走的是另一条思路STM32F407 本身有 MAC所以主控只多承担 lwIP 协议栈的计算而已LAN8720 只负责物理层收发。它单颗芯片价格大概在 10 元上下支持 10/100Mbps输出 RMII 接口信号线只有 7 根对 PCB 布线和 GPIO 占用都很友好。这就是我最终选它的核心原因把协议栈和物理层分开各自做自己最擅长的事而且成本可控。这套组合还有一个隐藏优势STM32CubeMX 里可以直接生成 ETH lwIP 中间件开发流程比你想的要顺。但前提是硬件初始化必须正确否则后面每一步都会被 PHY 寄存器读不到这种问题卡住。所以原理图这部分我单独拿出来详细讲。2. 原理图设计照着这张表连线基本不会翻车标题说含原理图但我不能只给一张图就不管了。很多人照着开发板原理图抄抄完不知道每一根线为什么这么接出了问题也不知道从哪里查。这里把 LAN8720 相关的外围电路拆成电源、RMII 信号、时钟、网络变压器四个部分来讲。2.1 电源和复位电路VDDCR 这个引脚千万别接地LAN8720 的主电源是 3.3VVDD 和 VDDIO 都要接 3.3V并且每个电源引脚旁边都要放一个 0.1uF 去耦电容再整体并一个 10uF 的钽电容或者陶瓷电容。原理图上这些电容看起来是基础操作但实际布线时如果放得离芯片太远电源纹波会在高速收发时直接反映到差分信号上造成偶发丢包。有一个容易出问题的引脚叫 VDDCR这是芯片内部数字核心稳压器的输出手册要求外接一个 1uF 电容到地。有些人在画原理图时习惯性地把所有电源引脚并在一起处理结果把 VDDCR 直接接到 3.3V 或者直接接地芯片要么不工作要么发热异常。正确做法是严格按照数据手册让 VDDCR 通过电容独立滤波。复位电路我用的是 10k 上拉电阻加 0.1uF 电容到地的经典阻容复位。nRST 是低有效上电瞬间电容充电拉低复位引脚之后被上拉电阻拉到高电平完成复位。有一个经验要分享如果 STM32 的复位时序比 LAN8720 晚或者你有手动复位按键导致两边复位不同步PHY 在上电后偶尔会进入奇怪状态表现为 Link 灯不亮但 MDIO 能读到寄存器。这种情况下建议用 STM32 的一个 GPIO 直接控制 nRST复位时间完全自己掌控。2.2 RMII 信号线连接一个引脚冲突能让你排查一下午RMII 接口相比 MII 已经少了很多线但每一根线都必须接对。以下是 STM32F407VET6 与 LAN8720 之间的连接表可以直接照着画STM32F407 引脚功能LAN8720 引脚PA1ETH_RMII_REF_CLKREF_CLKPA2ETH_MDIOMDIOPA7ETH_RMII_CRS_DVCRS_DVPB11ETH_RMII_TX_ENTX_ENPB12ETH_RMII_TXD0TXD0PB13ETH_RMII_TXD1TXD1PC1ETH_MDCMDCPC4ETH_RMII_RXD0RXD0PC5ETH_RMII_RXD1RXD1任意 GPIO复位控制可选nRST这里面最容易踩的坑是引脚冲突PA2 同时也是 USART2 的 TX很多人在原理图里既要调试串口又要走 MDIO结果把 PA2 分配给了串口MDIO 换到别的引脚ETH 初始化就直接失败。我建议调试串口用 USART1把 PA9/PA10 留出来PA2/PC1 稳定留给 ETH可以少很多麻烦。另外 MDIO 是开漏输出必须有外部上拉电阻一般用 2k 上拉到 VDDIO。没有这个上拉MDIO 通信时好时坏读寄存器偶尔返回错误值这种问题最难查因为看起来像软件问题。2.3 时钟配置25M 晶振才是主要推荐方案RMII 模式要求一个 50MHz 的参考时钟这个时钟负责 PHY 和 MAC 之间的同步。LAN8720 内部有一个 PLL可以在 XI/XO 引脚之间接一颗 25MHz 无源晶振然后从 REF_CLK 引脚输出 50MHz 给 STM32 的 ETH_RMII_REF_CLK。这是最常用的方案成本低、BOM 简单晶振两端各接 18pF 到 22pF 的负载电容。另一种方案是给 XI/CLKIN 引脚直接输入外部 50MHz 时钟常见做法是 STM32 的 MCO1 引脚输出 50MHz或者干脆用一颗独立有源晶振。这个方案确实能工作在一些 ESP32 LAN8720 的设计里也常见。但如果你不是对时钟相位有特殊要求我不建议在 STM32F407 上这么做原因很简单MCO 输出 50MHz 需要额外配置 PLL而且这个时钟域的相位噪声和抖动不一定比晶振方案更好。我自己测试下来无源晶振方案的稳定性完全够用没必要增加设计复杂度。这里有一个细节需要留意REF_CLK 信号到 STM32 之间要尽量短不要交叉穿越其他高速信号。REF_CLK 是 PHY 和 MAC 的基准时钟一旦被干扰表现出来的现象是网口能 Link 上但收发包全是 CRC 错误非常头痛。2.4 网络变压器与 RJ45能选集成式就不选分离式LAN8720 的差分信号线出来之后不能直接捅到 RJ45 座子上中间必须经过网络变压器。网络变压器的作用是隔离共模噪声、提供电气隔离同时完成电平转换。我建议直接选用集成了网络变压器的 RJ45 座比如 HR911105A这种座子内部已经包含变压器和共模电感画原理图时省事很多PCB 面积也更小。如果用分离式变压器需要注意发送和接收通道的中心抽头接法。常见设计是中心抽头通过 0.1uF 电容接信号地有些参考电路会接一个 3.3V 电压具体要看变压器器件手册。画完原理图之后最好对照官方评估板的参考设计检查一遍不要凭感觉接。LAN8720 还有两个 LED 输出引脚LED1、LED2可以直接通过限流电阻接到 RJ45 的指示灯端口。这两个引脚对应 Link 和 Activity 状态调试时非常有用。我通常会留两个 1k 电阻亮度不够再调小但别小于 330 欧姆否则可能影响 PHY 内部驱动。2.5 一个决定成败的引脚PHYAD0 必须接 GND这一小节单独拿出来因为超过一半的 LAN8720 不工作 问题都出在这个引脚上。LAN8720 的 PHYAD0 不只是 PHY 地址选择引脚它同时还兼作 MODE 选择。当 PHYAD0 为低电平时PHY 地址是 0芯片正常工作当 PHYAD0 为高电平时PHY 地址虽然是 1但同时芯片会进入 Isolate 模式RMII 输出引脚全部变成高阻状态等于和你的 MCU 断开了。也就是说千万别以为 PHYAD0 拉高只是换个地址而已。我见过有人参考了其他 PHY 芯片的接法把 PHYAD0 拉高来避免地址冲突结果网口完全不通。正确的做法是 PHYAD0 接 GNDPHY 地址固定为 0CubeMX 里 PHY Address 也填 0。另外 nINT/REGOFF 这个引脚也不能简单悬空。它既是中断输出又控制内部稳压器是否关闭建议按照参考设计接上拉到 3.3V如果你不用中断功能至少也要通过电阻上拉避免引脚浮空导致 PHY 复位时序异常。3. 软件初始化CubeMX 生成工程和 PHY 地址的坑硬件能正常工作的前提下软件配置就只是按部就班的操作。但 CubeMX 里那几个参数如果不理解生成的代码照样跑不起来。3.1 CubeMX 配置步骤与关键参数先建立工程选择 STM32F407VET6在 Connectivity 里使能 ETH接口选 RMII。此时 CubeMX 会自动列出 RMII 所需引脚如果引脚下显示橙色说明有其他外设冲突需要自己调整。我强烈建议不要换默认引脚因为 RMII 引脚是硬件固定的你换不了只能换其他外设。ETH 参数设置界面里有一个 PHY Address默认值可能是 0也可能根据你选的 PHY 自动填。由于我们的 PHYAD0 接地这里必须保持 0。如果 CubeMX 里你能选 PHY 型号可能会看到 LAN8720A 或 LAN8742 之类的预设。老版本 CubeMX 没有 LAN8720 预设选 User PHY 就行新版本有些已经内置选上之后 HAL 库会自动带上对应的 PHY 驱动。MAC Address 可以随便填一个不冲突的比如 02:00:11:22:33:44。这个地址是烧在内存里的每次上电都一样所以只要局域网内不跟别的设备重复即可。3.2 PHY 地址踩坑记录为什么 HAL_ETH_Init 一直超时这里讲一个我实际踩过的坑。当时参考了某开发板的例程它把 PHY 地址宏定义成 1我第一次把代码烧进去之后程序卡在MX_LWIP_Init()里串口完全没反应。后来加打印发现是HAL_ETH_Init()返回 HAL_ERROR。原因就在于 HAL 库初始化 ETH 的时候会通过 MDIO 总线读取 PHY 的寄存器来判断 PHY 是否存在。HAL 读取 PHY 寄存器的地址是你在 CubeMX 里填的那个值如果你的 PHY Address 填 1但硬件上 LAN8720 的 PHYAD0 接地MDIO 总线上根本没有地址为 1 的 PHYHAL 自然超时。排查时最简单的方法是直接调HAL_ETH_ReadPHYRegister读寄存器 0 或者寄存器 1打印返回值。如果返回 0xFFFF说明 MDIO 上没读到设备要么地址错要么 MDIO 引脚没接对如果返回 0x0000说明 PHY 还处于复位状态检查复位电路如果返回一个合理值比如 0x782D说明 MDIO 通信正常再去看 Bit 位的含义就能定位 Link 状态。3.3 LWIP 初始化与 Link 状态检测代码CubeMX 生成的 lwIP 中间件会负责 netif 的创建和 PHY 初始化。你需要做的额外工作是在主循环里检测 PHY 的 Link 状态并且在网线插拔时及时通知 lwIP。否则网线断了之后协议栈不知道TCP 连接就一直挂着。最简单的检测方式是用定时器每 500ms 读一次 PHY 寄存器 1Basic Status Register的 bit 2也就是 Link Status 位同时对比 netif 当前状态状态变了就更新void ethernet_link_check(void) { uint32_t phy_reg 0; uint32_t link_up 0; if (HAL_ETH_ReadPHYRegister(heth, 1, phy_reg) ! HAL_OK) { return; } link_up (phy_reg (1 2)) ? 1 : 0; if (link_up !netif_is_link_up(gnetif)) { netif_set_link_up(gnetif); } else if (!link_up netif_is_link_up(gnetif)) { netif_set_link_down(gnetif); } }注意读取 PHY 寄存器之前要确认MX_LWIP_Init()已经执行过否则heth句柄还没初始化完成。3.4 为什么链接正常但 ping 不通如果 Link 状态已经 upIP 地址也配置在同网段却还是 ping 不通先别怀疑协议栈。最常见的原因是 PC 防火墙拦了 ICMP把防火墙临时关掉试一下然后是确认 STM32 和 PC 的 IP 是不是真的在同一网段比如 STM32 是 192.168.1.10PC 是 192.168.1.20子网掩码都是 255.255.255.0最后检查 PC 上有没有多个网卡有时候流量走了 Wi-Fi 而不是有线网卡。这三步我用一个检查顺序表总结现象优先检查可能原因程序卡在 HAL_ETH_InitPHY 地址是否正确PHYAD0 接高或 CubeMX 地址错误Link 灯亮但 ping 不通PC 防火墙、IP 网段ICMP 被拦、IP 不在同一网段Link 灯不亮网线、变压器、PHY 电源差分线接反、PHYAD0 拉高进 Isolate偶尔丢包去耦电容位置、REF_CLK电源纹波、REF_CLK 受干扰4. 上电调试从 Link 灯熄灭到 Ping 通的完整排查链路这一部分是我个人经验里最值得看的内容。很多初学者把工程生成完烧进去发现 ping 不通就开始疯狂改代码其实大多数问题不在代码而在硬件。正确的排查应该是从物理层往上层一层一层走。4.1 先用硬件手段确认物理层状态上电之后第一件事看板子上的 RJ45 绿色指示灯。LAN8720 的 LED 驱动默认情况下绿灯表示 Link 建立灯亮代表物理层已经协商成功。如果灯亮说明 PHY 正常工作、网络变压器没问题、网线也没问题下一步直接从协议栈找原因如果灯不亮就不要再盯着 lwIP 的代码看了先查电源、复位、晶振和差分线。没有示波器的情况下可以先用万用表测 LAN8720 晶振引脚的直流电压。正常工作的晶振引脚会有大约 0.3 到 0.5 倍 VDD 的直流偏置如果量出来是 0V 或者 3.3V 满电压晶振大概率没有起振。另外一个非常有效的检查点是 PHY 本身的 3.3V 引脚电压有些 PCB 在通电瞬间会出现电压跌落导致 PHY 没有正常复位。4.2 通过 MDIO 读寄存器判断问题所在硬件状态看着正常但 Link 还是起不来的情况就要读寄存器了。我一般会在代码初始化之后加一段调试打印间隔一秒读一次 PHY 寄存器 1 和寄存器 0通过串口输出。寄存器 1 的 bit 2 是 Link Statusbit 5 是 Auto-Negotiation Complete。如果读回来 bit 2 一直为 0说明 PHY 和交换机之间没有完成物理协商这时候要怀疑网线是交叉线还是直通线、RJ45 座子焊接有没有连锡、变压器中心抽头接法对不对。如果 bit 2 为 1 但 lwIP 仍然认为 Link down那问题就出在你的状态检测代码而不是硬件。这里有一个容易被忽略的点MDIO 的读操作要给它足够的时钟周期。如果 STM32 的系统时钟配置改过ETH 的 MDC 时钟频率可能会超规格导致读寄存器不稳定。CubeMX 里如果开启 ETH 后没有对 MDC 分频做限制默认值有时会让 MDC 超过 2.5MHz 甚至更高LAN8720 就通信不稳。遇到读寄存器时好时坏的情况优先查这个。4.3 ping 不通的逐层排查顺序我把完整的排查链路整理成下面这个顺序照着走能省很多时间Link 灯是否亮不亮回到硬件检查亮了继续。串口能不能打印调试信息不能打印先解决串口和时钟树后面所有步骤都依赖调试输出。能不能读到 PHY 寄存器读不到检查 MDIO 地址、上拉电阻、PHY 电源。PHY 是否 Link up读寄存器 1 bit 2为 0 说明物理层没协商成功。网卡是否获得了 IP静态 IP 就确认 PC 和 STM32 是否同网段。能否 ping 通网关如果 STM32 自身就是网关跳过。能否 ping 通 PC不通检查防火墙、ARP 缓存。有一个很有意思的细节当你 ping 不通的时候先看看 PC 的 ARP 表里有没有 STM32 的 MAC 地址。Windows 下用arp -a查看如果在列表里能看到 STM32 的 IP 和 MAC说明二层通信已经通了ping 不通多半是 IP 层或防火墙的问题如果 ARP 表里根本找不到说明二层都有问题重点去查物理层。4.4 一个我曾经卡了两天的坑REF_CLK 信号反向输出配置第一次画板时我选用了 25M 无源晶振但原理图里把 LAN8720 的 REF_CLK 引脚漏接了 STM32 的 PA1导致 PHY 的时钟无法传过去。这个错误在原理图评审时很难发现因为眼睛会下意识觉得晶振已经给了 PHY 时钟就足够了。实际上 RMII 模式下MAC 必须从 PHY 获取 50MHz 的 REF_CLK如果 PA1 悬空ETH 的 DMA 接收和发送时钟完全不同步现象就是 Link 灯能亮但 PC 端持续收到乱包ping 根本通不过。这类问题用万用表量不出来因为 REF_CLK 引脚的电平是稳定的一半 VDD 偏置但波形是否正常需要示波器才能看到。如果你手头没有示波器最靠谱的方法就是对照官方参考设计逐根线核对特别是 REF_CLK、CRS_DV 这两条它们在 RMII 里承担的是同步和载波侦听功能缺一不可。5. 稳定性与吞吐量的优化能 Ping 通只是起点Ping 通之后很多人就认为模块完成了。实际上 Ping 通只能证明通路存在距离稳定传输数据还有一段距离。我做完第一版 TCP 服务器之后用 PC 端向板子发文件很快就发现了丢包和速度慢的问题。5.1 吞吐量实测与 DMA 描述符数量调整先用最简单的 TCP 回环测试PC 端连续发送数据板子收多少回多少。我最初使用 CubeMX 默认的 4 个 DMA 描述符实测 TCP 单向吞吐大约只有 6Mbps而且 PC 端大块数据发送时丢包严重。后来把 ETH_RX_DESC_CNT 和 ETH_TX_DESC_CNT 从 4 调到 16吞吐量直接翻了几倍跑到了接近 30Mbps。原因是 lwIP 接收数据是中断驱动如果 DMA 描述符太少硬件收到网络包之后没有足够的缓冲位置放后续的包就会被丢弃。尤其当 PC 端突发传输时几个包可能在同一时刻到达描述符不够用就会出现「看起来像是丢包」的现象。这个参数在 CubeMX 的 ETH 配置里可以直接改改完之后重新生成代码即可。5.2 丢包问题先去查电源和走线不要上来就调软件吞吐量和丢包问题不一定都是软件引起的。我之前有一版 PCBTCP 传输几十兆字节之后就出现断流link 灯仍然亮着但 ping 不通了。最终定位到问题是 LAN8720 的模拟电源 AVDD 和数字电源 DVDD 走的是同一条细线收发数据时电源电流变化导致模拟部分受到干扰。后来把 AVDD 用磁珠加 10uF 电容单独滤波再重新铺铜加大电源走线宽度问题彻底消失。所以如果遇到传一段时间后挂掉这种问题优先怀疑电源。3.3V 对所有 PHY 芯片来说都是命脉LAN8720 在工作时电流变化快电源走线电感稍大一点就容易出现毛刺。PCB 上电源平面要尽量完整去耦电容要靠近电源引脚模拟电源和数字电源在源头处合并不要交叉绕线。5.3 长期运行的看门狗与 PHY 自动复位策略产品做出来后要 7x24 小时跑这时候就要考虑 PHY 偶发的异常状态。最稳妥的做法是把 LAN8720 的 nRST 引脚接到 STM32 的 GPIO平时输出高电平。当系统检测到长 ping 不通、TCP 连接反复超时、PHY 寄存器读取异常时拉低 nRST 并保持至少 10ms再拉高完成一次硬复位。硬复位之后重新初始化 lwIP 和 netif比软复位可靠得多。lwIP 本身也建议开启看门狗喂狗程序设计但要注意如果看门狗超时导致 MCU 重启PHY 也会跟着重新上电初始化所以只要硬件复位链路没问题PHY 的偶发故障是可以通过系统重启兜底的。我再补充一个小优化把 LAN8720 的 nINT 中断引脚接到 STM32 的外部中断。这样网线插拔时 PHY 会主动拉低 nINT触发中断去更新 link 状态不需要定时轮询。代价是代码里多一个 EXTI 回调但换来的实时性很值特别是要做 TCP 自动重连的场景插拔网线之后能迅速恢复通信。最后分享一个我栽过跟头的检查习惯拿到一块新板子不要急着烧 lwIP 大工程先写一个只循环读 PHY 寄存器 1 的小程序打印 Link 状态。这一步确认物理层稳定之后再上协议栈。能把这个习惯坚持下来的人在局域网通信这块走的弯路会少很多。