4. LwIP协议栈在RTOS上的集成
任务目标
在已有RTOS(以FreeRTOS为例)基础上完成LwIP协议栈的集成,使LwIP的tcpip_thread作为独立RTOS任务运行,并通过信号量与邮箱机制实现协议栈与应用程序之间的同步通信,最终验证TCP Echo功能可用。
环境与依赖
- 硬件:任意支持以太网的MCU(本例以STM32F407为例)
- RTOS:FreeRTOS V10.4+(需配置heap_4.c内存管理)
- LwIP:V2.1.2(使用
NO_SYS=0即操作系统模式) - 网卡驱动:自实现或使用STM32 HAL库以太网驱动
核心实现
LwIP在RTOS上的集成核心在于:将协议栈主循环封装为RTOS任务,并让LwIP内部使用RTOS提供的信号量(用于同步)和邮箱(用于消息传递)替代裸机轮询。以下代码展示最小可复现的集成框架:
// lwipopts.h 关键配置
#define NO_SYS 0 // 使用操作系统
#define LWIP_TCPIP_CORE_LOCKING 0 // 使用邮箱+信号量机制(非核心锁定)
#define LWIP_NETCONN 1 // 启用netconn API
#define LWIP_SOCKET 0 // 本示例不使用socket API
#define MEM_ALIGNMENT 4
#define TCPIP_THREAD_STACKSIZE 1024
#define TCPIP_THREAD_PRIO 3
#define DEFAULT_THREAD_STACKSIZE 512
#define DEFAULT_THREAD_PRIO 2
#define DEFAULT_ACCEPTMBOX_SIZE 8
#define DEFAULT_RAW_RECVMBOX_SIZE 8
#define DEFAULT_TCP_RECVMBOX_SIZE 8
#define DEFAULT_UDP_RECVMBOX_SIZE 8
#define TCP_MSS 1460
#define TCP_WND (4 * TCP_MSS)
#define PBUF_POOL_SIZE 20
#define MEM_SIZE 16000
// 集成代码(简化)
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "lwip/init.h"
#include "lwip/tcpip.h"
#include "lwip/api.h"
#include "netif/etharp.h"
#include "ethernetif.h"
// 网卡接口(由驱动层提供)
extern struct netif g_netif;
// 信号量:用于通知协议栈有数据到达
static sys_sem_t rx_semaphore;
// 网卡接收回调(中断或轮询中调用)
void ethernetif_rx_notify(void) {
sys_sem_signal(&rx_semaphore);
}
// 网卡接收线程(RTOS任务)
static void rx_thread(void *arg) {
struct pbuf *p;
while (1) {
// 等待接收信号量(超时100ms轮询)
if (sys_arch_sem_wait(&rx_semaphore, 100) == SYS_ARCH_TIMEOUT) {
continue;
}
// 从网卡驱动获取数据包
while ((p = low_level_input(&g_netif)) != NULL) {
if (g_netif.input(p, &g_netif) != ERR_OK) {
pbuf_free(p);
}
}
}
}
// TCP Echo服务器任务(使用netconn API)
static void echo_task(void *arg) {
struct netconn *conn, *newconn;
struct netbuf *buf;
err_t err;
conn = netconn_new(NETCONN_TCP);
netconn_bind(conn, IP_ADDR_ANY, 7); // 监听端口7
netconn_listen(conn);
while (1) {
err = netconn_accept(conn, &newconn);
if (err != ERR_OK) continue;
while ((err = netconn_recv(newconn, &buf)) == ERR_OK) {
netconn_write(newconn, buf->p->payload, buf->p->len, NETCONN_COPY);
netbuf_delete(buf);
}
netconn_close(newconn);
netconn_delete(newconn);
}
}
// 主函数:初始化并启动RTOS
int main(void) {
// 硬件初始化(时钟、GPIO、以太网MAC/PHY)
hardware_init();
// 创建接收信号量
sys_sem_new(&rx_semaphore, 0);
// 初始化LwIP(内部会创建tcpip_thread)
tcpip_init(NULL, NULL);
// 添加网卡接口
netif_add(&g_netif, &ipaddr, &netmask, &gw, NULL, ethernetif_init, tcpip_input);
netif_set_default(&g_netif);
netif_set_up(&g_netif);
// 创建应用任务
xTaskCreate(rx_thread, "rx", 256, NULL, 2, NULL);
xTaskCreate(echo_task, "echo", 512, NULL, 2, NULL);
// 启动调度器
vTaskStartScheduler();
while (1);
}
同步机制说明
- 信号量:
rx_semaphore用于网卡驱动通知接收线程有新数据包到达,避免CPU空转轮询。 - 邮箱:LwIP内部通过
tcpip_mbox(在tcpip_thread中创建)接收来自各网络接口的tcpip_msg消息,实现协议栈与驱动的解耦。 - 线程安全:
LWIP_TCPIP_CORE_LOCKING=0时,LwIP使用邮箱传递API调用请求,由tcpip_thread统一处理,避免多任务并发访问协议栈核心。
运行与验证
- 编译并烧录固件到目标板,确保PHY芯片初始化成功(可通过LED或串口日志确认)。
- 将开发板通过网线连接至PC,配置PC网卡IP为
192.168.1.2,子网掩码255.255.255.0。 - 开发板默认IP设为
192.168.1.10(在代码中通过ipaddr变量配置)。 - 在PC终端执行:
ping 192.168.1.10,观察是否收到回复。 - 执行TCP Echo测试:
echo "hello" | nc -w 2 192.168.1.10 7,应返回hello。
通过条件:Ping通且Echo服务正确返回数据,说明LwIP协议栈已在RTOS上稳定运行,信号量与邮箱同步机制工作正常。
常见问题
- 编译错误
undefined reference to sys_sem_signal:检查sys_arch.c中是否已实现所有LwIP要求的系统抽象函数。 - Ping不通:先检查PHY链路是否建立(通过状态寄存器),再确认IP地址配置正确,最后用逻辑分析仪查看RMII信号。
- 协议栈卡死:增大
TCPIP_THREAD_STACKSIZE至2048,并检查tcpip_thread优先级是否低于中断优先级。