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统一处理,避免多任务并发访问协议栈核心。

运行与验证

  1. 编译并烧录固件到目标板,确保PHY芯片初始化成功(可通过LED或串口日志确认)。
  2. 将开发板通过网线连接至PC,配置PC网卡IP为192.168.1.2,子网掩码255.255.255.0。
  3. 开发板默认IP设为192.168.1.10(在代码中通过ipaddr变量配置)。
  4. 在PC终端执行:ping 192.168.1.10,观察是否收到回复。
  5. 执行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优先级是否低于中断优先级。