ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

STM32+LAN8720A+RT-Thread以太网驱动实战:从硬件设计到LWIP调优

STM32+LAN8720A+RT-Thread以太网驱动实战:从硬件设计到LWIP调优 1. 为什么选STM32LAN8720A这套组合来跑RT-Thread做嵌入式联网方案的时候我最早用过W5500硬协议栈也用过ESP8266这类Wi-Fi模组后来转到STM32内置MAC加外部PHY的方案才发现这才是把“以太网”这件事吃透的正道。RT-Thread作为轻量级RTOS本身对以太网的支持已经相当成熟配合STM32内置的以太网MAC控制器再外挂一颗LAN8720A PHY芯片就能以很低的BOM成本拿到标准的100M以太网接口。这套组合在工业网关、数据采集器、边缘计算盒子里太常见了。很多人一上来就问为什么不用带MAC的PHY比如W5500或者CH395这类芯片确实省事SPI接口接上就能跑但它们内部跑的是独立协议栈用户拿不到底层帧做流量整形、VLAN过滤、原始套接字抓包都非常别扭。STM32内置MAC走的是RMII接口接PHY协议栈可以放在RT-Thread的LWIP里所有网络报文都能被系统接管这才是标准的嵌入式Linux或者RTOS联网思路。适合什么场景如果你的产品需要稳定的TCP长连接、需要跑MQTT、需要做TFTP固件升级、需要抓包调试这套组合会舒服很多。STM32F407、F429、H743这些带MAC的型号都支持配合LAN8720A这颗极其常见的10/100M PHY成本压得住资料也好找。再说LAN8720A本身。这颗PHY是SMSC现在被Microchip收了出的10/100M自适应RMII接口功耗低外围电路简单QFN封装也不大。它在国内流行到什么程度正点原子、野火这些开发板的以太网方案几乎全是它网上随便一搜就是现成的原理图和驱动。这意味着你踩过的坑基本都有人踩过排查问题时有大量经验可以借鉴。对于第一次做以太网产品的人来说选一颗生态成熟的芯片能少走很多弯路。2. 硬件层面的关键细节原理图中有四个地方最容易翻车软件配置排错之前硬件必须是对的。我见过太多人在RT-Thread里调了半天结果问题出在原理图上。LAN8720A看起来外围简单但实际上至少有四个地方特别容易留隐患。2.1 RMII接口的时钟源选择RMII接口最大的特点就是时钟简单收发共用一个50MHz参考时钟。LAN8720A的CLK_OUT引脚可以输出50MHz时钟也可以由外部晶振提供50MHz这里有个常见的误区很多人以为LAN8720A必须外接50MHz有源晶振其实它可以靠25MHz无源晶振配合内部PLL倍频到50MHz然后从CLK_OUT输出给STM32的ETH_RMII_REF_CLK。但在实际工程里我更推荐用STM32的MCO引脚输出50MHz给LAN8720A的REF_CLK而不是依赖PHY的CLK_OUT。为什么因为STM32F407的RMII参考时钟要求由外部提供50MHz如果用PHY的CLK_OUT回灌时钟抖动和PCB走线引入的噪声不好控制调试时要是出现偶发丢包你很难判断到底是时钟问题还是驱动问题。MCO输出的50MHz是从STM32内部PLL分频出来的和MAC逻辑同源信号质量更稳定。这里给一个关键参数LAN8720A的REF_CLK输入高电平最小宽度要求大概9.8ns50MHz周期是20ns理论上占空比稍微差点都能工作但别在原理图阶段就埋雷。我自己的习惯是STM32的PA8配置为MCO输出50MHz直接连LAN8720A的XI/CLKIN引脚LAN8720A的XT2悬空X1不用接晶振。2.2 PHY地址配置与复位电路LAN8720A的PHY地址由RXER/PHYAD0引脚的电平决定默认配置是0x01也就是说PHYAD0引脚推荐接下拉电阻。如果你在RT-Thread的驱动配置里把PHY地址填成0x00或者0x02上电后就会一直检测不到PHY。这个问题很隐蔽因为开发板原理图里PHYAD0可能已经接了电阻你自己画板子的时候漏了就会陷入“驱动明明没问题但就是读不到PHY ID”的困境。另外LAN8720A的复位引脚nRST低电平有效要求复位脉冲宽度至少1ms。很多人在程序里初始化完GPIO就直接去读PHY的ID结果读到0xFFFF。正确的做法是上电后先延时至少10ms再对PHY做一次软复位或者直接用GPIO拉低nRST保持10ms再释放。RT-Thread的eth驱动框架里也有PHY复位逻辑但你最好在硬件上确认复位电路是RC延时还是GPIO控制。如果用RC复位电容计算不好可能出现复位不彻底表现为“偶尔能拿到IP、偶尔拿不到”。2.3 1.2V电压、LED寄存器、变压器中心抽头LAN8720A内部有1.2V LDO只需要提供3.3V电源就行但要特别注意它的VDDCR引脚必须外接滤波电容而且走线要尽量靠近芯片。还有LED驱动引脚nINTSEL和REGOFF这些引脚的电平组合决定了LED模式、内部稳压器模式画原理图的时候如果抄别人电路务必把这些引脚的上拉下拉电阻也一起抄全不能省。变压器的中心抽头也很关键。100M以太网变压器中心抽头一般接3.3V或者通过RC到地具体要看PHY的驱动能力。LAN8720A的TX/RX中心抽头典型接法是接到3.3V电源如果接错成0.1uF电容对地链路能建立但传输距离和信号质量会受影响跑千兆测速时不容易暴露长时间大流量就丢包。3. RT-Thread以太网驱动框架速览从设备驱动到LWIP很多初学者拿到RT-Thread工程第一反应是“ETH驱动到底是谁在调”这里我把框架拆开讲。RT-Thread的网络体系从上到下大概是应用层Socket接口 - LWIP协议栈 - eth_device驱动 - STM32 HAL库的ETH驱动 - 底层MAC寄存器。你写的应用只管socket不要直接去操作MAC而rt_device_eth这个抽象层把PHY初始化、链路检测、报文收发都封装好了。这套框架最大的价值在于协议栈和驱动的分离。你在应用层改TCP参数不需要动驱动换了PHY芯片也不需要动LWIP。LAN8720A驱动本质上就是让RT-Thread知道“这颗PHY的地址怎么读、状态寄存器在哪、怎么复位”而数据通路早就由HAL库的ETH DMA搞定了。这也是为什么我建议不要自己从头写LWIP移植直接用RT-Thread的组件包。RT-Thread Studio里勾选Ethernet、勾选LWIP再配合STM32CubeMX生成的HAL库ETH配置整个框架的代码量比你手写少了几个数量级而且社区验证过很多次稳定得多。3.1 驱动框架中的三层关系具体到函数调用关系上你会在工程里看到这几个关键层stm32_eth.c这是HAL库和RT-Thread的中间层负责把STM32 HAL的收发函数包装成RT-Thread的eth_device_ops。lan8720.c这是PHY芯片驱动重点实现rt_phy_init、rt_phy_read_status这些回调里面要读PHY ID、状态寄存器等。eth_device层RT-Thread组件包里已经有通用逻辑包括网线插拔检测、自动协商状态轮询等。改配置的时候你先分清楚自己在改哪一层。如果换了PHY芯片只需要替换lan8720.c如果是改了RMII引脚映射那就是在CubeMX里改重新生成stm32_eth.c相关的HAL初始化代码。不要满工程乱找宏定义框架的功劳就是让你定位问题时有清晰边界。3.2 打开ETH驱动和LWIP相关的配置项在RT-Thread Studio里用CubeMX生成的工程默认网络功能不一定全开。你需要到rtconfig.h检查这几个关键宏RT_USING_LWIP必须定义否则网络相关组件不会被编译。RT_LWIP_ETH使能以太网设备支持一般由软件包自动添加。RT_LWIP_TCP、RT_LWIP_UDP、RT_LWIP_DHCP按需打开。RT_LWIP_REASSEMBLY通常不需要嵌入式报文重组反而浪费内存。经验上第一次调试我建议把DHCP关掉直接用静态IP。不是DHCP有问题而是DHCP失败时的报错容易和PHY链路问题混淆。你先把静态IP配上发现能ping通再开DHCP做动态获取。这样定位问题的维度一下子就少了一层。4. 从零到一配置全过程实测步骤与关键配置项这里我以STM32F407VET6 LAN8720A RT-Thread Studio 5.x为例完整走一遍从创建工程到ping通的流程。4.1 STM32CubeMX生成基础工程先用CubeMX配置STM32的ETH外设这里选择RMII接口。重点检查以下几个引脚ETH_RMII_REF_CLKPA8配置为MCO输出50MHz、ETH_RMII_CRS_DVPA7、ETH_RMII_RXD0PC4、ETH_RMII_RXD1PC5、ETH_RMII_TX_ENPB11、ETH_RMII_TXD0PB12、ETH_RMII_TXD1PB13、ETH_MDCPC1、ETH_MDIOPA2。看到这里有朋友会问为什么不用PA1作为ETH_RMII_REF_CLK因为当使用RMII时STM32F407的REF_CLK来自外部一般用MCO输出50MHz到PA8再在CubeMX里把ETH_RMII_REF_CLK设置为PA1也是可以的。但要注意PA1模式是输入它接收外部PHY提供的时钟而PA8 MCO是输出。两种方案都能跑我前面为什么推荐MCO因为时钟源来自主芯片链路调试更可控。CubeMX里选RMII后PA1会自动被ETH_RMII_REF_CLK占用如果不想用PA8 MCO也可以外部用有源晶振。但千万别两个都不选那MAC就根本没时钟。CubeMX里的ETH配置我没改太多主要确认勾选了RMII然后配置一下DMA描述符数量默认4个TX描述符、4个RX描述符。对一般应用够用。生成工程后把ETH的HAL初始化代码移植到RT-Thread Studio工程中或者在RT-Thread Studio的“硬件配置”向导里直接配置也能生成但用CubeMX生成再导入是保守路径不容易出错。4.2 在RT-Thread Studio中配置PHY类型与地址RT-Thread Studio里打开rtconfig.h找到BSP_USING_ETH和PHY_USING_LAN8720A这两个宏定义确保已经打开。如果软件包列表里没有LAN8720A驱动可以在RT-Thread Settings的“在线软件包”里搜索lan8720添加PHY驱动包。然后最关键的是确认PHY地址。stm32_eth.c或lan8720.c里通常会写#define PHY_ADDR 0x01这个必须和硬件一致。怎么确认看原理图LAN8720A的PHYAD0引脚如果接地就是0默认LAN8720A复位后的地址是1因为芯片内部有默认偏置。绝大多数开发板出厂都配置成0x01所以驱动默认值也是0x01。还有一个容易忽视的配置PHY_STATUS_MASK用于判断链路状态和自动协商结果。LAN8720A的基本状态寄存器地址是0x01bit2表示链路状态bit5表示自动协商完成。RT-Thread的驱动包里已经有专门针对LAN8720A的状态掩码不要再手动改成通用PHY的掩码否则状态轮询会失灵。4.3 上电时序与静态IP验证代码编译下载后我不是直接看串口打印而是先做三件事用万用表量LAN8720A的供电3.3V是否正常最好在PHY的电源引脚附近量别在板子输入端量因为走线压降可能导致PHY供电偏低。用示波器看REF_CLK引脚是否有50MHz时钟这个信号只要PHY和MAC需要工作就必须存在没有时钟一切白搭。串口终端连接RT-Thread的msh输入list_device看是否有eth设备。在RT-Thread终端里如果设备注册成功你会看到eth或e0设备。然后执行ifconfig如果配置的是静态IP应该能看到IP地址、MAC地址、网关信息。第一次调这个环节我建议把自动协商强制关闭直接指定100M全双工模式减少变量。具体做法在lan8720.c的初始化函数里往LAN8720A的BCR寄存器地址0x00写入0x2100这个值表示“100M全双工 重启自动协商”或者更直接一点写入0x2000重启PHY但保持手动模式。LAN8720A的BCR寄存器bit13是速度选择bit8是双工模式bit9是自动协商使能。你可以在初始化后临时强制写入先跑通链路再改回自适应模式测自动协商。我当时实测时的终端输出大致是msh /ifconfig eth0 Link up IP address: 192.168.1.100 Gateway address: 192.168.1.1 MAC address: 00:80:e1:00:00:00看到Link up不等于链路稳定还需要ping一下网关。如果ping丢包先别怪LWIP多半是硬件问题或者RMII时钟问题这是我最想说的一点。5. 调试中的血泪教训上电后没有IP、连接断开、丢包率高网络调试不像串口一个问题背后可能藏了三四个原因。我复盘过自己在STM32LAN8720A上踩过的坑基本上集中在下面几类。5.1 最常见PHY复位时序不对导致检测不到PHY症状RT-Thread启动后ifconfig一直显示Link down或者读PHY ID为0xFFFF。用逻辑分析仪抓MDIO总线发现STM32一直在发读操作但PHY没有回应。第一次遇到这个问题我以为是PHY芯片坏了换了三颗芯片都一样。后来查代码发现RT-Thread的PHY驱动在初始化时会调用rt_pin_write控制复位脚但我的硬件复位引脚根本没接GPIO而是RC复位电路。RC的复位时间是100ms而RT-Thread的PHY探测函数在初始化后只延时了20ms就去读PHY ID此时PHY还没准备好自然读不到。解决办法有两个要么硬件上把PHY复位引脚接到STM32的GPIO由软件控制复位完成后等足够长时间要么在驱动初始化时增大延时到100ms以上。我后来查LAN8720A手册复位释放后需要大概1ms才能访问寄存器但那只是芯片内部逻辑就绪如果RC复位时间不够在电压爬坡阶段就开始复位释放时间更长。所以代码里最好这样处理#define PHY_RESET_DELAY 50 rt_pin_write(PHY_RST_PIN, PIN_LOW); rt_thread_mdelay(10); rt_pin_write(PHY_RST_PIN, PIN_HIGH); rt_thread_mdelay(PHY_RESET_DELAY);注意延迟不是越大越好。如果复位释放后延迟超过2秒PHY可能已经完成自协商又进入了低功耗模式反而状态不对。50ms到100ms是比较稳的区间。5.2 网线插拔检测失败中断引脚配置错了RT-Thread的eth设备支持网线插拔检测这个功能依赖PHY的中断输出引脚或轮询状态寄存器。LAN8720A的INT引脚是推挽输出默认低有效当PHY检测到link状态变化时会拉低INT引脚。RT-Thread驱动里如果配置成中断模式需要把这个引脚接到STM32的外部中断输入。如果你没接INT引脚驱动默认会走轮询模式这不是问题。但问题出在有些开发板的INT引脚同时被其他功能占用了或者根本没引出而rtconfig.h里又打开了PHY_USING_INT导致驱动初始化中断引脚失败整个eth设备注册异常。我的建议是第一版调试关掉PHY中断让驱动轮询PHY状态寄存器。轮询间隔一般是100ms到1s对普通应用完全够用。等整个链路稳定了再考虑要不要开中断来降低轮询开销。别一上来把功能全打开排查问题时变量越多越难搞。5.3 丢包率高、速度上不去DMA描述符和缓冲区配置不合理网络能ping通但是ping一个大包或者持续高流量就丢包这个问题我觉得比PHY检测不到更折磨人因为它不是“不能用”而是“不稳定的能用”。排查思路分两步。第一步看RX描述符数量。HAL库默认配置4个TX描述符、4个RX描述符每个描述符对应的缓冲区大小一般设为1518字节以太网最大帧。如果你的应用跑TCP大包下载4个RX描述符容易在突发流量下被占满HAL库来不及释放新来的包就直接被DMA丢弃。把RX描述符增加到8个或16个丢包会明显改善。第二步看RT-Thread的网卡缓冲区配置。在LWIP的配置里MEM_SIZE、PBUF_POOL_SIZE这些参数直接影响TCP吞吐量。默认的PBUF_POOL_SIZE通常是16个每个PBUF池大小为1518字节。对于100M以太网这个池子还是偏小至少调到32个或更多。可以这样估算100Mbps的线速也就是大约8100帧/秒每帧1518字节如果系统处理速度跟不上缓冲池不够就会丢包。内存够就多分配一点但STM32F407内部RAM只有192KB别一次性拉满要结合你的其他业务内存占用情况调整。我实测的一个配置组合供参考#define RT_LWIP_MEM_SIZE (32 * 1024) #define RT_LWIP_PBUF_POOL_SIZE 32 #define RT_LWIP_RX_ETH_BUFFER_SIZE 1518 #define RT_LWIP_TX_ETH_BUFFER_SIZE 1518 #define RT_LWIP_TCP_SND_BUF (16 * 1024) #define RT_LWIP_TCP_WND (16 * 1024)这套配置在F407上跑TCP下载实测能到90Mbps左右接近百兆极限。继续调大TCP_SND_BUF到32KB也可以但内存占用明显上去如果你的产品还要跑MQTT、文件系统、OTA就得权衡。6. 动手调优前必须理解的RMII信号完整性问题很多人把网络调不通归结于代码其实RMII接口在硬件走线上就有严格要求。RMII的数据线只有RXD0、RXD1、TXD0、TXD1、CRS_DV、TX_EN、REF_CLK这几根看起来少但50MHz时钟对走线等长还是有要求。如果你在自己画的板子上遇到“软件完全没问题但网速忽高忽低”的情况多半是如下几个信号完整性问题REF_CLK走线太长或者经过了过孔太多导致时钟边沿变缓。RXD/TXD数据线与REF_CLK之间长度差太大造成建立保持时间不足。PHY的电源去耦不够特别是RMII收发切换瞬间电流变化大电源纹波会耦合到信号上。这些问题从代码层面无法解决。我在调一块两层板的时候用STM32的MCO输出时钟REF_CLK走线大概5cm没有包地结果100M速率就是跑不满速率降到10M才稳定。后来把REF_CLK走线缩短到2cm以内并在PHY旁边加了一颗10uF钽电容问题就消失了。这让我深刻意识到以太网不像串口那么“宽容”它对硬件布局的要求属于射频级别。如果做PCB布局我建议STM32和LAN8720A之间的距离尽量短RMII信号线等长误差控制在5mm以内REF_CLK用地线包起来或者至少旁边有完整的地平面。LAN8720A的电源引脚至少放一个0.1uF和一个10uF电容靠近引脚放置千万别在电源输入端共享一颗电容。另外LAN8720A的TXD/RXD引脚内部有可编程的驱动强度默认配置就行不要为了追求“信号更好”修改寄存器里的驱动强度参数。曾经有一版代码我参考Linux驱动把LAN8720A的EDCR寄存器改了结果10M模式正常100M模式反而出现大量CRC错误。后来恢复默认值一切正常。某些寄存器不是“这样调会更优”而是“芯片出厂默认就是最优”特别是对于这种老牌PHY厂商的默认值经过大量测试不要盲目模仿Linux下的高级配置。7. 从驱动到应用RT-Thread上跑通TCP Server和MQTT的关键步骤驱动通了最终目的是跑应用。我建议按下面这个顺序从底往上验证每一步确认了再往上走。先跑通LWIP的回环测试。在RT-Thread终端里输入ping 192.168.1.100本机IP如果能ping通说明协议栈内部收发路径没问题。这个测试不经过PHY纯粹验证LWIP层。然后跑外网测试用开发板ping电脑的IP。如果通了说明MAC和PHY链路没问题。如果这步不通网络一定有问题先回到PHY的link状态排查。最后才去跑TCP Server。RT-Thread软件包中心有tcpclient示例或者直接写一个简单的socket服务端。我在这里提一个容易被忽略的点LWIP的内存分配和RT-Thread的内存堆是分开的。LWIP自己管理一块内存池默认大小通过RT_LWIP_MEM_SIZE配置默认挺小。如果你的TCP连接数一多或者单次收发缓冲区超过限制会出现tcp_enqueue: too many segments或pbuf_alloc: Out of memory之类的错误。看到这类日志别再查网络了直接调大LWIP内存。RT-Thread还提供了一份非常完善的文档中心关于如何配置LWIP和DHCP以及如何实现断线重连基本商用的需求都能覆盖。但要注意文档描述的“默认配置”跑demo可以跑真实产品必须调优尤其是TCP超时时间、重传次数、保活探测间隔。举一个实际参数调节的例子。STM32F407做MQTT客户端连接到云服务器如果网络不稳定TCP会长时间卡在重传环节导致应用层无法感知掉线。在LWIP配置里可以这样调整#define RT_LWIP_TCP_KEEPALIVE 1 #define RT_LWIP_TCP_KEEPIDLE 30000 #define RT_LWIP_TCP_KEEPINTVL 5000 #define RT_LWIP_TCP_KEEPCNT 3开启TCP保活后如果30秒内没有数据传输协议栈会以5秒为间隔发探测包连续3次无响应就判定连接断开。这样应用层才能及时重连。这个配置直接关系产品在网络抖动时的恢复速度重要程度不亚于驱动本身。8. 最后分享一个定位问题非常高效的办法抓包对比法遇到网络问题不管你怎么看代码都不如抓包来得直观。我调试STM32LAN8720A时会在PC上开Wireshark把开发板和PC的网线都接到一个普通交换机上然后对比分析。比如你怀疑是PHY自发包的问题可以在Wireshark里看有没有大量CRC错误帧。如果交换机上看到CRC错误包激增基本可以断定是硬件信号完整性问题而不是RT-Thread代码的问题。反过来如果PC端能看到开发板发出的ARP请求但开发板收不到ARP应答说明RX方向有问题可能是RMII的数据线虚焊或者PX_DMA没有及时释放描述符。还有一个比较冷门但很实用的点通过观察Wireshark的“Delta time”可以判断协议栈的响应速度。如果TCP三次握手时开发板对你的SYN-ACK响应总是延迟几十毫秒说明系统中断响应或者LWIP的tcpip线程调度有问题可以考虑提高网卡接收线程的优先级或者把LWIP的tcpip_thread优先级调高一点。我在RT-Thread里调试这类问题时还会开一个list_thread命令实时查看tcpip线程和eth_rx线程的栈使用率。如果栈满了也会出现偶发的不响应现象但那不是网络问题而是系统资源问题一旦走弯路就很难回头。另外一个经验是关于MAC地址的。LAN8720A本身不包含MAC地址需要从STM32的OTP或者外部EEPROM读取。开发板默认在代码里写死了一个MAC地址如果多台设备没改MAC就直接上云会出现IP地址冲突、服务器拒绝连接等奇怪现象。我在做批量设备时就吃过这个亏后来在产品初始化时用芯片唯一ID生成MAC地址从HW_UID的低24位加上OUI前缀组合。这样每台设备MAC唯一省去烧录步骤的麻烦。生成MAC的代码大概是这样uint32_t uid[3]; uid[0] HAL_GetUIDw0(); uid[1] HAL_GetUIDw1(); uid[2] HAL_GetUIDw2(); mac[0] 0x00; mac[1] 0x80; mac[2] 0xe1; mac[3] (uid[0] 16) 0xff; mac[4] (uid[1] 8) 0xff; mac[5] uid[2] 0xff;用唯一ID生成MAC不会和局域网内其他设备冲突还能避免手动烧录MAC的工序省时省力。写到这里基本覆盖了从硬件设计、RT-Thread驱动配置、LWIP调优到抓包定位的完整链路。基于STM32和LAN8720A的以太网驱动配置难点不在某个单独环节而在于把硬件、驱动、协议栈三者串起来排查。我的体会是第一次调通后一定要把关键节点记录下来尤其是PHY复位时序、REF_CLK来源、描述符数量这些参数它们直接影响产品能不能稳定运行。希望这篇实战记录能帮你少走几步弯路。
返回列表