ARTICLE DETAIL

资讯详情

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

LAN8720与STM32F7以太网驱动:从RMII到LwIP的全面解析

LAN8720与STM32F7以太网驱动:从RMII到LwIP的全面解析 简介本资源是一套面向嵌入式开发工程师与STM32进阶学习者的LAN8720以太网PHY芯片驱动工程专为STM32F7系列Cortex-M7内核设计解决百兆以太网在高性能MCU上的底层通信集成难题适用于工业控制、物联网网关等需稳定网络接入的实战场景。压缩包共627个文件涵盖201个头文件.h定义寄存器、结构体与API接口、175个源文件.c含HAL库适配、RMII接口配置、DMA收发管理、MIIM寄存器读写及中断服务例程、68个目标文件.o与调试相关文件.dbgconf、.map、.axf等整体大小25.96MB目录结构完整包含FSMC/ETH外设初始化、LwIP协议栈对接模块及典型网络测试用例。目前已有922人学习下载代码已通过实际硬件验证提供可直接移植的初始化流程、链路状态轮询机制、收发缓冲区管理策略及常见异常如PHY未连接、DMA溢出的排错提示是理解STM32F7以太网底层驱动架构的优质参考工程。1. 项目概述与驱动设计思路最近处理了一个很典型的工程包名字叫“LAN8720网口驱动STM32F7系列单片机驱动程序”。这类驱动包在嵌入式项目里出现频率极高因为LAN8720这颗百兆PHY芯片太常见了STM32F7系列又自带MAC控制器俩一搭配就是标准的RMII以太网方案。这个包解决的问题很直接让你手上的STM32F7比如F746、F767快速跑通网口能ping通、能收发UDP/TCP数据不用自己从零抠PHY寄存器。先说清楚硬件分工STM32F7内部集成的是MAC层媒体访问控制层负责数据帧的封装、拆封、CRC校验、DMA传输这些活儿而LAN8720是PHY芯片物理层负责把MAC给它的并行数据转成差分信号怼到网线上同时处理链路协商、速率匹配、MDI/MDIX自动翻转这些模拟和物理层面的东西。也就是说MCU负责“想”PHY负责“说”。没有PHYMAC就是个哑巴。1.1 核心需求解析拆解这个驱动包本质上是三件事的集合第一RMII接口的引脚初始化。STM32F7的ETH外设支持MII和RMII两种模式RMII只用了7根信号线REF_CLK、CRS_DV、RXD[1:0]、TXD[1:0]、TX_EN、MDC、MDIO比MII的16根线省了一半还多非常适合引脚紧张的板子。驱动包里已经把这些引脚的GPIO复用模式、速度等级都配好了。第二PHY芯片LAN8720的寄存器初始化。PHY上电后不会自动进入理想工作状态需要通过MDIO总线读写它的寄存器完成软复位、速度/双工模式配置、LED配置、中断使能等操作。这一步跑通了物理链路才算真正“活”了。第三与LwIP协议栈的对接。光有MAC和PHY只能发裸帧要ping通、要跑TCP必须把底层驱动接到LwIP上。驱动包里的低层接口ethernetif.c这类文件负责把LwIP的PBUF和STM32的DMA描述符接起来。对谁有用我觉得这东西适合三种人看一是做物联网网关、数据采集器的工程师需要快速给板子加上网口二是学生做毕设想用STM32F7跑以太网通信三是想搞懂RMII底层机制、想自己手写PHY驱动的爱好者。驱动包是现成的但如果你不知道它每一步在干什么出了问题基本两眼一抹黑所以我这篇会把它拆开来讲。1.2 驱动文件构成与整体架构一个规范的可用的驱动包文件架构大致是这样几块我列个表说明各自的职责文件/模块职责eth.c / stm32f7xx_hal_eth.c外设寄存器级的MAC初始化、DMA描述符配置、收发中断处理lan8720.c / phy.cPHY寄存器读写封装、复位、自动协商、状态查询ethernetif.cLwIP底层接口初始化、输出、接收、轮询状态负责PBUF和DMA描述符的数据搬运lwipopts.hLwIP配置裁剪内存池大小、超时时间、协议开关stm32f7xx_hal_conf.hHAL库功能开关确认ETH模块已开启sys_arch.c / sys_arch.h操作系统适配层如果跑FreeRTOS或空实现裸机轮询典型的数据流向是这样的网线进来→LAN8720把差分信号转成4位RMII数据RXD[1:0]CRS_DV→STM32的ETH外设接收FIFO→DMA描述符搬运到内存→HAL以太网驱动丢给LwIP的接收线程或裸机轮询→应用程序拿到数据。发送方向完全反过来应用层构造PBUF→LwIP封装→DMA从内存搬运→ETH外设转成RMII时序→LAN8720发到网线。这个架构是ST官方和LwIP社区多年磨合的产物稳定可靠可裁剪性也强。如果后续要换PHY芯片比如换成DP83848、KSZ8081只需要替换lan8720.c里的寄存器配置函数上层完全不用动这就是分层设计的好处。2. 核心底层细节与关键实操要点2.1 RMII时钟配置整个链路最容易翻车的地方我在调试中见过太多人卡在RMII时钟上说“PHY寄存器读不到”“RX一直没数据”最后排查发现REF_CLK根本就没起振。RMII协议要求PHY和MAC共享一个50MHz的参考时钟这个时钟可以是MAC输出给PHY也可以是外部有源晶振同时供两边。STM32F7系列的数据手册里写得很清楚ETH外设的时钟源有两个选择AHB1时钟的二分频一般72MHz或108MHz不是精确的50MHz或者MCO引脚输出的精确50MHz。关键点来了RMII模式下REF_CLK的精度直接影响数据采样稳定性。如果你用AHB1分频出来的凑合时钟跑百兆以太网时眼图余量会很差偶尔能通、偶尔掉包。正确的做法是用PLL的一个专用输出产生50MHz从MCO2引脚PC9输出同时接到LAN8720的REF_CLK引脚和STM32的ETH_RMII_REF_CLK引脚PA1。我第一次弄这个的时候还纳闷“同一个时钟为什么要分两路”后来才想明白LAN8720需要这个时钟做收发时序基准STM32的MAC也需要这个时钟做数据采样同步两边必须同源否则收发数据就会有相位漂移。代码上用STM32CubeMX配置时要注意三点一是RCC时钟树里把MCO2使能时钟源选PLLCLK分频系数选4PLL输出200MHz的话4分频正好50MHz二是PC9引脚的复用功能必须设为MCO不能默认当GPIO用三是同时把PA1配置成ETH_RMII_REF_CLK复用功能。很多CubeMX版本会自动处理但如果你用的是老版本或者手动改代码这两个引脚的配置容易漏。补充一句如果你板子上用了外部50MHz有源晶振直接给LAN8720那么STM32这边只需要把PA1配成输入模式接收REF_CLKPC9不用动。这种方案的好处是PHY不依赖MCU的启动时序坏处是得多焊一个晶振。以我经验CubeMX默认生成的MCO2方案更省事也够稳定。2.2 PHY地址与复位时序问题LAN8720的PHY地址由AD0引脚的上下拉决定大多数模块上AD0默认下拉PHY地址是0x00如果AD0接了上拉电阻比如1k到3.3V地址就是0x01。有些模块甚至把这个引脚引出来让你飞线改地址方便一总线上挂多个PHY但很少见。我建议你拿到模块先看原理图确认或者写段代码扫描0x00~0x1F所有地址读到PHY ID寄存器寄存器2和3值正确的话就锁定地址。LAN8720的PHY ID是0x0007C0F1这个值可以作为判断PHY是否响应的重要依据。再说复位时序这里有个坑。LAN8720的NRST脚需要至少保持低电平1ms再释放高电平内部复位才能完成。有些模块的复位引脚直接接MCU的GPIO那你在初始化代码里要先输出低电平、延时至少10ms、再拉高保守一点比较好。另一个坑是某些开发板把PHY的复位脚和MCU的复位脚连在一起那MCU复位后PHY会跟着复位但PHY的RC复位电路时间常数可能不够导致MCU跑起来时PHY还没准备好这时你去读PHY寄存器大概率超时。解决办法是在PHY初始化函数开头加个毫秒级延时等PHY稳定再执行软复位寄存器0的bit15拉1。2.3 DMA描述符与CACHE一致性F7平台特有的大坑Cortex-M7内核带L1-CacheSTM32F7的D-Cache默认是关闭的但很多人一开D-Cache就发现以太网数据全乱了发出去的数据是脏的收进来的数据莫名其妙丢字节。原因在于DMA是直接搬运内存的它不经过CPU的Cache。如果CPU写了发送缓冲区数据还在D-Cache里没刷到SRAMDMA读到的就是旧数据反过来DMA把收到的数据写进SRAMCPU去读的时候命中的却是Cache里旧的内容。解决办法有三个层次。最省事的是把DMA描述符和收发缓冲区放在不会被Cache“污染”的内存区也就是MPU配置成non-cacheable属性其次是在每次DMA操作前后调用SCB_CleanDCache_by_Addr和SCB_InvalidateDCache_by_Addr手动维护一致性最推荐的做法是两者结合描述符放non-cacheable区数据缓冲区用Cache维护函数这样性能和安全都兼顾。驱动包里如果没做这些处理你在开启D-Cache后必须自己补上。具体操作就是在MPU配置里给ETH的DMA描述符数组和缓冲区数组所在的RAM区域设置一个regionCacheable属性关掉这个细节能帮你省下大量掉头发的时间。3. 实操过程与核心环节实现3.1 STM32CubeMX初始化ETH外设完整步骤如果从零开始建项目我建议直接用CubeMX生成基础工程然后手动补PHY驱动和LwIP配置。具体步骤如下第一步选芯片。以STM32F746ZGT6为例在CubeMX里选好型号后先配RCC和时钟树。以太网需要50MHz的RMII参考时钟我在时钟树里把HSE设为25MHz视板子而定PLL源选HSEPLLM25、PLLN200、PLLP2、PLLQ8这个值用于USB等、PLLR2这样SYSCLK是200MHzPLL的另一个输出经MCO2分频后得到50MHz。具体参数跟着CubeMX时钟树可视化界面调它自动计算合法性省得我手算。第二步配置ETH外设。在Categories里找到Connectivity→ETHMode选“RMII”全局中断使能打开。CubeMX会自动占用PA1、PA2、PA7、PC1、PC4、PC5、PB11、PB12等引脚对应ETH_RMII_REF_CLK、ETH_MDIO、ETH_MDC、ETH_RMII_CRS_DV、ETH_RMII_RXD0、ETH_RMII_RXD1、ETH_RMII_TX_EN、ETH_RMII_TXD0、ETH_RMII_TXD1。这时候留意PA1它默认可能被其他外设占用优先把冲突打开。第三步配置MCO2引脚。在Pinout视图中找到PC9把它的功能设为MCO2然后在Clock Configuration里把MCO2源选为PLLCLK分频系数选4。这样PC9输出50MHz给LAN8720的REF_CLK。有些板子REF_CLK从外部晶振来那这步可以跳过但大多数国内模块设计都是让MCU提供REF_CLK所以我默认按这个做。第四步生成工程。在Project Manager里选好工具链MDK-ARM或IAR生成代码后打开main.c在while(1)之前把MX_ETH_Init()调用的返回值检查一下。如果返回HAL_ERROR八成是PLL配置不对或者PHY地址不对稍后说排查方法。3.2 LAN8720驱动代码对接细节CubeMX生成的HAL库本身不含LAN8720的专用寄存器配置它只负责MAC和DMA初始化。真正和LAN8720打交道的是HAL_ETH_InitConfig结构体里的两个回调函数PHY_Init和PHY_Write。在stm32f7xx_hal_eth.c的HAL_ETH_Init里会调用HAL_ETH_ReadPHYRegister和HAL_ETH_WritePHYRegister来读写PHY寄存器。如果你的初始化代码里没有为PHY地址赋值默认是0而有些PHY地址是1那就要改这里的PHY_ADDRESS宏。驱动包里一般会封装一个lan8720_init函数流程是配置PHY地址LAN8720_ADDR根据硬件设0或1。软复位写寄存器0的0x8000等待15.2bit清零。启用自动协商寄存器0设为0x1200100M全双工使能、自动协商使能让PHY自动和交换机对速率和双工。配置LED写寄存器0x1C比如LED1link/activityLED2100M速度指示。延时100ms查询寄存器1的bit5自动协商完成标志确认链路已建立。需要特别提一点速率和双工的匹配。如果自动协商没成功PHY可能保持在10M半双工这时候ping可能通但吞吐量惨不忍睹。可以在代码里做一次PHY状态检查读寄存器1的bit2连接状态、bit5自动协商完成、寄存器0的bit13和bit8确认当前速度和双工模式。测试时用iperf或UDP高速多发观察实际吞吐量是否接近100Mbps。3.3 接入LwIP实现UDP/TCP通信驱动包里的lwipopts.h怎么配置直接决定内存占用和性能表现。基于F7平台我推荐几个建议值配置项建议值原因MEM_SIZE1600~2560LwIP内部堆大小太小会分配失败PBUF_POOL_SIZE6~16接收缓冲数量太小高负载下丢包PBUF_POOL_BUFSIZE1536需大于最大以太网帧长含FCS预留TCP_MSS1460标准以太网MTU 1500去掉IP/TCP头NO_SYS0或1裸机设1跑RTOS设0LWIP_DHCP1自动获取IP方便测试在ethernetif.c里low_level_init函数会读MAC地址、初始化DMA收发描述符。MAC地址可以是48位随机数但建议用STM32芯片唯一的UID生成这样每台设备地址不冲突后续项目也不会遇到“两台板子MAC一样导致局域网抖动”的问题。生成方法就是读UID的3个32位寄存器取低16位合成后三个字节前三个字节用固定OUI比如0x02 0x00 0x00。LwIP初始化后配置IP地址。测试阶段我建议直接静态IP比如192.168.1.250掩码255.255.255.0网关192.168.1.1。主要原因是DHCP失败时链路状态不好定位问题静态IP一配就能拉起来测连通性。等稳定后再切DHCP也不迟。实现一个最基础的回环功能验证网络栈是否正常在main循环里调用tcpip_thread或裸机轮询的ethernetif_input收到UDP数据后原样回给发送方。LwIP的udp_recv回调里注册一个处理函数把收到的payload长度和内容打印出来再调用udp_sendto返回。这个回环测试能一次性验证发送、接收、中断、协议栈全部链路比单纯ping更能说明问题。3.4 实测流程与数据我实际在一个STM32F746G-DISCO板子上跑过这套驱动配置8MHz HSE、PLL倍频到216MHz、ETH走RMII、MCO2输出50MHz。LAN8720模块用的是淘宝常见的工业级小板带网络变压器RJ45接口。实测流程上电后串口打印“PHY init OK, link up, 100Mbps Full Duplex”。PC网口设置静态IP 192.168.1.1板子接交换机ping 192.168.1.250延迟约1ms丢包0%。用Wireshark抓包确认发送的UDP数据帧的源MAC、目的MAC、IP头部都正确。用Iperf3测试UDP吞吐MSS配置合理时达到约95Mbps说明百兆链路基本打满没有明显瓶颈。这个流程里最耗时间的阶段其实是PHY寄存器调试因为一上来总是读不到ID后面发现是复位时序不够加了20ms延时后一切正常。所以无论驱动包多“现成”硬件时序问题永远是第一位要排查的。4. 常见问题与排查技巧实录4.1 问题速查表结合我多年调试以太网的经验把LAN8720STM32F7最常见的故障和排查方向整理成下表你可以直接拿来当排查清单用现象可能原因排查与解决方法PHY寄存器读超时或读到全FPHY地址不对REF_CLK没起来复位没完成扫描0x00-0x1F地址示波器测PC9和PA1有无50MHz时钟检查复位时序LINK状态一直down网线、交换机或PHY协商失败换网线/换对端设备读寄存器1确认bit2连接状态用短路环自发自收排除外部问题能ping通但UDP吞吐极低自动协商卡在10M半双工PBUF不足读寄存器0确认速度和双工增大PBUF_POOL_SIZE检查代码固定了速度导致协商失败发送正确但收不到数据中断未使能DMA描述符没配好D-Cache未做一致性操作检查ETH_IRQHandler是否调用HAL_ETH_IRQHandler打印描述符状态清理D-Cache收到的数据CRC错误时钟抖动大PCB走线干扰MCO2时钟源没配准确认REF_CLK是否精确50MHz降低ETH引脚翻转速率检查差分线阻抗匹配一开D-Cache就死机DMA缓冲区在cacheable区一致性被破坏用MPU把DMA描述符和数据区配成non-cacheable或手动Clean/Invalidate4.2 具体排查案例PHY地址对不上我遇到过最经典的案例驱动包代码里写死PHY地址是0x00但板子上的LAN8720模块AD0确实接了下拉地址理论上是0。用ST-Link单步调试读寄存器2和3却都返回0xFFFF这代表MDIO总线上根本没PHY响应。当时第一反应是MDIO和MDC引脚有虚焊但用示波器量波形MDC有稳定的2.5MHz时钟MDIO上也有数据脉冲说明MCU发得没问题问题大概率在PHY侧没回应。后来仔细看了模块的规格书发现它内部把AD0通过10k电阻上拉到3.3V也就是模块层面已经把PHY地址定成了0x01。哪怕外部引脚看起来悬空但内部设计已经决定地址了。我改代码里的地址为1再读立即读到0x0007C0F1问题解决。这个教训就是千万不要用板子外观或经验臆断PHY地址一切以模块原理图或至少一段扫描代码为准。直接写个for循环扫描0到31地址读PHY ID寄存器哪个地址返回0x0007C0F1就把地址定在哪这比人肉猜可靠得多。4.3 具体排查案例RMII时钟源没起振另一个高频问题是在HAL_ETH_Init返回HAL_ERROR时查看错误码经常是PHY read timeout。这个例子中CubeMX的时钟树配好了MCO2也从PC9引出用示波器测却是0Hz但PC9的GPIO输出电平有1.2V。排查后发现CubeMX生成的GPIO代码如下GPIO_InitStruct.Pin GPIO_PIN_9; GPIO_InitStruct.Mode GPIO_MODE_AF_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_LOW; GPIO_InitStruct.Alternate GPIO_AF0_MCO; HAL_GPIO_Init(GPIOC, GPIO_InitStruct);看起来没问题但MCO2的时钟源没使能。在main函数里MX_RCC_Init先执行了但如果时钟配置时PLL没用的情况下MCO2就无源可出。解决办法是在SystemClock_Config里确保PLL已启用并把MCO2Prescaler和ClockSource正确赋给RCC_OscInitStruct和RCC_ClkInitStruct最后调用HAL_RCC_MCOConfig(RCC_MCO2, RCC_MCO2SOURCE_PLLCLK, RCC_MCO2_DIV4)。设置完用示波器量PC9能看到完整的50MHz方波信号幅度约3.3V。我总结了一个经验调试以太网先测三个时钟——MCU主频、PC9的50MHz、PA1的REF_CLK。三个都对再谈PHY寄存器否则后面全是浪费时间。4.4 调试技巧手把手教你读PHY寄存器调试过程中串口打印PHY寄存器状态是非常有用的工具。我建议在初始化之后至少把以下寄存器打印出来寄存器1状态寄存器bit2是LINKbit5是Auto-Negotiation Complete、寄存器0控制寄存器bit13是速度选择bit8是双工模式、寄存器2和3PHY ID确认。一个简单的打印函数如下配合串口重定向就能用void lan8720_dump_regs(void) { uint16_t reg 0; for (uint8_t i 0; i 2; i) { uint16_t val lan8720_read_reg(1); printf(Phy Reg1 (BCR) 0x%04X\r\n, val); val lan8720_read_reg(1); printf(Phy Reg1 (BSR) 0x%04X\r\n, val); val lan8720_read_reg(2); printf(Phy Reg2 (ID1) 0x%04X\r\n, val); val lan8720_read_reg(3); printf(Phy Reg3 (ID2) 0x%04X\r\n, val); } }如果读出的BCR确实为0x1000或0x1200说明链路协商已完成如果BSR的bit21说明物理连接正常。我可以把“寄存器1 bit21且bit51”作为驱动就绪的核心判据这一条符合了基本就稳了。5. 驱动包裁剪与后续扩展建议驱动包导入你的项目后强烈建议做一次裁剪去掉不需要的功能和调试代码这样既能减小Flash占用也让代码清晰。比如LwIP里如果你只跑UDP那TCP相关的宏可以关掉如果不跑DHCP把LWIP_DHCP设为0如果不做DNSLWIP_DNS也可以裁剪。此外日志打印建议加开关宏生产版本默认关闭不然串口打印那点时间在循环收发时也会拖慢吞吐。扩展方向考虑的话这个驱动接上不同上层业务就能演变成各种产品。最常见的扩展示例是Modbus TCP从站可以用freemodbus的TCP版本基于netconn API实现也可以做MQTT客户端把采集的传感器数据定时上报如果跑FreeRTOSLwIP的sys_arch层配好之后还可以开HTTP服务器做设备管理页面这些方向驱动层复用度极高。有个环节提醒一下如果你要把这套驱动移植到STM32H7系列注意H7的D-Cache问题和F7类似但更严重因为H7主频更高Cache行为也不一样。务必把ETH DMA描述符和缓冲区放到配置正确的内存区域MPU配置和链接脚本都要配合处理不然同样会出现“发出去是错的、收进来是乱的”的情况。这个包虽然是F7专用但移植到H7的技术路线基本一样差异主要在HAL库的API和时钟配置上。从操作上看我做驱动的习惯是先让LED亮本地调试→再让串口说话能看到状态→再让网口通能ping→最后才加业务逻辑。每一步都有可验证的中间结果不会出现“改了一堆代码但不知道哪一步错误”的情况。驱动包也一样建议你先用最小工程跑通ping再逐步添加自己的业务代码。本文还有配套的精品资源点击获取
返回列表