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();
}
}
运行与验证
- 将开发板IP设为
192.168.1.10,PC网口IP设为192.168.1.100(同一网段) - 编译烧录后,PC端打开网络调试助手,选择TCP Client,连接
192.168.1.10:8080 - 发送任意字符串(如
Hello lwIP),观察是否收到相同内容的回显
通过条件:PC端能成功建立TCP连接,发送数据后立即收到完全一致的回显内容,且连续收发50次无断连、无数据错乱。
常见问题
- 连接不上:检查PHY芯片复位引脚是否拉高、网线是否直连、PC防火墙是否放行
- 能连接但无回显:确认
tcp_write后调用了tcp_output(RAW API需手动发送),或在echo_recv中增加tcp_output(pcb) - 数据乱码:检查DMA描述符缓冲区大小是否大于最大帧长,以及
PBUF_POOL_BUFSIZE是否足够