ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

STM32+LAN8720以太网通信模块开发:从RMII硬件设计到LwIP移植

STM32+LAN8720以太网通信模块开发:从RMII硬件设计到LwIP移植 搞嵌入式这两年有个需求几乎躲不掉让设备联网。Wi-Fi 方案虽然方便但论稳定性、实时性和工业场景的适应性有线以太网依然是绕不开的老大哥。STM32LAN8720 这套组合算是入门以太网通信模块最经典、资料最全、踩坑也最有参考价值的一条路线。这篇文章我打算从头到尾梳理一遍基于 STM32F407 和 LAN8720A 搭建以太网通信模块的完整流程包括硬件选型思路、原理图设计要点、RMII 接口时序、软件配置和 LwIP 协议栈的移植验证。哪怕你是第一次接触以太网照着这套思路走也能在一天内让板子 ping 通。1. 方案选型与整体设计思路1.1 为什么是 STM32 LAN8720而不是集成 MAC 的芯片STM32 里面带以太网 MAC 控制器的型号其实不少F407、F429、H743 这些都有。但 MAC 只是数据链路层的东西你得给它配一个 PHY 物理层收发器才能把数字信号变成差分电压在网线上跑。市面上 PHY 芯片很多DP83848、KSZ8081、LAN8720 是最常见的三款。我推荐新人首选 LAN8720A理由很实在价格便宜淘宝几块钱一片开发成本低RMII 接口只需要 7 根信号线连接 MCU比 MII 的 16 根线省了一半多的引脚3.3V 单电源供电不需要额外做 1.8V 电源轨DP83848 也要 3.3V 但功耗大不少自带 50MHz 时钟输出可以不买有源晶振芯片体积小QFN-24 封装手工焊接难度可控。当然它也有短板——LAN8720 是 10/100M 自适应不支持千兆这对于绝大多数物联网设备、数据采集终端、工业控制板来说完全够用。真到了要跑千兆的时候你大概率也不该选 STM32 了上一颗 Linux 平台才是正路。1.2 RMII 接口的关键信号与时序理解RMIIReduced Media Independent Interface把 MII 接口的 16 根信号压缩到 7 根核心是通过把时钟频率从 25MHz 提高到 50MHz让数据线可以做到 2bit 并行传输。它的信号定义是这样的信号名方向功能说明REF_CLK输入50MHz 参考时钟PHY 和 MAC 共用TXD[1:0]MAC→PHY发送数据2bit 并行TX_ENMAC→PHY发送使能RXD[1:0]PHY→MAC接收数据2bit 并行CRS_DVPHY→MAC载波侦听/数据有效MDIO双向管理接口数据线MDCMAC→PHY管理接口时钟这里最需要理解的是 REF_CLK 时钟源的问题。STM32F407 的 MAC 工作在 RMII 模式时需要外部提供 50MHz 的 REF_CLK。最省事的做法是让 LAN8720 工作在从模式由外部 50MHz 有源晶振给 PHY 和 MAC 同时供时钟但更经济的是让 LAN8720 用 25MHz 无源晶振内部 PLL 倍频后从 CLKOUT 引脚输出 50MHz 给 STM32 的 REF_CLK。实际设计时两种方案都有人用但我倾向于用 25MHz 无源晶振 LAN8720 输出 50MHz 的接法原因有三个无源晶振便宜、不需要额外的 3.3V 供电电路、REF_CLK 是 LAN8720 输出驱动 STM32时序天然对齐。后面软件配置部分我会再细说一下 CubeMX 里对应怎么选。1.3 整体架构MCU 做协议PHY 做物理层搬运工记住一个核心概念STM32 内置的 MAC 控制器负责处理 Ethernet 帧的组装、拆解、CRC 校验这些事情LAN8720 只负责把 MAC 给它的并行数据变成可以跑在双绞线上的差分信号反过来也一样。TCP/IP 协议栈你没有必要从零写直接用现成的 LwIP 即可ST 官方已经把它集成到了 Cube 生态里CubeMX 勾选一下就行。所以典型的软件架构分三层硬件层STM32 MAC 外设 LAN8720 PHY 网络变压器/RJ45接口层HAL 库的 ETH 驱动 PHY 驱动HAL_ETH 自带LAN8720 需写寄存器配置协议层LwIP 协议栈处理 ARP、IP、ICMP、TCP、UDP。理解了这个分层后面遇到问题就知道该去哪一层查。ping 不通是 ARP 的问题网页打不开是 LwIP 配置的问题寄存器读出来全是 0xFFFF 是 MDIO 时序问题——各找各妈不乱枪扫射。2. 硬件设计要点与原理图拆解2.1 LAN8720 最小系统电路先看芯片本身LAN8720A 的典型外围电路需要这几个部分电源滤波、时钟电路、复位电路、RMII 接口上拉/下拉、差分信号对。电源方面我没用 LDO直接在 3.3V 电源进芯片每一个 VDD 引脚旁边放一个 0.1uF 去耦电容另外在 AVDD 引脚单独加一个 4.7uH 磁珠隔离数字噪声实测对 PHY 的稳定性很有帮助。LAN8720 的 VDDCR 引脚需要注意这个引脚是内部 1.2V 核心电压的输出厂商手册要求外部再接一个 0.1uF 去耦电容很多人漏掉会导致芯片工作不稳定。时钟电路上面说了我用的是 25MHz 无源晶振两个负载电容取 18pF。晶振的两个引脚分别接 XTAL1 和 XTAL2注意并联一个 1MΩ 的反馈电阻部分晶振不需要但加上无害)。LAN8720 的数据手册里明确写了晶振电路参考设计按着来就行。复位电路用一个 10kΩ 上拉电阻加 0.1uF 电容到地实现上电自动复位同时把 NRST 引脚引到 STM32 的一个 GPIO 上做成软复位方便调试的时候做 PHY 复位时序控制。2.2 RMII 接口连接STM32 引脚映射STM32F407 的 ETH 外设和 GPIO 不是随意连的必须按照芯片手册的复用功能映射来接线不能像 SPI 一样随便映射。F407 的 RMII 接口固定分配在 PA1、PA2、PA7、PC1、PC4、PC5、PG11 这些引脚上我列一下实际连线STM32 引脚复用功能LAN8720 引脚PA1ETH_RMII_REF_CLKREF_CLKCLKOUTPA2ETH_MDIOMDIOPC1ETH_MDCMDCPA7ETH_RMII_CRS_DVCRS_DVPC4ETH_RMII_RXD0RXD0PC5ETH_RMII_RXD1RXD1PB11ETH_RMII_TX_ENTX_ENPB12ETH_RMII_TXD0TXD0PB13ETH_RMII_TXD1TXD1很多人第一次画图容易踩的坑是默认 LAN8720 的 PHYAD0 引脚接了上拉电阻导致 PHY 地址不是 0 而是 1。这个引脚对应芯片的 PHYAD0/RXER 引脚内部有下拉电阻如果你想让它默认地址为 0就什么都不接或者直接接地如果接了上拉PHY 地址就变成了 00001b即 0x01。CubeMX 里默认 PHY 地址填的是 0两者对不上会导致 MDIO 读不到寄存器。所以硬件设计时就明确PHYAD0 接一个 10kΩ 下拉电阻保证 PHY 地址为 0。2.3 网络变压器有什么用怎么选你可能看到过 LAN8720 的差分引脚 TXP/TXN、RXP/RXN 后面接了一坨东西那个就是网络变压器通常和 RJ45 座集成在一起比如 HR911105A。网络变压器不是可选项它承担三重任务电气隔离避免网线上的共模干扰和地环路直接影响 MCU 系统信号电平转换把 PHY 的 3.3V 差分信号耦合到网线上反过来也一样改善 EMC 性能变压器自带的中心抽头可以滤除共模噪声。选型上新手不用纠结直接用带变压器的 RJ45 集成座就可以HR911105A 是最常见的和 LAN8720 的典型应用原厂方案直接兼容。连线也很固定LAN8720 的 TXP/TXN 接变压器对应通道RXP/RXN 接另一边中心抽头按参考设计接 3.3V 或通过电阻电容网络接地。我在最初一版设计中偷懒买过那种不带变压器的 RJ45 座结果连上路由器死活协商不上速率最后排查了一晚上才意识到是隔离变压器的问题。这一课告诉我省什么都不能省变压器。2.4 电源与 PCB 布局经验LAN8720 对电源质量不是特别挑剔只要保证 3.3V 纹波别超过 50mV 就行。需要注意的是 MDIO 上拉电阻一般取 10kΩ 上拉到 3.3V 即可。MDC 不需要上拉它是推挽输出。PCB 布局上有几个经验值得分享LAN8720 尽量靠近 RJ45 座差分线 TXP/TXN、RXP/RXN 要等长、差分阻抗控制 100Ω走线越短越好REF_CLK 走线不要跟其他高速信号并行走长线它会串扰到 RX/TX 上导致收包出错晶振离芯片引脚控制在 5mm 以内晶振下面不要铺地铜我之前铺了地结果起振不稳数字地和模拟地如果芯片有独立 AGND 引脚单点连接即可不要分成一大片。3. 软件环境配置与 DP83848 的“冒名顶替”3.1 CubeMX 工程初始化软件部分我以 STM32CubeIDE 为例也可以用 Keil CubeMX 的组合本质流程一样。首先在 CubeMX 里选好你的 MCU 型号我用的是 STM32F407VET6然后在 Connectivity 选项卡里打开 ETH模式选 RMII。这里有个非常关键的配置点ETH 外设的 RMII 时钟来源。在 STM32F407 的时钟树里ETH 的 RMII 时钟是从 PLL48CK 分频出来的所以你需要确认系统时钟配置中 PLL 的 Q 值让 48MHz 的时钟正确生成。CubeMX 里把系统时钟设为 168MHz 后会自动生成 48MHzETH 会把它分频成 50MHz不对这里细讲一下。F407 的 RMII REF_CLK 有两种来源。一种是外部 PHY 提供 50MHz 时钟给到 STM32 的 REF_CLK 引脚另一种是 STM32 内部从 PLL48CK 产生 50MHz 输出给 PHY。但注意F407 内部产生的 50MHz REF_CLK 是 MCO 引脚输出的。所以你在 CubeMX 里看到 ETH RMII 的时钟配置实际是时钟树里选择 RMII 时钟源是 PLL48CK 还是外部时钟。如果你用 LAN8720 从模式由 STM32 提供 50MHz 时钟给 PHY需要在 GPIO 配置里把 MCO 引脚PA8设为 MCO1选择 PLL 的 Q 输出 50MHz。不过这种方式用的人少因为 MCO 的输出抖动和驱动能力一般不如 PHY 自己产生时钟稳。所以我推荐的方案是LAN8720 用 25MHz 晶振CLKOUT 输出 50MHz 到 STM32 的 PA1ETH_RMII_REF_CLK。CubeMX 里 ETH 配置不变PA1 自动变成 ETH_RMII_REF_CLK 复用功能然后你不需要额外配置时钟源因为 REF_CLK 是外部 PHY 给的STM32 只是接收。这样时钟树里 ETH 那部分除了用于 MAC 的 AHB 时钟AHB1 的 ETHMAC 时钟之外不需要给 RMII 额外生成时钟但前提是在时钟树里把 ETH 时钟使能否则外设寄存器都不工作。3.2 LAN8720 的 PHY 驱动重写ST 的 HAL 库自带的 PHY 驱动默认是针对 ST 自家评估板的 PHY 芯片通常是 DP83848LAN8720 的寄存器地址和厂商 OUI 不同直接跑默认驱动读状态会读到 0xFFFF。你需要手写一个 LAN8720 的底层驱动函数核心就三个LAN8720_Init()读取 PHY IDR1/IDR2 确认芯片型号配置基本的 BCR 寄存器LAN8720_Get_Link_Status()读 BSR 寄存器的 bit2Link StatusLAN8720_Get_Speed_Duplex()读 PHYSCSR 寄存器地址 31bit2 是速度1 表示 100M0 表示 10Mbit1 是双工模式1 表示全双工。实际代码里HAL 库的 ETH 外设结构体中有个成员 Init.MediaInterface 需要设置为 HAL_ETH_RMII_MODE还有一个 PHY 地址的成员需要设置为 0x00。重点注意的是 HAL_ETH_ReadPHYRegister() 和 HAL_ETH_WritePHYRegister() 这两个函数它们的第二个参数是 PHY 地址第三个才是寄存器地址别传反了。3.3 LwIP 协议栈的集成与测试CubeMX 里勾选 LWIP配置项里我通常会改这几个地方IP 地址设为静态模式192.168.1.10掩码 255.255.255.0网关 192.168.1.1MEM_SIZE 默认 1600 可以不动但 PBUF_POOL_SIZE 建议从默认值调大一点比如 32避免大数据吞吐时 pbuf 耗尽TCP_WNDTCP 窗口大小默认 4096如果你的应用是大包传输调到 8192 或更高LWIP_DHCP 默认关闭我做测试用静态 IP 更省事实际产品再考虑 DHCP。编译下载后用网线把板子和电脑直连电脑配同网段的静态 IP然后打开命令行 ping 192.168.1.10。如果你用的是直连线现在的网卡和交换机、PHY 都支持 Auto-MDIX不区分交叉和直通了随便插。如果 ping 不通不要急着怀疑代码先查硬件链路状态。看 LAN8720 的 BSR 寄存器 bit2 是否为 1可以用调试器在代码里读出来。如果 Link 状态是断开的问题大概率在硬件变压器接线、RJ45 座、晶振有没有起振、复位引脚有没有被拉低。如果 Link 状态已连接但 ping 不通再去查 LwIP 初始化和内存配置。4. 常见问题与调试实录4.1 MDIO 读寄存器全 0xFFFF怎么排查这是我见过最多人卡住的问题而且新手很容易被误导去查 LwIP。其实 MDIO 读不到数据就是底层连接的问题按这个顺序排查确认 STM32 和 LAN8720 的接线正确MDIO/MDC 有没有虚焊用万用表量一下两个焊盘之间的导通确认 PHY 地址正确读寄存器用的地址到底是 0 还是 1用 PHYAD0 引脚的连接状态来判断确认 MDC 时钟频率。STM32 的 MDC 时钟来源于 APB2 时钟分频HAL_ETH_Init() 里会自动设置但如果你改了系统时钟MDC 频率可能超过 2.5MHz 这个 PHY 支持的极限确认 LAN8720 的供电和复位。VDDCR 引脚的电容没焊会导致芯片内部核心电压不稳表现为寄存器读值随机跳变。4.2 Link 状态正常但收不到任何数据包这类问题往往是 RMII 信号的问题。CRS_DV 这个信号在 RMII 接口里承载了载波侦听和数据有效两个功能它必须和 RXD[1:0] 在时间上和相位上对齐。如果 REF_CLK 不是来自 PHY 而是来自外部有源晶振那么晶振输出的时钟抖动和延迟可能导致采样错位。另一个常见原因是 RXD0/RXD1 和 CRS_DV 的上拉/下拉配置。LAN8720 在复位时会采样这些引脚的电平来决定芯片的默认配置比如 RXD0/MODE0 引脚的电平决定时钟源选择和 MAC 接口类型。如果你的 RXD 引脚被外部电阻拉到了错误电平芯片可能进入了 MII 模式而不是 RMII 模式或者工作在从时钟模式和 STM32 期望的模式匹配不上。我在调试时遇到过一次奇怪的现象Link 能建起来收发也有中断产生MAC 能收到帧但 CRC 全错。最后发现是 RX 差分对的两根线在 PCB 绘制时弄反了极性TXP/TXN 接反不会影响 Link 协商但数据全错。所以画原理图时一定对照 PHY 数据手册的引脚功能逐一核对不能想当然。4.3 通信偶尔掉线过一会儿又能恢复这种间歇性问题排查起来最费劲。如果你用的是开发板而不是自己画的 PCB这个问题可能出在电源上。LAN8720 在 100M 全双工工作时电流会突然增大如果 3.3V 供电路径太长或去耦电容不足电压跌落会导致 PHY 内部锁相环失锁表现就是链路断开重新协商后又能恢复。经验做法在 LAN8720 的电源引脚附近加一个大一点的电容器比如 10uF 钽电容并联 0.1uF 陶瓷电容并且给 PHY 单独走一路电源不要和 MCU 的数字电源搅在一起。如果你用开发板可能没办法改硬件那就先跑一个循环任务持续读 PHY 的 Link 状态寄存器做 log看掉线时电源电压有没有波动——很多人靠这个方法找到了根源。4.4 LwIP 内存不足导致的并发连接问题这个问题在功能上基本正常、但并发访问时变慢或死机的情况下出现。LwIP 的内存管理分为内存池memp和内存堆memmem_malloc 和 pbuf_alloc 频繁申请释放会产生碎片。ESP32 移植 LwIP 时因为堆内存够大很少遇到但 STM32F407 默认的 RAM 只有 192KB如果你开了多个 TCP 连接、UDP 广播全开memp 池很快就耗尽了。解决办法不是调大 MEM_SIZE 就完事而是裁剪协议。把不需要的协议关掉比如不用的 PPP、SNMP、IGMP 这些宏定义直接注释。还有 LWIP_NETCONN 和 LWIP_SOCKET 这两个选项如果你没有用到 sockets API只用了裸的 raw API比如 httpd 回调这两个宏可以关掉能省下不少内存。4.5 一个教训不要用 printf 在中断里调试网络最后分享个个人经历。调试 LwIP 收发时我习惯性地在接收回调函数里加了一行 printf 来打印收到的字节数。百兆网卡全速收包的时候每秒有上万帧结果串口输出比接收还慢丢包率直接爆表。排查了整整两天以为是 DMA 配置有问题最后把打印关掉瞬间正常了。网络调试的正确姿势是不打印、不延时、不中断嵌套。要观察收发状态就用 GPIO 翻转示波器测或者把计数变量放在内存里最后统一输出。这个道理也适用于其他高速外设的数据通路调试。5. 从调试通过到实际项目落地的几点建议5.1 直接抄作业的参考设计电路清单如果你不想从零开始画原理图把下面这些元件清单理清楚画板成功率会高很多类别型号/参数数量备注MCUSTM32F407VET61或其他带 MAC 的 F4 系列PHYLAN8720A1QFN-24网络变压器座HR911105A1集成变压器晶振25MHz 无源1负载电容 18pF x2复位10kΩ 上拉 0.1uF1套可引至 MCU GPIO去耦0.1uF 陶瓷若干每个电源引脚一个大电容10uF 钽/陶瓷2PHY 电源入口和 MCU 电源入口差分终端49.9Ω 电阻对2部分设计需要看数据手册这个方案可以直接用来做毕业设计、产品原型也可以作为你学习 LwIP 的硬件平台。5.2 日志系统的价值调网络协议栈和调串口程序最大的区别在于网络问题的时序性很强偶发问题很难复现。我强烈建议在项目第一天就搭建一套带时间戳的日志系统把系统启动、PHY 初始化、LwIP 初始化、Link 状态变化、TCP 连接建立/断开这些关键事件全部记录下来。这套日志在开发阶段看着麻烦但等产品上线之后遇到“用户说设备连不上网”这种问题日志能帮你直接定位到是 PHY 没协商上、还是 DHCP 超时、还是服务器地址配错了。没有日志就全靠猜有日志就是科学排查。5.3 后续功能扩展路径如果你已经实现了基本的 ping 通和 TCP 收发下一步扩展方向可以考虑这几个用 CubeMX 的 LwIP 插件直接生成 HTTP Server做一个 Web 配置页面这是目前物联网设备最常见的交互方式移植 MQTT 客户端协议让设备接入主流的物联网云平台实现数据上报和远程控制加上 SNTP 客户端从 NTP 服务器同步时间对数据采集类应用非常有用热搜词里也提到了 STM32 SNMP trap v2c这种管理协议在工业网络监控里很常见和 FreeRTOS 结合把 LwIP 跑在独立任务里配合信号量实现高并发。这些扩展都是在同一个硬件平台上完成的ST 的库支持做得已经很成熟关键是你把底层这关打通了。回到标题本身STM32LAN8720 这套方案之所以能成为经典不只是因为它便宜好用更重要的是它覆盖了从 MCU 外设到物理层芯片、从链路层到 TCP/IP 协议栈的完整知识链条。做通这个模块你对嵌入式网络的整体理解框架就搭起来了以后不管换什么 MCU、什么 PHY思路都是相通的。我实际用过之后最大的感受是以太网调试不难难的是把原理图和芯片手册读到每一个引脚的功能都了然于胸做到这一步剩下的就是时间问题。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进