ARTICLE DETAIL

资讯详情

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

STM32H745双核移植lwIP:从Cache一致性到网络调试全指南

STM32H745双核移植lwIP:从Cache一致性到网络调试全指南 最近在调一块 STM32H745 的双核板子M7 核跑主逻辑M4 核做数据采集网络部分用 lwIP 协议栈接以太网。听起来分工挺明确的但真正调起来才发现H745 这颗芯片上的 lwIP 问题比普通单核 MCU 多了一倍不止——一开始是 ping 不通然后是跑几分钟硬故障HardFault再后来是速度死活上不去。这一路踩坑踩下来我觉得值得把 Stm32h745 lwIP issue 相关的经验系统整理一遍从双核架构的资源划分、lwIP 移植配置到缓存一致性、FreeRTOS 集成再到 ping 不通、死机、网速低的完整排查思路。这篇就写给正在同样坑里的朋友尤其是第一次在 H7 双核平台上面调以太网的人能少走不少弯路。1. H745 这颗双核芯片给 lwIP 带来了哪些新问题很多人第一次用 STM32H745是冲着“双核”来的Cortex-M7 跑高性能主逻辑Cortex-M4 跑外围采集或者协议处理听起来很完美。但问题恰恰出在这个“双核”上。lwIP 能在单核单片机上跑得好好的不代表它在双核上也能随随便便跑起来。H745 的以太网外设、内存架构、缓存机制每一项都跟以前用的 F4、F1 系列不一样而这些差异正是绝大部分 issue 的源头。1.1 两个内核抢一个以太网控制器怎么分配才对STM32H745 实际上继承自 STM32H7 家族的高端配置它内部有 Cortex-M7 和 Cortex-M4 两个内核两个核都能访问绝大部分外设寄存器包括以太网 MAC 控制器。H745 系列的以太网外设还不止一个——它有两个 MAC 控制器ETH1 和 ETH2每个都支持 MII/RMII 接口速率都是 10/100Mbps。但问题是一个工程里你大概率只用其中一个而且不可能让两个核同时去操作同一个 MAC。两个核同时读写 ETH 寄存器、同时响应 ETH 中断会导致描述符状态错乱、寄存器配置被覆盖表现出来就是随机死机、收发异常。我见过有人把 ETH 中断放在 M7又在 M4 里初始化了一遍 ETH结果两个核都有中断处理函数板子一上电就乱套。正确的做法是以太网外设必须由单一内核独占。要么全部放 M7要么全部放 M4另一个核彻底不碰 ETH 寄存器。如果两个核之间需要共享网络数据就通过共享内存加硬件信号量HSEM来传递不要让另一个核直接调 lwIP API。HSEM 是 H7 系列专门为双核互斥设计的硬件信号量比软件关中断可靠得多尤其是在 M7 和 M4 同时访问同一个内存区域的时候。1.2 DMA 能访问的内存和 M7 的 Cache是两大隐形杀手STM32H7 的内存布局和 F4 很不一样它有多个物理上独立的 RAM 区域分散在不同电源域里。这对 DMA 来说是个大坑。以太网 DMA 要访问描述符和缓冲区但它并不是所有 RAM 都能访问。我整理了一个表方便对照RAM 区域容量所属域ETH DMA 能否访问备注DTCM128KBM7 内核域不能只能被 M7 内核访问DMA 完全摸不到ITCM64KBM7 内核域不能同上一般跑关键代码用AXI SRAM512KBD1 域能最常用强烈推荐放描述符SRAM1128KBD2 域能可以放 lwIP 内存池或接收缓冲区SRAM2128KBD2 域能同上SRAM332KBD2 域能较小适合放少量数据SRAM464KBD3 域能双核都能访问适合核间共享我第一次把 lwIP 的 PBUF 池放在了 DTCM 里因为那个地址编译起来最顺眼、速度最快。结果 DMA 根本写不进去接收描述符永远是空的网口完全收不到数据。后来才意识到 DTCM 是 M7 私有的DMA 总线根本绕不过去。所以内存区域选错是最隐蔽的“一切正常但网络不通”的原因之一。另一个隐形杀手是 M7 的 Cache。M7 有独立的指令 Cache 和数据 Cache速度很快但 DMA 不经过 Cache它直接读写物理内存。这就导致一个经典问题DMA 往内存里写了一包数据但 M7 读的时候还命中着旧 Cache读到的是旧数据。反过来M7 在 Cache 里改了描述符DMA 却去读物理内存读到的也是旧值。表现出来就是“关掉 Cache 一切正常一开 Cache 就 lwIP 死掉”非常折磨人。这个问题后面专门讲。2. lwIP 移植与配置这些参数决定了你后面会不会崩lwIP 本身是个非常成熟的协议栈源码层面一般不需要大改。真正让项目崩掉的往往是配置参数没调好、内存布局不对、缓存没处理好。所以配置阶段多花点心思比后期调试省力得多。2.1 从 STM32CubeH7 官方例程改不要自己从零搭如果你不是 lwIP 移植专家我强烈建议先从 STM32CubeH7 固件包里的 Ethernet 例程起步不要自己从零搭建。官方例程已经把 PHY 驱动、MAC 初始化、DMA 描述符、lwIP 的 ethernetif 层都写好了你要做的是剪裁和适配。STM32CubeH7 的例程默认是基于 H743 的但 H745 和 H743 的以太网外设基本一致主要是引脚号和时钟树不同。用 CubeMX 重新生成 H745 的时钟和引脚配置然后把官方例程的 lwIP 部分拿过来替换掉 CubeMX 自动生成的 ethernetif.c 即可。这样能避开很多低级的寄存器配置错误。我见过有人自己从头写 MAC 初始化写出来的代码在 F4 上能跑到 H7 上就不行原因是 H7 的 MAC 和 DMA 寄存器与 F4 不完全兼容。所以能用官方代码就用官方代码别在底层寄存器上浪费生命。2.2 必调参数内存池、PBUF、TCP 窗口lwIP 的性能和稳定性很大程度上由 opt.h 和 lwipopts.h 里的几个参数决定。我常用的配置参数如下大家可以参考但具体数值要根据你的实际 RAM 宽裕度来调参数推荐值说明MEM_SIZE16KB ~ 64KBlwIP 堆大小跑 TCP 建议不低于 32KBPBUF_POOL_SIZE16 ~ 32接收 PBUF 池数量太小会导致丢包PBUF_POOL_BUFSIZE默认即可每个 PBUF 的大小TCP_MSS1460标准以太网 MSS 值TCP_WND4 * TCP_MSS 或更大TCP 接收窗口窗口太小速度上不去TCP_SND_BUF4 * TCP_MSS 或更大TCP 发送缓冲LWIP_DHCP1需要 DHCP 时开启LWIP_NETCONN1使用 netconn API 时开启LWIP_SOCKET0 或 1使用 socket API 时开启不用则关掉省内存一个常见误区是把 MEM_SIZE 和 PBUF_POOL_SIZE 调得特别大以为这样网络就更稳。实际上 RAM 是有限的M7 的 AXI SRAM 虽然大但也要留给应用使用。如果内存不够lwIP 会在运行时因为分配失败而丢包甚至走到 LWIP_ASSERT 直接卡死。我一般先把 MEM_SIZE 配成 32KBPBUF_POOL_SIZE 配成 24跑通之后再根据压力测试结果微调。还有一点容易被忽略LWIP_NETCONN 和 LWIP_SOCKET 如果不用一定要关掉。它们会占不少 RAM还让编译体积变大。如果你的应用只是靠 raw API 做简单的 TCP/UDP 收发完全没必要开这两个选项。3. 解决缓存一致性一篇讲透 MPU 与 SCB 操作H745 上跑 lwIP最核心也最麻烦的问题就是 M7 的 Cache 与 DMA 的一致性。这个问题不解决后面所有调试都是白费功夫。我刚开始调的时候代码逻辑怎么看都对但就是 ping 不通后来发现就是 Cache 没处理好。3.1 MPU 配置把描述符和缓冲区设为非缓存区域解决 Cache 一致性问题最简单粗暴也最可靠的方法是用 MPU 把以太网 DMA 描述符和 lwIP 的 PBUF 内存区域配置为 Non-cacheable不可缓存。MPU 配置的核心代码如下这段代码在系统初始化时调用static void MPU_Config(void) { MPU_Region_InitTypeDef MPU_InitStruct {0}; HAL_MPU_Disable(); /* 配置 ETH DMA 描述符所在的 AXI SRAM 区域为不可缓存 */ MPU_InitStruct.Enable MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress 0x24000000; /* AXI SRAM 起始地址 */ MPU_InitStruct.Size MPU_REGION_SIZE_512KB; 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.TypeExtField MPU_TEX_LEVEL0; MPU_InitStruct.SubRegionDisable 0x00; MPU_InitStruct.DisableExec MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(MPU_InitStruct); HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); }这样配置之后M7 访问 AXI SRAM 里的以太网描述符和缓冲区时就不会经过 CacheCPU 和 DMA 看到的是同一份数据。这个方案的好处是简单、稳定不需要在代码里到处插异常 Cache 维护代码。代价是访问这个区域的速度稍慢一些但对以太网这种带宽需求来说完全足够。3.2 手动 Clean/Invalidate 的时机与顺序如果你不想把整个区域设为 Non-cacheable比如你想把 AXI SRAM 的一部分留作高速计算用那就要手动管理 Cache 一致性。手动维护 Cache需要记住几个关键时机CPU 写描述符给 DMA 之前要对描述符区域执行 CleanDCache把 Cache 里的脏数据写回内存DMA 写完内存、CPU 要读取之前要对对应区域执行 InvalidateDCache让 Cache 失效强制从内存重新读取具体到 lwIP 的 ethernetif.c改动点一般在 low_level_output 和 low_level_input 这两个函数里/* 发送前把发送缓冲区写回内存确保 DMA 能看到最新的数据 */ SCB_CleanDCache_by_Addr((uint32_t *)q-payload, q-len); /* 接收后让 Cache 失效确保 CPU 读到的是 DMA 写入的最新数据 */ SCB_InvalidateDCache_by_Addr((uint32_t *)p-payload, p-len);这个过程有个容易踩的坑Clean 和 Invalidate 的地址必须按照 Cache Line 对齐。Cortex-M7 的 Cache Line 是 32 字节所以传入的地址和长度最好都对齐到 32 字节否则可能会把相邻的数据也一并清掉或忽略。实测下来最省心的做法还是上面说的 MPU 方案把整个区域设为 Non-cacheable然后用普通指针操作永远不用担心对齐问题。4. 实操流程从点灯到 ping 通的完整记录下面我把从零开始调通 H745 lwIP 的完整流程记录一遍包括硬件检查、CubeMX 配置、裸机链路自测、FreeRTOS 集成几个阶段。这套流程我在这块板子上实测走通过照着来能少走很多弯路。4.1 硬件检查PHY、时钟、复位一个都不能少软件调不通很多时候是硬件设计的问题。H745 的以太网对硬件要求比较高在写代码之前先把硬件确认清楚。第一是 PHY 芯片型号和地址。常见的搭配有 LAN8720、DP83848、RTL8201F 等。每个 PHY 的地址不一样比如 LAN8720 的默认地址通常是 0DP83848 是 0x01 或 0x0C。这个地址必须和代码里 ETH PHY Address 配置一致否则 MDIO 读写不到 PHY链路状态永远不对。第二是时钟。RMII 接口需要 50MHz 参考时钟MII 接口需要 25MHz。这个时钟可以由外部有源晶振提供也可以由 STM32 的 MCO 引脚输出还可以由 PHY 芯片自己产生并回传给 STM32。比如 LAN8720 常见方案是给 PHY 一个 25MHz 晶振PHY 内部倍频到 50MHz再通过 REF_CLK 引脚输出给 STM32。CubeMX 里的 ETH 配置要和实际硬件接法保持一致否则 MAC 和 PHY 的时钟不同步link 直接起不来。第三是 PHY 复位引脚。很多原理图上 PHY 的复位直接接到了 STM32 的 GPIO 上但 GPIO 默认状态可能是高电平导致 PHY 一直处于复位状态。所以初始化代码里要先确认复位引脚的电平逻辑拉低一段时间再拉高完成一次明确的硬件复位。4.2 CubeMX 配置要点CubeMX 配置 H745 的 ETH有几个关键点选择 ETH1 还是 ETH2取决于你硬件上用的是哪个 MAC接口选 RMII 还是 MII要和硬件一致PHY 地址填成和硬件一致使能 ETH 的中断中断优先级建议设置成 5~10FreeRTOS 环境下不要高于 configMAX_SYSCALL_INTERRUPT_PRIORITY如果要用 FreeRTOS在 Middleware 里勾选 LWIP并配置成带 OS 的模式生成代码之后重点检查一下 eth.c 里的时钟配置是否正确。H745 的以太网时钟来自 PLL如果 PLL 配置不对MAC 和 PHY 之间会出现大量 CRC 错误。4.3 裸机链路自测先别急着跑协议栈在把 lwIP 接进去之前我强烈建议先做一次裸机链路自测。这一步的目的是确认 MAC、DMA、PHY、网线链路全是通的把问题范围缩小到 lwIP 之外。具体做法是初始化完 ETH 和 PHY 之后循环读取 PHY 的状态寄存器比如 PHY BSR 寄存器打印 link 状态。如果网线插上了状态寄存器里应该看得到 link up。然后再检查 ETH 的 MAC 接收统计寄存器看有没有收到来自对端比如 PC 持续 ping 或者发广播包的数据帧。如果能看到接收帧计数在增加说明硬件链路和数据通路已经通了问题大概率在 lwIP 层面。这一步看着简单但它能帮你省下大把的 debug 时间。我每次调新板子都会先写一个十几行的裸机测试程序确认链路无碍再往上叠协议栈。4.4 FreeRTOS 与 lwIP 的协作方式H745 上跑 FreeRTOS lwIP是很典型的组合。lwIP 在带 OS 的模式下会创建一个 tcpip_thread 来处理协议栈核心逻辑应用通过 API 跟它通信。ETH 的中断服务函数里不要做太多事情只做两件读取接收描述符然后触发信号量或直接向接收任务发通知。真正的协议栈处理放在任务上下文里。中断优先级的设置很关键。H7 的 NVIC 优先级配置里如果某个中断的优先级数值比 FreeRTOS 的 configMAX_SYSCALL_INTERRUPT_PRIORITY 小数值越小优先级越高那么这个中断里就不能调用任何 FreeRTOS API。ETH 中断如果不在允许范围内调用 osSemaphoreRelease 就直接崩了。我这边常用的做法是ETH 中断优先级设为 10FreeRTOS 的 configMAX_SYSCALL_INTERRUPT_PRIORITY 默认是 5这样 ETH 中断可以被 FreeRTOS API 调用同时又不影响系统调度。以太网数据都要经过 DMA中断服务程序只是通知一下不会长时间占用 CPU优先级不需要太高。5. 经典问题实录ping 不通、随机死机、速度上不去这一节把我实际遇到的几个典型问题原原本本记录下来包括现象、排查过程和最终解法。这些问题是 H745 lwIP 最常见的几个大坑其他地方可能也会遇到但 H745 上尤其多人踩。5.1 Ping 不通链路检测却正常这个现象非常迷惑裸机链路自测明明能看到 link upMAC 接收计数也在涨但 PC 上 ping 就是不通。我排查了很久最后发现是 DMA 描述符的内存放在了不可访问的地址上。很多人容易犯这个错误描述符数组定义在 DTCM 或者某个非 DMA 区域。H745 的描述符如果放在 DTCM 里DMA 根本读不到发送描述符一直是空转状态。解法就是前面说的把描述符放到 AXI SRAM 或者 SRAM1/2/3 里然后用 MPU 把对应区域设为不可缓存。这种情况的典型排查方法是在发送函数里设置一个断点看看发送描述符的状态位有没有被 DMA 清掉。如果 DMA 读不到描述符状态位永远不变那基本就是内存区域选错了。5.2 lwIP 跑几分钟后 HardFault跑了一段时间才死机的问题是最头疼的。我遇到过的情况是板子连续运行五到十分钟之后突然 HardFault复位之后又能跑一阵。这类问题多半是内存踩踏或者缓冲区溢出。我排查的突破口是定位 HardFault 的进栈寄存器。在 HardFault_Handler 里读一下堆栈指针把 PC、LR、堆栈内容 dump 出来然后对照 map 文件看代码落在哪个函数里。我那次查下来跑到了 lwIP 的 mem_free 附近基本能确定是堆被踩了。根源其实是 TCP_SND_BUF 配得比较小但发送任务又一直往里面塞数据导致某个缓冲区被越界写坏。解决方法是把 TCP_SND_BUF 和 TCP_WND 调成匹配的值比如 4 * TCP_MSS并且给发送任务加一个流控数据量大的时候等一等不要无脑往发送缓冲区里灌。另外还有一种 HardFault 原因栈溢出。FreeRTOS 默认给每个任务分配的栈大小是有限的如果 lwIP 的接收任务或者 tcpip_thread 栈不够大调用层级深一点就会踩到栈底。建议给 tcpip_thread 分配 1024 到 2048 字的栈并且开启 FreeRTOS 的栈溢出检测跑起来之后看是哪个任务触发。5.3 实测带宽只有 1~2MB/s上不去H745 的以太网是 100Mbps 的物理层理论极限大概 11MB/s实际打满也就 9~10MB/s。但如果实测只有 1~2MB/s那肯定有配置问题。最常见的瓶颈是 TCP 窗口太小。如果 TCP_WND 只是 2 * TCP_MSS也就是 4KB 左右那 TCP 的滑动窗口太小效率会大打折扣。建议至少配到 16KB 以上条件允许的话上 32KB 或者 64KB。第二个瓶颈是 Cache 没有正确处理。如果 DMA 写入数据后CPU 每次读取都要走 Cache miss 去内存拿速度会受到影响更严重的是如果 Cache 一直拿到旧数据还会触发出错重传进一步拉低速度。前面讲的 MPU 配置在这里会体现得特别明显。第三个瓶颈是接收中断太频繁。100Mbps 的流量下每来一包数据就触发一次中断CPU 都被中断打满了。解决思路是用 lwIP 的零拷贝接收或者在中断里快速把数据搬走减少中断处理时间。实测下来把描述符数量和 PBUF_POOL_SIZE 加大到 32 个左右吞吐量会有比较明显的提升。还有一点如果 M7 的 D-Cache 没开靠 CPU 从内存里逐个字节拷贝数据速度也会很感人。H745 的 M7 跑 480MHz不开 Cache 也能跑 TCP但效率天差地别。正确姿势是开 Cache用 MPU 把网络区域设为不缓存其余区域正常走 Cache。6. 排查工具与 GitHub Issue 的正确用法最后分享一些排查和资料查找的经验。遇到 lwIP 问题不可怕可怕的是没有系统的排查方法和资料来源。6.1 日志、寄存器、调试器三板斧排查网络问题我一般按这个顺序来看 PHY 寄存器确认 link 状态、是否 auto-negotiation 完成看 MAC 统计寄存器确认收到多少帧、丢弃多少帧、有没有 CRC 错误在 lwIP 的接收和发送路径上加打印日志确认协议栈有没有收到数据、有没有在正常发数据用调试器观察描述符状态确认 DMA 有没有正常搬运数据最后才去猜协议栈配置问题这套流程走下来大部分问题都能定位到具体环节。网上也有人说“先把 lwIP 的 LWIP_DEBUG 打开”但 LWIP_DEBUG 打开之后日志量非常大在嵌入式串口上打印会拖慢速度我建议只在问题初期打开定位之后马上关掉。6.2 怎么用好 GitHub Issue 和社区案例说到查资料GitHub issue 绝对是个被低估的资源。很多你在百度上搜不到的冷门问题GitHub issue 里早就有人踩过并给出了解决方案。检索方法是直接在 GitHub 搜索stm32h7 lwip、stm32h745 ethernet、lwip hardfault这类关键词然后看 issue 列表。有些 issue 讨论得非常深入比如官方驱动在某个版本下存在 cache 维护遗漏或者某个 lwIP 版本在 M7 上需要额外配置 MPU这些细节在官方文档里根本找不到。在提 issue 之前注意先把你的问题信息准备齐全芯片型号、IDE 版本、lwIP 版本、PHY 型号、复现步骤、日志信息。信息越详细别人越可能帮你定位。如果你只是写一句“我的网络不通”基本没人理的。另外ST 官方论坛的 STM32 以太网板块也值得定期逛。很多 H7 相关的已知问题官方工程师会直接回复 workaround。我的经验是H7 系列的以太网问题90% 都能在官方论坛或者 GitHub issue 里找到线索关键是搜索词要准别用太泛的词。最后再分享一点个人体会调 H745 lwIP 这段时间我最大的感受是这类问题极少是 lwIP 协议栈本身的 bug绝大多数是工程问题——内存放错位置、Cache 没处理好、参数不匹配。调的时候保持耐心按链路顺序一步步排查比乱试配置靠谱得多。特别是 H745 这种双核高性能芯片它的资源比单核 MCU 多但对内存和总线的要求也更严格。先把底层的内存、Cache、DMA 这几个基础打牢lwIP 跑起来其实也就顺理成章了。希望这篇文章能帮你少熬几个夜。
返回列表