裸机移植LwIP不能直接用POSIX版sys_arch.c,因单片机无OS,pthread_*等函数链接失败;必须重写sys_arch.c,用标志位+轮询实现信号量/互斥锁,用SysTick实现非阻塞msleep,并确保ethernetif_input()和sys_check_timeouts()在主循环中持续调用,否则ARP、TCP重传、DHCP超时均失效。

为什么裸机移植 LwIP 不能直接用 sys_arch.c 的 POSIX 示例
因为单片机没操作系统,sys_arch.c 里那些 pthread_*、sem_wait、select 调用全会链接失败。LwIP 的 NO_SYS=0(带 OS 抽象层)模式依赖底层提供 sys_sem_t、sys_mutex_t、sys_thread_t 等类型和对应函数,裸机必须自己实现——不是“删掉” sys_arch,而是“重写”它,用裸机定时器+状态机或极简轮询调度模拟信号量与延时。
常见错误是照抄 Linux 版 sys_arch.c,编译报 undefined reference to 'pthread_create' 才意识到问题。更隐蔽的坑是:哪怕只用 NO_SYS=1(无 OS 模式),仍需手动调用 ethernetif_input() 和 sys_check_timeouts(),否则 ARP、TCP 重传、DHCP 超时全不工作。
怎么写裸机版 sys_arch.c 的核心三要素
裸机下不需要线程切换,但必须满足 LwIP 对“等待”“互斥”“超时”的语义要求。三个关键点:
-
sys_sem_new():用一个volatile uint8_t标志位 + 循环检测实现“伪信号量”,sys_sem_wait()就是 while(!flag); flag = 0; -
sys_mutex_new():同上,但初始化为 1,sys_mutex_lock()是 while(!flag); flag = 0;sys_mutex_unlock()是 flag = 1; -
sys_msleep():不能用阻塞延时(会卡死协议栈),必须基于 SysTick 或硬件定时器实现非阻塞轮询——LwIP 内部靠它驱动tcpip_timeouts链表,漏掉就 TCP 不发 ACK、DHCP 不续租。
示例片段(STM32 + HAL):
static volatile uint8_t lwip_sem_flag = 0;<br>sys_sem_t sys_sem_new(u8_t count) { lwip_sem_flag = count; return (sys_sem_t)&lwip_sem_flag; }<br>u32_t sys_arch_sem_wait(sys_sem_t sem, u32_t timeout_ms) {<br> uint32_t start = HAL_GetTick();<br> while (!lwip_sem_flag && (HAL_GetTick() - start < timeout_ms)) {<br> __WFI(); // 进低功耗等待中断<br> }<br> if (lwip_sem_flag) { lwip_sem_flag = 0; return 0; }<br> return SYS_ARCH_TIMEOUT;<br>}
立即学习“C++免费学习笔记(深入)”;
网卡驱动怎么对接 ethernetif.c 而不卡死主循环
裸机没有中断线程化,必须把以太网中断处理拆成“收包中断触发标志”+“主循环中轮询处理”。否则在中断里调 ethernetif_input() 会因 LwIP 内部 malloc/free 触发 HardFault(裸机没堆管理或堆太小)。
关键步骤:
- 在 ETH 中断服务程序里只做最轻量操作:置位
rx_ready_flag,清除中断标志,退出 - 主循环中检查
rx_ready_flag,为真则调用ethernetif_input();同时每 10ms 调一次sys_check_timeouts() -
low_level_input()必须用 DMA 接收缓存区直拷贝到pbuf,禁止用pbuf_alloc(PBUF_RAW, ...)动态分配——裸机 heap 往往只有几 KB,TCP 分段可能失败 - 发送路径同样绕过动态分配:预分配一组固定大小
pbuf(如 4 × 1536B),用环形队列管理,low_level_output()直接 memcpy 到 DMA 发送缓冲区
为什么 DHCP 获取 IP 后 ping 不通,但静态 IP 可以
裸机环境下 DHCP 成功不代表网络就通了——LwIP 的 dhcp_start() 启动后,必须持续调用 sys_check_timeouts() 才能推进 DHCP 状态机(DISCOVER → OFFER → REQUEST → ACK)。如果主循环里只在初始化时调一次,DHCP 就永远卡在第一步。
另一个高频问题是:未启用 LWIP_ARP 或 LWIP_ICMP。即使 IP 配好了,ARP 请求发不出去,目标 MAC 地址未知,所有数据包都会被丢弃;而 ping 依赖 ICMP,关闭它就收不到 Echo Reply。检查 lwipopts.h:
#define LWIP_ARP 1#define LWIP_ICMP 1#define LWIP_DHCP 1-
#define LWIP_NETIF_LINK_CALLBACK 1(用于监测网线插拔)
最后确认 PHY 初始化是否完成:很多 STM32 工程漏掉 HAL_ETH_WritePHYRegister() 配置自协商,导致链路状态为 down,LwIP 直接跳过该 netif 的数据收发。
裸机移植最难的不是代码量,而是时间点控制:每个 sys_* 函数的返回时机、每个 sys_check_timeouts() 的调用间隔、每个中断里的操作边界,差几毫秒就可能让 TCP 连接卡在 SYN_SENT 或 FIN_WAIT_2。建议先用静态 IP + ping 通,再开 DHCP,最后加应用层协议——层层隔离,不然问题会相互掩盖。


















