基于lwIP的嵌入式网络调试工具开发实战
在嵌入式Linux/RTOS设备联调中,常需一个轻量网络调试助手——既能发RAW TCP/UDP包、又能做简单HTTP查看,以验证PHY、交换机或云端连通性。lwIP(lightweight IP stack) 以其极小footprint(~40KB ROM/10KB RAM)成为STM32/RT-Thread/NXP平台首选。本文以STM32F407 + lwIP RAW API为例,实现TCP Echo Server + UDP Send/Recv + 简易HTTP状态页。
一、lwIP移植与基础初始化(简述)
假定你已完成:
• ethernetif.c 对接 HAL_ETH_ReadFrame / HAL_ETH_Transmit
• lwip_init() + netif_add(..., ðernetif_init) + dhcp_start() 或静态IP
关键片段(main中):
struct netif g_netif;
ip4_addr_t ipaddr, netmask, gw;
IP4_ADDR(&ipaddr, 192,168,1,100);
IP4_ADDR(&netmask, 255,255,255,0);
IP4_ADDR(&gw, 192,168,1,1);
netif_add(&g_netif, &ipaddr, &netmask, &gw,
NULL, ethernetif_init, ethernet_input);
netif_set_default(&g_netif);
netif_set_up(&g_netif);
dhcp_start(&g_netif); // 或 static 用 netif_set_addr()
// 周期性调用(通常在sys_check_timeouts或RTOS任务)
sys_check_timeouts();
二、TCP Echo Server(RAW API)
lwIP RAW API无需socket层,适合资源紧张场景。
#include "lwip/tcp.h"
/* 接受新连接回调 */
static err_t echo_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
{
LWIP_UNUSED(arg);
if (err != ERR_OK || newpcb == NULL)
return ERR_VAL;
tcp_arg(newpcb, NULL);
tcp_recv(newpcb, echo_recv); // 注册接收回调
tcp_err(newpcb, NULL);
tcp_poll(newpcb, NULL, 4); // 空闲轮询间隔
return ERR_OK;
}
/* 数据接收回调 —— 原样回显 */
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(需置置PBUF_FLAG_REF)*/
tcp_write(tpcb, p->payload, p->tot_len, TCP_WRITE_FLAG_COPY);
tcp_output(tpcb);
pbuf_free(p);
return ERR_OK;
}
/* 启动TCP Echo(端口7或用户定义) */
void start_tcp_echo(uint16_t port)
{
struct tcp_pcb *pcb = tcp_new();
if (!pcb) return;
tcp_bind(pcb, IP_ADDR_ANY, port);
pcb = tcp_listen(pcb);
tcp_accept(pcb, echo_accept);
}
在main()初始化后调用 start_tcp_echo(2323);,PC端用nc 192.168.1.100 2323即可测试Echo。
三、UDP收发(调试发包与抓包)
UDP更简单,常用于发送传感器快照或接收广播命令。
#include "lwip/udp.h"
static void udp_recv_cb(void *arg, struct udp_pcb *pcb,
struct pbuf *p, const ip_addr_t *addr, u16_t port)
{
if (p) {
// 简单打印长度(实际可解析协议)
printf("[UDP] from %d.%d.%d.%d:%d len=%d\n",
ip4_addr1(addr),ip4_addr2(addr),ip4_addr3(addr),ip4_addr4(addr),
port, p->tot_len);
pbuf_free(p);
}
}
void start_udp_debug(uint16_t port)
{
struct udp_pcb *upcb = udp_new();
udp_bind(upcb, IP_ADDR_ANY, port);
udp_recv(upcb, udp_recv_cb, NULL);
}
/* 主动发送UDP(例:心跳包) */
void udp_send_heartbeat(const ip_addr_t *dst, uint16_t dport)
{
struct udp_pcb *pcb = udp_new();
struct pbuf *pb = pbuf_alloc(PBUF_TRANSPORT, 4, PBUF_RAM);
if (!pb) { udp_remove(pcb); return; }
((uint32_t*)pb->payload)[0] = (uint32_t)HAL_GetTick();
udp_sendto(pcb, pb, dst, dport);
pbuf_free(pb);
udp_remove(pcb);
}
四、简易HTTP状态页(验证Web栈)
仅返回动态拼接的文本/HTML,展示IP、MAC、 uptime——用于现场浏览器确认网络存活。
static err_t http_recv(void *arg, struct tcp_pcb *tpcb,
struct pbuf *p, err_t err)
{
if (!p) { tcp_close(tpcb); return ERR_OK; }
char html[256];
uint32_t up = HAL_GetTick()/1000;
snprintf(html, sizeof(html),
"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"
"<h2>Dev Info</h2>"
"IP: 192.168.1.100<br>"
"Uptime: %lus<br>"
"<a href=\"/refresh\">Refresh</a>", up);
tcp_write(tpcb, html, strlen(html), TCP_WRITE_FLAG_COPY);
tcp_output(tpcb);
pbuf_free(p);
return ERR_OK;
}
void start_http_status(uint16_t port)
{
struct tcp_pcb *p = tcp_new();
tcp_bind(p, IP_ADDR_ANY, port);
p = tcp_listen(p);
tcp_accept(p, [](void*, struct tcp_pcb *n, err_t e){
tcp_recv(n, http_recv); return ERR_OK;
});
}
浏览器访问 http://192.168.1.100:80 即看到设备状态。
五、调试技巧与常见坑
现象 原因 解决
能Ping不通TCP ethernetif_input()未周期性调用或中断未连接 确认 HAL_ETH_GetRxDataLength + low_level_input 正常
TCP偶发断连 sys_check_timeouts()调用周期 > 250ms 放RTOS任务或SysTick 10ms周期调
UDP sendto返回 -1 目标IP未ARP解析完 先ping目标或等arp表填充
HTTP响应乱码 Content-Length缺失但浏览器宽容;建议加Content-Length 计算strlen再发
DHCP拿不到IP PHY未Link Up或MAC地址全0 检查 phy_link_status(), 设唯一MAC
六、结语
基于lwIP打造嵌入式网络调试工具,核心是正确周期调用sys_check_timeouts() + RAW API回调注册。上述TCP Echo/UDP/HTTP三合一固件只需额外~2KB RAM,却能在现场快速验证物理链路、IP配置及防火墙策略,是嵌入式网络开发的“瑞士军刀”。





