尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32F407上FreeRTOS与LwIP协同设计实战

STM32F407上FreeRTOS与LwIP协同设计实战 1. 为什么在 STM32F407 上同时跑 FreeRTOS 和 LwIP 不是“加个库就完事”FreeRTOS 和 LwIP 这两个名字对嵌入式开发者来说几乎等同于“嵌入式网络应用”的默认组合。但真正动手把它们塞进 STM32F407 的 192KB SRAM 和 1MB Flash 里并让它们不打架、不卡死、不丢包——这绝不是 Keil 里点几下鼠标、复制粘贴几段初始化代码就能搞定的事。我第一次在 F407 上跑通这两个组件时花了整整三周前两天以为是网口硬件没接好中间五天怀疑是 PHY 芯片DP83848配置错了寄存器最后十天全耗在任务堆栈溢出和内存碎片上。最离谱的一次系统跑着跑着突然 ping 不通用逻辑分析仪抓到的是 FreeRTOS 的 SysTick 中断被 LwIP 的ethernetif_input()卡住超过 8ms而这个时间刚好超过了 TCP 重传定时器的最小阈值。问题根源不在代码写得对不对而在于你有没有真正理解这两个组件在 Cortex-M4 架构上的资源争夺本质。FreeRTOS 是一个抢占式实时内核它靠 SysTick 定时器驱动调度所有任务切换、延时、信号量操作都依赖精确的 tick 计数LwIP 则是一个轻量级协议栈它不强制要求实时性但对中断响应延迟极其敏感——尤其是以太网接收中断ETH_IRQn一旦处理不及时DMA 接收缓冲区就会溢出帧直接被丢弃上层根本不知道发生了什么。而 STM32F407 的 ETH 外设又偏偏没有独立的 DMA 通道仲裁器它的 ETH_RX 和 ETH_TX 共享同一组 AHB 总线带宽。这意味着当 LwIP 正在 memcpy 一帧 1500 字节的 TCP 数据时FreeRTOS 的 PendSV 中断可能正在排队等待总线访问权限导致任务切换延迟飙升。更隐蔽的是内存模型冲突。FreeRTOS 默认使用静态内存分配heap_4.c所有任务堆栈、队列缓冲区都在一块连续的ucHeap[]数组里管理而 LwIP 在lwipopts.h里默认启用动态内存MEM_LIBC_MALLOC0时走mem_malloc()它自己维护一套小块内存池MEM_SIZE。这两套内存管理器如果共用同一片 RAM 区域不出三天必崩——不是指针越界而是heap_4的链表头被 LwIP 的memp_malloc()操作意外覆盖。我见过最典型的症状是xTaskCreate()返回pdFAIL但xPortGetFreeHeapSize()显示还有 40KB 空闲查到最后发现是 LwIP 的memp内存池初始化时把ucHeap的前 16 字节当成了自己的 pool header。所以这篇笔记不叫“FreeRTOS LwIP 移植教程”它叫“STM32F407 上的双核级资源协同设计”。你要做的不是把两个开源项目拼在一起而是像设计一个微型操作系统那样给 CPU 时间、总线带宽、RAM 空间这三项硬资源画一张清晰的“地籍图”。提示不要迷信官方 demo。ST 提供的STM32Cube_FW_F4_V1.27.0里的freertos_lwip例程默认关闭了 FPU禁用了 Cache且将 LwIP 的tcp_timer_interval设为 250ms——这在实验室 ping 通就算成功但在真实工业现场这种配置会让 Modbus TCP 从站响应延迟波动超过 120ms直接触发主站超时重连。2. STM32F407 硬件层必须确认的七项生死细节在写任何一行 C 代码之前你得先用万用表和示波器把板子的物理层钉死。很多“移植失败”案例90% 根源都在这里。我整理了一份 STM32F407 网络硬件检查清单每一项都对应一个经典故障现象2.1 PHY 芯片供电与复位时序DP83848 为例DP83848 的RESET引脚不是低电平复位那么简单。它的 datasheet 明确要求VDDA/VDDIO 上电稳定后RESET必须保持低电平至少10ms然后拉高再等待300ms才能读取寄存器。很多开发板把 RESET 直接连到 STM32 的 NRST结果 MCU 复位完成时 PHY 还在内部振荡器起振阶段。实测表现是PHY_ReadReg(0, PHY_REG_BMSR)永远返回 0x0000LwIP 初始化卡在phy_init()。正确做法用 STM32 的 GPIO如 PG10单独控制 PHY RESET在SystemInit()后插入硬延时HAL_GPIO_WritePin(PHY_RESET_GPIO_PORT, PHY_RESET_PIN, GPIO_PIN_RESET); HAL_Delay(15); // 确保 10ms HAL_GPIO_WritePin(PHY_RESET_GPIO_PORT, PHY_RESET_PIN, GPIO_PIN_SET); HAL_Delay(350); // 确保 300ms2.2 RMII 接口信号完整性重点REF_CLKSTM32F407 的 ETH 使用 RMII 模式时REF_CLK50MHz必须由 PHY 提供DP83848 的CLK_OUT引脚而非 STM32 自己生成。这是 RMII 协议的硬性规定。但很多原理图错误地将REF_CLK接到 STM32 的MCO引脚试图用 RCC 输出 50MHz——这会导致 PHY 和 MAC 的时钟域完全异步表现为ETH-DMABMR ETH_DMABMR_SR永远为 0DMA 未就绪或者偶发性接收中断但ETH-DMARLAR指向的缓冲区数据全是 0xFF。验证方法用示波器测 DP83848 的CLK_OUT引脚必须看到干净的 50MHz 方波峰峰值 3.3V抖动 1ns。如果无信号检查 PHY 的MODE引脚是否接地RMII 模式以及CRS_DV是否正确连接到 PA7。2.3 ETH 引脚重映射与电气匹配F407 的 ETH 引脚默认在 PB11/PB12/PC1/PC4 等位置但这些引脚需要开启SYSCFG时钟并调用__HAL_RCC_SYSCFG_CLK_ENABLE()。更致命的是PA1/PA2TXD0/TXD1和 PA7CRS_DV必须配置为推挽输出 50MHz 速度 上拉否则在长网线30m场景下信号边沿会严重过冲导致 PHY 误判帧起始。我遇到过一个案例网线换到 50m 后ping 命令成功率从 100% 降到 30%用示波器一看PA1 的上升沿有 2.5V 过冲直接削顶。关键配置代码GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_AF_PP; // 注意必须是复用推挽 GPIO_InitStruct.Pull GPIO_PULLUP; // 必须上拉 GPIO_InitStruct.Speed GPIO_SPEED_FREQ_VERY_HIGH; // 50MHz GPIO_InitStruct.Alternate GPIO_AF11_ETH; HAL_GPIO_Init(GPIOA, GPIO_InitStruct);2.4 DMA 缓冲区地址对齐Cache 陷阱F407 开启了 I-Cache 和 D-Cache而 ETH DMA 的描述符Descriptor和数据缓冲区Buffer必须位于非缓存区域否则会出现“写缓冲区后读不到最新值”的经典 Cache 一致性问题。典型症状ETH-DMASR显示RS接收状态为 1但ETH-DMARDLAR指向的缓冲区数据仍是旧的。解决方案将 DMA 描述符和缓冲区定义在特定内存段// 在 linker script (.ld) 中添加 MEMORY { RAM (xrw) : ORIGIN 0x20000000, LENGTH 192K NON_CACHE_RAM (xrw) : ORIGIN 0x2001C000, LENGTH 8K // 最后 8KB 设为 non-cache } SECTIONS { .non_cache_data (NOLOAD) : { *(.non_cache_data) } NON_CACHE_RAM }在 C 文件中声明__attribute__((section(.non_cache_data))) ETH_DMADescTypeDef DMARxDscrTab[ETH_RXBUFNB]; __attribute__((section(.non_cache_data))) ETH_DMADescTypeDef DMATxDscrTab[ETH_TXBUFNB]; uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE] __attribute__((section(.non_cache_data)));2.5 VBUS 检测与 Type-C 兼容性针对 PA8标题里提到的 “stm32f407 pa8 vbus typec” 是个高频坑点。PA8 在 F407 上复用为 USB OTG_FS_VBUS但如果你的板子用的是 Type-C 接口PA8 必须接一个分压电阻网络比如 100kΩ 100kΩ到 Type-C 的 CC1/CC2 引脚而不是直接接 VBUS。因为 Type-C 的 VBUS 是 5V而 PA8 是 3.3V 容限 IO直连会永久损坏芯片。实测烧毁三片 F407 的案例中有两片是这个原因。2.6 FPU 开启与浮点 ABI 一致性FreeRTOS 的port.c默认编译为soft-float但如果你在main()里调用了HAL_Delay()内部用SysTick和uwTick而uwTick是uint32_t看似无关。但一旦你开启 FPU__FPU_PRESENT 1Keil/IAR 默认使用hard-floatABI此时printf(%f, 3.14)会从 s0-s15 寄存器取参数而port.c的上下文保存/恢复代码却只保存 r0-r12、sp、lr——s0-s15 完全丢失导致任务切换后浮点运算结果错乱。必须同步配置在FreeRTOSConfig.h中定义#define configUSE_TASK_FPU_SUPPORT 2 // 2 表示所有任务都使用 FPU在 Keil 中Options → Target → Floating Point Hardware → Use FPU在 IAR 中Options → General Options → Library Configuration → Use floating point library → Full在 GCC 中编译选项加-mfloat-abihard -mfpufpv42.7 时钟树中的 ETH 时钟源选择F407 的 ETH 时钟必须来自PLLQ48MHz不能用HSE直接分频。这是因为 RMII 的REF_CLK需要 50MHz而 PHY 内部 PLL 会将 48MHz 锁相到 50MHz。如果错误地将RCC_PLLI2SN配置为 192MHz 并分频给 ETH会导致ETH-MACCR的FES速率为 10/100位无法置位ETH-MACFCR的流控寄存器写无效。正确配置片段HAL 库RCC_PeriphCLKInitTypeDef PeriphClkInitStruct {0}; PeriphClkInitStruct.PeriphClockSelection RCC_PERIPHCLK_ETH; PeriphClkInitStruct.EthClockSelection RCC_ETHCLKSOURCE_PLL; // 必须是 PLL不是 HSE HAL_RCCEx_PeriphCLKConfig(PeriphClkInitStruct);3. FreeRTOS 内存与任务架构的底层重构把 FreeRTOS 移植到 F407 上最大的陷阱不是 API 调用错误而是对heap_x.c的盲目信任。F407 的 192KB SRAM 是一块珍贵的连续资源而heap_4.c的隐式碎片化会在运行 72 小时后让xPortGetFreeHeapSize()显示 32KB实际却无法分配一个 2KB 的队列缓冲区。这不是 Bug是设计使然——heap_4用双向链表管理空闲块频繁 malloc/free 后小块内存被切割得支离破碎。3.1 为什么放弃 heap_4转向 heap_5 的显式分区heap_5.c的核心思想是把 RAM 划分为多个独立、不重叠的内存池每个池专供特定用途。例如Pool 0128KB给 FreeRTOS 任务堆栈和队列Pool 132KB专供 LwIP 的MEM_SIZEPool 28KB留给pvPortMalloc()的临时大块分配如 OTA 固件解压Pool 34KB作为heap_5自身的管理元数据存储区。这样做的好处是彻底隔离干扰。LwIP 的mem_malloc()只能在 Pool 1 里操作即使它把 Pool 1 的 32KB 全部碎片化Pool 0 的 128KB 依然完整可用xTaskCreate()永远不会失败。heap_5 初始化代码// 定义四个内存池需在 linker script 中预留 extern uint8_t _heap_pool0_start; // 0x20000000 extern uint8_t _heap_pool0_end; // 0x20020000 (128KB) extern uint8_t _heap_pool1_start; // 0x20020000 extern uint8_t _heap_pool1_end; // 0x20028000 (32KB) extern uint8_t _heap_pool2_start; // 0x20028000 extern uint8_t _heap_pool2_end; // 0x2002A000 (8KB) extern uint8_t _heap_pool3_start; // 0x2002A000 extern uint8_t _heap_pool3_end; // 0x2002B000 (4KB) void vApplicationMallocFailedHook(void) { // 当所有池都满时触发可点亮 LED 或进入死循环 __BKPT(0); } void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) { // 任务堆栈溢出记录日志并重启 Error_Handler(); } void vPortDefineHeapRegions(const HeapRegion_t * const pxHeapRegions) { static HeapRegion_t xHeapRegions[] { { (uint8_t *)_heap_pool0_start, 0x20000 }, // 128KB { (uint8_t *)_heap_pool1_start, 0x8000 }, // 32KB { (uint8_t *)_heap_pool2_start, 0x2000 }, // 8KB { (uint8_t *)_heap_pool3_start, 0x1000 }, // 4KB { NULL, 0 } // 结束标志 }; vPortDefineHeapRegions(xHeapRegions); }3.2 任务堆栈大小的科学计算法不是拍脑袋很多人设usStackDepth 256理由是“别人这么写”。但 F407 的 Cortex-M4F 在执行浮点运算时一次vadd.f32指令会压栈 16 字节s0-s15 寄存器而printf()的格式化引擎会递归调用strlen()和memcpy()深度可达 8 层。实测一个带printf(Temp: %f\n, temp)的任务在configUSE_TASK_FPU_SUPPORT2下最小安全堆栈是384 字注意单位是字不是字节。计算公式最小堆栈 (函数调用最大深度 × 16) (浮点寄存器保存空间 × 16) (局部变量总大小) 256安全余量其中函数调用深度用 Keil 的View → Windows → Call Stack查看浮点寄存器configUSE_TASK_FPU_SUPPORT2时为 16×464 字节局部变量在调试模式下右键变量 →Go To Definition查看结构体大小。我推荐一个保守但可靠的初始值空闲任务512 字必须最大因它处理所有后台工作LwIP TCP/IP 任务1024 字tcpip_thread会处理 ARP、ICMP、TCP 定时器应用任务如 Modbus 服务768 字中断服务任务如按键扫描256 字。3.3 LwIP 与 FreeRTOS 的 IPC 机制选型队列 vs 信号量 vs 直接通知LwIP 的tcpip_input()函数需要把接收到的网络帧交给tcpip_thread处理。传统做法是创建一个xQueueCreate(10, sizeof(struct pbuf*))然后在 ETH 中断里xQueueSendFromISR()。但这是低效的——每次发送都要拷贝struct pbuf*指针4 字节还要触发队列互斥锁。FreeRTOS 5.0 提供了更优的方案直接任务通知Direct to Task Notification。它比队列快 47%且零内存分配。改造步骤在tcpip_init()后获取tcpip_thread的句柄static TaskHandle_t xTcpipTaskHandle NULL; err_t tcpip_init_done_callback(void *arg) { xTcpipTaskHandle xTaskGetCurrentTaskHandle(); return ERR_OK; } tcpip_init(tcpip_init_done_callback, NULL);在 ETH 中断服务程序中void ETH_IRQHandler(void) { BaseType_t xHigherPriorityTaskWoken pdFALSE; if (__HAL_ETH_DMA_GET_IT_SOURCE(heth, ETH_DMA_IT_R) ! RESET) { // 清中断标志 __HAL_ETH_DMA_CLEAR_IT(heth, ETH_DMA_IT_R); // 通知 tcpip_task传递 pbuf 地址用通知值的高 16 位存地址 ulTaskNotifyTakeIndexed(0, pdTRUE, 0); // 清除通知值 ulTaskNotifyValueClearIndexed(0, 0xFFFF0000UL); // 清除高 16 位 ulTaskNotifyValueSetIndexed(0, 0, (uint32_t)pbuf_ptr); // 存入 pbuf 地址 vTaskNotifyGiveIndexed(xTcpipTaskHandle, 0); // 发送通知 } }在tcpip_thread主循环中void tcpip_thread(void const * argument) { for(;;) { ulTaskNotifyTakeIndexed(0, portMAX_DELAY); // 等待通知 uint32_t pbuf_addr ulTaskNotifyValueGetIndexed(0, 0) 0xFFFF0000UL; struct pbuf *p (struct pbuf*)pbuf_addr; if (p) { ethernetif_input(gnetif, p); // 正常处理 } } }注意ulTaskNotifyValueSetIndexed()的第二个参数是索引0-7F407 的 Cortex-M4 支持最多 8 个通知索引足够隔离 ETH、UART、SPI 等不同外设的事件。4. LwIP 协议栈的裁剪与性能调优实战LwIP 默认配置是为 Linux PC 设计的直接搬到 F407 上会吃掉 80KB Flash 和 40KB RAM且 TCP 吞吐量不足 1.2Mbps。我们必须做外科手术式裁剪。4.1 lwipopts.h 的关键参数重设基于 100Mbps RMII以下是我经过 127 次压力测试iperf3 Modbus TCP 混合流量后确定的最优值参数默认值推荐值说明NO_SYS00必须为 0否则无法与 FreeRTOS 集成LWIP_NETIF_API10关闭 netif API用 raw API 更高效LWIP_IPV600关闭 IPv6省下 12KB FlashLWIP_ARP11必须开启否则无法解析网关 MACLWIP_ICMP11必须开启用于 ping 和错误反馈LWIP_RAW10关闭 raw socket用 tcp/udp socket 足够LWIP_UDP11必须开启DNS 和 NTP 依赖 UDPLWIP_TCP11必须开启TCP_MSS5361460RMII 最大帧长 1518减去 IP/TCP 头 40 1460TCP_SND_BUF25608192发送缓冲区影响吞吐量TCP_WND20488192接收窗口必须 ≥TCP_SND_BUFMEM_SIZE16KB32KBheap_5 中 Pool 1 的大小必须 ≥MEMP_NUM_PBUF * 256MEMP_NUM_PBUF1632pbuf 数量每帧至少需 1 个 pbufMEMP_NUM_TCP_SEG1664TCP 分段数量影响并发连接数MEMP_NUM_NETBUF1032netbuf 数量UDP 传输必需特别注意TCP_SND_BUF和TCP_WND的关系TCP_SND_BUF是发送端的滑动窗口大小单位字节TCP_WND是接收端通告的窗口大小如果TCP_WND TCP_SND_BUF发送端会因等待 ACK 而阻塞实测 F407 在TCP_WND8192时iperf3 TCP 吞吐量达 92Mbps理论 100Mbps 的 92%而TCP_WND2048时仅 23Mbps。4.2 PHY 寄存器的深度初始化DP83848LwIP 的phy_reset()只做了基础复位但 DP83848 有 32 个寄存器其中 5 个对稳定性至关重要寄存器地址推荐值作用PHY_REG_BMCR0x000x3100100Mbps 全双工 自协商使能PHY_REG_ANAR0x040x01E1通告支持 100BASE-TX 全/半双工、10BASE-T 全/半双工PHY_REG_PHYCR0x190x3000启用节能以太网EEE降低功耗PHY_REG_PHYCTRL0x1F0x0000关闭自动 MDI/MDIX避免握手失败PHY_REG_PHYSTS0x10读取检查LINK_STATUS和DUPLEX_STATUS位初始化函数void phy_init_dp83848(void) { uint16_t reg_val; // 复位 PHY_WriteReg(0, PHY_REG_BMCR, 0x8000); HAL_Delay(10); // 配置 BMCR PHY_WriteReg(0, PHY_REG_BMCR, 0x3100); // 100Mbps, full-duplex, auto-negotiation // 配置 ANAR PHY_WriteReg(0, PHY_REG_ANAR, 0x01E1); // 配置 PHYCR (节能) PHY_WriteReg(0, PHY_REG_PHYCR, 0x3000); // 关闭 MDI/MDIX PHY_WriteReg(0, PHY_REG_PHYCTRL, 0x0000); // 等待链路建立 for(int i0; i100; i) { PHY_ReadReg(0, PHY_REG_PHYSTS, reg_val); if(reg_val 0x0004) break; // LINK_STATUS bit HAL_Delay(10); } }4.3 TCP 连接数与内存的硬约束平衡F407 的 RAM 有限MEMP_NUM_TCP_PCBTCP 控制块数量不能无限制增加。每个 TCP PCB 占用约 280 字节 RAM32 个就是 8.96KB。但更重要的是MEMP_NUM_TCP_SEGTCP 分段数量它决定了并发发送能力。一个 TCP 连接在TCP_SND_BUF8192时最多需要8192 / 1460 ≈ 6个分段所以MEMP_NUM_TCP_SEG至少为MEMP_NUM_TCP_PCB × 6。我的生产环境配置MEMP_NUM_TCP_PCB 8支持 8 个并发 TCP 连接MEMP_NUM_TCP_SEG 648×616 冗余MEMP_NUM_UDP_PCB 4DNS/NTP/Modbus UDP 各占 1 个MEMP_NUM_NETBUF 32UDP 接收缓冲这样总 RAM 占用PCB8×280 2240 字节Seg64×128 8192 字节UDP PCB4×120 480 字节Netbuf32×128 4096 字节PBUF32×256 8192 字节合计23.2KB占 Pool 132KB的 72.5%留有足够余量。4.4 LwIP 的调试技巧如何定位“Ping 通但 HTTP 不通”这是最折磨人的故障。现象ping 192.168.1.100成功率 100%但浏览器访问http://192.168.1.100超时。原因几乎总是 TCP 状态机卡在SYN_SENT或ESTABLISHED但无数据流动。排查链路用 Wireshark 抓包看客户端是否发出SYNF407 是否回复SYN-ACK如果SYN-ACK发出但客户端收不到检查ETH-MACFFR的RA接收所有位是否置位以及ETH-MACFCR的TFE发送流控是否关闭如果SYN-ACK收到但无后续ACK检查tcpip_thread是否被更高优先级任务饿死——用uxTaskGetSystemState()查看各任务运行时间占比如果ACK收到但 HTTP GET 不响应检查httpd服务任务的堆栈xTaskGetStackHighWaterMark(NULL)返回值 100说明堆栈溢出HTTP 解析器崩溃。终极调试工具在tcp_input()函数开头插入if(p-len 64) { printf(TCP len%d, src%d.%d.%d.%d:%d, dst%d.%d.%d.%d:%d\n, p-len, ip4_addr1(ip_current_src_addr()), ip4_addr2(ip_current_src_addr()), ip4_addr3(ip_current_src_addr()), ip4_addr4(ip_current_src_addr()), ntohs(((struct tcphdr*)((u8_t*)p-payload IP_HLEN))-src), ip4_addr1(ip_current_dest_addr()), ip4_addr2(ip_current_dest_addr()), ip4_addr3(ip_current_dest_addr()), ip4_addr4(ip_current_dest_addr()), ntohs(((struct tcphdr*)((u8_t*)p-payload IP_HLEN))-dest)); }这会打印每帧 TCP 的长度和端口一眼看出是 SYN、ACK 还是 HTTP 数据。5. 工程级联调与稳定性加固完成单模块功能只是开始。真正的挑战是让 FreeRTOS 和 LwIP 在 7×24 小时运行中不出现内存泄漏、任务饥饿、网络抖动。5.1 堆栈溢出的实时检测与自愈FreeRTOS 的vApplicationStackOverflowHook()只在溢出发生时触发但此时任务控制块已损坏无法安全重启。我们需要前置预警。实现方法在每个任务创建时用xTaskGetStackHighWaterMark()定期采样void stack_monitor_task(void const * argument) { for(;;) { // 检查所有任务堆栈水位 TaskStatus_t task_status[10]; uint32_t num_tasks uxTaskGetNumberOfTasks(); if(num_tasks 10) num_tasks 10; uxTaskGetSystemState(task_status, num_tasks, NULL); for(int i0; inum_tasks; i) { uint32_t high_water uxTaskGetStackHighWaterMark(task_status[i].xHandle); if(high_water 128) { // 剩余 128 字危险 // 记录日志触发软复位 printf(STACK WARNING: %s, free%d\n, task_status[i].pcTaskName, high_water); HAL_NVIC_SystemReset(); } } osDelay(5000); // 每 5 秒检查一次 } }5.2 LwIP 内存泄漏的黄金检测法LwIP 的pbuf_free()必须与pbuf_alloc()配对。漏调一次MEMP_NUM_PBUF就少一个最终导致pbuf_alloc()返回 NULLETH 接收中断停止。检测宏在pbuf.c中修改pbuf_alloc()struct pbuf* pbuf_alloc(pbuf_layer l, u16_t length, pbuf_type type) { struct pbuf *p NULL; static uint16_t pbuf_count 0; p pbuf_alloc_new(l, length, type); if(p) { pbuf_count; printf(pbuf alloc: %d total\n, pbuf_count); } return p; } void pbuf_free(struct pbuf *p) { if(p) { static uint16_t pbuf_count 0; pbuf_count--; printf(pbuf free: %d total\n, pbuf_count); pbuf_free_real(p); } }在串口终端观察pbuf alloc和pbuf free的数字是否始终相等。如果不等说明某处pbuf_free()被遗漏。5.3 网络抗干扰设计ARP 缓存老化与 ICMP 重定向工业现场电磁干扰强可能导致 ARP 表项失效。LwIP 默认ARP_QUEUEING关闭ARP_TABLE_SIZE10ARP_MAXAGE3005 分钟。这意味着如果网关 MAC 地址变化如交换机重启F407 会继续向旧 MAC 发包 5 分钟。加固方案开启ARP_QUEUEING让未解析 ARP 的包排队等待将ARP_MAXAGE降至 601 分钟在ethernetif.c的low_level_output()中加入 MAC 地址校验if(memcmp(ehdr-dest, gnetif-hwaddr, 6) 0 || memcmp(ehdr-dest, ethbroadcast_addr
返回列表