ARTICLE DETAIL

资讯详情

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

STM32F107+LAN8720A以太网实战:从CubeMX配置到LwIP稳定传输

STM32F107+LAN8720A以太网实战:从CubeMX配置到LwIP稳定传输 1. 芯片选型与硬件设计为什么是STM32F107搭配LAN8720A提到STM32F107这颗芯片很多人的第一反应是“带以太网MAC的STM32”。确实在F1系列里它算特殊的一个——内部集成了10/100M以太网MAC控制器但PHY物理层收发器必须外挂。这个“MAC 外置PHY”的架构决定了选型思路MAC是芯片自带的PHY则要自己挑。LAN8720A是市场上性价比极高的一款10/100M以太网PHY芯片支持RMII接口引脚少、外围电路简单非常适合STM32F107这种资源不算阔绰的MCU。1.1 MAC与PHY的分工逻辑先梳理一下硬件层面的大框架。STM32F107内部集成的以太网MAC负责数据链路层的逻辑比如帧的封装、解封装、地址过滤、CRC校验等。而LAN8720A作为PHY负责物理层的信号转换把MAC传过来的数字信号转成差分模拟信号放到网线上反过来把网线上的模拟信号解码成数字信号交给MAC。这两者的接口标准有MII和RMII两种。MII需要16根数据线时钟频率25MHzRMII只需要7根线时钟频率50MHz。LAN8720A支持RMII模式而且它的REF_CLK可以由外部提供50MHz时钟也可以由自身产生。这一点非常关键直接决定了你的硬件电路怎么设计——如果外部提供50MHz时钟那MCU和PHY的时钟必须严格同步否则数据采样就会出错如果由LAN8720A产生则需要在XTAL1和XTAL2引脚接25MHz晶振芯片内部通过PLL倍频到50MHz。我实测下来STM32F107 LAN8720A最稳的做法是外部有源晶振提供50MHz时钟REF_CLK给LAN8720A的XTAL1/CLKIN引脚同时LAN8720A的REF_CLK引脚输出50MHz同步时钟给STM32F107的ETH_RMII_REF_CLKPA1引脚。这种方案的好处是时钟链路干净不依赖PHY内部PLL的精度尤其在高低温环境下更稳定。1.2 LAN8720A外围电路的关键细节LAN8720A的参考电路在网上能找到很多版本但真踩过坑的人会告诉你有几个地方特别容易出错。首先是PHY地址配置。LAN8720A的PHY地址由RXERPHYAD0引脚的电平决定上拉为1下拉为0。默认情况下LAN8720A的PHY地址是0如果你把RXER接了下拉电阻那地址就是0接上拉就是1。STM32的ETH驱动里有个PHY地址参数比如ETH_PHY_ADDR必须和硬件实际配置一致。很多人的板子能通但ping不通查了半天发现是PHY地址写错了这种低级错误在调试时最耗时间。其次是LED驱动引脚。LAN8720A的nLED1LED1和nLED2LED2引脚一般直接驱动两个LED指示灯一个表示Link状态一个表示收发活动。电路上记得串接限流电阻阻值选330Ω到1kΩ之间都不算错看你的LED亮度需求。然后是变压器与网络接口。LAN8720A的差分信号线TXP、TXN、RXP、RXN要通过网络变压器比如HR911105A这类带变压器的RJ45座连接到网线。这里有个常见误区有的人图省事直接用网络变压器而不用带屏蔽的RJ45座结果EMI测试过不了。另外变压器的中心抽头接法也很讲究——如果你的RJ45座自带变压器且没有内部端接电阻一般需要在中心抽头处接2kΩ电阻到地。1.3 电源设计的坑LAN8720A的供电有三种VDDCR核心逻辑、VDDIOI/O、VDD33模拟。其中VDDCR需要1.2V这个电压通常由芯片内部的LDO从3.3V降压得到但你必须在VDDCR引脚处加一个1μF的退耦电容而且电容要尽量靠近引脚。如果没有这个电容芯片可能直接不工作或者工作不稳定。还有一个特别容易被忽略的引脚VDD2A和VDD2BPHY的模拟电源这两个引脚必须接3.3V而且退耦电容要选得讲究。我习惯在VDD2A/VDD2B上放一个10μF钽电容加一个0.1μF陶瓷电容并联这样电源纹波能控制在比较理想的范围。如果电源纹波大最常见的现象是网络能协商成功但数据传输偶尔出错CPU利用率还查不出来非常迷。2. CubeMX工程配置从时钟树到RMII引脚映射STM32F107的以太网配置在CubeMX里看着不复杂但细节相当多。很多人在这一步就埋下了隐患——时钟配置不对、RMII引脚复用错误、DMA没开后面程序跑起来各种莫名其妙的问题。2.1 时钟树以太网PLL的硬性要求STM32F107的系统时钟最高72MHz以太网模块需要两个时钟一个用于MAC控制器的AHB时钟HCLK另一个是RMII接口需要的外部50MHz REF_CLK。CubeMX里配置时钟树时要确保PLLCLKPLL的48MHz输出被正确使能并分配给以太网模块。具体来说STM32F107的以太网MAC使用PLLCLK作为时钟源这个PLLCLK必须精确等于48MHz。如果你在CubeMX的Clock Configuration界面里看到PLLCLK的值不是48MHz那以太网模块就无法正常工作。实现方式是通过PLL倍频到72MHz系统时钟那一路的同时PLL还有一个专门的48MHz输出。我见过有人把System Clock调成64MHz为了省电或者配合其他外设结果以太网完全不通。原因很简单PLLCLK不是48MHz。所以在配置时钟树时可以把其他外设的时钟需求放一放先把PLLCLK锁定在48MHz这个约束上。2.2 RMII引脚映射与复用设置启用ETH外设后CubeMX会自动弹出引脚配置界面默认是MII接口。你需要手动切到RMII模式然后确认以下引脚是否被正确分配信号STM32F107引脚说明ETH_RMII_REF_CLKPA150MHz参考时钟输入由LAN8720A提供ETH_RMII_CRS_DVPA7载波侦听/数据有效ETH_RMII_RXD0PC4接收数据位0ETH_RMII_RXD1PC5接收数据位1ETH_RMII_TX_ENPB11发送使能ETH_RMII_TXD0PB12发送数据位0ETH_RMII_TXD1PB13发送数据位1ETH_MDCPC1管理接口时钟ETH_MDIOPA2管理接口数据有一个非常关键的点PA1RMII_REF_CLK的复用功能选择是AF11不是默认的GPIO。很多人在CubeMX里看到PA1已经自动变成ETH_RMII_REF_CLK就以为万事大吉了实际上要点击该引脚确认AF是否为AF11。如果AF设错了即使时钟和PHY都正确MAC也采不到数据。还有一个引脚容易遗漏PA8ETH_RMII_CLK输出模式。如果你选择让STM32F107自己输出50MHz时钟给LAN8720A即PHY的REF_CLK由MCU提供那PA8就是时钟输出引脚。但这个方案不推荐——STM32F107的MCO引脚输出50MHz方波抖动和驱动能力都不如有源晶振方案稳定。2.3 DMA与中断配置吞吐量的关键以太网收发数据不经过DMA的话CPU中断会被高频触发内存拷贝和协议栈处理效率极低。CubeMX里ETH外设有专门的DMA设置选项需要使能ETH_TX发送和ETH_RX接收两个DMA通道。这里推荐把DMA的中断优先级设置成高优先级比如Very High因为以太网的实时性要求比普通串口高得多。另外DMA的模式必须设置为循环模式Circular否则收发几次之后DMA就停了表现为ping通几次后彻底不通重启才能恢复。硬件流控和描述符数量在CubeMX里也可以配。我一般把RX描述符数量设为4TX描述符数量设为4在STM32F107这种RAM只有64KB的芯片上这个数量已经能平衡性能和内存占用。如果你内存充裕把RX描述符加到8个在突发大流量场景下丢包率会明显下降。2.4 LwIP的堆栈与RTOS选择CubeMX里启用LwIP有两种方式带RTOS和不带RTOS。STM32F107的资源跑LwIP裸机也能跑但如果你同时有多个外设任务需要处理建议上FreeRTOS。CubeMX可以同时勾选FreeRTOS和LwIP它会自动生成互斥锁和信号量的对接代码省去很多手工移植的麻烦。LwIP的配置里有几个关键参数内存池大小MEM_SIZELwIP内部堆的大小至少设到16KB否则TCP传输时内存不够用。PBUF池大小PBUF_POOL_SIZE网络缓冲池的数量默认10个偏少建议设为16个以上。TCP窗口大小TCP_WND接收窗口STM32F107上建议设成8KB或12KB太大容易内存不足。TCP_SND_BUF发送缓冲区建议8KB起步。这些参数在CubeMX的中间件配置界面里都能直接改。我见过有人直接用默认参数跑TCP下载速率只有几十KB/s后来发现是TCP_WND太小接收窗口只有2KB带宽被白白浪费了。3. 与PHY握手LAN8720A的寄存器与驱动匹配MAC和PHY之间通过MDIO/MDC接口进行管理。CubeMX生成的ETH驱动里有个关键环节是等待PHY的自动协商完成。不同的PHY芯片自动协商完成后的状态寄存器位不同这就导致CubeMX默认生成的驱动不一定能直接匹配LAN8720A。3.1 关键寄存器BCR、BSR和PHY IDLAN8720A的寄存器遵循IEEE 802.3标准最常用的是寄存器0BCR控制寄存器bit15是软复位bit12是自动协商使能bit8是全双工/半双工bit13是速度选择。寄存器1BSR状态寄存器bit5是自动协商完成标志bit2是链接状态1为已连接。寄存器2和3PHY ID寄存器LAN8720A的PHY ID是0x0007具体拆分为寄存器20x0000寄存器30x7C00或者不同版本有细微差异。硬件调试时读一下这两寄存器能快速确认MDIO通信是否正常。CubeMX生成的HAL库函数HAL_ETH_ReadPHYRegister和HAL_ETH_WritePHYRegister可以直接操作这些寄存器。如果读出来的数据是全0xFFFF或者全0x0000说明MDIO没通优先检查PHY地址和MDC/MDIO引脚。3.2 修改PHY地址与协商超时STM32F107的标准HAL库驱动里PHY地址默认是0。如果你的板子上LAN8720A的PHYAD0上拉到1那必须把eth.c里的ETH_PHY_ADDR宏改成1否则驱动在初始化时会一直读不到正确的PHY ID直接卡死在等待协商完成的循环里。等待协商完成的超时时间也值得关注。默认超时是PHY_NEGOTIATION_TIMEOUT通常设成4秒即PHY_NEGOTIATION_TIMEOUT 0x10000。如果网线没插或者对端设备没通电超过这个时间驱动会返回超时。这在产品里是个重要行为——如果在没有网线的环境下启动设备最好把超时时间缩短或者直接在应用层忽略这个错误让系统先跑起来等插入网线后再动态检测链接状态。3.3 手动读取PHY状态的调试方法在调试网络不通信的问题时我强烈建议先写一段简单的测试代码循环读取PHY的BCR和BSR寄存器并打印。这一步能快速区分问题是在MAC侧还是PHY侧。uint16_t bcr, bsr; HAL_ETH_ReadPHYRegister(heth, 0, bcr); HAL_ETH_ReadPHYRegister(heth, 1, bsr); printf(BCR0x%04X BSR0x%04X\r\n, bcr, bsr);如果BSR的bit2为1说明链路已经建立网线和对端设备都没问题。如果bit2为0检查网线和对端设备。如果bit5为0说明自动协商还没完成。这样一步步缩小排查范围比盲目调代码高效得多。4. LwIP移植后必须做的事初始化顺序、收发路径与常见坑CubeMX生成的LwIP代码基本能跑通ping但要从“能ping通”到“稳定收发数据”中间还有几个关键的坎这些都是实战中总结出来的。4.1 初始化顺序先PHY后协议栈STM32F107上电后以太网的初始化顺序必须严格遵循先初始化ETH外设包括PHY的复位和协商再初始化LwIP协议栈最后开启中断和DMA。如果顺序反了LwIP的底层接口low_level_init可能在PHY还没有就绪时就注册了网卡接口导致后续数据收发异常。症状通常是ping第一次有回包第二次开始丢包或者干脆不通。CubeMX生成的代码里MX_ETH_Init()和MX_LWIP_Init()已经按正确顺序排好了但你如果自己加代码一定要注意别在ETH初始化之前就去调用netif_set_up之类的函数。4.2 收发路径的性能陷阱LwIP的裸机收发是轮询中断混合模式。CubeMX默认生成的中断服务函数会调用HAL_ETH_IRQHandler后者会触发LwIP的接收回调。这个链路里最容忽略的是接收描述符的释放时机。在HAL库的以太网驱动中接收完一帧数据后必须调用HAL_ETH_ReadData释放描述符否则RX描述符会被耗尽设备表现为能发不能收。LwIP的low_level_input函数里已经有这部分逻辑但如果你在应用层自定义了接收处理千万别漏掉。还有一个性能瓶颈是内存拷贝。LwIP的PBUF分为RAM类型和ROM类型CubeMX默认使用PBUF_RAM数据到达后直接从DMA缓冲区拷贝到PBUF。在STM32F107上这个拷贝的开销不算大但如果你用的PHY支持描述符回写Ring模式可以尝试锁定DMA缓冲区直接让LwIP引用减少一次拷贝。不过这样实现复杂度会高不少建议先把标准方案跑通再考虑优化。4.3 常见现象能ping通但TCP传输卡死这是STM32F107 LwIP最经典的问题。现象是ICMP的ping一切正常但TCP连接建立后发送数据到一定量就卡死或者传输速度极慢。排查方向有两个第一检查TCP窗口和内存池是否够大。如果TCP_WND设置得过小接收窗口满了之后对端会停止发送表现就是卡死。解决办法是把TCP_WND调大同时确保MEM_SIZE够LwIP堆使用。第二检查TX描述符是否被占满。LwIP发送数据时如果TX描述符没有被及时释放驱动会一直等待空闲描述符造成发送阻塞。在CubeMX生成的low_level_output函数里发送完成后会调用HAL_ETH_TransmitFrame但HAL库的发送是同步的——如果描述符没释放函数不会立即返回。所以TX描述符数量不能太少建议至少4个。另外TCP_NODELAY也是一个影响因素。如果开了Nagle算法小数据包会被积攒到一定量才发送某些交互场景下感觉像卡死。在LwIP的tcp_connect回调里设置tcp_nagle_disable(pcb)可以关闭Nagle交互性好很多。4.4 断网重连与看门狗联动实际产品在网络环境不稳定的情况下运行断网重连是个躲不开的问题。LwIP的自动协商是PHY层面的但TCP连接一旦断开协议栈里的连接状态需要应用层自己维护。我的做法是开一个定时任务每隔2秒读取一次PHY的BSR寄存器。如果链接状态从1变成0就调用netif_set_link_down通知协议栈如果从0变成1调用netif_set_link_up。同时把TCP服务器或客户端的重连逻辑挂在这个检测机制上——链路恢复后自动重新建立连接。如果系统里还挂了看门狗要注意以太网初始化时的协商等待时间。在网线未插的情况下协商等待超时会拖长初始化流程可能触发看门狗复位。解决方案是先初始化系统其他部分再初始化以太网或者把超时时间缩短。5. 实战案例CubeMX生成工程到ping通全流程为了让你能直接参照操作这里把从零开始的完整流程串一遍每一步都配实际参数适合第一次接触STM32F107 LAN8720A的读者跟做。5.1 CubeMX工程创建与配置清单打开CubeMX选择芯片STM32F107RCT6如果你用的是其他封装引脚有所不同但逻辑相同。时钟树配置HSE选择外部晶振数值填25MHz你的板上晶振频率是多少就填多少系统时钟SYSCLK设为72MHz确保PLLCLK48MHz的数值精确为48MHzETH外设配置模式选择RMIIPHY地址填0对应你的硬件实际情况使能DMA发送和接收LwIP配置开启LwIP选择裸机或FreeRTOSIP地址填192.168.1.10子网掩码255.255.255.0网关192.168.1.1可据你实际网络调整TCP窗口设8KBPBUF池数量设16生成工程后主要修改的文件是ethernetif.c和lwip.c或CubeMX生成的其他LwIP相关文件。最需要改的地方是low_level_init里关于PHY的初始化逻辑——使用HAL库的HAL_ETH_Init会自动进行PHY复位和协商。5.2 接线确认清单动手焊接或连接开发板之前先确认这些信号线STM32F107LAN8720APA1 (REF_CLK)REF_CLK (50MHz输入)PA2 (MDIO)MDIOPC1 (MDC)MDCPA7 (CRS_DV)CRS_DVPC4 (RXD0)RXD0PC5 (RXD1)RXD1PB11 (TX_EN)TX_ENPB12 (TXD0)TXD0PB13 (TXD1)TXD13.3VVDD33, VDDIO, VDD2A, VDD2B注意LAN8720A是3.3V逻辑电平不能接5V否则芯片烧毁。MDC和MDIO是开漏输出需要外部上拉电阻一般4.7kΩ到10kΩ。5.3 编译烧录后的验证步骤烧录后第一步先看LAN8720A的两个LED是否亮起。Link灯亮说明PHY已经和对端协商成功如果Link灯都不亮优先排查网线、变压器和对端设备别急着看代码。然后打开串口调试助手查看打印的初始化日志。正常的初始化流程应该能看到ETH init OK PHY link up LwIP init OK IP address: 192.168.1.10如果卡在某个阶段用之前提到的方法读PHY寄存器定位。最后用PC去ping192.168.1.10。丢包率应为0平均延迟根据网络环境在1ms左右。如果ping通了再尝试用TCP调试助手建立连接发送数据验证双向通信。5.4 一个完整的PHY状态监控测试代码最后分享一段独立的PHY状态监控代码在调试和排查网络问题时非常实用。它每隔2秒读一次PHY寄存器并通过串口打印状态变化。void PHY_Link_Monitor_Task(void *argument) { uint16_t bsr; uint8_t last_link_status 0xFF; for (;;) { HAL_ETH_ReadPHYRegister(heth, 0x01, bsr); uint8_t link_status (bsr 0x0004) ? 1 : 0; if (link_status ! last_link_status) { if (link_status) { printf([PHY] Link UP\r\n); netif_set_link_up(gnetif); } else { printf([PHY] Link DOWN\r\n); netif_set_link_down(gnetif); } last_link_status link_status; } osDelay(2000); } }把这任务挂到FreeRTOS里网络状态变化会实时反映到串口和协议栈对排查“网线拔了但应用层不知道”的问题特别有帮助。6. 避坑锦囊STM32F107 LAN8720A工程排错实战记录这个章节里我把过去项目中真正踩过的坑集中整理出来按从硬件到软件的逻辑排开每个坑都有具体的现象分析和解决路径。6.1 坑一LAN8720A的RESET引脚常见错误现象烧录程序后PHY寄存器读出来全是0xFFFFLink灯不亮。原因排查检查复位电路后发现LAN8720A的NRST引脚一直处于低电平PHY芯片处于持续复位状态。原因是我在CubeMX配置ETH时忘了使能RESET引脚的输出GPIO初始化为默认的低电平。解决方案在CubeMX里把LAN8720A的NRST连接的GPIO我用的是PB14配置为输出高电平这样PHY上电后正常进入工作状态。如果需要软件复位可以先把PB14拉低至少1ms再拉高然后等待PHY内部初始化完成。6.2 坑二MDC/MDIO无响应读寄存器全0现象用调试函数读PHY寄存器返回的全是0x0000。原因排查MDC和MDIO是开漏信号需要外接上拉电阻。我的板上没有给MDIO接上拉导致读操作时数据线拉不上去读回来全是0。解决方案在MDIO上加一个10kΩ上拉电阻到3.3VMDC上如果也需要可以加一个。重新上电后寄存器能正常读回。6.3 坑三RMII时钟抖动导致随机丢包现象ping通后延迟正常但传输大文件时随机丢包偶发重传。原因排查示波器看PA1REF_CLK的波形发现上升沿不够陡有时还有毛刺。原因是PCB上时钟线走线过长且没有串接终端电阻。解决方案在LAN8720A的REF_CLK引脚处串接一个22Ω的电阻同时优化时钟走线尽量靠近PHY。如果有条件使用有源晶振直接驱动REF_CLK不要依赖PHY内部PLL问题基本消失。6.4 坑四LwIP初始化死循环现象程序卡死在MX_LWIP_Init里看门狗不断复位。原因排查low_level_init里会等待PHY自动协商完成如果在无网线环境下运行超时时间内PHY一直未协商成功HAL库返回超时后LwIP初始化代码里没有对错误做处理导致进入死循环。解决方案在MX_LWIP_Init之前先判断PHY链接状态。如果链接未建立可以在应用层标记为“网络未就绪”直接跳过LwIP初始化或延迟初始化。我的做法是链接建立后再调用MX_LWIP_Init这样既节省启动时间也避免崩溃。6.5 坑五TCP客户端无法重连现象服务器重启后TCP客户端一直在重连但始终连不上。原因排查LwIP的TCP连接有TIME_WAIT状态服务器重启后端口状态未完全释放客户端的SYN没人响应。而本地PCB在重连时没有重新绑定本地端口数据包可能发不出去。解决方案在TCP重连逻辑里先调用tcp_abort(pcb)终止旧连接再新建PCB发起连接。另外可以开启SO_REUSEADDR选项允许端口复用。err_t tcp_connect_with_reuse(struct tcp_pcb *pcb, const ip_addr_t *ipaddr, u16_t port, tcp_connected_fn connected) { ip_set_option(pcb, SOF_REUSEADDR); return tcp_connect(pcb, ipaddr, port, connected); }7. 从能跑到好用性能调优与稳定性加固思路很多人的项目卡在“能ping通”阶段就收工了但从工程角度看能跑通只是第一步后面怎么保证7x24小时稳定运行、怎么提升吞吐量这才是真正考验功力的地方。7.1 提升TCP吞吐量的三个手段STM32F107的CPU主频只有72MHz以太网理论带宽100Mbps实际上TCP吞吐能跑到40-60Mbps已经不错。想进一步提升可以从三个方向入手增大缓冲池PBUF池数量和TCP窗口大小直接影响吞吐。我测试过PBUF池从10加到20TCP窗口从4KB加到12KB吞吐大约提升30%。减少拷贝DMA缓冲区直接映射为LwIP的PBUF可以减少一次内存拷贝CPU占用能降低20%。关掉校验和计算如果你的网络环境足够可靠可以在LwIP配置里关闭TCP校验和计算CHECKSUM_CHECK_TCP设为0能减少不少CPU开销。但要注意关了就完全没有数据完整性校验了只适合对可靠性要求不高的场景。7.2 链路稳定性的监测机制在长期运行的设备上建议开一个心跳任务周期性发送特定报文同时监控接收队列的长度。如果接收队列长期不为空且持续增长说明应用层处理不过来了需要考虑加强应用层并发处理能力。链接状态的动态监控前面已经讲过这里再补充一个细节如果你的PHY支持中断LAN8720A的nINT引脚需要额外配置可以通过中断方式感知链接变化响应更快功耗也更低。但STM32F107的EXTI引脚资源有限建议还是用轮询方式——2秒间隔的轮询对现代MCU来说开销几乎可以忽略。7.3 内存碎片的规避策略LwIP的内存管理使用堆分配长时间运行后难免产生内存碎片。我的经验是在初始化时把PBUF池和内存池的大小设得足够充裕尽量避免运行时频繁申请和释放大块内存。另外周期性检查堆的剩余空间如果剩余空间持续下降就要考虑重启任务或者重新初始化协议栈了。在STM32F107这种RAM有限的芯片上基于PBUF池的内存管理比动态堆分配稳定得多。CubeMX里PBUF_POOL_SIZE相关的配置就是干这个的尽量用池化管理不要频繁使用mem_malloc。
返回列表