ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

STM32F407+LAN8720A移植lwIP:CubeMX配置与调试全攻略

STM32F407+LAN8720A移植lwIP:CubeMX配置与调试全攻略 前阵子帮朋友调试一块STM32F407开发板的以太网功能烧完lwIP例程后串口打印死活卡在MX_LWIP_Init()我第一反应就是查PHY地址。打开原理图一看LAN8720A的PHYAD0引脚被上拉了地址是0x01而CubeMX默认生成的头文件里写的是0x00。就差了这一个数字整个协议栈都起不来。这类问题在CubeMX STM32F407 LAN8720A FreeRTOS移植lwIP的流程里非常典型。这篇文章就是给第一次用CubeMX在STM32F407上做以太网通信的开发者看的。它会带你从硬件连接开始一路过一遍CubeMX的时钟树、ETH外设、lwIP中间件和FreeRTOS整合最后把几个容易翻车的细节集中列出来。我的目标是你看完之后不只是“照着抄能过”而是清楚每一步为什么要这么配出了问题能自己定位。1. 为什么是F407 LAN8720A CubeMX先搞清这套组合的底层逻辑1.1 MAC、PHY、RMII到底是怎么回事很多新手第一次接触以太网移植容易把“网卡”当成一个整体。实际上在STM32F407上以太网功能被拆成了两部分MAC层Media Access Control集成在F407芯片内部负责组帧、拆帧、DMA搬运、地址过滤这些“数字层面”的活。PHY层Physical Layer是外部的物理层收发器负责把MAC送过来的并行数字信号变成差分模拟信号从网线的RJ45座子上发出去接收方向则反过来。LAN8720A就是一颗典型的10/100M以太网PHY芯片。打个比方MAC像是快递分拣中心处理的是“包裹单号和面单”PHY像是快递员真正骑车把包裹送出去。F407的MAC和外部PHY之间通过MII或RMII接口连接。MII需要16根信号线RMII精简到9根左右代价是接口时钟从25MHz提到50MHz。对于F407这种引脚资源比较金贵的芯片RMII是更常见的选择也是本文的主角。1.2 为什么用CubeMX而不是纯手工移植纯手工移植lwIP不是不行我自己早年也干过要手动配置ETH外设寄存器、手动挂DMA描述符、手动把lwIP的ethernetif.c和sys_arch.c移植到自己的工程里中间任何一个环节出错都很难排查。CubeMX的价值在于它把这些“体力活”自动生成了ETH外设的HAL驱动初始化DMA描述符和接收/发送缓冲区的管理lwIP协议栈与STM32 HAL层之间的ethernetif.c适配层FreeRTOS任务与lwIP线程的挂载。但有一点必须清醒CubeMX生成的是“通用模板”不是“为你这块板子定制好的方案”。PHY地址、REF_CLK来源、MAC地址、时钟树参数这些都得你自己根据原理图去核对。换句话说CubeMX帮你省掉了99%的代码量但剩下那1%的“硬件相关假设”恰恰是大多数人踩坑的地方。1.3 选型之前先确认的三个前提板子上的PHY芯片确实是LAN8720A。会看原理图找到U3或U5位置上的丝印很多国产开发板用YT8512、DP83848寄存器定义和PHY地址配置逻辑会有差异。你的F407型号要带以太网MACF407基本都带但如果你用的是F401、F411这些精简型号没有ETH外设后面就不用看了。网口变压器和RJ45座子已经接好。开发板一般都带如果是自己画板LAN8720A到RJ45之间需要网络变压器这部分最好直接抄参考设计。2. 硬件连接RMII信号逐线核对以及LAN8720A最小系统的关键细节2.1 F407与LAN8720A的RMII接线表F407的RMII引脚是固定的没有第二套映射可选。CubeMX里勾选ETH后它会自动占好这9个引脚。下面是完整接线对应关系直接对着原理图核就行RMII信号F407引脚LAN8720A引脚说明REF_CLKPA1REF_CLKO / REF_CLK50MHz参考时钟CRS_DVPA7CRS_DV载波侦听/数据有效TX_ENPB11TX_EN发送使能TXD0PB12TXD0发送数据位0TXD1PB13TXD1发送数据位1RXD0PC4RXD0接收数据位0RXD1PC5RXD1接收数据位1MDIOPA2MDIOSMI管理接口数据线MDCPC1MDCSMI管理接口时钟线这里面我会特别强调三根信号REF_CLK整个RMII链路都是按它来采样的50MHz必须干净稳定。这个信号如果出小问题表现就是“能PING通但丢包率高”“偶尔断网”。MDIO/MDC用来让MCU透过SMI总线读写PHY寄存器比如读PHY的ID、读链路状态、配置自协商。初始化时读不到PHY基本就是MDIO/MDC这两根线的问题。RXD0/RXD1/CRS_DV接收方向。接收不到数据优先查这三根线的虚焊和REF_CLK质量。2.2 REF_CLK到底谁产生两套方案先搞清楚你的板子是哪种这是F407以太网移植里最常被忽略的硬件细节。RMII接口需要50MHz的参考时钟但这个时钟可以有两个来源方案时钟来源优点缺点CubeMX时钟树需要做什么ALAN8720A自己产生并输出50MHz时钟链路简单与MCU主频无关板子上必须有25MHz晶振给LAN8720A不需要额外配置MCO直接让ETH外设使用外部引脚输入BMCU的MCO2引脚输出50MHz给PHY省掉PHY侧晶振PLL配置复杂调整系统时钟时要兼顾MCO输出必须在时钟树里使能MCO2并保证输出精确50MHz绝大多数带LAN8720A的开发板用的是方案APHY旁边有一颗25MHz无源晶振LAN8720A内部PLL把它倍频到50MHz然后从REF_CLKO输出给F407的PA1。方案B常见于部分NUCLEO板原理图上可以看到PA1标的是MCO2PHY的REF_CLK输入引脚直接和PA1相连PHY侧没有晶振。我个人的建议是拿到板子先查原理图看PA1的网络名到底叫什么。如果是ETH_RMII_REF_CLK或RMII_REF_CLK这类名字大概率是方案A如果是MCO2那就是方案B。我曾经见过一块板子明明没有PHY晶振网友却按方案A配了半天初始化永远超时。2.3 PHY地址、复位、自协商硬件上的三个隐藏变量先说PHY地址。LAN8720A的PHY地址由PHYAD[4:0]决定其中高4位芯片内部固定为0000只有PHYAD0可以通过外部引脚也就是RXER/PHYAD0引脚的电平来设置。拉低就是0x00拉高就是0x01。CubeMX默认生成的PHY地址是0x00这一点很关键。如果你的板子和正点原子探索者一样把PHYAD0上拉了那实际地址就是0x01。你把初始化代码跑起来HAL_ETH_Init()会去读PHY的ID寄存器读不到就返回HAL_ERROR表现就是卡在MX_LWIP_Init()。再看复位。LAN8720A的复位引脚叫NRST低电平有效。如果用RC电路做上电复位一般问题不大但芯片手册要求复位信号要保持一定时间RC参数如果太小可能复位不彻底。很多工业板会用MCU的GPIO控制复位这时候要特别注意MCU的GPIO初始化必须早于ETH外设初始化而且在拉高复位后最好延时几百毫秒再操作PHY否则PHY还没readySMI读寄存器照样失败。最后是自协商。lwIP在初始化netif时底层low_level_init()里会等待PHY完成自协商并检测link状态。如果网线没插或者对端设备没开机初始化过程会一直等到超时。这不是程序卡死是它在等物理链路就绪。调试阶段最好先把网线插上或者把直连电脑的网口起来。2.4 电源与晶振的工程细节LAN8720A是单3.3V供电芯片内部会生成1.2V核压外部只需要在电源引脚附近放好滤波电容一般0.1uF加10uF的组合就够。但如果你发现板子能PING通但一传大量数据就断流排除软件问题后一定要回头查供电。我见过一块板子电源纹波超过100mVRX方向错包率明显上升。25MHz无源晶振的两个负载电容一般是10~22pF具体看LAN8720A手册的参考电路不是什么大事但别省。REF_CLK信号在PCB上要短走线不要过孔乱绕这类问题在低速电路里看不出来到了50MHz就藏不住了。3. CubeMX逐项配置从时钟树到中间件每一步都有说法3.1 创建工程与选择芯片CubeMX新建工程时在MCU Selector里搜STM32F407ZGT6或STM32F407VET6具体看你手里的板子丝印。要注意同一个工程模板芯片后缀不同引脚数量不同如果选错型号后面生成了代码也烧不进去。如果你用的是淘宝常见的“F407ZGT6最小系统板”选ZGT6即可。3.2 时钟树HSE频率必须先确认很多人一上来就去调系统主频结果卡在HSE上。F407的系统时钟是168MHz但HSE外部晶振频率不是固定的正点原子探索者用的是8MHz很多国产核心板用25MHz。先看原理图上HSE晶振的丝印再配时钟树。以8MHz HSE为例PLLM 4HSE分频后得到2MHzPLLN 168倍频到336MHzPLLP 2分频后得到168MHz系统时钟。如果HSE是25MHz则PLLM 25PLLN 336PLLP 2结果同样是168MHz。如果你的板子走的是第二章方案APHY自出50MHz时钟树到这里就结束了ETH外设的RMII参考时钟来自PA1外部输入不需要你额外配置MCO。如果你的是方案B就必须在时钟树里打开MCO2分支让MCO2输出50MHz给PHY。这里有个很现实的问题当系统主频168MHz时PLLR是168MHz直接分频出不了50MHzCubeMX会自动调整PLL参数。你会看到系统主频可能不再是168MHz这是正常的方案B的代价就是主频和PHY时钟互相牵制。3.3 ETH外设与引脚确认在Pinout Configuration页面左侧Connectivity找到ETH勾选RMII模式。勾完之后芯片视图上PA1、PA2、PA7、PB11、PB12、PB13、PC1、PC4、PC5会自动变成绿色表示这些引脚被ETH功能占用。这时候要做两件事检查有没有引脚冲突。比如你的USART2刚好用了PA2那就要改串口引脚ETH的RMII引脚是固定的不能挪只能其他外设让路。确认PHY Address选项。有的CubeMX版本里ETH参数页有“PHY Address”字段这里先不要填后面代码生成完了去stm32f4xx_hal_conf.h里改更可靠因为不同版本这个字段的位置不一样直接改头文件最稳妥。3.4 lwIP中间件参数这些数值不是让你一路默认的在Middleware and Software Packs下面启用LWIP。核心参数我建议这么设参数建议值原因IP ModeStatic调试阶段DHCP在局域网里会因路由器和交换机差异带来变量先静态IP把基础打通IP Address192.168.1.10看你所在网段别和电脑冲突Netmask255.255.255.0标准C类掩码Gateway192.168.1.1直连电脑时网关随便填同一网段即可MEM_SIZE40960默认1600太小TCP收发稍微大点就申请失败PBUF_POOL_SIZE16~20pool太小接收方向容易丢包TCP_WND32768影响TCP吞吐量TCP_SND_BUF32768发送缓存太小会导致发送慢TCP_MSS1460标准以太网MTU 1500减去IP头20再减去TCP头20TCP_RECVMBOX_SIZE / UDP_RECVMBOX_SIZE20接收邮箱队列太小会阻塞协议栈这几个参数不是拍脑袋定的它们的核心逻辑是lwIP在运行时需要一块连续的堆MEM_SIZE和一组预分配的内存池PBUF_POOL。MEM_SIZE太小连接一多或者数据量一大mem_malloc返回NULL表现就是TCP连接时断时续。PBUF_POOL_SIZE太小网卡DMA收到数据后申请不到pbuf就只能丢包。3.5 FreeRTOS的挂载任务、Heap和中断优先级一起交代在中间件里启用FreeRTOS接口建议选CMSIS_RTOS_V2。CubeMX此时会自动生成一个叫tcpip_thread的任务和一个ethernet_link_thread任务前者跑lwIP协议栈后者每2秒轮询一次PHY链路状态。FreeRTOS的堆大小我建议先给8~16KB。如果你的应用只需要ping通、简单TCP收发这个量够用如果后面要开HTTP Server、MQTT堆再往上加比如24~32KB。还有一个很容易被忽略的地方ETH中断优先级。ETH_IRQHandler的中断优先级不要设成0最高优先级。因为ethernetif.c里的接收回调会通过信号量通知lwIP的tcpip_thread这涉及FreeRTOS的ISR安全API调用如果优先级高于configMAX_SYSCALL_INTERRUPT_PRIORITY调度器会出问题。我建议在NVIC设置里把ETH_IRQn的抢占优先级设为5左右分组取Group4这样既不会太慢又不会破坏FreeRTOS的中断管理机制。3.6 生成代码后先别急着烧点GENERATE CODE生成工程后先看一眼目录结构Core/Src/main.c主流程和FreeRTOS任务入口LWIP/Target/lwip.clwIP初始化、netif配置LWIP/Target/ethernetif.cETH外设与lwIP的适配层DMA描述符、中断回调、链路轮询都在这。确认这些文件生成出来了再打开工程。4. 代码生成后的几个必改点从PHY地址到DMA描述符4.1 PHY地址0x00还是0x01决定你能不能初始化成功打开Core/Inc/stm32f4xx_hal_conf.h找这一行#define ETH_PHY_ADDR 0x00U根据原理图把PHYAD0引脚的高低电平确认之后改成实际地址。我用的那块板子是0x01就改成#define ETH_PHY_ADDR 0x01U这一行不改后面什么都白搭。HAL库的HAL_ETH_Init()会尝试读PHY的ID寄存器地址不对就直接返回HAL_ERRORMX_LWIP_Init()当然就转不下去了。4.2 网卡MAC地址全零地址在很多设备上会被直接丢弃打开LWIP/Target/lwip.c找到MAC地址数组__ALIGN_BEGIN static uint8_t MACAddr[6] __ALIGN_END {0x02, 0x00, 0x00, 0x00, 0x00, 0x01};如果CubeMX默认生成的MAC全零一定要改。推荐用02:00:00:00:00:01这种本地管理单播地址。我解释一下为什么第一个字节0x02二进制是00000010最低bit为0表示单播第二低bit为1表示本地管理地址不会和任何厂商的全球唯一MAC冲突。全零MAC在Windows的ARP缓存里经常被当无效条目你怎么ping都ping不通换一台设备又好了很容易让人误判成程序问题。4.3 DMA描述符和缓冲区别放错地方F407的内部SRAM分三块主SRAM1112KB、SRAM216KB、CCM RAM64KB。其中CCM RAM虽然速度快但DMA访问不到。ETH的DMA描述符和收发缓冲区如果被人为挪到CCM里网卡DMA会直接内存访问异常。CubeMX默认生成的代码里DMA描述符和缓冲区定义在ethernetif.c中ETH_DMADescTypeDef DMARxDscrTab[ETH_RXBUFNB] __attribute__((aligned(4))); ETH_DMADescTypeDef DMATxDscrTab[ETH_TXBUFNB] __attribute__((aligned(4))); uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE] __attribute__((aligned(4)));这些定义默认放在普通SRAM不用动。但要注意如果你后续为了性能优化手动给某个大数组加了__attribute__((section(.ccmram)))那就要睁大眼睛确认这段内存有没有参与ETH DMA。我见过一个项目为了提速把接收缓冲区放进了CCM结果网卡一收包就hardfault。4.4 自协商等待与定位打印如果初始化还是卡住最有效的定位方式是在ethernetif.c的low_level_init()函数里紧跟在HAL_ETH_Init()后面加一段打印把PHY的基本状态寄存器读出来uint32_t regval 0; if (HAL_ETH_ReadPHYRegister(heth, PHY_BSR, regval) HAL_OK) { printf(PHY BSR 0x%08lX\r\n, regval); }PHY_BSR就是PHY的状态寄存器地址1bit 5表示自协商完成bit 2表示link up。如果你读出来bit 2是0说明物理链路都没建立重点查网线和PHY供电如果连读都失败重点查MDIO/MDC引脚的接法和PHY地址。printf要从串口打出来前提是你已经把UART初始化好并重定向了fputc。调试阶段这个打印特别值等稳定了再删掉。4.5 静态IP在CubeMX里配置后别忘了确认生成结果如果你在CubeMX的LWIP设置里填写了静态IP生成代码后lwip.c里会看到类似IP_ADDR4(ipaddr, 192, 168, 1, 10); IP_ADDR4(netmask, 255, 255, 255, 0); IP_ADDR4(gw, 192, 168, 1, 1);建议核对一遍防止有些版本生成时没把IP参数带进去。5. FreeRTOS整合的关键任务分层、中断优先级与内存边界5.1 CubeMX生成的线程模型谁负责干什么启用FreeRTOS之后lwIP的集成代码里实际有三个线程在跑线程名位置默认优先级职责tcpip_threadlwip.cNormallwIP核心协议栈TCP、UDP、ARP、IP都在这个线程里处理ethernet_link_threadethernetif.cLow每2秒轮询PHY BSR寄存器感知网线插拔defaultTaskmain.cNormal用户自己的任务一般从main函数里创建这三个线程不是随便分的。lwIP的所有协议栈操作必须集中在tcpip_thread里外部代码不要直接操作pbuf或者netif链表而是通过tcpip_input或socket API把数据丢给这个线程。这个设计避免了多任务同时访问协议栈数据结构的竞态问题。5.2 中断到协议栈的桥ETH中断、信号量与接收回调接收方向的完整链路是网线上来了一个以太网帧LAN8720A通过RMII接口把数据给F407的MACF407的ETH DMA把数据搬到接收缓冲区ETH外设触发ETH_IRQHandler中断HAL层调用HAL_ETH_RxCpltCallback这个回调里通过FreeRTOS信号量通知ethernetif的接收线程lwIP拿到数据后交给tcpip_thread处理。CubeMX生成代码时这整条链路已经帮你接好了你基本不用改。但你要理解一点链路里任何一环断掉现象都是“网口灯亮但PING不通”。所以排查的时候先用串口在HAL_ETH_RxCpltCallback里放一个计数器看看接收中断有没有触发。如果计数一直为0说明数据根本没到MAC问题在硬件链路如果计数不停涨但PING不通问题在协议栈或IP配置。这一步能直接把排查范围缩小一大半。5.3 优先级设计不是所有任务都往高了设很多新手喜欢把所有任务优先级都设成High结果发现以太网反而变得不稳定。原因很简单tcpip_thread如果长期得不到CPU时间协议栈收包、发包都会滞后而用户任务里如果有HAL_Delay这种阻塞调用又会把高优先级任务干等住。我建议的优先级分配tcpip_threadosPriorityHigh数值5左右。协议栈必须及时处理收发包否则TCP的ACK超时重传会把吞吐量拖垮。用户网络任务比如TCP客户端、UDP发送osPriorityNormal4。ethernet_link_threadosPriorityLow2左右。链路轮询不着急慢一点没关系。后台LED闪烁、按键扫描这类任务可以更低。这个分配的核心原则是让协议栈线程保持相对高优先级但不能高到把用户关键任务饿死。如果你的应用需要对某个外部事件做毫秒级响应那个任务的优先级应该高于tcpip_thread但同时要控制它的执行时间避免长时间占用CPU。5.4 内存边界FreeRTOS Heap、lwIP内存池与SRAM预算F407ZGT6的内部SRAM是192KB看上去不少但要精打细算占用项估算大小说明ETH DMA描述符约256字节收发各4个每个32字节占用极小ETH RX/TX缓冲区约12KB收发各4个每个1.5KB左右lwIP MEM_SIZE40KB协议栈动态堆PBUF_POOL每个约1.5KB16个约24KB收发缓冲池FreeRTOS Heap8~16KB任务栈、队列、信号量分配加起来大概70~90KB看起来还剩一半。但要注意F407的192KB里包含64KB CCM不能给DMA用实际可被ETH DMA访问的普通SRAM是128KB左右。如果你的程序还有其他大型全局数组、各种任务栈RAM就会紧张。我踩过的一个坑是把MEM_SIZE调到80KB想提升性能结果系统跑一段时间后malloc失败表现为TCP连接建立不了。原因就是总内存超了。所以调到40KB左右已经足够应付大部分场景追求极限性能时再做精细内存预算。6. 调试心法从“Link灯亮”到“PING通”再到Wireshark验证6.1 排查链路的正确顺序以太网移植的调试最忌讳的就是“病急乱投医”。我的排查顺序一直是硬件链路插上网线看LAN8720A的Link LED亮不亮晶振是否起振。LED不亮后面全是废话。PHY寄存器通过串口打印PHY BSR寄存器确认link up和自协商完成。协议栈初始化确认MX_LWIP_Init()正常返回lwIP的netif已添加。IP层确认板子和PC在同一网段先静态IP不要一上来就DHCP。抓包验证用Wireshark看ARP和ICMP确认回包真的从板子发出去了。按这个顺序走多数问题十分钟之内能定位。6.2 常见故障现象与根因对照表现象可能原因排查手段初始化卡在MX_LWIP_Init串口无输出PHY地址不对、PHY未复位、网线没插改ETH_PHY_ADDR确认NRST引脚电平插网线串口打印PHY寄存器读失败MDIO/MDC接线错、PHY供电异常万用表量PHY电源查MDIO/MDC是否虚焊Link灯亮但PING不通MAC地址全零、IP不在同一网段、电脑防火墙拦截改MAC地址核对IP临时关防火墙能PING通但延迟大或丢包REF_CLK信号质量差、电源纹波大、CPU任务优先级不合理示波器看REF_CLK和3.3V调整任务优先级初始化有时成功有时失败上电时序问题、PHY复位时间不够在ETH初始化前增加100~500ms延时能PING通但TCP连接一建立就断开MEM_SIZE太小、TCP_WND/TCP_SND_BUF不够调大lwIP内存参数观察空闲RAM烧录后偶发hardfaultDMA描述符或缓冲区放到了CCM RAM检查所有section属性定义6.3 用Wireshark实证协议栈真的在工作PING通之后不要急着开心我建议你做一次抓包验证确认数据链路完全正确。PC上打开Wireshark选择连着板子的以太网网卡设置过滤条件ip.addr 192.168.1.10然后从PC ping板子ping 192.168.1.10。这时候你应该能看到ARP请求PC广播询问192.168.1.10的MAC地址ARP应答来自02:00:00:00:00:01说明板子正确回应了ICMP Echo RequestPC发出的ping包ICMP Echo Reply板子的回应包。如果只看到ARP请求和ICMP Request看不到Reply说明数据进了MCU但处理结果没发出来问题在协议栈或发送路径。这时候再回HAL_ETH_TxCpltCallback里加计数器确认发送DMA有没有完成中断。这个验证方法的价值在于它能确认不是电脑自己回了包而是你的板子真的跑通了整个协议栈。6.4 从模板到应用的扩展思路静态IP PING通之后建议按这个顺序做扩展把网络接进路由器从微软DHCP模式改成DHCP模式确认板子能从路由器自动拿到地址。在CubeMX的LWIP应用组件里开启HTTP Server用浏览器访问板子的IP能看到一个简单的网页。这一步能验证TCP服务端能力。把TCP Socket封装成一个独立任务实现简单的远程控制命令。如果后面要上MQTT大部分工作已经在lwIP FreeRTOS这层做好了只需要新增一个客户端任务。我个人在实际项目里始终保留这份“最小可移植模板”CubeMX生成 改PHY地址/MAC 调优先级 静态IP。后面换板子、换PHY都是在这个模板基础上改而不是重新折腾一遍。换到其他PHY芯片时比如DP83848或YT8512核心流程一样的只多了两件事确认PHY地址和寄存器定义以及对照数据手册检查PHY的中断或状态引脚是否复用。硬件细节千差万别但排查思路是通用的先让PHY正常再谈协议栈。
返回列表