
做单片机开发只要一碰到需要联网、远程数据上报、OTA升级这类需求嵌入式以太网基本就是绕不开的选择。STM32F407这颗芯片内部集成了10/100M以太网MAC控制器外部再配一颗PHY芯片就能组成完整网口协议栈用lwIP轻量不占内存任务调度交给FreeRTOS。这套组合在国产开发板和工业控制板里出现得非常多也是我过去几个项目里反复在用的方案。这篇文章就从实际工程角度把通过CubeMX配置STM32F407、驱动LAN8720A、整合lwIP和FreeRTOS的完整过程捋一遍顺便把那些不翻手册根本发现不了的坑也一起说清楚。想给F407加网口、或者第一次玩lwIP的朋友按这套流程走下来基本能少走不少弯路。1. 整体架构与方案选型分析1.1 STM32F407以太网外设到底是个什么结构很多新手刚开始接触STM32以太网时会有一个误区以为芯片本身集成了完整的以太网功能外部只要接个网口座就能通。实际上STM32F407内部集成的只是以太网MAC层控制器包括DMA控制器、流控、MAC地址过滤等物理层信号编码、电平转换、线路驱动这些工作必须由外部PHY芯片来完成。STM32F407通过MII或RMII两种接口和外部PHY芯片通信再用SMI管理接口也就是MDIO/MDC这一对线去读写PHY的寄存器实现对PHY的控制和状态查询。这就引出了整套系统的分层逻辑最底层是PHY芯片负责把MAC发过来的并行数据变成差分信号送到网线上中间是STM32自带的MAC外设负责做以太网帧的发送接收、CRC校验、DMA搬运上层才是lwIP协议栈负责把IP包、TCP/UDP连接、ARP等协议逻辑跑起来而FreeRTOS则负责把所有这些逻辑放进独立的线程里让协议栈和用户应用互不阻塞。分层清晰的最大好处是调试好定位哪一层出问题就查哪一层不会出现牵一发动全身的窘境。1.2 为什么大家普遍选LAN8720A FreeRTOS这套组合市面上能配合STM32F407使用的PHY芯片不少DP83848、KSZ8081、LAN8720A都是经常见到的型号。但如果你去翻主流开发板的设计会发现F407板载PHY几乎清一色用LAN8720A。原因也很实在这颗芯片3.3V单电源供电内部自带了1.2V稳压输出外围电路极简不需要额外一组电源RMII接口信号线只有7根左右数据、时钟、控制占用的GPIO比MII少了快一半芯片价格低、货源稳定在淘宝上十几块就能买到带网络变压器的模块。更重要的是这颗PHY和ST官方评估板上的LAN8742A寄存器定义基本同源CubeMX生成的LAN8742驱动代码可以直接兼容省去了自己手写PHY驱动的工作量。FreeRTOS的选择更不用多说开源免费、CubeMX原生支持、教程资料遍地都是。lwIP则是嵌入式TCP/IP协议栈事实上的标准和FreeRTOS的适配层ST官方已经做好了。这仨组合在一起从硬件到软件都有现成代码可用对项目开发来说是最低成本的起步方式——你要做的不是发明轮子而是把这些轮子正确地装到车上。2. 硬件准备与关键电路细节2.1 LAN8720A核心外围电路与时钟方案LAN8720A最常用的时钟方案是25MHz无源晶振。芯片内部有PLL电路会把25MHz倍频到50MHz这个50MHz信号再通过芯片的REF_CLK输出引脚提供给STM32作为RMII参考时钟。需要注意的是如果你用的是那种裸芯片自己画板XI/XO两个引脚要接25MHz晶振晶振两个引脚对地各接一个12pF~22pF负载电容如果你买的是集成模块一般在板子上已经画好了直接供电就能用。还有一种方案是用外部50MHz有源晶振给LAN8720A提供时钟同时把这一路50MHz时钟也接到STM32的RMII_REF_CLK引脚上。两种方案各有利弊25MHz无源晶振成本低但需要确认LAN8720A的REF_CLK输出是否默认开启50MHz有源晶振可靠性高但成本和PCB面积都会增加。市面上主流的LAN8720A模块默认用25MHz晶振方案因此在CubeMX里配置时RMII_REF_CLK引脚PA1是作为输入使用的时钟来自PHY芯片回传。我还碰到过有人想省晶振直接用STM32的MCO引脚输出50MHz时钟给LAN8720A。这种方案理论可行但MCO输出能力有限驱动PHY的时钟输入时需要留意波形质量而且配置复杂新手不推荐一上来就搞。2.2 RMII接口引脚分配与注意事项RMII接口相对MII省引脚但信号时序要求更严格。一套典型的STM32F407 LAN8720A的RMII连接长这样具体引脚以你的开发板原理图为准这是最常见映射RMII信号STM32引脚方向REF_CLKPA1PHY → STM32CRS_DVPA7PHY → STM32TX_ENPB11STM32 → PHYTXD0PB12STM32 → PHYTXD1PB13STM32 → PHYRXD0PC4PHY → STM32RXD1PC5PHY → STM32MDIOPA2双向MDCPC1STM32 → PHY这里面有几个硬件设计上的坑值得单独提。首先MDIO是双向信号STM32这边要配置为推挽输出但MDIO链路还需要上拉电阻一般4.7kΩ拉高到3.3V。其次LAN8720A芯片的PHY地址由PHYAD0引脚的电平决定拉低地址是0x00拉高地址是0x01很多模块出厂默认是0x00但也有一部分做模块的厂家会把PHYAD0拉高导致地址变成0x01这在软件配置时要格外留意。另外LAN8720A的NRST复位引脚不要直接悬空最好由STM32的GPIO控制上电后拉低一段时间再拉高给PHY一个可靠复位否则很容易出现SMI接口读不到寄存器、link一直起不来的问题。复位引脚其实不挑IO哪个空闲用哪个但一定要在软件初始化PHY之前完成复位动作。3. CubeMX工程配置全流程实操3.1 时钟树配置48MHz和50MHz从哪来打开CubeMX新建工程选择STM32F407系列对应型号。RCC这里要把HSE设成Crystal/Ceramic Resonator因为以太网和系统时钟都依赖外部高速晶振提供基准。进入Clock Configuration页面目标是把主频配置到168MHzF407最高主频同时保证PLL48CLK是48MHz。这里有个隐藏的要求STM32F407的USB和以太网外设都依赖48MHz时钟如果PLL48CLK不对以太网的DMA可能会出现莫名其妙的问题甚至完全不通。我的建议是直接在CubeMX的时钟树图形界面里把HCLK那栏输入168并回车让CubeMX自动求解然后仔细看右边有没有出现PLL48CLK48MHz的提示。如果求解失败多半是PLL分频比设置问题手动调整下PLL_M、PLL_N、PLL_P、PLL_Q即可。RMII的50MHz参考时钟不经过STM32内部时钟树它由PHY芯片提供这点不需要在CubeMX里配置时钟源但你要理解整个链条LAN8720A的25MHz晶振经过内部PLL倍频到50MHz再送到PA1引脚给STM32的RMII接口做参考时钟。3.2 ETH外设参数与PHY地址配置在Pinout Configuration界面的Connectivity分类里找到ETH打开后把Mode选为RMII。此时CubeMX会自动把前面表格里那组RMII引脚分配到对应的GPIO上。如果你的开发板引脚与CubeMX自动分配的不一致可以直接在芯片引脚视图上手动点击并重新选择引脚然后右键设为ETH功能后生成代码时就会按你的设计生成。ETH外设本身可配置的参数不多最重要的是确认PHY Address。CubeMX在ETH Configuration里会有一个PHY Address选项有些版本在LWIP配置界面里也有。对LAN8720A来说绝大多数模块地址是0x00你填0即可少数模块是0x01如果后面PHY ID读不到第一个排查点就是这里。PHY Reset GPIO这项新版CubeMX可以直接选择一个复位引脚选中后生成代码会自动包含复位时序如果你用的版本没有这个选项也不慌初始化代码里自己控制GPIO拉低拉高就行。这里顺带说一句CubeMX在lwIP相关代码生成时会带上LAN8742A的PHY驱动代码lan8742.c和lan8742.h名字虽然是LAN8742但它和LAN8720A的寄存器体系基本一致可以直接沿用。你不需要去网上找个专门给LAN8720A写驱动ST官方的这套代码就能用只要PHY地址对上后面读写寄存器、检测link状态都会正常。3.3 FreeRTOS和lwIP中间件的正确打开方式在Middleware分类下先打开FreeRTOSInterface选CMSIS_V1或CMSIS_V2这个根据你的HAL库版本习惯来CMSIS_V2对应较新的封装功能上差别不大。再打开LWIPGeneral Settings里的OS Mode一定要选FreeRTOS否则生成出来的是裸机版本和FreeRTOS整合时还得自己改信号量、邮箱适配非常痛苦。lwIP参数这里有几个关键项。IP Address我习惯配192.168.1.20Netmask配255.255.255.0Gateway配192.168.1.1这样静态IP方便调试。如果打算用DHCP自动获取IP需要另外把DHCP选项打开并在代码里调用dhcp_start刚开始调试不建议直接上DHCP固定静态IP更容易定位网络问题。Memory Settings里的默认值大参数在F407上一般够用如果编译时报内存不足或者运行时报pbuf不足再调整PBUF_POOL_SIZE或MEMP_NUM_TCP_SEG这些选项。配置完全部中间件后记得在Project Manager里设置好工程名和IDE类型MDK-ARM或STM32CubeIDE然后Generate Code。生成完先编译一遍确保基础工程能过再往下写代码。4. 代码整合与网络功能验证4.1 生成代码结构分析每个文件是干什么的打开生成的工程你会发现多出了好几个和网络相关的文件。eth.c和eth.h是由HAL库封装的以太网MAC控制器初始化代码MX_ETH_Init函数就在里面负责配置MAC地址、DMA描述符、速度模式等。ethernetif.c是网卡底层收发接口lwIP的low_level_init、low_level_output、low_level_input都在这里实现相当于协议栈和硬件之间的桥梁。lan8742.c是PHY驱动提供LAN8742_Init、LAN8742_GetLinkState这类函数虽然名字是LAN8742实际工作就是通过SMI接口读写PHY寄存器。lwip.c里是MX_LWIP_Init和MX_LWIP_Process前者初始化协议栈后者在轮询模式下处理网络数据包接收。还有个容易忽略的点CubeMX在生成了FreeRTOS相关文件后会在main函数里调用MX_FREERTOS_Init来创建任务但MX_LWIP_Init默认不会自动执行需要你手动安排调用时机。根据我的实践最推荐的做法是在进入调度器之前调用MX_LWIP_Init也就是在main函数里MX_FREERTOS_Init之后、osKernelStart之前调用。这样lwIP的tcpip_thread会在FreeRTOS启动后就开始跑不需要额外写一个专门的初始化任务。4.2 在FreeRTOS里规划任务与lwIP收包机制到这一步你需要在freertos.c里创建两个基础任务。第一个任务是网络链路监测每隔500ms检查一次PHY的link状态打印网线是否连接、协商速率等信息第二个任务是TCP回环服务启动一个TCP server监听端口收到什么数据就原样返回方便验证网络通路。如果你的应用是HTTP Server或者MQTT客户端套路也是一样的无非是任务里的业务逻辑换成对应协议。lwIP在FreeRTOS模式下的收包机制是底层网卡在接收到数据帧后DMA把数据放到内存缓冲区然后协议栈的任务定期调用MX_LWIP_Process来处理。因此需要在某个FreeRTOS任务的主循环里循环调用MX_LWIP_Process不能像裸机那样放main的while里。我习惯单独开一个优先级略低的网络任务专门做这件事这样即使网络流量大也不会把高优先级的控制任务堵死。任务栈大小建议给到256 words以上lwIP协议栈处理数据包时会使用栈空间栈太小容易进HardFault。这里有个整合时的关键细节lwIP的tcpip_thread、ethernetif_input相关线程在FreeRTOS里运行而eth的HAL库中断回调可能会访问一些共享变量。如果遇到数据收发不稳定记得用临界区或信号量保护关键资源的访问不要想当然觉得DMA和协议栈各跑各的没事。4.3 编译下载与Ping通全程记录编译烧录后先用串口把调试信息打出来确认PHY芯片ID能读到。LAN8720A的PHY ID通常是0x0007C0F1能读到这个数说明SMI通路正常PHY地址没问题。如果读到0xFF或者0xFFFF先检查PHY地址和复位时序。把网线一头插到开发板的网口另一头插到电脑或路由器上这时串口应该会打印出link up的信息。接着给电脑的以太网卡配一个同网段的静态IP比如192.168.1.10然后ping 192.168.1.20。如果通了TTL值一般显示64说明整个以太网通路已经打通。如果这时候你发现ping的延迟偶尔很高或者时通时断先检查网线和交换机端口再考虑RMII信号线是不是过长、GPIO有没有配置成复用推挽等问题。TCP回环测试就更直接了电脑上用网络调试工具连接192.168.1.20的7号端口随便发一串字符串能原样收到就说明TCP协议栈跑通了。到这一步整个移植工作就算完成了后面你在这个框架上加HTTP、MQTT、TCP自定义协议都是在这个任务基础上填业务逻辑而已。5. 踩坑记录与问题排查大全5.1 常见问题速查与解决对照表这几年做F407以太网项目真正让我卡住的问题翻来覆去就是那几类。这里整理成一张速查表按现象排了一下优先级。故障现象可能原因解决办法PHY ID读不到0xFF/0xFFFFPHY地址配置不对尝试把PHY Address从0改为1或反过来PHY ID读不到0xFF/0xFFFF复位不完整确认复位GPIO拉低时间至少100μs后拉高PHY ID读不到0xFF/0xFFFFMDC/MDIO引脚电压异常检查MDIO上拉电阻、接线正确性网线插了但link一直downREF_CLK没起来示波器测PA1有无50MHz时钟网线插了但link一直downPHY地址不对导致自协商状态读不到改PHY地址重新初始化ping通但延迟巨大或丢包网线质量问题或距离过长换一根标准超五类及以上网线测试ping通但延迟巨大或丢包RMII信号线走线过长干扰大硬件上缩短走线软件锁定10M全双工编译通过但运行进入HardFaultFreeRTOS任务栈过小网络相关任务栈加大到512 wordsDHCP获取不到IP静态IP没关确认lwIP里IP地址已设为0.0.0.0DHCP获取不到IPMDIO不正常导致link状态判断失败回退到静态IP优先定位硬件通路裸机版本正常FreeRTOS版本不正常OS Mode没有选FreeRTOS重新在LWIP General Settings里设置OS Mode5.2 进阶避坑经验与调试建议第一次调PHY时不要上来就怀疑lwIP配置先把裸PHY的寄存器层打通。比如在初始化代码里读一下寄存器1BMSR如果Bit5是1说明link已经协商上了这时候ping不通就要查MAC侧如果Bit5是0说明物理层都没通你折腾协议栈都是白费功夫。我习惯在串口初始化后、lwIP启动前加一段测试代码把PHY ID、BMSR寄存器的值打印出来这对判断故障域非常有效。另外LAN8720A模块从淘宝买来不同批次甚至不同店铺的PHY地址都可能不同千万别想当然我踩过最疼的一次就是换了个模块后整块板子“网络消失”折腾了两天才发现新的模块把PHYAD0拉高了地址从0变成了1。所以固定一个初始化的检查逻辑PHY ID不对时自动尝试另一个地址能省不少调试时间。关于时钟还有个容易犯迷糊的地方有些F407核心板或以太网模块上用的是50MHz有源晶振PHY的参考时钟由晶振直接供给不需要从PA1再回传。这种情况下CubeMX只管RMII配置PA1作为REF_CLK输入就必须和外部时钟连接不能空着。说实话这两种方案我都在项目里见过你手上的板子具体是哪种最好看一眼原理图再决定怎么接线不然很容易出现“明明外部有晶振但PA1没接信号STM32根本没拿到参考时钟”的困境。5.3 从Link Up到稳定通信的调试节奏如果你已经读到PHY ID链路也已经link up但上层ping还是不通这时候建议按顺序排查MAC地址配置、DMA描述符、lwIP内存池。MAC地址在eth.c里有个静态数组一般默认是ST的地址网络通信其实不太在意MAC是否唯一但如果撞到了网络里其他设备的MAC就可能产生地址冲突。DMA描述符这块CubeMX生成的代码是没问题的但我见过有人动了编译器优化等级或者修改了内存段布局导致描述符没有对齐问题表现就是接收中断偶尔触发、数据包丢失严重。调试节奏上我个人喜欢分三步走。第一步只验证PHY读寄存器能把ID和link标志读出来物理层就算过了。第二步验证裸MAC通没通把网卡回环或直接用lwIP的回环测试接口做loopback。第三步才把FreeRTOS任务调度和业务逻辑加进来。这样做的好处是每次引入的变量少出了问题能在最小范围内定位而不是打开调试器看到一堆不知所错的寄存器。顺带分享一个小技巧在FreeRTOS lwIP这种多优先级的代码里用串口打印调试信息时别直接在中断上下文里做否则实时性会很难看。我是专门用一个调试任务其他任务用队列把调试字符串丢进去由调试任务统一打印这样既不阻塞主流程又能保证串口输出不会互相抢占。做完整套LAN8720A驱动与FreeRTOS整合后我的体会是这套方案真正的难点不在“把代码跑通”而在“把底层的信号、时钟、PHY地址这些物理层细节抠明白”。前面可能因为一个地址不对折腾一整天但当你把这些链路全部摸熟之后后面再在这个基础上做HTTP服务器、MQTT上云、固件升级都是很顺理成章的事。最后再把这套经验的边界补一句不同厂家的PHY芯片寄存器体系会有差异比如DP83848和LAN8720A的关键寄存器地址就不完全一致换芯片时不能直接套驱动但这个排查思路是通用的。