FEATURED · 精选文章

STM32H750以太网失联排查:Cache一致性与DMA描述符配置详解

发布时间 / 2026/8/31 22:25:29
来源 / 创域科博编辑部
栏目 / 资讯中心
STM32H750以太网失联排查:Cache一致性与DMA描述符配置详解 1. 问题现象明明链路正常可设备就是“失联”了做嵌入式网络开发的朋友应该都遇到过这种让人抓狂的场景STM32H750板子刚上电ping 192.168.1.100 一切正常ARP也能正确解析数据收发顺畅得很。结果跑了十几分钟、几个小时之后突然ping不通了ARP请求也没响应像是设备直接“死”在了网络上。可是串口打印还在跑RTOS任务还在调度LED还在闪整块板子分明活得好好的——就是网络层面完全失联。这种“半死不死”的状态最坑人因为它不像硬件故障那么明显也不像代码崩溃那么直接。你接上调试器一看程序明明在跑以太网外设的中断还在触发可就是回不了包。拔网线重插通了按一下复位键又通了然后过一段时间再次失联。我最早碰到这个问题是在一个数据采集项目上设备需要在现场连续运行几个月网络不能断。当时排查了一个多星期试过换PHY芯片、换变压器、换网线、换交换机端口问题依旧。后来静下心把STM32H75x的参考手册翻了几遍才真正搞明白这个芯片在以太网设计上的一些暗坑。这篇文章就把我排查这类问题的完整思路、根因分析和解决方案整理出来。方向基于STM32H750自带MAC加外部PHY的典型方案但很多排查思路在STM32F4、H7系列其他芯片上同样适用。如果你也遇到“网络跑着跑着就失联”的诡异问题这篇文章应该能帮你省下至少一个星期的排查时间。2. 先搞清楚硬件架构为什么H750的以太网这么容易出问题2.1 STM32H750的MAC控制器和外部PHY的分工STM32H750内置了10/100M以太网MAC控制器但物理层收发器PHY在芯片外面绝大多数据项目用的都是RMII接口加一颗外部PHY芯片像LAN8720A、DP83848、KSZ8081这类。MAC和PHY之间走RMII接口时钟50MHz数据线2根加上CRS_DV和TX_EN总共也就7根线。因为引脚少、布线简单RMII几乎是STM32以太网项目的默认选择。但RMII模式有一个非常关键的细节50MHz的REF_CLK时钟源有两种接法一种是外部PHY提供50MHz时钟给STM32的ETH_CLK引脚另一种是STM32的MCO引脚输出50MHz时钟给PHY。两种方案都在用但时钟源不同对软件配置的影响也不同。如果你用的是MCO输出方式还要额外注意MCO引脚本身能输出的最大频率——STM32H750的MCO引脚在输出50MHz时有没有问题这本身就是一个隐藏的坑后面我会专门讲。从软件层面看STM32的以太网MAC通过DMA和内核交互数据描述符结构、DMA引擎、MAC配置、PHY管理这几个模块各司其职。硬件看起来不复杂但正因为分工明确任何一个环节的配置出了偏差都可能表现为“工作一段时间后失联”。2.2 一个反直觉的现象运行越久缓存越脏绝大多数“运行一段时间后网络失联”的问题根因都不在PHY上而在STM32的DMA描述符、Cache缓存、以及MAC地址过滤这几块的配置上。之前查资料的时候看到很多人一遇到ping不通就怀疑PHY芯片有问题其实这种概率很低。PHY芯片工作状态一般很稳定除非硬件设计有问题导致信号质量太差。真正容易出问题的是内核和MAC之间的数据通路——特别是你开了D-Cache之后如果DMA描述符和收发缓冲区的内存属性没有配置对大概率会复现“跑一会儿就失联”的经典故障。原因也很直白STM32H750的内核是Cortex-M7带L1-Cache。DMA控制器在搬运数据的时候直接访问物理内存不会经过Cache这时候CPU和DMA看到的数据就可能不一致。如果描述符被Cache缓存了DMA更新了描述符内容CPU读到的却是Cache里的旧数据就会误以为缓冲区还没有收到数据对应的接收描述符永远不会被释放回DMA时间一长可用的接收描述符耗尽网络就彻底挂掉了。类似的如果发送缓冲区的数据还停留在Cache里没写回内存DMA搬运的其实是旧数据或者干脆搬运了非法地址也会导致发送失败最终表现为ping不通。2.3 排查这类问题的核心原则在进入具体配置之前先确立一个排查思路网络失联类问题先软件后硬件先内存后PHY。我的习惯是先看代码里对MPU、Cache、DMA描述符的配置有没有问题因为这类问题有个共同特征是“运行一段时间后才复现”非常符合Cache一致性被破坏的特征。硬件信号完整性问题一般从一上电就不稳定或者是偶发性的重启恢复而不太会像Cache问题这样规律性地“跑一段时间就挂”。3. 排查全过程从现象定位到根因3.1 复现实验稳定复现是排查的命根子排查这种间歇性问题最怕的是半天不复现。所以第一步就是要稳定复现这个故障。我的做法是写了一个自动化脚本来帮忙每500ms ping一次板子的IP连续ping记录丢包时间点板子端用串口持续打印接收中断次数、DMA描述符状态、PHY寄存器状态。实测下来大概跑15到30分钟就会复现一次。复现时串口打印显示接收中断还在触发说明MAC收到了数据但应用层收不到任何包DMA接收描述符的状态看起来也不对PHY的Link状态寄存器显示链路是正常的一直是100M全双工。这就很有意思了。MAC收到了数据、链路正常、但数据就是上不来基本锁定问题出在DMA描述符和内存数据一致性上。3.2 检查DMA描述符和Buffer的内存属性STM32H750的DMA描述符是放在内存里的一般我们用一个全局数组来定义ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT] __attribute__((section(.RxDescripSection))); ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT] __attribute__((section(.TxDescripSection))); uint8_t Rx_Buff[ETH_RX_DESC_CNT][ETH_RX_BUF_SIZE] __attribute__((section(.RxArraySection))); uint8_t Tx_Buff[ETH_TX_DESC_CNT][ETH_TX_BUF_SIZE] __attribute__((section(.TxArraySection)));关键在于这些内存区域的MPU属性配置。对于DMA要访问的内存最稳妥的做法是配置成非Cache、可共享Normal, Non-Cacheable或者Device属性确保CPU和DMA看到的数据完全一致。如果你没有配置MPUCortex-M7默认情况下D-Cache是开启的DMA描述符和缓冲区都被Cache缓存那么恭喜你你中奖了。这个配置错误几乎100%会导致网络跑一会儿就挂。我当时用的配置是这样的把描述符和缓冲区所在的内存段全部配置为Non-Cacheablestatic void MPU_Config(void) { MPU_Region_InitTypeDef MPU_InitStruct {0}; /* Disable MPU before configuration */ HAL_MPU_Disable(); /* Configure the DMA descriptors and buffers as Non-Cacheable */ MPU_InitStruct.Enable MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress 0x24000000; MPU_InitStruct.Size MPU_REGION_SIZE_32KB; MPU_InitStruct.AccessPermission MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable MPU_REGION_NOT_BUFFERABLE; MPU_InitStruct.IsCacheable MPU_REGION_NOT_CACHEABLE; MPU_InitStruct.IsShareable MPU_REGION_NOT_SHAREABLE; MPU_InitStruct.Number MPU_REGION_NUMBER0; MPU_InitStruct.TypeExtField MPU_TEX_LEVEL0; MPU_InitStruct.SubRegionDisable 0x00; MPU_InitStruct.DisableExec MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(MPU_InitStruct); HAL_MPU_Enable(MPU_CONTROL_HRD_MEM_FORCE); }这里要注意0x24000000是STM32H750的DTCM RAM吗不是。H750的紧耦合内存TCM是0x00000000ITCM和0x20000000DTCM而0x24000000是AXI SRAM。DMA能不能访问TCM这是一个关键点。3.3 注意DMA无法访问TCMSTM32H750的内存布局里TCM是直接挂在CPU内核上的DMA控制器访问不到。所以你的DMA描述符和以太网收发缓冲区绝对不能放在TCM区域。我记得刚接触H7的时候很多人图方便把变量直接扔到默认的RAM区域。在H7上链接脚本里默认的RAM区域如果指向了DTCM那么以太网DMA根本没法工作表现也很奇葩——要么完全不通要么偶尔通一下然后挂掉。所以必须把以太网相关的所有内存显式放到DMA可访问的区域一般是AXI SRAM0x24000000或者SRAM1/2/30x30000000/0x30020000/0x30040000。在链接脚本里把这些区域单独分配出来然后通过__attribute__((section))指定。这里贴一段我实际使用的链接脚本片段GCC工具链把以太网描述符和缓冲区固定放到AXI SRAM/* Specify the memory areas */ MEMORY { FLASH (rx) : ORIGIN 0x08000000, LENGTH 128K DTCMRAM (xrw) : ORIGIN 0x20000000, LENGTH 128K SRAM1 (xrw) : ORIGIN 0x30000000, LENGTH 128K AXI_SRAM (xrw) : ORIGIN 0x24000000, LENGTH 512K } /* Ethernet DMA descriptors and buffers in AXI SRAM */ .eth_dma (NOLOAD) : { . ALIGN(4); *( .RxDescripSection ) *( .TxDescripSection ) *( .RxArraySection ) *( .TxArraySection ) } AXI_SRAM注意这个flash大小STM32H750的Flash是128KB但实际片内Flash物理大小不止这个不过这是另一个话题不要在这里跑偏。3.4 检查接收描述符的状态机还有一种情况是MPU配置看起来没问题Cache属性也对了但问题出在接收描述符的处理逻辑上。STM32的以太网DMA接收描述符有一个OWN位第31位表示该描述符当前归DMA所有还是归CPU所有。初始化时所有接收描述符的OWN位都是1表示DMA可以写入数据。CPU在处理完一个描述符后需要手动把OWN位置1把描述符还给DMA。这块的逻辑如果写错了比如在清OWN位之前先读了描述符的其他字段或者处理完忘了置OWN位那么很快就会耗尽接收描述符网络也是跑一段时间就挂。我梳理一下正确的处理流程检查当前接收描述符的OWN位如果是0说明CPU拥有该描述符数据已就绪读取描述符中的数据长度和数据地址把数据拷贝到应用层缓冲区清空描述符状态重新设置OWN位为1如果是Ring模式递增描述符索引回绕必要时触发一次接收DMA的轮询恢复。这里有一个很容易踩的坑接收描述符的缓冲区地址在初始化时已经写死了CPU在处理数据时不要修改这个地址除非你故意做动态缓冲。我见过一些代码在收包之后把缓冲区地址换掉了然后忘了恢复结果描述符指向了一个无效内存DMA往错误地址写数据直接hardfault或者内存踩踏。3.5 一个重要的怀疑对象MAC地址过滤还有一个容易被忽略的坑是MAC地址过滤配置。H750的以太网MAC支持多种地址过滤模式包括单播地址过滤、组播地址过滤、广播地址过滤等。如果配置有误设备可能会在某些情况下错过发给自己的ARP请求或ICMP包。最典型的问题是你初始化时设置了MAC地址过滤但地址比较大端小端搞反了。MAC地址的字节序在寄存器里是从高字节到低字节存储的如果软件配置时把顺序弄反那么设备只能收到部分包或者干脆收不到任何单播包。表现就是偶尔能通、偶尔不通因为ARP请求是广播包可能还能收到但单播的ping包就全丢了。我当时排查的时候先用Wireshark抓包发现板子能发出ARP应答但收不到后续的ICMP请求——这种现象非常像MAC地址过滤出错。后来检查寄存器果然发现MAC地址高32位和低16位的配置函数里参数顺序写错了。所以如果你遇到“设备能发不能收”的诡异情况先把MAC地址寄存器的值读出来和实际MAC地址对一下保证字节序完全一致。4. Cache一致性H750以太网失联的头号元凶4.1 从一次“死等”现象说起继续回到Cache问题。我排除了MAC过滤的问题之后故障依旧。所以重新聚焦到Cache一致性上。在不开Cache的情况下CPU和DMA都直接访问内存数据是实时同步的问题不会暴露。但H750的Cortex-M7默认开D-Cache性能提升明显代价就是你需要自己管理数据一致性。以太网场景下有两处关键的数据一致性必须处理接收路径DMA把网线收到的数据写到内存缓冲区然后置位描述符的OWN位为0。CPU在读取数据前必须保证Cache中的数据是无效的。如果之前CPU访问过这段地址Cache里可能还有旧数据CPU读出来是老的就会丢包。发送路径CPU把要发送的数据写入内存缓冲区然后置位描述符OWN位为1。DMA在搬运前必须保证内存里的数据是最新的。如果数据还留在Cache里没写回DMA搬运的就是旧数据。4.2 正确的Cache维护操作在HAL库中有对应的操作接口/* 接收路径在CPU读取DMA写入的Buffer之前使Cache无效 */ SCB_InvalidateDCache_by_Addr((uint32_t *)rx_buffer, length); /* 发送路径在DMA搬运Buffer之前将Cache写回内存 */ SCB_CleanDCache_by_Addr((uint32_t *)tx_buffer, length);关键点在于接收路径用Invalidate使无效因为数据在内存里Cache里的副本可能是旧的发送路径用Clean写回因为数据在Cache里需要先写回内存。有些代码偷懒收发都用CleanInvalidate虽然也能工作但效率略低。更坑的是如果你用了错误的组合——比如接收路径用Clean而不是Invalidate——就可能读到旧的脏数据丢包会变得极不规律。4.3 更稳妥的方案所有以太网内存全部Non-Cacheable对于嵌入式以太网这种高频数据收发的场景我个人的习惯是不跟Cache较劲直接把以太网相关的DMA描述符和收发缓冲区全部配置为Non-Cacheable。这样做的优点是彻底避免Cache一致性问题无论是CPU还是DMA访问数据都是实时同步的排查难度大大降低不需要在每一个收包/发包路径上仔细检查Cache操作性能损失基本可以忽略因为以太网本身的瓶颈在PHY和MAC而不在CPU访问内存的速度。STM32H750有512KB的AXI SRAM以太网缓冲区通常只占几十KB非Cache化这点区域完全不影响整体性能。有同学可能会担心非Cache区域访问速度会不会太慢实测下来以太网收发数据的瓶颈主要在DMA和MAC频率上CPU访问非Cache内存的延迟虽然比Cache高但对于100M以太网来说完全够用不会成为瓶颈。4.4 如果坚持用Cache这些细节必须做对如果你出于某种原因不想把整个区域设为Non-Cacheable坚持用Cache模式那么有几个细节必须做对第一描述符本身建议放到Non-Cacheable区域。因为描述符的更新频率极高每次收发都要读写任何一点Cache不一致都会导致严重的状态错乱而且很难排查。第二缓冲区可以在Cache区域但每次收发必须做Clean/Invalidate操作。而且要注意Clean/Invalidate的地址必须4字节对齐长度也要对齐到32字节Cache line大小否则会破坏相邻内存的数据。以ST的HAL库为例SCB_CleanDCache_by_Addr要求传入的起始地址是4字节对齐的但内部处理时会对整个Cache line做操作。如果你传入的地址不是32字节对齐的可能会把同一Cache line里其他数据也Clean掉反过来Invalidate也会把同一Cache line里其他有效数据丢掉。所以如果把缓冲区定义在普通全局数组里要确保每个缓冲区都32字节对齐uint8_t Rx_Buff[ETH_RX_DESC_CNT][ETH_RX_BUF_SIZE] __attribute__((aligned(32)));对齐这个东西不出问题的时候没什么存在感一出问题就是莫名其妙的数据错乱、运行一段时间挂掉。强烈建议从一开始就约定好所有DMA缓冲区64字节对齐起步。5. 收发描述符数量配置太小了也会“跑一会就挂”5.1 描述符数量和网络负载的关系另一个典型坑是收发描述符数量配置得太少。HAL库的默认配置里ETH_RX_DESC_CNT和ETH_TX_DESC_CNT是可以自己定义的。很多人用默认值4个接收描述符、4个发送描述符在低负载情况下没问题但网络负载一旦上来或者应用层处理速度稍有延迟接收描述符很快耗尽DMA无法接收新数据网络就“挂”了。这种“挂”和Cache问题不同它更像是设备暂时性失去接收能力等描述符释放后又可能会恢复。但如果你应用层收包处理不及时或者描述符释放逻辑有BUG就可能一直挂着。我遇到过一个项目接收描述符数量配置为4应用层每收到一个包要解析、存储、上报处理时间大概几毫秒。在持续大流量ping包下DMA接收速度远快于应用层处理速度4个描述符根本不够用运行几分钟后就出现丢包再久一点就直接失联了。解决办法很简单把接收描述符配置到16个或者32个发送描述符8到16个。#define ETH_RX_DESC_CNT 16 #define ETH_TX_DESC_CNT 85.2 描述符耗尽时如何自救除了增加描述符数量还要在应用层做一层保护当接收描述符即将耗尽时主动丢弃一些非关键包或者触发一次DMA的接收恢复操作。H7的以太网DMA有一个机制如果接收描述符耗尽DMA会停止接收并置位接收状态错误位。你可以通过中断或者轮询捕获到这个状态然后重新启动接收DMA。在HAL库中对应的处理逻辑在HAL_ETH_ReadData函数内部采用Ring模式时如果返回值是HAL_ETH_ERROR_RX_NOT_READY说明描述符还没有被DMA释放你需要稍后重试或者调整处理流程。我个人的建议是网络应用都尽量采用中断信号量的方式让以太网接收线程优先处理收包尽快释放描述符避免应用层业务逻辑阻塞收包线程。5.3 系统负载和DMA仲裁H750的多个DMA控制器共享总线带宽如果系统里同时跑着ADC采样、SPI通信、SD卡写入等高负载外设以太网DMA的带宽可能被抢占导致收发延迟增大、缓冲数据堆积表现出来也是“网络卡顿、时延增大、最后失联”。这种情况下可以调整DMA仲裁优先级。在HAL库中可以通过配置DMA控制器的主/从优先级设置让以太网DMA的优先级高于其他外设。不过要提醒一句优先级调高后要留意其他外设是否会出现数据丢失尤其是对实时性要求高的采集类外设。6. 中断处理为什么“只有一个网络中断”反而更安全6.1 中断优先级和抢占STM32H750的以太网DMA中断是走NVIC的中断处理里通常会做两件事读取接收描述符数据、释放描述符。如果你用的是FreeRTOS这种RTOS中断优先级需要设置成不超过FreeRTOS允许的最高优先级一般是5到15具体看中断分组配置否则会触发断言或者导致系统不稳定。还有一个细节以太网中断处理函数的执行时间不能太长。中断里尽量只做数据搬运和描述符释放真正的协议解析和业务处理放到任务上下文里。否则中断占用时间过长会导致其他高优先级事件被延迟甚至触发看门狗复位。6.2 校验接收数据长度的合法性在中断处理或者收包处理里一定要校验DMA描述符里的接收长度。如果长度值是0或者超过缓冲区最大长度说明数据有问题直接丢弃不要继续处理。我见过一个bug是收包长度未校验把一个超大的长度值传给后面处理函数结果内存越界拷贝把整个系统搞崩了。这种问题一旦出现极难排查因为它表现为随机崩溃网络丢包只是表象。uint32_t frame_length ETH_GetRxFrameLength(); if ((frame_length 0) || (frame_length ETH_RX_BUF_SIZE)) { /* 丢弃异常帧释放描述符 */ HAL_ETH_ReleaseRxBuffer(...); return; }6.3 Broadcast和ARP风暴的冲击排查网络失联时还要注意外界网络环境对设备的影响。如果设备所在的局域网内有广播风暴、ARP风暴或者有人跑P2P下载器占满带宽设备的网络栈都可能受影响。最大流量的极端情况下如果设备每秒钟收到几千个包而你的接收描述符只有4个那基本是秒挂。这不是设备硬件问题是你系统处理能力跟不上。对策有两个方向一是增加描述符数量提高缓冲能力二是在MAC层做简单的帧过滤只接收发给自己的单播帧和必要的ARP、ICMP帧丢弃其他帧。STM32以太网MAC自带帧过滤功能可以配置只接收单播、组播或广播帧。在初始化时可以设置HAL_ETH_InitStruct中的帧过滤字段heth.Init.RxMode ETH_RXINTERRUPT_MODE; heth.Init.ChecksumMode ETH_CHECKSUM_BY_HARDWARE;更极端的做法是直接在硬件层面禁用不需要的帧类型。不过大部分项目用软件层的帧过滤就够了在接收中断里判断以太网帧类型非目标帧直接丢弃。7. PHY端排查寄存器状态是骗不了人的7.1 用串口把PHY寄存器打出来排除完软件问题后如果故障还在就要回到PHY环节做排查。虽然我前面说PHY出问题的概率不高但也不能排除硬件问题的可能性。排查PHY最简单的方法是在设备失联时通过串口命令读取PHY的关键寄存器。以LAN8720A为例几个关键寄存器寄存器地址寄存器名称关键内容0x00Basic Control Register软复位、速度/双工设置速度指示位0x01Basic Status RegisterLink状态、速度/双工检测结果0x04Auto-Negotiation Advertisement协商能力广播0x05Auto-Negotiation Link Partner Ability对端协商能力0x1FPHY Special Control/Status中断状态、能量检测等我在排查的时候通过串口命令实时读取0x01寄存器发现Link状态一直正常这说明PHY的物理链路没有问题。随后读0x04和0x05寄存器确认协商结果是100M全双工也没有问题。7.2 PHY复位时序很多“跑一会挂”其实是复位时序不对还有一个容易被忽略的点是PHY的复位时序。很多硬件设计里PHY的复位引脚由STM32的GPIO控制上电后需要延迟一段时间再拉高复位引脚等PHY内部上电稳定后才能真正工作。如果复位时序太短PHY可能没有完全初始化表现为上电能通跑一段时间后失联复位后又能通。LAN8720A的数据手册建议复位低电平持续时间至少1ms上电到PHY就绪时间在10ms左右。如果片子外接的25MHz晶振起振时间偏长这个时间还需要更充裕。我项目里的处理方式是上电后先把PHY复位引脚拉低延时至少10ms再拉高复位引脚延时至少10ms再对PHY写寄存器进行初始化。这组时序放在系统上电初始化里不要放在以太网初始化之前太早。如果你在上电后立即初始化以太网PHY可能还没就绪后续配置就会丢失或者部分生效。7.3 PHY的中断引脚和状态检测有些方案的PHY芯片会接一个中断引脚到STM32用来检测Link状态变化。如果这个中断引脚配置不当或者PHY的中断被触发但软件没有及时处理可能导致PHY进入异常状态。所以如果项目里用了PHY中断务必把中断处理函数写完整读取中断状态寄存器清除中断标志根据Link状态变化做相应处理。如果PHY中断处理不及时在Link状态频繁抖动的时候中断标志会堆积PHY芯片的某些内部状态机可能卡住最终表现为“网络失联”。我当时排查时把PHY中断暂时关闭直接用轮询方式读取Link状态结果故障复现周期变慢了虽然没完全解决但也佐证了PHY状态管理对稳定性的影响。8. 优化方案一套能稳定跑几个月的配置思路8.1 完整的初始化流程综合前面的排查我最终沉淀了一套稳定的初始化配置供大家参考。这套配置在STM32H750 LAN8720A FreeRTOS lwIP环境下稳定运行了几个月没有出现网络失联。完整流程如下硬件上电等电源稳定GPIO控制PHY复位引脚拉低延时10ms拉高复位引脚延时10ms等PHY就绪配置STM32的RMII引脚复用功能如果使用MCO输出50MHz时钟给PHY先配置MCO时钟到50MHz并确认输出稳定初始化ETH外设的DMA描述符和缓冲区放置在AXI SRAM区域配置MPU将以太网描述符和缓冲区区域设为Non-Cacheable调用HAL_ETH_Init初始化MAC设置MAC地址、帧过滤模式等调用PHY的初始化驱动读取PHY ID确认PHY通信正常配置PHY的自动协商或强制100M全双工等待Link up启动ETH的DMA传输开启接收中断在RTOS中创建网络接收任务通过信号量或消息队列与中断交互配置lwIP的netif接入启动TCP/IP协议栈。8.2 收发缓冲区的对齐建议前面提到缓冲区要对齐到32字节我建议直接对齐到64字节应对各种Cache line大小和DMA对齐要求。定义方式#define ETH_RX_BUF_SIZE 1536 /* 标准以太网帧最大长度 头部 */ #define ETH_TX_BUF_SIZE 1536 uint8_t Rx_Buff[ETH_RX_DESC_CNT][ETH_RX_BUF_SIZE] __attribute__((aligned(64))); uint8_t Tx_Buff[ETH_TX_DESC_CNT][ETH_TX_BUF_SIZE] __attribute__((aligned(64)));缓冲大小1536字节刚好可以容纳一个VM maximum以太网帧——1500字节负载加14字节以太网头加CRC考虑VLAN标签的话可以再放宽一点到1536以上。我见过一些项目把缓冲区设成2048其实也没问题只是浪费点内存。H750的RAM足够大没必要太抠门。8.3 轮询模式和中断模式的选择H7以太网驱动支持轮询模式和中断模式。轮询模式主循环周期调用HAL_ETH_ReadData适合协议栈有周期tick的场合简单可靠但实时性略差。中断模式接收中断触发后在中断里或通过信号量通知任务处理实时性好但代码复杂度略高。我在FreeRTOS lwIP场景下推荐中断模式。以太网接收中断里调用HAL_ETH_ReadData拿到数据放到队列或者直接交给netif-input。这么做的核心原因是避免在中断里做过多处理保证中断快速退出把业务逻辑放到任务里。一个典型的配置是void ETH_IRQHandler(void) { HAL_ETH_IRQHandler(heth); } void HAL_ETH_RxCpltCallback(ETH_HandleTypeDef *heth) { /* 在中断回调中释放信号量通知网络任务处理数据 */ BaseType_t xHigherPriorityTaskWoken pdFALSE; vTaskNotifyGiveFromISR(NetworkTaskHandle, xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); }8.4 lwIP层降低丢包风险lwIP配置里的几个关键参数对稳定性影响很大MEMP_NUM_PBUFPBUF数量如果太小在突发流量下会丢包MEMP_NUM_TCP_SEGTCP分段数量太小会影响TCP发送吞吐PBUF_POOL_SIZEPBUF池大小建议增大到16到32TCP_WNDTCP接收窗口适当增大可以提高吞吐。这几个参数在lwipopts.h里配置项目里我习惯把PBUF_POOL_SIZE设到32MEMP_NUM_PBUF设到32TCP_WND设到16KB左右。这些参数占用的RAM在H750的512KB SRAM面前不算什么但收益很明显。8.5 用Watchdog做最后一道防线嵌入式设备往往有看门狗。对于网络通信我建议加一个“网络看门狗”逻辑如果超过一定时间没有收到任何网络包或者协议栈长时间无响应软件主动软复位以太网外设而不是整个系统复位。比如在应用层维护一个计时变量每次收到有效网络包时清零。如果超过10秒没收到任何包则调用以太网外设的重新初始化流程从第6步开始重新配置ETH。这个软复位逻辑不会影响其他任务运行比整个系统复位温和很多。对现场维护来说“断网10秒后自动恢复”比“设备彻底失联”要容易接受得多。实测下来即便我后来的配置已经解决了根因网络看门狗仍然是生产环境中的必备保险丝。网络环境千奇百怪无法预测所有异常场景有这一层保护心里会更踏实。9. 实测数据调整前后对比为了让读者直观看到配置调整带来的变化我把两次测试的数据列出来。测试环境板卡自制STM32H750板卡PHYLAN8720A软件FreeRTOS lwIP 2.1.2上位机PC持续ping间隔1秒ping 10000次配置项调整前调整后接收描述符数量416发送描述符数量48MPU配置未配置全Cache以太网内存Non-Cacheable缓冲区对齐未对齐64字节对齐PHY复位时序1ms20ms网络看门狗无有ping丢包率15分钟后开始丢包30分钟后失联10000次全部成功0%丢包长时间运行约30分钟失联连续运行数月无失联这个表格很直观前几项配置调整针对性解决了根因网络看门狗相当于兜底。实际项目中如果只改前几项问题就能彻底解决剩下的看门狗只是“保险丝”。10. 这些问题背后的常见误区10.1 “是不是我PHY芯片坏了”不是首选排查方向遇到网络失联很多人第一反应是查PHY芯片。从我排查过的项目来看PHY芯片物理损坏的概率极低绝大多数问题还是出在软件配置和内存管理上。做嵌入式怀疑硬件之前先把软件的每个配置都过一遍特别是MPU、Cache、描述符数量、缓冲区对齐、MAC地址字节序、中断处理这些点。10.2 “为什么别人用默认配置就没问题”有人可能会说我照着例程跑配置也是默认的为什么别人没出问题这个问题得分两种情况一是例程可能只是在低负载、短时间的测试条件下跑通没有做长时间稳定性验证。你把它改到实际项目里数据量大了、运行时间长了隐藏问题才暴露出来。二是你的网络环境、以太网负载、系统中断负载和别人不一样。别人可能刚好没有触发问题但你触发了。所以做嵌入式网络稳定性验证不能只跑几分钟至少要连续跑几小时甚至一整天然后分析丢包率、响应时间这些指标。10.3 “不就是一个ping吗哪里来这么多讲究”这是很多初学者的想法。但其实ARP、ping这种最简单的协议恰恰涉及网络协议栈最底层的机制MAC寻址、DMA收发、描述符管理、中断处理、数据缓存。这些问题一旦出现往往意味着底层机制有Bug而不是简单的命令配置问题。能把ARP和ping调通、调稳才是嵌入式网络开发真正入门的标志。11. 写在最后一个排查经验清单如果你正在被“STM32H750以太网跑一段时间就失联”折磨给你一份可以直接照做的排查清单确认DMA描述符和缓冲区放在DMA可访问的AXI SRAM区域不能在TCM里配置MPU将以太网相关内存设为Non-Cacheable或者确保每次收发做Cache Clean/Invalidate操作缓冲区32或64字节对齐接收描述符数量不少于16个发送描述符不少于8个检查MAC地址寄存器字节序确认和实际MAC地址一致读取PHY寄存器确认Link状态和协商结果正常PHY复位时间至少10ms上电初始化不要太急收包处理要快速释放描述符不要在中断里做重活校验接收长度异常帧直接丢弃网络负载大时增加lwIP的PBUF池和描述符数量加一个网络看门狗超过设定时间无包自动重新初始化以太网。大概能解决90%以上的“网络跑一会就挂”问题。剩下的个别情况可能集中在硬件信号完整性问题比如RMII信号线过长、REF_CLK抖动过大、PHY供电波动等等那就需要示波器上场了。回头想想这个Bug之所以难查不是因为配置复杂而是因为它只在运行一段时间后才暴露很多人没耐心等到复现也没想到把内存属性、描述符这些底层细节一一过筛。嵌入式开发里很多诡异问题都是这样——答案其实不神秘关键是把基础知识吃透把排查步骤走全。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻