简介面向使用GD32F407进行裸机网络开发的嵌入式工程师与学生这份资料聚焦在无操作系统环境下移植LWIP这一典型难题覆盖以太网MAC驱动适配、内存池与pbuf管理、接收中断处理、定时轮询及socket应用接口等关键环节适合希望打通MCU联网链路的初、中级开发者参考。压缩包共641个文件约12.15MB以216个h头文件和165个c源文件为主体辅以Keil工程、编译中间文件、映射与说明文档可较完整地还原工程结构与构建过程。已有1317人学习下载说明该主题具备一定关注度。内容中包含LWIP协议栈源码、GD32F4xx以太网驱动、TCP/UDP、HTTP服务及mDNS等模块并提供可直接对照的工程配置与排错线索便于读者理解裸机TCP/IP移植的目录组织、依赖关系和调试要点。1. 先别急着写代码GD32F407 裸机跑 LWIP 的版本与硬件前提GD32F407 无操作系统移植 LWIP最容易让人误判的一步不是写 lwipopts.h而是先入为主地以为“裸机等于简单”。我见过太多板子卡在 PHY 能读 ID、网口灯也亮但 ping 始终不通最后发现问题不在协议栈而在 ENET DMA 描述符放错内存区、RMII 参考时钟没起来或者 sys_now 没给 LWIP 心跳。这套环境在工业网关、串口服务器、采集板上非常常见适合已经能把 GD32F4 点灯跑通、想省掉 RTOS 开销的人。装不好不会报编译错误只会给你一个沉默的网口所以下面按“环境准备—配置—驱动—排错—验证”走一遍。2. 环境准备与工程骨架GD32F407 固件库与 LWIP 2.x 源码2.1 选版本固件库、LWIP 和工具链怎么配GD32F407 的 ENET 外设和常见 F4 系列用法接近但固件库的寄存器命名、位定义、启动文件不能混用。我一般先确认三件事芯片型号是 GD32F407工程里用的是对应型号的 GD32F4xx 固件库LWIP 选 2.x 系列源码。LWIP 1.4 和 2.x 的 API 差异不小尤其是netif-linkoutput、sys_now、pbuf类型和tcpip相关接口网上不少“移植成功”的代码是 1.4 的直接拖进 2.x 工程会编译出一串类型错误。工具链用 Keil MDK 或arm-none-eabi-gcc都行关键是调试器能读内存、能看 map 文件。裸机 LWIP 出问题时能打印 PHY 寄存器、能看描述符地址比单步断点有用得多。抓包工具建议在 PC 端准备好后面验证 ARP 和 ICMP 时会省很多时间。组件建议选择说明MCUGD32F407ENET 使用 RMII 接口DMA 描述符在 SRAM固件库与芯片型号匹配的 GD32F4xx 库不要拿其他系列库改宏硬套LWIP2.x 系列裸机用NO_SYS1不移植sys_arch信号量PHYLAN8720A 等 RMII PHYPHY 地址由硬件引脚决定常见 0 或 1工具链Keil MDK / GCC能输出 map便于检查描述符地址调试SWD 串口打印先看 PHY ID再看描述符 own 位版本确定后工程目录不要随手摊在根目录。我习惯把 LWIP 源码、移植层、应用层分开后面换芯片或换 PHY 时只动移植层。目录结构可以照下面搭mkdir -p Middlewares/Third_Party/LwIP/{src,include,arch} mkdir -p Drivers/GD32F4xx/{inc,src} mkdir -p Application/Network # 将 lwip-2.x 的 src、include 拷入 Middlewares/Third_Party/LwIP # arch 下放 ethernetif.c、lwipopts.hNO_SYS1 时 sys_arch.c 可留空实现这段命令只做目录骨架不涉及具体下载来源。src和include保持 LWIP 原样arch只放自己写的网卡驱动和配置Application/Network放ping测试、TCP 回环或业务代码。后面编译报错时能一眼看出是协议栈源码问题还是移植层问题。2.2 建工程骨架把 lwip 源码放进 Keil/GCC 工程工程分组建议按“启动与时钟—固件库—LWIP 核心—LWIP 移植层—应用”分。启动文件、系统时钟、SysTick 属于板级基础先保证delay_ms和串口打印能用。LWIP 核心源码文件很多不要一股脑全加至少包含core/、core/ipv4/、netif/ethernet.c、api/里实际用到的部分NO_SYS1时api/下很多 socket 相关文件可以不编。GCC 工程可以用 Makefile 管理包含路径。下面是一段最小示例重点是路径和宏# GD32F407 裸机 LWIP 工程片段 C_SOURCES Middlewares/Third_Party/LwIP/src/core/init.c C_SOURCES Middlewares/Third_Party/LwIP/src/core/mem.c C_SOURCES Middlewares/Third_Party/LwIP/src/core/memp.c C_SOURCES Middlewares/Third_Party/LwIP/src/core/pbuf.c C_SOURCES Middlewares/Third_Party/LwIP/src/core/tcp.c C_SOURCES Middlewares/Third_Party/LwIP/src/core/udp.c C_SOURCES Middlewares/Third_Party/LwIP/src/core/ipv4/ip4.c C_SOURCES Middlewares/Third_Party/LwIP/src/core/ipv4/etharp.c C_SOURCES Middlewares/Third_Party/LwIP/src/netif/ethernet.c C_SOURCES Middlewares/Third_Party/LwIP/arch/ethernetif.c C_INCLUDES -IMiddlewares/Third_Party/LwIP/src/include C_INCLUDES -IMiddlewares/Third_Party/LwIP/arch C_INCLUDES -IDrivers/GD32F4xx/inc这里的文件列表不是全集而是裸机 ICMP、ARP、TCP、UDP 都打开时的核心部分。C_INCLUDES必须把arch放在前面或至少让编译器能找到lwipopts.h否则 LWIP 会用默认配置出现“代码里明明关了 socket编译还在找sys/socket.h”的怪事。ethernetif.c放在arch下不要塞进 LWIP 源码目录升级源码时好替换。堆栈也要提前看。裸机 LWIP 的MEM_SIZE和PBUF_POOL_SIZE都从 SRAM 里分配启动文件的堆大小如果设得太小mem_malloc返回空现象是 TCP 能连但一发数据就断。GD32F407 的 SRAM 够用但要把 ENET DMA 描述符和缓冲区放在 DMA 能访问的区域不能放到 CCM 里。这个坑后面第 4 章会展开。2.3 时钟与引脚RMII 接口和 PHY 复位脚RMII 接口第一件事不是配 GPIO而是确认 50MHz 参考时钟从哪来。常见做法是 PHY 输出 50MHz 给 MCU 的RMII_REF_CLK或者板子上有源晶振直接供。若时钟没起来MAC 寄存器看起来能写PHY 读 ID 也可能正常但 DMA 收发包就是不动。我一般先量时钟引脚再谈软件。GPIO 配置按板级原理图来下面是 GD32F4xx 固件库风格的片段复用编号以数据手册为准/* 使能 ENET 和 RMII 相关 GPIO 时钟 */ rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_GPIOB); rcu_periph_clock_enable(RCU_GPIOC); rcu_periph_clock_enable(RCU_ENET); /* PA1REF_CLK, PA2MDIO, PA7CRS_DV实际复用号查手册 */ gpio_af_set(GPIOA, GPIO_AF_11, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_7); gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_7); gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_100M, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_7); /* PC1MDC, PC4RXD0, PC5RXD1 */ gpio_af_set(GPIOC, GPIO_AF_11, GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5); gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5); gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_100M, GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5);GPIO_AF_11只是示例GD32F4 不同封装、不同引脚可能有差异必须对着数据手册复用表核对。GPIO_PUPD_NONE对 RMII 的 TX、REF_CLK 一般够用RX 侧如果板子没有外部上拉可以按 PHY 要求调整。GPIO_OSPEED_100M对 50MHz 参考时钟和 RMII 数据线是常见选择速度太低会导致边沿变差。PHY 复位不要省。很多模块上电后 PHY 处于不确定状态MCU 跑起来直接读 ID 可能读到 0xFFFF。复位脚单独控制时序按 PHY 手册留够/* PHY 硬复位低电平至少 25ms释放后等 100ms 再读 ID */ gpio_mode_set(PHY_RST_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, PHY_RST_PIN); gpio_output_options_set(PHY_RST_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_2M, PHY_RST_PIN); gpio_bit_reset(PHY_RST_PORT, PHY_RST_PIN); delay_ms(50); gpio_bit_set(PHY_RST_PORT, PHY_RST_PIN); delay_ms(100);这段代码放在 ENET MAC 初始化之前。复位后先读 PHY 的 ID 寄存器 2 和 3能读到稳定值再继续配置自协商。若读回 0x0000 或 0xFFFF先查 MDIO/MDC 引脚、PHY 地址和复位时序不要急着改 LWIP 参数。注意PHY 地址不是固定 0LAN8720A 这类芯片由硬件引脚决定原理图上常见 0 或 1。扫描 0 到 31 找到能读 ID 的地址比猜更快。3. 配置 lwipopts.hNO_SYS1 下每个开关都要有理由3.1 必须改的宏NO_SYS、MEM_SIZE、MEMP_NUM_PBUF裸机移植 LWIP 的第一行配置就是NO_SYS1它告诉协议栈没有操作系统不能用信号量、邮箱和线程。紧接着SYS_LIGHTWEIGHT_PROT设为 0LWIP_SOCKET和LWIP_NETCONN关掉否则编译会去找sys/socket.h和sys_arch里的线程接口。很多人到这里能被编译错误拦住反而是好事真正麻烦的是内存参数配小了编译通过运行到 TCP 发送才死。我一般从下面这组配置起步再按 SRAM 和业务裁剪#ifndef LWIPOPTS_H #define LWIPOPTS_H #define NO_SYS 1 #define SYS_LIGHTWEIGHT_PROT 0 #define LWIP_SOCKET 0 #define LWIP_NETCONN 0 #define LWIP_NETIF_API 0 #define MEM_ALIGNMENT 4 #define MEM_SIZE (16 * 1024) #define MEMP_NUM_PBUF 16 #define PBUF_POOL_SIZE 16 #define PBUF_POOL_BUFSIZE 1524 #define LWIP_ARP 1 #define LWIP_ICMP 1 #define LWIP_UDP 1 #define LWIP_TCP 1 #define LWIP_DHCP 1 #define LWIP_DNS 1 #define TCP_MSS 1460 #define TCP_SND_BUF (4 * TCP_MSS) #define TCP_WND (4 * TCP_MSS) #define LWIP_NETIF_LINK_CALLBACK 1 #define LWIP_NETIF_STATUS_CALLBACK 1 #define LWIP_NETIF_HOSTNAME 1 #define CHECKSUM_GEN_IP 1 #define CHECKSUM_GEN_TCP 1 #define CHECKSUM_GEN_UDP 1 #define CHECKSUM_CHECK_IP 1 #define CHECKSUM_CHECK_TCP 1 #define CHECKSUM_CHECK_UDP 1 #endifMEM_SIZE是 LWIP 自己管理的堆16KB 对裸机 TCP 客户端不算大但加上 pbuf pool 后总内存要算总账PBUF_POOL_SIZE * PBUF_POOL_BUFSIZE约 24KB再加描述符、发送缓冲和业务缓冲40KB 左右是常见占用。GD32F407 的 SRAM 能承受但前提是这些缓冲不在 CCM 里。MEMP_NUM_PBUF是 pbuf 结构数量不是数据缓冲数量PBUF_POOL_SIZE才是池里预分配的 pbuf 数。PBUF_POOL_BUFSIZE设 1524 是为了覆盖最大以太网帧并留余量设 1500 容易在 VLAN 或对齐场景下踩边界。TCP_SND_BUF和TCP_WND先用 4 个 MSS稳定后再加到 8 个。有人一上来把窗口拉到 16 个 MSS结果 SRAM 不够mem_malloc失败现象是 TCP 连接能建立发几包就卡住。裸机环境没有 RTOS 帮忙回收内存池宁小勿大先跑通再压测。3.2 协议裁剪DHCP、DNS、TCP 与 UDP 取舍LWIP_DHCP打开后板子启动会发 DHCP Discover网络里没有 DHCP 服务器时要等超时才回落到静态 IP。调试阶段我通常先关 DHCP写死 IP、网关和掩码把 ARP 和 ICMP 跑通再打开 DHCP 验证。DHCP 超时不是协议栈坏了先看网线、交换机、DHCP 服务器和sys_now是否在走。LWIP_DNS只在需要域名解析时开。裸机 DNS 查询会占用一个 UDP 控制块查询超时依赖sys_check_timeouts主循环卡住时 DNS 也会卡。TCP 和 UDP 按业务选只做 Modbus TCP 就关 UDP只做 SNMP 就关 TCP两个都开也能跑但内存和 pbuf 要留够。链路状态回调也值得打开。LWIP_NETIF_LINK_CALLBACK和LWIP_NETIF_STATUS_CALLBACK打开后可以在 PHY 插拔网线时更新netif标志避免 LWIP 一直以为链路是 UP。回调里不要做耗时操作裸机主循环里置个标志就行。3.3 sys_now 与超时轮询裸机的心跳从哪来NO_SYS1时 LWIP 不启动线程所有超时都靠sys_check_timeouts()在主循环里推。sys_now()必须返回毫秒计数否则 ARP 表项不过期、TCP 重传定时器不走、DHCP 永远超时。实现很简单但必须保证中断里只做自增volatile uint32_t g_ms_ticks; void SysTick_Handler(void) { g_ms_ticks; } uint32_t sys_now(void) { return g_ms_ticks; }SysTick配 1ms 中断g_ms_ticks用volatile修饰。sys_now返回 32 位毫秒值约 49 天溢出LWIP 的超时比较用无符号差值短时间回绕能处理但业务里不要拿它做绝对时间戳。主循环里要同时轮询网卡和超时while (1) { ethernetif_input(gnetif); /* 从 ENET DMA 取包并交给 LWIP */ sys_check_timeouts(); /* 推进 ARP、TCP、DHCP 定时器 */ if ((g_ms_ticks - last_print) 1000) { last_print g_ms_ticks; printf(link%d ip%s\r\n, netif_is_link_up(gnetif), ip4addr_ntoa(netif_ip4_addr(gnetif))); } }ethernetif_input里如果一次取到多个包可以循环取到没有为止避免高流量下丢包。sys_check_timeouts调用频率不要低于 1ms 一次主循环里有长延时就会拖慢 TCP 重传。串口打印别放在 1ms 里放 1 秒一次足够。提示先确认sys_now每秒增加约 1000再看 DHCP 和 ARP。心跳不对后面所有超时都是玄学。4. 网卡驱动落地ethernetif.c 与 GD32F4 ENET DMA 描述符4.1 ENET 初始化顺序GPIO、时钟、DMA 描述符、PHYethernetif.c是 LWIP 和 GD32F4 ENET 之间的翻译层初始化顺序错一步后面就会用现象折磨人。我一般按“GPIO 和时钟—PHY 复位—DMA 描述符—MAC 配置—DMA 启动—netif 注册”走。描述符必须在 DMA 启动前初始化而且地址要 4 字节对齐放在 DMA 能访问的 SRAM。typedef struct { volatile uint32_t status; uint32_t control; uint32_t buffer1; uint32_t buffer2; } enet_dma_desc_t; /* 描述符和缓冲放普通 SRAM不要放 CCM */ __attribute__((aligned(4))) static enet_dma_desc_t tx_desc[4]; __attribute__((aligned(4))) static enet_dma_desc_t rx_desc[4]; __attribute__((aligned(4))) static uint8_t tx_buf[4][1524]; __attribute__((aligned(4))) static uint8_t rx_buf[4][1524];aligned(4)对 Cortex-M4 的 DMA 描述符是底线缓冲区也建议 4 字节对齐。发送和接收各 4 个描述符是保守配置跑百兆全双工够用要压测吞吐可以加到 8 个但内存也要跟着加。描述符里的status有 OWN 位CPU 写、DMA 读初始化时要把 OWN 位清掉接收描述符则相反交给 DMA 持有。low_level_init里先配 GPIO 和 PHY 复位再初始化描述符环最后开 MAC 和 DMA。PHY 自协商可以放在 MAC 配置之后等链路 UP 再更新netif标志。初始化函数里不要加太多printf串口太慢会拖长启动时间先点灯或置标志。4.2 收发包路径low_level_output 与 low_level_input发送路径的核心是把 LWIP 的pbuf链拷到连续发送缓冲然后让 DMA 描述符持有 OWN 位并启动发送。下面是最小实现省略了错误恢复static err_t low_level_output(struct netif *netif, struct pbuf *p) { struct pbuf *q; uint8_t *dst tx_buf[tx_idx]; uint32_t len p-tot_len; if (len 1524) { return ERR_BUF; } /* pbuf 可能是链式逐段拷贝到连续 DMA 缓冲 */ for (q p; q ! NULL; q q-next) { memcpy(dst, q-payload, q-len); dst q-len; } tx_desc[tx_idx].status ENET_DMATXDESC_OWN; tx_desc[tx_idx].control len | ENET_DMATXDESC_LS | ENET_DMATXDESC_FS; tx_desc[tx_idx].buffer1 (uint32_t)tx_buf[tx_idx]; ENET_DMA_TX_POLL_DEMAND 1; /* 通知 DMA 取发送描述符 */ tx_idx (tx_idx 1) % 4; return ERR_OK; }ENET_DMATXDESC_OWN、LS、FS这些宏名按固件库实际定义替换。LS表示最后一个段FS表示第一个段单缓冲发送两个都要置。ENET_DMA_TX_POLL_DEMAND是发送轮询寄存器写 1 触发 DMA。发送索引自增后要处理描述符是否被 DMA 释放简单做法是发送前检查 OWN 位忙等或丢包更稳的做法是在发送完成中断里回收。接收路径反过来从接收描述符拿到帧长度分配pbuf把数据拷进去再交给ethernetif_input上层。接收描述符的 OWN 位由 DMA 置位CPU 取包后要清 OWN 并更新缓冲地址。长度要减去 4 字节 FCS除非 MAC 配置里保留了 FCS。拷贝时注意pbuf_alloc的层类型用PBUF_RAW让 LWIP 按以太网帧解析。4.3 缓存与对齐Cortex-M4 上最容易翻车的一公里GD32F407 是 Cortex-M4通常没有 M7 那样的 D-Cache所以不用做 cache invalidate但有一个更隐蔽的坑CCM 内存不能被 ENET DMA 访问。GD32F4 的 CCM 地址从0x10000000开始链接脚本如果默认把.bss或大数组放进去描述符和缓冲地址落在 CCMDMA 读写就是空的。现象很怪PHY 正常、MAC 寄存器正常、发送计数也涨但线上没有包。我一般做三件事第一链接脚本里确认 ENET 描述符和缓冲段放在0x20000000起始的 SRAM第二编译后看 map 文件搜tx_desc、rx_desc、tx_buf的地址第三在初始化时打印这几个地址低于0x10000000或高于 CCM 范围都要警惕。对齐用aligned(4)缓冲区不要用局部变量局部变量在栈上栈也可能被链接到不可 DMA 的区域。还有内存屏障。CPU 写完描述符和控制字后要保证 DMA 看到的是最新值Cortex-M4 上可以用__DSB()或在启动 DMA 前加一条屏障指令。接收侧 CPU 读完数据再清 OWN 位顺序不能反。裸机没有 RTOS 的临界区保护主循环和中断都会碰描述符简单做法是收包只在主循环做中断里只置标志减少竞争。5. 避坑与排查GD32F407 裸机 LWIP 的五个现场故障5.1 现象PHY 能读 ID但 ping 不通现象很典型串口打印 PHY ID 正常网口灯亮PC 上arp -a看不到板子ping全超时。原因通常不在 LWIP而是 ENET DMA 没真正启动或者 RMII 参考时钟没起来。PHY 读 ID 走的是 MDIO/MDC慢速管理接口能通不代表数据路径能通。另一个常见原因是 MAC 配置里没有开收发使能描述符 OWN 位初始化反了。解决顺序先量RMII_REF_CLK是否有 50MHz再看 ENET 的 MAC 配置寄存器收发位是否置位然后打印发送描述符的 OWN 位和ENET_DMA_TX_POLL_DEMAND是否写进去。用调试器看tx_desc[0].status如果 CPU 写了 OWN 但 DMA 一直不 clears说明 DMA 没取描述符。最后检查描述符地址是否落在 CCMmap 文件比猜靠谱。5.2 现象收到包但协议栈没反应抓包能看到 PC 发来的 ARP 请求板子侧接收描述符也有数据但 LWIP 不回 ARP。原因多是ethernetif_input没有把 pbuf 交给netif-input或者帧长度算错把 FCS 也当数据传上去。还有一种情况是pbuf_alloc失败代码里没判断返回值直接memcpy到空指针程序跑飞后看门狗复位现象像“收到包但不处理”。解决在low_level_input里打印帧长度和目的 MAC确认长度在 60 到 1514 之间pbuf_alloc返回值必须判空交给上层时用netif-input(p, netif)不要直接调ethernet_input。如果 ARP 请求能进但回复发不出去回头查发送描述符和low_level_output的返回值。5.3 现象跑几分钟后死机或丢包短时间 ping 正常跑几分钟或一压测就死机、丢包、串口打印乱码。原因常见是内存池不够或描述符环没有回收。MEM_SIZE太小TCP 发送申请不到内存PBUF_POOL_SIZE太小接收路径频繁丢包发送描述符 OWN 位没检查CPU 覆盖了 DMA 正在发送的缓冲。还有一种是主循环里sys_check_timeouts被长任务拖住TCP 重传定时器失准。解决先把MEM_SIZE和PBUF_POOL_SIZE加一档观察是否还死再在发送函数里检查描述符 OWN 位忙时返回ERR_WOULDBLOCK或丢包主循环里把长任务切片保证sys_check_timeouts至少 1ms 进一次。用串口打印mem_malloc失败计数比看现象快。5.4 现象DHCP 一直超时DHCP 打开后串口一直打印超时静态 IP 却能用。原因可能是sys_now没走、DHCP Discover 没发出去、或者网络里没有 DHCP 服务器。先确认sys_now每秒增加约 1000再抓包看板子有没有发 DHCP Discover。如果 Discover 发了但没 Offer查交换机端口和 DHCP 服务器如果连 Discover 都没有查 UDP 发送路径和netif的广播标志。解决调试阶段先写静态 IP 跑通 ARP/ICMP再开 DHCP确认LWIP_DHCP1且netif-flags有NETIF_FLAG_BROADCASTDHCP 超时时间不要设太短裸机主循环卡顿时容易误判。DHCP 拿到地址后LWIP_NETIF_STATUS_CALLBACK里更新业务状态。5.5 现象TCP 连接建立慢、吞吐低TCP 能连上但握手慢、发数据吞吐只有几十 KB/s。原因可能是TCP_WND和TCP_SND_BUF太小、PBUF_POOL_BUFSIZE不够、或者每包都走memcpy且主循环收包不及时。裸机没有线程接收窗口靠主循环推主循环里做 Flash 写或长延时TCP 窗口就卡住。解决先把TCP_WND和TCP_SND_BUF加到 8 个 MSS看吞吐是否上升再把ethernetif_input改成循环取包直到空发送路径如果 pbuf 是单链可以开LWIP_NETIF_TX_SINGLE_PBUF减少拷贝。校验和先软件算稳定后再考虑硬件卸载硬件校验和配错会让包被对端丢弃现象像“连接建立但数据不通”。6. 跑通验证从 PHY 寄存器、ARP 到 iperf 的逐层确认装完不要直接 ping 一下就说成功。我一般按层验证哪层失败就停在哪层不跨层猜。第一层看 PHY ID 和链路状态第二层看 MAC 收发计数第三层看 ARP第四层看 ICMP最后才看 TCP/UDP 吞吐。下面这张表是我常用的确认顺序层级检查点工具/命令通过标准PHYID 寄存器 2/3串口打印读出稳定值不是 0x0000/0xFFFF链路BSR 寄存器链路位串口打印插网线后链路 UPMAC收发描述符 OWN 位调试器看内存发送能清 OWN接收能置 OWNARPPC 的 ARP 表arp -a能看到板子 IP 和 MACICMPpingping -c 4 192.168.1.1004 包全通延迟稳定TCP自写回环或 iperfiperf -c 192.168.1.100连接建立吞吐稳定不崩PC 端用 Linux 或 Windows 都行命令略有差异# Linux/macOS 下确认 ARP 和 ICMP ping -c 4 192.168.1.100 arp -a | grep 192.168.1.100 # 如果板子端跑了 TCP 回环服务可以用 nc 做一次短连接测试 printf hello\r\n | nc -w 2 192.168.1.100 5001ping -c 4发 4 个 ICMP 包先看有没有回包再看延迟是否稳定。arp -a能看到板子 MAC说明二层 ARP 已经通了如果 ARP 表里是incomplete回头查接收路径和 ARP 回复。nc只测 TCP 连接和短数据不依赖板子端有完整 iperf 服务要测吞吐板子端需要有对应的 TCP 服务端PC 端再用 iperf 压。最后一步是稳定性让板子连续 ping 10 分钟同时串口打印mem_malloc失败计数和描述符错误计数。计数不涨、ping 不丢、TCP 连接不断才算真正跑通。从那以后我每次移植裸机 LWIP都先把 PHY ID、sys_now 和描述符地址这三件事打印出来再谈 ping这套习惯救过我好几次也让我少熬了很多夜。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?