ARTICLE DETAIL

资讯详情

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

STM32F407+LAN8720以太网实战:CubeMX配置与LwIP协议栈全解析

STM32F407+LAN8720以太网实战:CubeMX配置与LwIP协议栈全解析 做嵌入式网络通信这几年STM32F407LAN8720这个组合我前前后后折腾过不少次。说它“程序员友好”是因为F407片内自带以太网MAC和DMA代码层面可玩性很高说它“容易卡壳”是因为很多人第一步就败在50MHz的RMII参考时钟上紧接着又会被CubeMX、LwIP、FreeRTOS三者之间的配置联动整到怀疑人生。这篇文章以CubeMX 6.4 STM32CubeF4固件包为主线把从硬件接线、时钟路由、PHY驱动、协议栈配置到Ping通全过程的坑点一次性说清楚。内容偏实战适合做物联网网关、工业数据采集器、协议转换模块的开发者参考。1. 为什么这个组合值得折腾硬件架构和RMII时钟的三条路1.1 硬件选型逻辑先捋一下为什么大家都在用F407配LAN8720而不是直接上F429或者外挂一个MAC芯片。STM32F407内部集成了完整的以太网MAC控制器自带DMA和描述符管理唯独缺一个物理层PHY芯片。LAN8720是当今市面上性价比非常高的一款百兆PHYQFN封装修脚面积小3.3V单电源供电功耗也低非常适合做带以太网口的小型嵌入式设备。相比DP83848LAN8720的优势在于寄存器体系和TI、ST官方例程的匹配度较好而且目前市面上大多数开发板、核心板都预留了兼容接口资料好找。相比YT8512H这类国产PHYLAN8720的坑虽然也多但别人踩过坑之后留下的解决方案更丰富对新手更友好。整体成本算下来F407LAN8720方案比外挂MAC芯片的方案便宜不少而且MCU的算力也够承载LwIP协议栈加FreeRTOS调度。F407拥有1MB Flash和192KB SRAM跑LwIP 2.1.2这种轻量协议栈完全不需要为内存发愁。1.2 RMII接口信号与接线表F407的以太网MAC对外支持MII和RMII两种接口模式。MII需要16根信号线RMII只需要7根节省的IO全部可以挪作他用。但RMII是有代价的它把时钟频率从MII的25MHz提高到50MHz数据线从4位收/4位发压缩成2位收/2位发对信号质量的要求更高。F407在RMII模式下I/O引脚是固定的这也是很多人画板子时容易抄错的地方。下面这张表是我验证过多次的引脚映射全部复用在AF11功能上。信号名MCU引脚说明ETH_RMII_REF_CLKPA150MHz参考时钟输入必须由外部供给ETH_RMII_CRS_DVPA7载波侦听/数据有效ETH_RMII_RXD0PC4接收数据位0ETH_RMII_RXD1PC5接收数据位1ETH_RMII_TX_ENPB11发送使能ETH_RMII_TXD0PB12发送数据位0ETH_RMII_TXD1PB13发送数据位1ETH_MDCPC1管理接口时钟ETH_MDIOPA2管理接口数据硬件布线时有一个容易被忽略的点REF_CLK必须尽量短不要和DCDC功率电感、晶振这些强干扰源并行走线。发送组的TXD0、TXD1、TX_EN尽量等长接收组同理。虽然百兆以太网对等长要求不如千兆那么苛刻但走线太随意会导致高温或长距离传输时出现偶发丢包。1.3 50MHz参考时钟的三种来源RMII模式下LAN8720和F407的MAC都必须工作在同一路50MHz参考时钟上这是整个项目最容易出问题的地方。根据时钟来源一共有三种做法。方案AF407的PA8MCO1直接输出50MHz。这种方式省掉了一颗有源晶振但代价是时钟树会被一起绑架。要理解这一点先看F407的PLL结构HSE经过PLLM分频后进入VCOVCO输出经过PLLP分频得到SYSCLK经过PLLQ分频得到USB和以太网参考时钟。如果想用PLLQ得到50MHz经典配置是HSE为8MHz时PLLM8、PLLN400、PLLQ8此时VCO输出400MHzPLLQ分频得到50MHz。但PLLP一旦确定为2SYSCLK就是200MHz超过了F407的168MHz上限所以只能把PLLP调到4让系统主频降到100MHz。也就是说MCO方案通常以牺牲主频到100MHz为代价。方案BLAN8720外接25MHz无源晶振由PHY内部PLL倍频后从REF_CLK引脚输出50MHz给F407。这是我最推荐的方式因为完全不干扰MCU的时钟树F407可以稳定跑168MHz。但前提是你的LAN8720模块把REF_CLK或CLKOUT引出来了有些精简版模块没有引出这个引脚就只能换方案。选购模块时留意一下丝印能省不少麻烦。方案C外部50MHz有源晶振直接给PHY和MAC同时供时钟。这种方式最稳硬件排查时可以用来快速验证问题是否出在时钟上。缺点是增加BOM成本量产时不到万不得已一般不用。这里必须提一个经典误区有人直接把F407的PLLQ分频到48MHz给LAN8720当REF_CLK用因为48MHz和50MHz接近看起来协议栈也能初始化。但RMII对时钟精度有明确要求48MHz会导致数据错帧现象是偶尔能Ping通、马上又超时非常难排查。如果发现这种“时好时坏”的情况先检查REF_CLK是不是标准的50MHz用示波器或者频率计一量便知。2. CubeMX 6.4从零配置外设参数和PHY驱动的正确姿势2.1 工程初始化选项打开CubeMX 6.4选择具体型号我用的是STM32F407VGT6其他F407型号操作一致。进入RCC配置页HSE选项务必选Crystal/Ceramic Resonator否则以太网没有时钟源。SYS配置页把Debug选成Serial Wire否则下载程序后一旦断开调试器芯片会一直停在上电状态这个问题虽然和以太网无关但很容易让人误判。在Connectivity列表中找到ETH勾选RMII接口。如果这里选成MII后续引脚分配会乱掉生成的代码也不是RMII模式的。注意看一下引脚分配图确认PA1、PA2、PA7、PB11、PB12、PB13、PC1、PC4、PC5这些引脚都变成了绿色复用状态。如果某个引脚被其他外设占用CubeMX会提示冲突此时需要先处理资源共享。2.2 ETH外设参数详解在ETH配置页里有几个参数不能稀里糊涂跳过去。第一个是PHY Address。LAN8720A的默认PHY地址是0x00因为它的PHYAD0和PHYAD1引脚内部下拉模块上如果没做上拉地址就是0。CubeMX里默认值也恰好是0这个不用改。但如果你用的是其他PHY芯片比如DP83848默认地址可能是0x01必须按手册改。第二个是Mac Address。CubeMX默认填的可能是00:00:00:00:00:00这个地址在局域网里直接用会出问题。建议手动填一个以02开头的本地管理地址比如02:00:11:22:33:44这样量产多块板子时也不会冲突。第三个是PHY Clock。如果你决定走MCO方案在时钟配置页把MCO1时钟源选为PLLCLK分频系数按上一节推导配置。要是走LAN8720自身晶振方案这里就直接交给外部PHY提供CubeMX侧不需要额外配置。2.3 FreeRTOS和LwIP的联动配置Middleware这一层是CubeMX集成的核心价值所在。打开FREERTOS选项接口选CMSIS_V1即可CMSIS_V2在F407上也跑得通但很多现成例程基于V1移植起来少踩一个版本差异的坑。LwIP选Enabled此时CubeMX会自动把LwIP配置成“带操作系统”模式底层会生成一个tcpip_thread和对应的信号量、邮箱不需要你手动去移植。LwIP参数页里有几个关键项直接影响能不能Ping通参数推荐值说明MEM_SIZE20480协议栈内存池总量太小会导致分配失败PBUF_POOL_SIZE20收发包缓冲区数量PBUF_POOL_BUF_SIZE1518一个以太网帧的最大长度TCP_WND8192TCP接收窗口TCP_SND_BUF8192TCP发送缓冲TCPIP_THREAD_STACKSIZE2048tcpip_thread任务栈大小TCPIP_THREAD_PRIO4tcpip_thread优先级LWIP_DHCPDisabled直连电脑建议关闭用静态IP静态IP我用的是192.168.1.10网关192.168.1.1掩码255.255.255.0。如果你的局域网网段是192.168.0.x要自己对应调整。2.4 生成代码后必须手动处理的三件事CubeMX生成的代码并不能直接拿去编译至少有三件事需要手动检查。第一件事是PHY ID校验。CubeMX 6.4的PHY库里没有LAN8720对应的驱动选项很多人图省事会选LAN8742模板因为两者的寄存器体系高度相似。但LAN8742驱动在初始化时会读取PHY的ID寄存器和LAN8742的ID做比对LAN8720的ID和它不一致初始化会直接失败表现就是网络起不来。解决办法是把ethernetif.c里low_level_init函数对PHY ID的校验注释掉或者直接改成LAN8720对应的ID。更干净的做法是选择User PHY模板自己用HAL_ETH_ReadPHYRegister读写基本寄存器。第二件事是检查ETH中断是否使能。CubeMX生成NVIC配置时不一定把ETH_IRQn打开如果中断没开LwIP收包就完全依赖轮询大部分默认配置下会表现为收不到数据、Ping不通。去CMSIS配置页确认ETH全局中断勾选并且把优先级设成一个合理的值我一般放在6到8之间优先级太高会干扰FreeRTOS的临界区保护。第三件事是确认lwipopts.h里的LWIP_DHCP宏。如果你在CubeMX图形界面关掉了DHCP生成代码里也会同步关闭但有时候因为文本模板版本不一致生成出来的宏会残留DHCP开启状态而设备端又没有一个DHCP服务器导致板子一直无法获取IP。检查一下这个宏同时确认netif_config里的静态IP、网关、掩码是预期的值。3. Keil编译环境与代码生成的隐藏坑3.1 ARM编译器版本和C99开关CubeMX生成的工程拿到Keil MDK里第一轮编译大概率会刷屏报错。最经典的一个坑是ARM编译器版本不对。MDK 5.36及以上版本默认使用AC6编译器而STM32CubeF4固件包如果是较老版本HAL库的某些内联汇编和编译器特性在AC6下会报奇怪错误。我自己的习惯是先把编译器切回AC5虽然老但兼容性最稳。如果你特别想用AC6需要把固件包升级到和AC6兼容的新版本然后清理掉老工程的History信息。C99也必须打开。CubeMX生成的代码里大量使用for(int i 0; ...)这种C99语法Keil默认的C90模式下会直接编译失败。在Options for Target的C/C页里勾选C99或者直接在Misc Controls里加--c99。没有这个开关你会在前20个error里就卡死。3.2 MicroLIB和内存映射MicroLIB是MDK里一个经常被忽略的选项但对于LwIP这种依赖大量字符串处理和动态内存分配的协议栈来说至关重要。如果不勾选MicroLIBprintf和malloc相关的代码会链接到标准库的semihosting版本程序运行到格式化输出时容易直接卡死。勾选MicroLIB的路径是Options for Target里的Target页把Use MicroLIB勾上。内存映射这块F407的192KB SRAM对LwIP来说其实是够用的但要避免一个经典错误把大数组放进CCM RAM。F4系列有一块内核直连的CCM RAM不能给DMA访问。ETH DMA描述符和收发缓冲区必须放在普通SRAM中默认的链接脚本一般不会犯这个错但如果你自己写了分散加载文件要特别小心。启动文件里的Stack_Size和Heap_Size也建议加大Stack_Size给0x2000Heap_Size给0x1000。硬盘不够和F407的SRAM总量比虽然看起来占用多了但实际运行中能避免很多莫名其妙的HardFault。3.3 常见编译报错列表及解法报错信息原因解法identifier ETH_DMARxDesc_t is undefinedHAL库版本与CubeMX版本不匹配重新生成工程或升级固件包L6218E: Undefined symbol ...某些源文件未加入工程把LWIP/App、LWIP/Target下的.c全部加进工程unknown type name bool缺少stdbool头文件在lwipopts.h里包含stdbool.h#error Unsupported compilerLwIP检测到不认识的编译器检查是否用了AC5且定义正确HardFault after netconn_writeMicroLIB未勾选或栈不足勾选MicroLIB增大Stack_Size遇到编译链接错误时我的排查顺序是先确认所有LWIP源文件都被正确加入工程再看编译器版本和C99开关最后才去逐条看报错信息。多数时候前两个问题修完后面的报错会一次性消失一大半。4. Ping不通排查链路从物理层到协议栈逐段定位4.1 第一关物理层与PHY状态程序写完板子插上网线第一个动作不是去折腾代码而是确认物理链路。先看LAN8720模块上的Link指示灯是否点亮。如果灯不亮95%的可能是硬件问题剩下的5%是PHY初始化没完成。硬件检查顺序如下确认LAN8720的供电VDD 3.3V、VDDCR 1.2V是否正常。特别是nINT/REGOFF引脚这个脚如果被拉低芯片内部1.2V稳压器会被关闭芯片直接不工作。很多开发板模块上这个引脚默认拉高但如果自己画板子最容易漏的就是这个细节。确认复位电路NRST引脚有没有正确复位。用示波器或频率计量PA1引脚的REF_CLK必须是标准50MHz频率偏差在正负50ppm以内。看到48MHz、25MHz还是根本没有波形基本就能定位问题方向了。量LAN8720的X1/X2晶振两端如果是外接25MHz晶振方案这里应该有25MHz起振波形。硬件检查通过后把调试器接上去在main函数初始化完成后读一次PHY的寄存器。ST的HAL库提供了HAL_ETH_ReadPHYRegister读取PHY_BSR寄存器地址0x01时Bit2Link Status应该为1。再读PHY_IDR1和PHY_IDR2正常情况下LAN8720的ID是0x0007和0xA120能读出ID就说明MDIO/MDC管理接口工作正常LAN8720已经被正确枚举。4.2 第二关MAC和中断配置物理层正常、PHY寄存器也能读但就是Ping不通下一步检查MAC层和DMA路径。在调试器里给HAL_ETH_IRQHandler或者etharp_input打断点观察有没有数据包进到协议栈。如果没有中断触发优先怀疑ETH中断优先级和FreeRTOS的互斥问题。FreeRTOS要求中断优先级数值不能小于configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY也就是说不能在高于这个优先级的中断里调用带FromISR后缀的API。如果ETH中断优先级设置得太高信号量发不出去接收流程就会卡死。把ETH优先级放到6到8之间能避免这个问题。还要检查DMA收发描述符是否被正确初始化。在ethernetif.c的low_level_init里接收描述符链表如果没形成闭环DMA根本不会收包。正常情况下生成代码不用改但如果你手动改过描述符数量RX_DESC_CNT要确认数组大小和初始化循环次数一致。4.3 第三关IP地址与电脑端设置协议栈层面最常见的Ping不通原因其实是电脑和板子不在同一个网段。板子静态IP是192.168.1.10电脑网卡却是自动获取恰好在192.168.0.x网段那肯定不通。手动把电脑网卡IP改成192.168.1.100掩码255.255.255.0网关不填或填192.168.1.1再ping一次。Windows防火墙也经常参与搅局。关闭防火墙再测试能通再考虑加白名单。在电脑上执行arp -a命令如果能看到板子的IP和MAC对应关系说明ARP层面已经通了此时再Ping不通问题大概率出在ICMP或者更高层。用Wireshark抓包时观察有没有从板子MAC地址发出来的ARP应答。这个信息对判断问题层级非常关键。4.4 第四关协议栈配置与调试宏前三关都过了还不通就要开协议栈调试信息了。CubeMX生成的LwIP工程中lwipopts.h里默认关闭了大部分调试宏。把LWIP_DEBUG和LWIP_ICMP_DEBUG、LWIP_ARP_DEBUG打开重新编译下载串口会输出大量协议栈运行日志。我曾经遇到过一个非常隐蔽的问题MAC地址模块里配了一个全零地址ARP请求发出去没有应答Wireshark里能看到电脑疯狂发ARP但板子毫无响应。后来查出来是CubeMX生成代码时MAC地址没有写进netif结构体重新在MX_LWIP_Init里手动设置netif-hwaddr才解决。如果你改过MAC地址仍然无效优先怀疑这个字段是不是真的被写入了。最后检查一下MX_LWIP_Init函数是否在osKernelStart之前被调用。带FreeRTOS的工程必须保证LwIP协议栈先初始化再启动调度器顺序反了会导致tcpip_thread没有正常工作现象同样是Ping不通。5. FreeRTOSLWIP的任务级调优与TCP回显实战5.1 关键内存参数调整Ping通只是第一步真正向TCP收发迈进时内存参数就要认真调了。LwIP的内存分配方式有两种内存堆memp和内存池memp。收发包缓冲区走内存池TCP连接控制块走内存堆。如果MEM_SIZE设得只有1600一个TCP连接建起来后就再也没法创建第二个连接表现为第一次连接正常断开后重连失败。推荐参数组合如下参数初始值调优后MEM_SIZE160020480PBUF_POOL_SIZE820PBUF_POOL_BUF_SIZE15181518TCP_MSS15001460TCP_WND40968192TCP_SND_BUF40968192MEMP_NUM_TCP_SEG824调整参数时会发现LwIP对内存的需求和FreeRTOS的任务栈是竞争关系。F407的SRAM虽然大但也不能无限堆。如果开TCP多连接内存堆至少保证30KB左右如果只是UDP裸收发可以适当减小。5.2 tcpip_thread优先级和ETH中断优先级LwIP在带RTOS环境下所有网络操作都集中在tcpip_thread里。这个线程的优先级不能設得太高否则会抢占实时控制任务的CPU时间也不能设得太低否则网络吞吐量会明显下降。我在实际项目里一般放在一个中等偏上的位置比如比普通数据处理任务高一级但低于硬实时控制任务。ETH中断方面CubeMX里默认生成的优先级如果不合理务必手动改。经验值是优先级数值6到8这样既能保证网络中断及时响应又不会在FreeRTOS临界区里造成不可控的嵌套。如果发现接收丢包严重可以尝试关掉ETH中断改回轮询模式。轮询模式虽然效率低一些但稳定性高排错时也更容易定位问题。5.3 TCP回显服务器Demosocket APIPing通之后下一步值得做的是一个TCP回显服务器用标准socket API实现代码很短但能验证协议栈双向收发是否正常。先创建一个独立的FreeRTOS任务void tcp_echo_server(void *argument) { int sock_fd lwip_socket(AF_INET, SOCK_STREAM, 0); if (sock_fd 0) { vTaskDelete(NULL); } struct sockaddr_in local; local.sin_family AF_INET; local.sin_addr.s_addr INADDR_ANY; local.sin_port lwip_htons(8080); if (lwip_bind(sock_fd, (struct sockaddr *)local, sizeof(local)) ! 0) { lwip_close(sock_fd); vTaskDelete(NULL); } lwip_listen(sock_fd, 5); while (1) { int new_fd lwip_accept(sock_fd, NULL, NULL); if (new_fd 0) { vTaskDelay(10); continue; } char buf[1024]; int len lwip_recv(new_fd, buf, sizeof(buf), 0); if (len 0) { lwip_send(new_fd, buf, len, 0); } lwip_close(new_fd); } }然后在main函数里创建这个任务osThreadDef(TcpEchoServer, tcp_echo_server, osPriorityAboveNormal, 0, 1024); osThreadCreate(osThread(TcpEchoServer), NULL);电脑端用telnet或者nc工具测试。Windows上在命令行执行telnet 192.168.1.10 8080连上后随便输入一行字符板子原样发回来就说明整个链路完全打通了。Linux下用nc 192.168.1.10 8080也可以。这个Demo虽然简单却覆盖了socket创建、绑定、监听、接收、发送、关闭的完整流程后续改成HTTP服务器、MQTT客户端都只是在这个骨架上扩展。5.4 实测稳定性和性能心得TCP回显跑通后可以做一轮稳定性测试。电脑端持续ping 1000个包正常情况丢包率为0延迟稳定在1ms左右。再用脚本连续建立和断开TCP连接上百次观察内存是否泄漏。LwIP如果参数配置合理长时间运行不会出现内存耗尽。实际项目里我做过的性能测试中F407的MAC DMA在RMII百兆模式下可以跑到70Mbps以上的TCP吞吐量但对任务调度和中断延迟很敏感。如果同时跑着大量高优先级任务吞吐会明显下降这时候就要在实时性和网络性能之间做权衡。对大多数物联网场景来说LwIP处理远程配置、状态上报的流量绰绰有余。最后分享一个我踩过的比较深的坑一块板子用PB13做某个传感器片选同时又配了RMII的TXD1结果就是网络时通时断因为PB13的复用功能优先级覆盖了普通GPIO功能。CubeMX在引脚分配时其实会提示冲突但如果没仔细看确认表格很容易忽略。遇到网络时序不稳定的问题先把所有和RMII引脚有关的GPIO复用关系过一遍往往能省下半天排查时间。
返回列表