4. 移植lwIP并实现TCP回环服务器

任务目标

在STM32F407裸机环境中完成lwIP协议栈的移植,并基于RAW API实现一个TCP回环服务器:客户端发送任意数据,服务器将原数据返回。通过PC端网络调试工具验证回环功能。

环境与依赖

  • 硬件:STM32F407开发板(板载或外接PHY芯片DP83848),PC网线直连
  • 软件:STM32CubeIDE(或任意ARM GCC工具链)、lwIP 2.1.2源码包
  • 底层驱动:需已有可用的ETH MAC驱动(本文使用STM32 HAL库的Ethernet驱动)和CMSIS_OS可选(本案例不使用OS,直接裸机轮询)

移植要点

lwIP移植核心是提供三部分:操作系统抽象层(sys_arch)、网络接口底层(low_level_output/input)、时钟与临界区保护。本案例裸机运行,sys_arch仅需提供临界区保护和获取tick毫秒数;底层接口直接操作HAL的DMA描述符收发数据。

关键完整代码

// lwipopts.h 核心配置
#define NO_SYS                  1
#define LWIP_NETCONN            0
#define LWIP_SOCKET             0
#define LWIP_RAW                1
#define MEM_ALIGNMENT           4
#define MEM_SIZE                (10*1024)
#define PBUF_POOL_SIZE          20
#define PBUF_POOL_BUFSIZE       512
#define TCP_MSS                 1460
#define TCP_WND                 (4*TCP_MSS)
#define TCP_SND_BUF             (4*TCP_MSS)
#define CHECKSUM_GEN_IP         1
#define CHECKSUM_GEN_UDP        1
#define CHECKSUM_GEN_TCP        1
#define CHECKSUM_CHECK_IP       1
#define CHECKSUM_CHECK_UDP      1
#define CHECKSUM_CHECK_TCP      1
#define LWIP_STATS              0
#define LWIP_DHCP               0
#define LWIP_DNS                0

// sys_arch.c 裸机实现
#include "lwip/opt.h"
#include "lwip/sys.h"
#include "cmsis_os.h"
#include "stm32f4xx_hal.h"

static uint32_t sys_counter;

void sys_init(void) {
    sys_counter = 0;
}

u32_t sys_jiffies(void) {
    return sys_counter;
}

u32_t sys_now(void) {
    return HAL_GetTick();
}

sys_prot_t sys_arch_protect(void) {
    __disable_irq();
    return 0;
}

void sys_arch_unprotect(sys_prot_t pval) {
    (void)pval;
    __enable_irq();
}

// ethernetif.c 底层接口(关键部分)
static err_t low_level_output(struct netif *netif, struct pbuf *p) {
    struct pbuf *q;
    uint8_t *buffer = (uint8_t *)TX_BUF;
    uint16_t len = 0;

    for (q = p; q != NULL; q = q->next) {
        memcpy(&buffer[len], q->payload, q->len);
        len += q->len;
    }

    HAL_ETH_TransmitFrame(&heth, buffer, len);
    return ERR_OK;
}

static void low_level_input(struct netif *netif) {
    uint8_t *buffer;
    uint16_t len;
    struct pbuf *p, *q;

    if (HAL_ETH_GetReceivedFrame(&heth) != HAL_OK) return;

    buffer = (uint8_t *)heth.RxFrame->buffer;
    len = heth.RxFrame->length;

    p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
    if (p != NULL) {
        for (q = p; q != NULL; q = q->next) {
            memcpy(q->payload, buffer, q->len);
            buffer += q->len;
        }
        netif->input(p, netif);
    }
    HAL_ETH_ReleaseRxFrame(&heth);
}

// main.c 回环服务器
#include "lwip/tcp.h"
#include "lwip/init.h"
#include "ethernetif.h"

static struct netif g_netif;
static struct tcp_pcb *server_pcb;

static err_t echo_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) {
    if (p != NULL) {
        tcp_write(pcb, p->payload, p->len, 1);
        pbuf_free(p);
    } else if (err == ERR_OK) {
        tcp_close(pcb);
    }
    return ERR_OK;
}

static err_t echo_accept(void *arg, struct tcp_pcb *newpcb, err_t err) {
    tcp_recv(newpcb, echo_recv);
    return ERR_OK;
}

int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_ETH_Init();

    lwip_init();
    netif_add(&g_netif, &ipaddr, &netmask, &gw, NULL, ethernetif_init, ethernet_input);
    netif_set_default(&g_netif);
    netif_set_up(&g_netif);

    server_pcb = tcp_new();
    tcp_bind(server_pcb, IP_ADDR_ANY, 8080);
    server_pcb = tcp_listen(server_pcb);
    tcp_accept(server_pcb, echo_accept);

    while (1) {
        ethernetif_input(&g_netif);
        sys_check_timeouts();
    }
}

运行与验证

  1. 将开发板IP设为192.168.1.10,PC网口IP设为192.168.1.100(同一网段)
  2. 编译烧录后,PC端打开网络调试助手,选择TCP Client,连接192.168.1.10:8080
  3. 发送任意字符串(如Hello lwIP),观察是否收到相同内容的回显

通过条件:PC端能成功建立TCP连接,发送数据后立即收到完全一致的回显内容,且连续收发50次无断连、无数据错乱。

常见问题

  • 连接不上:检查PHY芯片复位引脚是否拉高、网线是否直连、PC防火墙是否放行
  • 能连接但无回显:确认tcp_write后调用了tcp_output(RAW API需手动发送),或在echo_recv中增加tcp_output(pcb)
  • 数据乱码:检查DMA描述符缓冲区大小是否大于最大帧长,以及PBUF_POOL_BUFSIZE是否足够