资讯动态

STM32F407+DP83848+LwIP以太网移植与调试实战

发布时间:2026/10/26 4:28:27 来源:尧图企业网站定制
简介这份例程包面向嵌入式开发学习者与工程师聚焦STM32F407平台上LWIP协议栈与DP83848以太网PHY的移植与联调提供一套无操作系统环境下的TCP/IP server实现可直接验证网络数据收发。硬件设计不依赖外部内存只需一块STM32F407开发板和DP83848模块即可搭建实验环境降低了入门与复现门槛。压缩包共3458个文件约22.92MB以C源码和头文件为主体辅以启动汇编、工程配置、网页与说明文档等既保留完整工程结构也便于按模块查阅移植细节。已有4769人学习下载说明其在STM32网络开发方向具有一定参考价值。读者可从中获取协议栈适配、PHY驱动、TCP服务端收发流程及工程组织思路也可重点参考内存池配置、收发回调与PHY初始化等关键环节适合课程设计、项目预研和移植排错时对照使用。1. 从“网口灯不亮”说起手里这块 F407 板子网口用的是 DP83848不是 ST 官方例程默认的 LAN8720。很多人直接把 STM32CubeMX 生成的 LwIP 工程烧进去结果网口灯不亮、ping超时盯了一下午也找不到原因。问题多半出在两个地方一是 PHY 地址没配对二是 50MHz 的 RMII 参考时钟根本没进来。这份 STM32F407LWIPDP83848 移植例程就是把 Cortex-M4 的 ETH MAC、DP83848 这颗物理层芯片和 LwIP 协议栈这三层拼成一条完整的 TCP/IP 链路从 GPIO 复用、PHY 复位、netif 注册一直走到能稳定收发 TCP 报文。适合手里已经有 F407 板子、想把裸机 TCP/IP 跑起来的开发者也适合把它当参照去对接自己板子上那颗 PHY。2. 硬件与协议栈选型F407 的 MAC、DP83848 的 RMII 和 LwIP 各自负责什么这三者在系统里各管一段边界很清晰但交界处最容易出问题。STM32F407 内部集成的是以太网 MAC 控制器负责帧的组装、CRC 校验、DMA 搬运DP83848 是物理层收发器负责把 MAC 出来的数字信号变成双绞线上的差分电平同时完成自动协商、链路检测LwIP 是跑在 MAC 之上的协议栈处理 IP、ARP、ICMP、TCP 这些上层逻辑。MAC 和 PHY 之间走的是 RMII 接口协议栈和 MAC 之间靠ethernetif.c这一层胶水代码连接。2.1 RMII 九根线和那根 50MHz 时钟RMII 相对 MII 最大的区别是把数据线从 4 位砍到 2 位引脚数从 16 根降到 9 根代价是时钟必须跑到 50MHz。这九根线在 F407 上全部走 AF11 复用具体对应关系如下信号名F407 引脚方向说明RMII_REF_CLKPA1输入50MHz 参考时钟RMII_MDIOPA2双向管理接口数据RMII_MDCPC1输出管理接口时钟RMII_CRS_DVPA7输入载波侦听/数据有效RMII_TX_ENPB11输出发送使能RMII_TXD0PB12输出发送数据 bit0RMII_TXD1PB13输出发送数据 bit1RMII_RXD0PC4输入接收数据 bit0RMII_RXD1PC5输入接收数据 bit1REF_CLK 这根线是整条链路的命门。RMII 规定 MAC 和 PHY 必须共用一个 50MHz 时钟源至于这个时钟由谁产生取决于板子的设计。常见的三种做法一是板上有 50MHz 有源晶振同时接到 PHY 的 X1 和 MCU 的 PA1二是用 STM32 的 MCO1PA8配置成 50MHz 输出送到 PHY 的 X1再从 PHY 的某个引脚回送到 PA1三是 PHY 自己带 25MHz 晶振在 RMII 模式下把内部 50MHz 输出到 PA1。这三种都能跑但代码里 MCO1 的配置项必须和实际硬件对得上否则 PHY 就是死的。提示上电第一件事拿示波器量 PA1 上有没有 50MHz、峰峰值 3.3V 左右的方波。量不到就别往下看了后面全是白费。GPIO 复用部分在HAL_ETH_MspInit里完成CubeMX 生成的版本基本就是这个样子void HAL_ETH_MspInit(ETH_HandleTypeDef *heth) { GPIO_InitTypeDef GPIO_InitStruct {0}; /* 三个端口的时钟必须全部打开漏一个就是部分信号没输出 */ __HAL_RCC_ETH_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); /* PA1REF_CLK PA2MDIO PA7CRS_DV */ GPIO_InitStruct.Pin GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_AF_PP; /* MDIO 是双向线外部没有上拉时这里要配成上拉否则读 PHY 会随机出错 */ GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate GPIO_AF11_ETH; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); /* PB11TX_EN PB12TXD0 PB13TXD1 */ GPIO_InitStruct.Pin GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); /* PC1MDC PC4RXD0 PC5RXD1 */ GPIO_InitStruct.Pin GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5; HAL_GPIO_Init(GPIOB NULL ? GPIOC : GPIOC, GPIO_InitStruct); /* DP83848 的 RESET_N 接在普通 GPIO 上这里一并配成推挽输出 */ GPIO_InitStruct.Pin GPIO_PIN_12; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOD, GPIO_InitStruct); }这段代码里几处要留意。Speed必须设成VERY_HIGHRMII 的 50MHz 时钟和数据沿很陡速度档位低一个等级就可能出现 CRC 错误。PA2 的Pull参数取决于板上有没有 1.5k~10k 的 MDIO 上拉电阻有就填GPIO_NOPULL没有就得填GPIO_PULLUP不然读 PHY 寄存器返回值会在 0xFFFF 和正确值之间跳。最后那段 PHY 复位引脚是我自己加的CubeMX 不会自动生成实际板子上 DP83848 的 RESET_N 一般都挂在某个 GPIO 上不主动拉低复位一次芯片可能停在未知状态。代码里GPIOB NULL ? GPIOC : GPIOC那行是走位写法正经工程直接写GPIOC就行。2.2 裸机和 FreeRTOS 两条路NO_SYS 决定 LwIP 的接法LwIP 有个总开关叫NO_SYS写在lwipopts.h里这个值一改整个工程的调用方式都跟着变。配置NO_SYS1裸机NO_SYS0RTOS协议栈运行位置主循环里轮询独立的tcpip_thread应用 APIraw API回调式netconn / socket API收包触发MX_LWIP_Process()信号量唤醒内存保护无有SYS_LIGHTWEIGHT_PROT典型资源占用Flash 约 40KBFlash 约 60KB多一个线程栈移植例程两种都给了默认按裸机跑。裸机模式的优点是链路清晰没有线程抢占调试时断点随便打缺点是主循环里一旦有耗时操作协议栈就停止喂狗TCP 重传、ARP 老化全乱套。选哪条路看你的应用里有没有必须跑的后台任务。只有简单的 Modbus TCP、HTTP 服务裸机足够要同时做串口协议解析、SD 卡记录、屏幕刷新那就上 FreeRTOS让tcpip_thread自己转。选 FreeRTOS 时要格外注意一点LwIP 的sys_arch.c需要适配 FreeRTOS 的邮箱和信号量ST 提供的中间件里带了现成的sys_arch.c但如果 CubeMX 版本和 LwIP 版本对不上编译时会冒出一堆sys_mbox_t未定义的错误。我一般会直接把中间件里的system/OS目录整体拷过来不去手动改。3. 工程目录与启动链路从 main() 走到 netif 挂上协议栈工程目录看着杂其实真正需要动的就那几个文件。搞清楚哪些是“只读”的库文件、哪些是“必须改”的板级适配文件后面改起来就不容易出事。3.1 目录分布哪些能改哪些不该碰解压后的目录大致是这个结构STM32F407_LWIP_DP83848/ ├── Drivers/ │ ├── CMSIS/ # 内核头文件和启动文件只读 │ └── STM32F4xx_HAL_Driver/ # HAL 库只读 ├── Middlewares/ │ └── Third_Party/LwIP/ # LwIP 2.x 源码只读 ├── Application/ │ ├── User/ │ │ ├── main.c # 主循环、外设初始化入口 │ │ ├── lwipopts.h # 协议栈裁剪参数必改 │ │ └── stm32f4xx_hal_conf.h # HAL 模块开关必改 │ └── Ethernet/ │ ├── ethernetif.c # MAC 与 LwIP 的胶水层必改 │ └── ethernetif.h ├── BSP/ │ ├── dp83848.c # PHY 复位、地址探测、链路检测 │ └── led.c / usart.c # 调试用的指示灯和串口 └── MDK-ARM/ # 工程文件与启动配置Drivers和Middlewares这两个目录我建议直接标成只读改库文件是自找麻烦升级的时候一合并全丢。真正的战场是Application/Ethernet/ethernetif.c和BSP/dp83848.c这两个文件前者管帧的进出后者管 PHY。lwipopts.h里的MEM_SIZE、PBUF_POOL_SIZE、TCP_MSS这些参数后面调性能时全在这里动。3.2 上电后的启动顺序六步走完才轮到 pingmain()里看着一堆初始化函数实际逻辑是一条直线。顺序错了就会出各种奇怪现象比如 ETH 初始化在时钟配置之前MAC 拿到的时钟就是错的。int main(void) { /* 1. HAL 初始化把 SysTick 和中断优先级组配好 */ HAL_Init(); /* 2. 系统时钟168MHz 主频同时确认 MCO1 输出 50MHz若板子用 MCO1 供时钟 */ SystemClock_Config(); /* 3. 调试外设串口打印 LED 指示出问题时全靠它们 */ MX_GPIO_Init(); MX_USART1_UART_Init(); /* 4. PHY 复位拉低 RESET_N 至少 10ms再等 100ms 让内部 PLL 稳定 */ DP83848_Reset(); /* 5. 以太网外设初始化此时 GPIO 复用、DMA 描述符、MAC 参数一并配好 */ MX_ETH_Init(); /* 6. LwIP 初始化netif 添加、IP 配置、启动协议栈 */ MX_LWIP_Init(); while (1) { /* 裸机模式下协议栈的收包、定时器全靠这一句驱动 */ MX_LWIP_Process(); } }SystemClock_Config()里如果板子用的是 MCO1 供时钟要额外配 PA8 复用成RCC_MCO1时钟源选 PLLCLK 分频到 50MHz。这一步漏了前面说的 REF_CLK 就是死的。DP83848_Reset()里的等待时间别省10ms 复位加 100ms 稳定省掉那 100ms 会出现“第一次上电能通、复位一次就不通”的玄学现象。MX_LWIP_Init()是 CubeMX 自动生成的核心就是这几句void MX_LWIP_Init(void) { /* 添加 netifethernetif_init 是胶水层入口里面会去探测 PHY */ tcpip_init(NULL, NULL); netif_add(gnetif, ipaddr, netmask, gw, NULL, ethernetif_init, tcpip_input); /* 设为默认网卡否则发包时找不到出口 */ netif_set_default(gnetif); /* 探测到链路后才真正把网卡置为 UP */ if (netif_is_link_up(gnetif)) { netif_set_up(gnetif); } else { netif_set_down(gnetif); } }netif_add的倒数第二个参数ethernetif_init是关键LwIP 在添加网卡时会回调它由它去初始化底层硬件、注册output和linkoutput两个函数指针。netif_set_up只有在链路已经建立时才调用否则协议栈会误以为网卡通着一直往一个死链路里发 ARP 请求。IP 地址这里是静态配的写在main.c开头的宏定义里一般设成192.168.1.10 / 255.255.255.0 / 192.168.1.1跟电脑同网段就行。4. 核心模块拆解PHY 探测、netif 注册和 DMA 收发真正决定能不能通的就是这三块PHY 有没有被正确识别、netif 有没有被正确注册、DMA 有没有把帧搬对地方。任何一环断了现象都是 ping 不通。4.1 PHY 复位与地址探测先确认对面坐着的是谁DP83848 的 PHY 地址由 PHYAD0 引脚决定悬空或上拉是 0x01下拉是 0x00。ST 的例程默认按 LAN8720 的 0x00 写直接拿来用大概率读不到东西。稳妥的做法是复位后把 0x00~0x1F 全扫一遍读寄存器 2 和寄存器 3 拼出 ID跟 DP83848 的0x20005C90比对。uint32_t DP83848_Probe(void) { uint32_t id1 0, id2 0; uint16_t addr; /* 硬件复位RESET_N 拉低 10ms 以上 */ HAL_GPIO_WritePin(PHY_RST_PORT, PHY_RST_PIN, GPIO_PIN_RESET); HAL_Delay(20); HAL_GPIO_WritePin(PHY_RST_PORT, PHY_RST_PIN, GPIO_PIN_SET); HAL_Delay(100); /* 等内部 PLL 锁定少于 50ms 容易读到全 0 */ for (addr 0; addr 32; addr) { /* 寄存器 2/3 是 PHY Identifier所有标准 MII 芯片都有 */ if (HAL_ETH_ReadPHYRegister(heth, addr, 0x02, id1) ! HAL_OK) continue; if (HAL_ETH_ReadPHYRegister(heth, addr, 0x03, id2) ! HAL_OK) continue; uint32_t phy_id (id1 16) | id2; if (phy_id 0x20005C90U) /* DP83848 的固定 ID */ { return addr; /* 返回 0x00 或 0x01 */ } } return 0xFFFFFFFFU; /* 全扫完都没有返回无效值 */ }HAL_ETH_ReadPHYRegister内部会做 MDIO 时序超时时间默认是PHY_TIMEOUT在stm32f4xx_hal_eth.h里定义。扫地址这一步如果全部返回HAL_TIMEOUT基本可以确定是 MDC/MDIO 的 GPIO 没配好或者 REF_CLK 没起来。如果扫到某个地址有返回但 ID 对不上说明那里挂着别的芯片比如有些板子在同一个 MDIO 总线上还挂了 EEPROM。返回值交给后面配置heth.Init.PHYAddress别硬编码。4.2 ethernetif_init把 MAC 和协议栈缝在一起这个函数是整份例程的核心LwIP 靠它拿到收发入口。err_t ethernetif_init(struct netif *netif) { struct ethernetif *ethernetif; LWIP_ASSERT(netif ! NULL, (netif ! NULL)); ethernetif mem_malloc(sizeof(struct ethernetif)); if (ethernetif NULL) return ERR_MEM; netif-state ethernetif; netif-name[0] IFNAME0; /* e */ netif-name[1] IFNAME1; /* n */ netif-output etharp_output; /* IP 层 - ARP 层 */ netif-linkoutput low_level_output; /* ARP 层 - MAC 硬件 */ netif-mtu NETIF_MTU; netif-flags NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET; /* 从 MAC 地址寄存器里读出出厂的 6 字节地址 */ low_level_init(netif); /* 探测 PHY失败就返回错误让上层知道网卡不可用 */ if (ethernetif_init_phy(netif) ! ERR_OK) { return ERR_IF; } netif-flags | NETIF_FLAG_LINK_UP; return ERR_OK; }netif-output和netif-linkoutput这两个函数指针是整个 LwIP 的数据出口。上层协议组好 IP 包交给etharp_output填 MAC 头再交给low_level_output丢进 DMA。这两个指针没设对包会直接消失串口上什么都看不到就是个黑匣子。NETIF_FLAG_LINK_UP这个标志位很重要DHCP、自动协商、链路检测都看它探测失败时不能设。low_level_output负责把 pbuf 链里的数据拷进发送缓冲再启动 DMAstatic err_t low_level_output(struct netif *netif, struct pbuf *p) { struct pbuf *q; uint8_t *buffer (uint8_t *)ETH_TX_BUF; /* pbuf 可能是链式的要逐个节点拷进连续的 DMA 缓冲 */ for (q p; q ! NULL; q q-next) { memcpy(buffer, q-payload, q-len); buffer q-len; } /* 告诉 HAL 这一帧多长然后启动发送 */ if (HAL_ETH_TransmitFrame(heth, p-tot_len) ! HAL_OK) { return ERR_IF; } return ERR_OK; }HAL_ETH_TransmitFrame触发 DMA 后是异步完成的函数返回只代表“已交给硬件”不代表“已发到线上”。所以这里返回值判断不了真实发送结果线路上的冲突、CRC 错误得看 MAC 的中断状态寄存器。我一般会在发送失败时点亮一个 LED方便现场排查。4.3 ethernetif_input 与主循环收包这条路要短收包路径比发包敏感得多因为它是中断或者轮询触发的处理慢了会丢包。err_t ethernetif_input(struct netif *netif) { struct pbuf *p NULL; err_t err ERR_OK; /* 从 DMA 环形缓冲里取一帧取到就返回 HAL_OK */ if (HAL_ETH_GetReceivedFrame_IT(heth) ! HAL_OK) { return ERR_OK; /* 没有新帧正常返回不是错误 */ } uint16_t len heth.RxFrameInfos.length; uint8_t *buf (uint8_t *)heth.RxFrameInfos.buffer; if (len 0) { /* 从内存池里申请 pbufPBUF_POOL_SIZE 不够时这里返回 NULL */ p pbuf_alloc(PBUF_RAW, len, PBUF_POOL); if (p ! NULL) { pbuf_take(p, buf, len); /* 整帧拷进 pbuf 链 */ /* 交给协议栈成功与否直接反馈给网卡层 */ if (netif-input(p, netif) ! ERR_OK) { pbuf_free(p); err ERR_IF; } } else { err ERR_MEM; /* 内存池耗尽计数后丢弃 */ } } /* 释放 DMA 描述符让硬件能继续收下一帧少这一句收几帧就卡死 */ HAL_ETH_ReleaseFrame(heth); return err; }HAL_ETH_ReleaseFrame这行绝对不能漏DMA 描述符的所有权是靠引用计数管理的不释放就再也不会有新帧进来。我见过一次现象特别怪ping前三个包能通第四个开始全丢查了半天发现是ethernetif_input里少了一句释放。裸机模式下ethernetif_input在MX_LWIP_Process()里被调用轮询节奏取决于主循环跑多快FreeRTOS 模式下则是在 ETH 中断里发信号量由tcpip_thread处理。两种模式下PBUF_POOL_SIZE都得留够一般 16 个起步收大包多的情况下给到 32。5. 避坑与排查从烧录到 ping 通要过的五道坎5.1 PHY 探测返回全 F现象串口打印 PHY 扫描结果32 个地址全部返回0xFFFF或者HAL_ETH_ReadPHYRegister直接超时。原因九成是 REF_CLK 没起来MDIO 的时序是靠 50MHz 时钟驱动的没时钟就没有 MDIO 的位传输剩下的一成是 MDC/MDIO 的 GPIO 复用没打开或者 PA2 没做上拉。时钟来源和硬件设计对不上也会这样。解决示波器先量 PA1没有 50MHz 就去查SystemClock_Config里的 MCO1 配置或者查板上有源晶振有没有焊有 50MHz 就量 PA2 和 PC1看 MDIO 在扫描期间有没有跳变纹丝不动说明 GPIO 复用配错了端口。5.2 链路建立但 ping 不通现象串口显示自动协商成功100M 全双工netif_is_link_up返回真但电脑ping 192.168.1.10一直超时。原因这种情况优先怀疑 IP 不在同一网段或者电脑那边的防火墙把 ICMP 拦了。还有一个容易忽略的点例程里netif_set_up只在链路建立后调用如果链路建立的时机晚于MX_LWIP_Init返回网卡就一直处于 down 状态。解决先把电脑网卡设成固定 IP192.168.1.100、掩码255.255.255.0网关留空再关掉防火墙试一次。还不行就打开 LwIP 的LWIP_DEBUG和ICMP_DEBUG在串口上看 ARP 请求有没有发出去、电脑有没有回。netif_set_up的时机问题一般靠 ETH 的中断里检测链路变化检测到 up 再调用一次netif_set_up。5.3 小包能通大包一收就丢现象ping -l 32通ping -l 1400就超时TCP 上传文件传到一半卡死。原因PBUF_POOL_SIZE太小加上PBUF_POOL_BUFSIZE小于一个完整以太帧1518 字节大包过来时pbuf_alloc返回 NULL直接被丢弃而且不会报错只在 LwIP 的统计计数里加一。解决在lwipopts.h里把PBUF_POOL_SIZE调到 32PBUF_POOL_BUFSIZE显式设成1520同时把MEM_SIZE从默认的 1600 提到 8192 以上。改完重新编译时注意lwipopts.h里的MEM_ALIGNMENT要设成 4否则结构体会因为对齐问题浪费内存。5.4 中断模式下偶发死机现象跑一段时间后程序跳进HardFault_Handler或者死在一个 while 循环里不动。原因ethernetif_input被放进了 ETH 中断回调而回调里又调用了pbuf_alloc这个函数内部有内存池操作在有 RTOS 的场合会申请信号量中断上下文里调阻塞式 API 是致命的。解决中断里只做一件事xSemaphoreGiveFromISR释放一个信号量真正解析和投递放到tcpip_thread或者一个专门的任务里做。裸机模式下则反过来把 ETH 中断关掉纯靠MX_LWIP_Process()轮询虽然 CPU 占用高一点但绝对稳定。5.5 换 FreeRTOS 后协议栈不工作现象编译链接都过程序也跑起来了但tcpip_init之后再没有任何协议栈活动。原因NO_SYS改了但lwipopts.h里的LWIP_NETCONN、LWIP_SOCKET、MEMP_NUM_NETCONN没跟着打开或者sys_arch.c里的邮箱实现没和 FreeRTOS 的版本对应上导致tcpip_thread起来了又在等一个永远不来的消息。解决确认NO_SYS0LWIP_NETCONN1LWIP_SOCKET0只用 netconn 就不用开 socketMEMP_NUM_NETCONN给到 8。sys_arch.c直接用中间件里的那份不要自己写然后检查tcpip_init传入的参数是不是 NULL传了非 NULL 的回调但要它做的事情又没实现也会卡住。6. 二次开发从 ping 通到真正能用的三处改造ping通了只是起点真要拿它做产品接下来这几处是必须动的。6.1 接一个 TCP echo 服务器验证协议栈能承载业务最快的验证方式是在裸机模式下挂一个 raw API 的 TCP 服务客户端发什么就回什么能打通说明整条链路都活了。static err_t echo_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) { if (p NULL) /* 对端关闭连接 */ { tcp_close(tpcb); return ERR_OK; } /* 直接把收到的 pbuf 回显注意引用计数别 double free */ tcp_write(tpcb, p-payload, p-tot_len, TCP_WRITE_FLAG_COPY); tcp_output(tpcb); pbuf_free(p); return ERR_OK; } static err_t echo_accept(void *arg, struct tcp_pcb *newpcb, err_t err) { /* 注册接收回调客户端连上来后所有数据都走 echo_recv */ tcp_recv(newpcb, echo_recv); return ERR_OK; } void echo_server_init(void) { struct tcp_pcb *pcb tcp_new(); ip_addr_t ip IPADDR4_INIT_BYTES(192, 168, 1, 10); ip_addr_t any IPADDR4_INIT_BYTES(0, 0, 0, 0); tcp_bind(pcb, any, 5000); /* 监听 5000 端口 */ pcb tcp_listen(pcb); /* listen 会返回一个新的 pcb */ tcp_accept(pcb, echo_accept); LWIP_UNUSED_ARG(ip); }tcp_recv注册的回调里参数p为 NULL 代表对端发起了 FIN这时必须调用tcp_close把控制块释放掉不然连接数越积越多最后tcp_new返回 NULL。tcp_write的第四个参数用TCP_WRITE_FLAG_COPY让协议栈自己拷贝一份数据这样pbuf_free之后不会出现野指针。tcp_listen的返回值是一个新的 pcb原来那个已经被内部释放了不能再对它做任何操作。写完这个服务用网络调试工具连上去发几十个字节看看回显对不对再发几 KB 看看会不会断基本就能判断出TCP_MSS和TCP_SND_BUF设得够不够。6.2 参数调优吞吐和稳定性的取舍ping通之后最常见的抱怨是“能通但特别慢”。LwIP 默认参数是按低资源场景配的拿来跑文件传输肯定不够。参数默认值建议值影响TCP_MSS5361460单包最大载荷TCP_SND_BUF2*MSS8*MSS发送窗口太小拷不住数据TCP_WND2*MSS8*MSS接收窗口直接限制对方发送速度MEM_SIZE160016384协议栈堆pbuf 和连接控制块都从这里分PBUF_POOL_SIZE1632收包缓冲池决定能扛多少突发ETH_RXBUFNB48DMA 接收描述符个数改TCP_MSS的时候有个前提netif-mtu必须跟着改成 1500两者不一致会导致分片逻辑出错。MEM_SIZE提到 16KB 之后F407 的 128KB SRAM 还算宽裕但要是同时用 FreeRTOS 开好几个任务得算一下栈加起来会不会超。我一般会在main.c里留一个空闲内存的打印跑压力测试的时候盯着它掉到 2KB 以下就该加内存或者砍功能了。6.3 链路检测和重连不能省现场设备拔网线是常态不处理的话协议栈会一直往一个不存在的链路上发数据TCP_SND_BUF满了之后应用层直接卡住。常见的做法是 500ms 轮询一次 BSR 寄存器的 bit2状态翻转时调用netif_set_link_up或者netif_set_link_down并把现有连接全部清掉。void link_check_task(void) { uint32_t bsr 0; static uint8_t last_state 0; /* BSR 的 bit2 是链路状态读两次能消掉锁存带来的抖动 */ HAL_ETH_ReadPHYRegister(heth, phy_addr, 0x01, (uint16_t *)bsr); uint8_t now (bsr 0x0004) ? 1 : 0; if (now ! last_state) { if (now) netif_set_link_up(gnetif); else netif_set_link_down(gnetif); last_state now; } }netif_set_link_down只是把网卡标成不可用已经建立的 TCP 连接不会自己断要在调用之前遍历tcp_active_pcbs链表逐个tcp_abort。这段逻辑不写设备重启后就会出现“网线插着但连不上”的情况客户现场第一反应就是设备坏了。那次因为少写HAL_ETH_ReleaseFrame导致收三包丢一包的问题我调了整整一个下午才定位到从那以后每次移植新 PHY我都会先把ethernetif_input从头到尾数一遍释放路径再上电。这套例程的代码量不大但把 F407 LwIP DP83848 这条路上的典型问题基本都覆盖到了拿来对着改自己的板子能省掉不少重复折腾。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑