最近有些痴迷于TCP/IP,于是想借着这股劲,好好研究一下LwIP这个开源协议栈,这篇文章记录了如何下载LwIP源码,看看LwIP里有些什么以及如何将这个协议栈适配到开发板上运行。

目录

一、LwIP源码下载
二、LwIP里面都有些什么
三、LwIP移植

一、LwIP源码下载

网址:LwIP下载地址

进入网站后,可以看到有两种压缩包

  • contrib包:提供移植文件和应用实例
  • LwIP源码包:提供TCP/IP协议栈的源码

此处选择contrib-2.1.0LwIP2.1.3进行下载

二、LwIP里面都有些什么

(1)contrib

目录详情
addonsLwIP的拓展插件
apps一些简单的应用示例
CoverityLwIP的静态分析工具
examples一些高阶的应用示例
ports操作系统的适配

我们重点关注 apps/examples/ports这三个目录。

apps目录

实现了一些简单的应用,如socket的演示,ping的实现等

examples

实现了mqtt、tftp等应用

ports

提供了操作系统的适配,比如FreeRTOS的适配LwIP就在这个目录里帮助我们实现了,后续如果使用FreeRTOS作为操作系统的话,可以直接使用这个目录下FreeRTOS中的sys_arch.c和sys_arch.h

(2)lwip-2.1.3

共有三个目录,分别是doc/src/test。doc存放了LwIP官方的技术文档;src里面是TCP/IP协议实现,我们需要重点关注;test是一些测试,我们暂时不需要管。

src目录下的文件夹如下

目录详情
apiNetcoon和Socket的API接口
apps应用程序,如mqtt、http等
coreLwIP的内核源文件
include内核的头文件
netif包括网卡的编写模板,通用的网络接口层

core目录

文件/文件夹描述
ipv4目录IPv4协议的代码实现,包括了autoip、dhcp、etharp、icmp、igmp等协议实现。
ipv6目录IPV6协议的代码实现(暂不研究)
altcp_alloc.c、altcp_tcp.c、altcp.c实现TCP连接的API函数(application layer)
def.c一些LwIP用到的通用函数(例如主机序和网络序的转换、字符串查找比较、整数字符串转换等等)
dns.c域名解析系统的代码实现
inet_chksum.c计算校验和功能
init.c检测LwIP的宏定义配置错误和提示
ip.cip协议相关函数(与ipv4和ipv6目录有关)
mem.c动态内存堆管理器,用于取代C库的malloc
memp.c动态内存池(memmory pool)管理器
netif.cLwIP网卡的操作函数,例如注册/删除网卡、使能/禁用网卡、设置网卡的IP/掩码/网关等
pbuf.cLwIP协议栈对网络数据包的各种操作
raw.craw操作(绕过传输层直接操控PCB)
stats.c统计模块
sys.c系统抽象层
tcp.c、tcp_in.c、tcp_out.cTCP协议相关(TCP连接、数据包的输入输出、TCP定时器等)
timeouts.cLwIP内核实现的各种协议的超时处理机制
udp.cudp协议相关

三、移植LwIP到板卡

LwIP的移植分为使用操作系统和不使用操作系统,我们主要研究使用操作系统的情景。

移植准备:
1、带网卡的mcu,本次使用STM32F407 + yt8512c作为示例
2、ST原厂的SDK(本人比较倾向于使用标准库,尽管HAL库已经非常便捷了)
3、能够运行Cortex-M4芯片的FreeRTOS工程。

(1)使用STM32的FreeRTOS模板

我们可以直接克隆仓库STM32的FreeRTOS模板

仓库目录如下所示:

进入/app/Hello,打开bash依次输入以下命令:

make clean
cmake -G 'Unix Makefiles'
make

输出结果如下所示:

使用openocd/Jlink将固件烧录至板卡,此时观察串口调试助手,调试串口正以1s的周期打印。

后续我们将使用这个例程进行LwIP的移植。

(2)移植LwIP

在STM32的工程模板根目录创建lwip文件夹,进入该文件夹,现在
1、将之前下载的LwIP-2.1.3源代码拷贝进去
2、创建lwip_adapter文件夹,之后的适配文件我们放在这个目录中
完成后如下图所示

[1]添加RTOS支持

LwIP在实现中引入了邮箱信号量互斥锁等多种操作系统相关的同步与通信机制。而这些机制,操作系统不同的情况下,具体实现也是不同的,所以需要开发者自己去适配。

在LwIP中,我们可以通过查看/src/include/lwip/sys.h来了解到底哪些操作系统接口需要我们提供,LwIP称其为操作系统抽象层,我们在lwip_adapter文件夹下将这些接口一个一个实现就好了。

不过对于像FreeRTOS和WIN32这样的主流操作系统,LwIP已在其代码库中维护了一套对应的操作系统适配层实现。我们作为开发者可以直接使用,实现的源码放在之前下载的contrib文件夹下的ports中。

我们直接将freertos这个目录拷贝到我们的STM32模板工程中,如下图所示:

之后在sys_arch.h的位置添加cc.h,代码如下(lwip协议栈包含了这个头文件,暂时不知道用途)

#ifndef LWIP_ARCH_CC_H
#define LWIP_ARCH_CC_H

/* see https://sourceforge.net/p/predef/wiki/OperatingSystems/ */
#if defined __ANDROID__
#define LWIP_UNIX_ANDROID
#elif defined __linux__
#define LWIP_UNIX_LINUX
#elif defined __APPLE__
#define LWIP_UNIX_MACH
#elif defined __OpenBSD__
#define LWIP_UNIX_OPENBSD
#elif defined __CYGWIN__
#define LWIP_UNIX_CYGWIN
#elif defined __GNU__
#define LWIP_UNIX_HURD
#endif

#define LWIP_TIMEVAL_PRIVATE 0
#include <sys/time.h>

#define LWIP_ERRNO_INCLUDE <errno.h>

#if defined(LWIP_UNIX_LINUX) || defined(LWIP_UNIX_HURD)
#define LWIP_ERRNO_STDINCLUDE    1
#endif

//#define LWIP_RAND() ((u32_t)rand())

/* different handling for unit test, normally not needed */
#ifdef LWIP_NOASSERT_ON_ERROR
#define LWIP_ERROR(message, expression, handler) do { if (!(expression)) { \
  handler;}} while(0)
#endif

#if defined(LWIP_UNIX_ANDROID) && defined(FD_SET)
typedef __kernel_fd_set fd_set;
#endif

#if defined(LWIP_UNIX_MACH)
/* sys/types.h and signal.h bring in Darwin byte order macros. pull the
   header here and disable LwIP's version so that apps still can get
   the macros via LwIP headers and use system headers */
#include <sys/types.h>
#define LWIP_DONT_PROVIDE_BYTEORDER_FUNCTIONS
#endif

struct sio_status_s;
typedef struct sio_status_s sio_status_t;
#define sio_fd_t sio_status_t*
#define __sio_fd_t_defined

typedef unsigned int sys_prot_t;

#endif /* LWIP_ARCH_CC_H */

添加完成后,如图所示,针对操作系统的适配我们就搞定了。

[2]添加网卡驱动

ST原厂标准库中默认是没有提供网卡的驱动文件的,而在STM32_Template中,已经添加了stm32f4x7_eth.c和stm32f4x7_eth.h以及stm32f4x7_eth_conf.h三个文件。

我们打开stm32f4x7_eth_conf.h,根据需要修改PHY芯片的寄存器地址。大多数PHY芯片的公共寄存器都是一样的,但不同PHY芯片的特殊寄存器往往是不一样的,这里需要手动指定的是SR寄存器。

2026-01-21T14:46:20.png
2026-01-21T14:46:20.png

现在我们使用ST提供的网卡驱动,编写网卡BSP层的代码。

进入lwip_adapter,新建文件stm32f4_eth_bsp.c

首先是用到的头文件

头文件定义

#include "misc.h"
#include "stm32f4xx.h"
#include "stm32f4x7_eth.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_syscfg.h"
#include <FreeRTOS.h>
#include "queue.h"
#include "semphr.h"

之后需要根据自己的原理图初始化网卡用到的引脚,我这里使用的是RMII,所以加上了#define RMII_MODE,如果使用的是MII,请使用#define MII_MODE

引脚初始化

/*
    ETH_MDIO -------------------------> PA2
    ETH_MDC --------------------------> PC1
    ETH_MII_RX_CLK/ETH_RMII_REF_CLK---> PA1
    ETH_MII_RX_DV/ETH_RMII_CRS_DV ----> PA7
    ETH_MII_RXD0/ETH_RMII_RXD0 -------> PC4
    ETH_MII_RXD1/ETH_RMII_RXD1 -------> PC5
    ETH_MII_TX_EN/ETH_RMII_TX_EN -----> PG11
    ETH_MII_TXD0/ETH_RMII_TXD0 -------> PG13
    ETH_MII_TXD1/ETH_RMII_TXD1 -------> PG14
*/
/* ETH_MDIO */
#define ETH_MDIO_GPIO_CLK               RCC_AHB1Periph_GPIOA
#define ETH_MDIO_PORT                   GPIOA
#define ETH_MDIO_PIN                    GPIO_Pin_2
#define ETH_MDIO_AF                     GPIO_AF_ETH
#define ETH_MDIO_SOURCE                 GPIO_PinSource2

/* ETH_MDC */
#define ETH_MDC_GPIO_CLK                RCC_AHB1Periph_GPIOC
#define ETH_MDC_PORT                    GPIOC
#define ETH_MDC_PIN                     GPIO_Pin_1
#define ETH_MDC_AF                      GPIO_AF_ETH
#define ETH_MDC_SOURCE                  GPIO_PinSource1

/* ETH_RMII_REF_CLK */
#define ETH_RMII_REF_CLK_GPIO_CLK       RCC_AHB1Periph_GPIOA
#define ETH_RMII_REF_CLK_PORT           GPIOA
#define ETH_RMII_REF_CLK_PIN            GPIO_Pin_1
#define ETH_RMII_REF_CLK_AF             GPIO_AF_ETH
#define ETH_RMII_REF_CLK_SOURCE         GPIO_PinSource1

/* ETH_RMII_CRS_DV */
#define ETH_RMII_CRS_DV_GPIO_CLK        RCC_AHB1Periph_GPIOA
#define ETH_RMII_CRS_DV_PORT            GPIOA
#define ETH_RMII_CRS_DV_PIN             GPIO_Pin_7
#define ETH_RMII_CRS_DV_AF              GPIO_AF_ETH
#define ETH_RMII_CRS_DV_SOURCE          GPIO_PinSource7

/* ETH_RMII_RXD0 */
#define ETH_RMII_RXD0_GPIO_CLK          RCC_AHB1Periph_GPIOC
#define ETH_RMII_RXD0_PORT              GPIOC
#define ETH_RMII_RXD0_PIN               GPIO_Pin_4
#define ETH_RMII_RXD0_AF                GPIO_AF_ETH
#define ETH_RMII_RXD0_SOURCE            GPIO_PinSource4

/* ETH_RMII_RXD1 */
#define ETH_RMII_RXD1_GPIO_CLK          RCC_AHB1Periph_GPIOC
#define ETH_RMII_RXD1_PORT              GPIOC
#define ETH_RMII_RXD1_PIN               GPIO_Pin_5
#define ETH_RMII_RXD1_AF                GPIO_AF_ETH
#define ETH_RMII_RXD1_SOURCE            GPIO_PinSource5

/* ETH_RMII_TX_EN */
#define ETH_RMII_TX_EN_GPIO_CLK         RCC_AHB1Periph_GPIOG
#define ETH_RMII_TX_EN_PORT             GPIOG
#define ETH_RMII_TX_EN_PIN              GPIO_Pin_11
#define ETH_RMII_TX_EN_AF               GPIO_AF_ETH
#define ETH_RMII_TX_EN_SOURCE           GPIO_PinSource11

/* ETH_RMII_TXD0 */
#define ETH_RMII_TXD0_GPIO_CLK          RCC_AHB1Periph_GPIOG
#define ETH_RMII_TXD0_PORT              GPIOG
#define ETH_RMII_TXD0_PIN               GPIO_Pin_13
#define ETH_RMII_TXD0_AF                GPIO_AF_ETH
#define ETH_RMII_TXD0_SOURCE            GPIO_PinSource13

/* ETH_RMII_TXD1 */
#define ETH_RMII_TXD1_GPIO_CLK          RCC_AHB1Periph_GPIOG
#define ETH_RMII_TXD1_PORT              GPIOG
#define ETH_RMII_TXD1_PIN               GPIO_Pin_14
#define ETH_RMII_TXD1_AF                GPIO_AF_ETH
#define ETH_RMII_TXD1_SOURCE            GPIO_PinSource14
 
#define RMII_MODE
void ETH_GPIO_Config(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  
 
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA| RCC_AHB1Periph_GPIOC|RCC_AHB1Periph_GPIOG ,ENABLE);

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);  

#ifdef MII_MODE 
 #ifdef PHY_CLOCK_MCO
  RCC_MCO1Config(RCC_MCO1Source_HSE, RCC_MCO1Div_1);
 #endif 

  SYSCFG_ETH_MediaInterfaceConfig(SYSCFG_ETH_MediaInterface_MII);
#elif defined RMII_MODE  
  SYSCFG_ETH_MediaInterfaceConfig(SYSCFG_ETH_MediaInterface_RMII);
#endif

/* Ethernet pins configuration ************************************************/
   /* Configure ETH_MDIO */
  GPIO_InitStructure.GPIO_Pin = ETH_MDIO_PIN;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_Init(ETH_MDIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(ETH_MDIO_PORT, ETH_MDIO_SOURCE, ETH_MDIO_AF);
    
    /* Configure ETH_MDC */
  GPIO_InitStructure.GPIO_Pin = ETH_MDC_PIN;
  GPIO_Init(ETH_MDC_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(ETH_MDC_PORT, ETH_MDC_SOURCE, ETH_MDC_AF);
    
    /* Configure ETH_RMII_REF_CLK */
  GPIO_InitStructure.GPIO_Pin = ETH_RMII_REF_CLK_PIN;
  GPIO_Init(ETH_RMII_REF_CLK_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(ETH_RMII_REF_CLK_PORT, ETH_RMII_REF_CLK_SOURCE, ETH_RMII_REF_CLK_AF);
    
    /* Configure ETH_RMII_CRS_DV */
  GPIO_InitStructure.GPIO_Pin = ETH_RMII_CRS_DV_PIN;
  GPIO_Init(ETH_RMII_CRS_DV_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(ETH_RMII_CRS_DV_PORT, ETH_RMII_CRS_DV_SOURCE, ETH_RMII_CRS_DV_AF);
    
    /* Configure ETH_RMII_RXD0 */
  GPIO_InitStructure.GPIO_Pin = ETH_RMII_RXD0_PIN;
  GPIO_Init(ETH_RMII_RXD0_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(ETH_RMII_RXD0_PORT, ETH_RMII_RXD0_SOURCE, ETH_RMII_RXD0_AF);
    
    /* Configure ETH_RMII_RXD1 */
  GPIO_InitStructure.GPIO_Pin = ETH_RMII_RXD1_PIN;
  GPIO_Init(ETH_RMII_RXD1_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(ETH_RMII_RXD1_PORT, ETH_RMII_RXD1_SOURCE, ETH_RMII_RXD1_AF);
    
    /* Configure ETH_RMII_TX_EN */
  GPIO_InitStructure.GPIO_Pin = ETH_RMII_TX_EN_PIN;
  GPIO_Init(ETH_RMII_TX_EN_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(ETH_RMII_TX_EN_PORT, ETH_RMII_TX_EN_SOURCE, ETH_RMII_TX_EN_AF);
    
    /* Configure ETH_RMII_TXD0 */
  GPIO_InitStructure.GPIO_Pin = ETH_RMII_TXD0_PIN;
  GPIO_Init(ETH_RMII_TXD0_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(ETH_RMII_TXD0_PORT, ETH_RMII_TXD0_SOURCE, ETH_RMII_TXD0_AF);
    
    /* Configure ETH_RMII_TXD1 */
  GPIO_InitStructure.GPIO_Pin = ETH_RMII_TXD1_PIN;
  GPIO_Init(ETH_RMII_TXD1_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(ETH_RMII_TXD1_PORT, ETH_RMII_TXD1_SOURCE, ETH_RMII_TXD1_AF);        
}

接下来是MAC和DMA的配置

#define CHECKSUM_BY_HARDWARE
ETH_InitTypeDef ETH_InitStructure;
static void ETH_MACDMA_Config(void)
{ 
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_ETH_MAC | RCC_AHB1Periph_ETH_MAC_Tx |
                         RCC_AHB1Periph_ETH_MAC_Rx, ENABLE);
  ETH_DeInit();

  ETH_SoftwareReset();

  while (ETH_GetSoftwareResetStatus() == SET);

  ETH_StructInit(&ETH_InitStructure);

  /*------------------------   MAC  -----------------------------------*/  
  ETH_InitStructure.ETH_AutoNegotiation = ETH_AutoNegotiation_Enable;
//  ETH_InitStructure.ETH_AutoNegotiation = ETH_AutoNegotiation_Disable; 
//  ETH_InitStructure.ETH_Speed = ETH_Speed_10M;
//  ETH_InitStructure.ETH_Mode = ETH_Mode_FullDuplex;
  ETH_InitStructure.ETH_LoopbackMode = ETH_LoopbackMode_Disable;
  ETH_InitStructure.ETH_RetryTransmission = ETH_RetryTransmission_Disable;
  ETH_InitStructure.ETH_AutomaticPadCRCStrip = ETH_AutomaticPadCRCStrip_Disable;
  ETH_InitStructure.ETH_ReceiveAll = ETH_ReceiveAll_Disable;
  ETH_InitStructure.ETH_BroadcastFramesReception = ETH_BroadcastFramesReception_Enable;
  ETH_InitStructure.ETH_PromiscuousMode = ETH_PromiscuousMode_Disable;
  ETH_InitStructure.ETH_MulticastFramesFilter = ETH_MulticastFramesFilter_Perfect;
  ETH_InitStructure.ETH_UnicastFramesFilter = ETH_UnicastFramesFilter_Perfect;
#ifdef CHECKSUM_BY_HARDWARE
  ETH_InitStructure.ETH_ChecksumOffload = ETH_ChecksumOffload_Enable;
#endif

  /*------------------------   DMA   -----------------------------------*/  
  ETH_InitStructure.ETH_DropTCPIPChecksumErrorFrame = ETH_DropTCPIPChecksumErrorFrame_Enable;
  ETH_InitStructure.ETH_ReceiveStoreForward = ETH_ReceiveStoreForward_Enable;
  ETH_InitStructure.ETH_TransmitStoreForward = ETH_TransmitStoreForward_Enable;
 
  ETH_InitStructure.ETH_ForwardErrorFrames = ETH_ForwardErrorFrames_Disable;
  ETH_InitStructure.ETH_ForwardUndersizedGoodFrames = ETH_ForwardUndersizedGoodFrames_Disable;
  ETH_InitStructure.ETH_SecondFrameOperate = ETH_SecondFrameOperate_Enable;
  ETH_InitStructure.ETH_AddressAlignedBeats = ETH_AddressAlignedBeats_Enable;
  ETH_InitStructure.ETH_FixedBurst = ETH_FixedBurst_Enable;
  ETH_InitStructure.ETH_RxDMABurstLength = ETH_RxDMABurstLength_32Beat;
  ETH_InitStructure.ETH_TxDMABurstLength = ETH_TxDMABurstLength_32Beat;
  ETH_InitStructure.ETH_DMAArbitration = ETH_DMAArbitration_RoundRobin_RxTx_2_1;

  ETH_Init(&ETH_InitStructure, 0);

  ETH_DMAITConfig(ETH_DMA_IT_NIS | ETH_DMA_IT_R, ENABLE);
}

配置中断优先级

static void ETH_NVIC_Config(void)
{
  NVIC_InitTypeDef   NVIC_InitStructure; 
  
  /* Enable the Ethernet global Interrupt */
  NVIC_InitStructure.NVIC_IRQChannel = ETH_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 12 ;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
}

我的PHY芯片并未使用中断引脚,所以我只能使用一个线程来进行状态轮询Phy的LinkUp和LinkDown
Phy检测任务

#define Phy_address 0x0
uint8_t __phy_link_status = 0;
static void __poll_phy_monitor_task_entry(void *pram) 
{
    uint16_t reg_value = 0;
    uint32_t temp_reg;
    uint8_t  link_status = 0;
    uint8_t  speed = 0;
    uint8_t  mode = 0;
    while(1) {

        reg_value =  ETH_ReadPHYRegister(Phy_address,PHY_BSR);
        link_status = (reg_value & 0x4) == 0x4?1:0;

        if((reg_value & PHY_DUPLEX_STATUS) != (uint32_t)RESET) {  
            mode = 1;  
        } else {
            mode = 0;
        }

        if(reg_value & PHY_SPEED_STATUS == PHY_SPEED_STATUS) {
            speed = 0; 
        } else {
            speed = 1;      
        }

        /* 当前phy处于LinkDown */
        if (0 == __phy_link_status) {
            if(link_status == 1) {
                __phy_link_status = 1;
                printf("eth0 Link Up ,");
                if (0 == speed) {
                    ETH_InitStructure.ETH_Speed = ETH_Speed_100M;
                    printf("100Mbps ,");
                } else if(1 == speed) {
                    printf("10Mbps ,");
                    ETH_InitStructure.ETH_Speed = ETH_Speed_10M;
                }
                if(mode) {
                    ETH_InitStructure.ETH_Mode = ETH_Mode_FullDuplex; 
                    printf("Full duplex\r\n");
                } else {
                    ETH_InitStructure.ETH_Mode = ETH_Mode_HalfDuplex;   
                    printf("Half duplex\r\n");
                }
                
              temp_reg = ETH->MACCR;
              temp_reg |= (uint32_t)(ETH_InitStructure.ETH_Speed | ETH_InitStructure.ETH_Mode);
              _eth_delay_(ETH_REG_WRITE_DELAY);
              ETH->MACCR = temp_reg;
              ETH_Start();
            }
        } else {
            if(link_status == 0) {
                __phy_link_status =  0;
                printf("eth0 Link Down\r\n");
            }
        }
        vTaskDelay(1000);
    }
}

我们将前面提到的网卡初始化函数进行整合

应用层的入口函数

void ETH_BSP_Config(void)
{
    ETH_GPIO_Config();
    ETH_NVIC_Config();
    ETH_MACDMA_Config();

    static StaticTask_t PhyTaskTCB;
    static StackType_t PhyTaskStack[ 1024 ];

    //静态创建任务
    ( void ) xTaskCreateStatic( __poll_phy_monitor_task_entry,
                                "main_task",
                                1024,
                                NULL,
                                configMAX_PRIORITIES - 1U,
                                &( PhyTaskStack[ 0 ] ),
                                &( PhyTaskTCB ) );
}

现在,我们对已有的功能进行简单的一个验证,修改CMakeLists.txt,加入ST的网卡驱动和刚刚编写的BSP代码,并在MAINTask中调用ETH_BSP_Config()函数。


编译后将固件烧录至板卡,此时我们使用网线连接PC和板卡,Phy检测任务会打印当前Phy的连接状态、协商的通信速率、工作模式。如果将网线断开,Phy检测任务会打印Link Down。

[3]netif

netif,即网络接口。在LwIP中,网络接口在下层调用网卡驱动程序提供的功能接口,在上层则为协议栈提供服务。

在使用多线程的LwIP中,针对网络接口,我们需要适配的接口主要分为三个部分,初始化、数据发送、数据接收。

在lwip/lwip_adapter下新建netif_eth文件夹,并在文件夹下创建netif_eth.c,相关代码如下,后面有时间再一点点讲解。
这里大致解释一下,针对初始化函数ethernetif_init,后面会使用netif_add将其添加到网络接口的初始化函数中。而发送和接收,后面会在特定的章节介绍。

#include "lwip/opt.h"
#include "lwip/def.h"
#include "lwip/mem.h"
#include "lwip/pbuf.h"
#include "lwip/timeouts.h"
#include "netif/etharp.h"
#include "err.h"
#include "stm32f4x7_eth.h"
#include <string.h>
#include "semphr.h"


#define netifMTU                                (1500)
#define netifINTERFACE_TASK_STACK_SIZE        ( 350 )
#define netifINTERFACE_TASK_PRIORITY        ( configMAX_PRIORITIES - 1 )
#define netifGUARD_BLOCK_TIME            ( 250 )
/* The time to block waiting for input. */
#define emacBLOCK_TIME_WAITING_FOR_INPUT    ( ( portTickType ) 100 )

/* Define those to better describe your network interface. */
#define IFNAME0 's'
#define IFNAME1 't'


static struct netif *s_pxNetIf = NULL;
xSemaphoreHandle s_xSemaphore = NULL;


/* Ethernet Rx & Tx DMA Descriptors */
extern ETH_DMADESCTypeDef  DMARxDscrTab[ETH_RXBUFNB], DMATxDscrTab[ETH_TXBUFNB];

/* Ethernet Receive buffers  */
extern uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE]; 

/* Ethernet Transmit buffers */
extern uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE]; 

/* Global pointers to track current transmit and receive descriptors */
extern ETH_DMADESCTypeDef  *DMATxDescToSet;
extern ETH_DMADESCTypeDef  *DMARxDescToGet;

/* Global pointer for last received frame infos */
extern ETH_DMA_Rx_Frame_infos *DMA_RX_FRAME_infos;


static void ethernetif_input( void * pvParameters );
static void arp_timer(void *arg);

#define MAC_ADDR0   02
#define MAC_ADDR1   00
#define MAC_ADDR2   00
#define MAC_ADDR3   00
#define MAC_ADDR4   00
#define MAC_ADDR5   00

void ETH_IRQHandler(void)
{
  portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;

  /* Frame received */
  if ( ETH_GetDMAFlagStatus(ETH_DMA_FLAG_R) == SET) 
  {
    /* Give the semaphore to wakeup LwIP task */
    xSemaphoreGiveFromISR( s_xSemaphore, &xHigherPriorityTaskWoken );
  }

  /* Clear the interrupt flags. */
  /* Clear the Eth DMA Rx IT pending bits */
  ETH_DMAClearITPendingBit(ETH_DMA_IT_R);
  ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS);

  /* Switch tasks if necessary. */    
  if( xHigherPriorityTaskWoken != pdFALSE )
  {
    portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
  }
}


static void low_level_init(struct netif *netif)
{
  uint32_t i;

  /* 设置MAC地址长度 */
  netif->hwaddr_len = ETHARP_HWADDR_LEN;

  /* 设置MAC地址 */
  netif->hwaddr[0] =  MAC_ADDR0;
  netif->hwaddr[1] =  MAC_ADDR1;
  netif->hwaddr[2] =  MAC_ADDR2;
  netif->hwaddr[3] =  MAC_ADDR3;
  netif->hwaddr[4] =  MAC_ADDR4;
  netif->hwaddr[5] =  MAC_ADDR5;

  /*设置网络接口的MTU,以太网的最大Payload为1500 */
  netif->mtu = 1500;


  netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP|NETIF_FLAG_UP ; 

  s_pxNetIf =netif;


  if (s_xSemaphore == NULL)
  {
    vSemaphoreCreateBinary(s_xSemaphore);
    xSemaphoreTake( s_xSemaphore, 0);
  }


  ETH_MACAddressConfig(ETH_MAC_Address0, netif->hwaddr); 


  ETH_DMATxDescChainInit(DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB);

  ETH_DMARxDescChainInit(DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB);

  /* Enable Ethernet Rx interrrupt */
  { 
    for(i=0; i<ETH_RXBUFNB; i++)
    {
      ETH_DMARxDescReceiveITConfig(&DMARxDscrTab[i], ENABLE);
    }
  }

#ifdef CHECKSUM_BY_HARDWARE

  {
    for(i=0; i<ETH_TXBUFNB; i++)
    {
      ETH_DMATxDescChecksumInsertionConfig(&DMATxDscrTab[i], ETH_DMATxDesc_ChecksumTCPUDPICMPFull);
    }
  } 
#endif

  /* create the task that handles the ETH_MAC */
  xTaskCreate(ethernetif_input, (signed char*) "rxTask", netifINTERFACE_TASK_STACK_SIZE, NULL,
              netifINTERFACE_TASK_PRIORITY,NULL);

}

static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
  static xSemaphoreHandle xTxSemaphore = NULL;
  struct pbuf *q;
  u8 *buffer ;
  __IO ETH_DMADESCTypeDef *DmaTxDesc;
  uint16_t framelength = 0;
  uint32_t bufferoffset = 0;
  uint32_t byteslefttocopy = 0;
  uint32_t payloadoffset = 0;

  if (xTxSemaphore == NULL)
  {
    vSemaphoreCreateBinary (xTxSemaphore);
  }

  if (xSemaphoreTake(xTxSemaphore, netifGUARD_BLOCK_TIME))
  {
    DmaTxDesc = DMATxDescToSet;
    buffer = (u8 *)(DmaTxDesc->Buffer1Addr);
    bufferoffset = 0;

    for(q = p; q != NULL; q = q->next) 
    {
      if((DmaTxDesc->Status & ETH_DMATxDesc_OWN) != (u32)RESET)
      {
        goto error;
      }

      /* Get bytes in current lwIP buffer  */
      byteslefttocopy = q->len;
      payloadoffset = 0;

      /* Check if the length of data to copy is bigger than Tx buffer size*/
      while( (byteslefttocopy + bufferoffset) > ETH_TX_BUF_SIZE )
      {
        /* Copy data to Tx buffer*/
        memcpy( (u8_t*)((u8_t*)buffer + bufferoffset), (u8_t*)((u8_t*)q->payload + payloadoffset), (ETH_TX_BUF_SIZE - bufferoffset) );

        /* Point to next descriptor */
        DmaTxDesc = (ETH_DMADESCTypeDef *)(DmaTxDesc->Buffer2NextDescAddr);

        /* Check if the buffer is available */
        if((DmaTxDesc->Status & ETH_DMATxDesc_OWN) != (u32)RESET)
        {
          goto error;
        }

        buffer = (u8 *)(DmaTxDesc->Buffer1Addr);

        byteslefttocopy = byteslefttocopy - (ETH_TX_BUF_SIZE - bufferoffset);
        payloadoffset = payloadoffset + (ETH_TX_BUF_SIZE - bufferoffset);
        framelength = framelength + (ETH_TX_BUF_SIZE - bufferoffset);
        bufferoffset = 0;
      }

      /* Copy the remaining bytes */
      memcpy( (u8_t*)((u8_t*)buffer + bufferoffset), (u8_t*)((u8_t*)q->payload + payloadoffset), byteslefttocopy );
      bufferoffset = bufferoffset + byteslefttocopy;
      framelength = framelength + byteslefttocopy;
    }

    /* Prepare transmit descriptors to give to DMA*/
    ETH_Prepare_Transmit_Descriptors(framelength);

    /* Give semaphore and exit */
  error:

    xSemaphoreGive(xTxSemaphore);
  }

  return ERR_OK;
}

static struct pbuf * low_level_input(struct netif *netif)
{
  struct pbuf *p= NULL, *q;
  u32_t len;
  FrameTypeDef frame;
  u8 *buffer;
  __IO ETH_DMADESCTypeDef *DMARxDesc;
  uint32_t bufferoffset = 0;
  uint32_t payloadoffset = 0;
  uint32_t byteslefttocopy = 0;
  uint32_t i=0;  

  /* get received frame */
  frame = ETH_Get_Received_Frame_interrupt();

  /* Obtain the size of the packet and put it into the "len" variable. */
  len = frame.length;
  buffer = (u8 *)frame.buffer;
  
  if (len > 0)
  {
    /* We allocate a pbuf chain of pbufs from the Lwip buffer pool */
    p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
  }
  
  if (p != NULL)
  {
    DMARxDesc = frame.descriptor;
    bufferoffset = 0;
    for(q = p; q != NULL; q = q->next)
    {
      byteslefttocopy = q->len;
      payloadoffset = 0;

      /* Check if the length of bytes to copy in current pbuf is bigger than Rx buffer size*/
      while( (byteslefttocopy + bufferoffset) > ETH_RX_BUF_SIZE )
      {
        /* Copy data to pbuf*/
        memcpy( (u8_t*)((u8_t*)q->payload + payloadoffset), (u8_t*)((u8_t*)buffer + bufferoffset), (ETH_RX_BUF_SIZE - bufferoffset));

        /* Point to next descriptor */
        DMARxDesc = (ETH_DMADESCTypeDef *)(DMARxDesc->Buffer2NextDescAddr);
        buffer = (unsigned char *)(DMARxDesc->Buffer1Addr);

        byteslefttocopy = byteslefttocopy - (ETH_RX_BUF_SIZE - bufferoffset);
        payloadoffset = payloadoffset + (ETH_RX_BUF_SIZE - bufferoffset);
        bufferoffset = 0;
      }

      /* Copy remaining data in pbuf */
      memcpy( (u8_t*)((u8_t*)q->payload + payloadoffset), (u8_t*)((u8_t*)buffer + bufferoffset), byteslefttocopy);
      bufferoffset = bufferoffset + byteslefttocopy;
    }
  }
  
  /* Release descriptors to DMA */
  DMARxDesc =frame.descriptor;

  /* Set Own bit in Rx descriptors: gives the buffers back to DMA */
  for (i=0; i<DMA_RX_FRAME_infos->Seg_Count; i++)
  {  
    DMARxDesc->Status = ETH_DMARxDesc_OWN;
    DMARxDesc = (ETH_DMADESCTypeDef *)(DMARxDesc->Buffer2NextDescAddr);
  }
  
  /* Clear Segment_Count */
  DMA_RX_FRAME_infos->Seg_Count =0;
  
  /* When Rx Buffer unavailable flag is set: clear it and resume reception */
  if ((ETH->DMASR & ETH_DMASR_RBUS) != (u32)RESET)  
  {
    /* Clear RBUS ETHERNET DMA flag */
    ETH->DMASR = ETH_DMASR_RBUS;
    /* Resume DMA reception */
    ETH->DMARPDR = 0;
  }
  return p;
}

void ethernetif_input( void * pvParameters )
{
  struct pbuf *p;
  
  for( ;; )
  {
    if (xSemaphoreTake( s_xSemaphore, emacBLOCK_TIME_WAITING_FOR_INPUT)==pdTRUE)
    {
TRY_GET_NEXT_FRAME:
      p = low_level_input( s_pxNetIf );
      if   (p != NULL)
      {
        if (ERR_OK != s_pxNetIf->input( p, s_pxNetIf))
        {
          pbuf_free(p);
        }
        else
        {
          goto TRY_GET_NEXT_FRAME;
        }
      }
    }
  }
}


err_t ethernetif_init(struct netif *netif)
{
  LWIP_ASSERT("netif != NULL", (netif != NULL));

#if LWIP_NETIF_HOSTNAME
  /* Initialize interface hostname */
  netif->hostname = "lwip";
#endif /* LWIP_NETIF_HOSTNAME */

  netif->name[0] = IFNAME0;
  netif->name[1] = IFNAME1;

  netif->output = etharp_output;
  netif->linkoutput = low_level_output;

  /* initialize the hardware */
  low_level_init(netif);

  etharp_init();
  sys_timeout(ARP_TMR_INTERVAL, arp_timer, NULL);

  return ERR_OK;
}


static void arp_timer(void *arg)
{
  etharp_tmr();
  sys_timeout(ARP_TMR_INTERVAL, arp_timer, NULL);
}

[4]lwipopts.h 配置文件

直接先用我的,放到lwip/lwip_adapter,里面的配置项在相应的章节都会介绍。

#ifndef __LWIPOPTS_H__
#define __LWIPOPTS_H__
/**
 * SYS_LIGHTWEIGHT_PROT==1: if you want inter-task protection for certain
 * critical regions during buffer allocation, deallocation and memory
 * allocation and deallocation.
 */
#define SYS_LIGHTWEIGHT_PROT    0

#define ETHARP_TRUST_IP_MAC     0
#define IP_REASSEMBLY           0
#define IP_FRAG                 0
#define ARP_QUEUEING            0

#define LWIP_IPV4               1


/**
 * NO_SYS==1: Provides VERY minimal functionality. Otherwise,
 * use lwIP facilities.
 */
#define NO_SYS                  0

/* ---------- Memory options ---------- */
/* MEM_ALIGNMENT: should be set to the alignment of the CPU for which
   lwIP is compiled. 4 byte alignment -> define MEM_ALIGNMENT to 4, 2
   byte alignment -> define MEM_ALIGNMENT to 2. */
#define MEM_ALIGNMENT           4

/* MEM_SIZE: the size of the heap memory. If the application will send
a lot of data that needs to be copied, this should be set high. */
#define MEM_SIZE                (5*1024)

/* MEMP_NUM_PBUF: the number of memp struct pbufs. If the application
   sends a lot of data out of ROM (or other static memory), this
   should be set high. */
#define MEMP_NUM_PBUF           100
/* MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One
   per active UDP "connection". */
#define MEMP_NUM_UDP_PCB        6
/* MEMP_NUM_TCP_PCB: the number of simulatenously active TCP
   connections. */
#define MEMP_NUM_TCP_PCB        10
/* MEMP_NUM_TCP_PCB_LISTEN: the number of listening TCP
   connections. */
#define MEMP_NUM_TCP_PCB_LISTEN 5
/* MEMP_NUM_TCP_SEG: the number of simultaneously queued TCP
   segments. */
#define MEMP_NUM_TCP_SEG        20
/* MEMP_NUM_SYS_TIMEOUT: the number of simulateously active
   timeouts. */
#define MEMP_NUM_SYS_TIMEOUT    10


/* ---------- Pbuf options ---------- */
/* PBUF_POOL_SIZE: the number of buffers in the pbuf pool. */
#define PBUF_POOL_SIZE          20

/* PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool. */
#define PBUF_POOL_BUFSIZE       500


/* ---------- TCP options ---------- */
#define LWIP_TCP                1
#define TCP_TTL                 255

/* Controls if TCP should queue segments that arrive out of
   order. Define to 0 if your device is low on memory. */
#define TCP_QUEUE_OOSEQ         0

/* TCP Maximum segment size. */
#define TCP_MSS                 (1500 - 40)      /* TCP_MSS = (Ethernet MTU - IP header size - TCP header size) */

/* TCP sender buffer space (bytes). */
#define TCP_SND_BUF             (5*TCP_MSS)

/*  TCP_SND_QUEUELEN: TCP sender buffer space (pbufs). This must be at least
  as much as (2 * TCP_SND_BUF/TCP_MSS) for things to work. */

#define TCP_SND_QUEUELEN        (4* TCP_SND_BUF/TCP_MSS)

/* TCP receive window. */
#define TCP_WND                 (2*TCP_MSS)


/* ---------- ICMP options ---------- */
#define LWIP_ICMP                       1


/* ---------- DHCP options ---------- */
/* Define LWIP_DHCP to 1 if you want DHCP configuration of
   interfaces. DHCP is not implemented in lwIP 0.5.1, however, so
   turning this on does currently not work. */
#define LWIP_DHCP               1


/* ---------- UDP options ---------- */
#define LWIP_UDP                1
#define UDP_TTL                 255


/* ---------- Statistics options ---------- */
#define LWIP_STATS 0
#define LWIP_PROVIDE_ERRNO 1

/* ---------- link callback options ---------- */
/* LWIP_NETIF_LINK_CALLBACK==1: Support a callback function from an interface
 * whenever the link changes (i.e., link down)
 */
#define LWIP_NETIF_LINK_CALLBACK        1

/*
   --------------------------------------
   ---------- Checksum options ----------
   --------------------------------------
*/

/* 
The STM32F4x7 allows computing and verifying the IP, UDP, TCP and ICMP checksums by hardware:
 - To use this feature let the following define uncommented.
 - To disable it and process by CPU comment the  the checksum.
*/
#define CHECKSUM_BY_HARDWARE 


#ifdef CHECKSUM_BY_HARDWARE
  /* CHECKSUM_GEN_IP==0: Generate checksums by hardware for outgoing IP packets.*/
  #define CHECKSUM_GEN_IP                 0
  /* CHECKSUM_GEN_UDP==0: Generate checksums by hardware for outgoing UDP packets.*/
  #define CHECKSUM_GEN_UDP                0
  /* CHECKSUM_GEN_TCP==0: Generate checksums by hardware for outgoing TCP packets.*/
  #define CHECKSUM_GEN_TCP                0 
  /* CHECKSUM_CHECK_IP==0: Check checksums by hardware for incoming IP packets.*/
  #define CHECKSUM_CHECK_IP               0
  /* CHECKSUM_CHECK_UDP==0: Check checksums by hardware for incoming UDP packets.*/
  #define CHECKSUM_CHECK_UDP              0
  /* CHECKSUM_CHECK_TCP==0: Check checksums by hardware for incoming TCP packets.*/
  #define CHECKSUM_CHECK_TCP              0
  /* CHECKSUM_CHECK_ICMP==0: Check checksums by hardware for incoming ICMP packets.*/
  #define CHECKSUM_GEN_ICMP               0
#else
  /* CHECKSUM_GEN_IP==1: Generate checksums in software for outgoing IP packets.*/
  #define CHECKSUM_GEN_IP                 1
  /* CHECKSUM_GEN_UDP==1: Generate checksums in software for outgoing UDP packets.*/
  #define CHECKSUM_GEN_UDP                1
  /* CHECKSUM_GEN_TCP==1: Generate checksums in software for outgoing TCP packets.*/
  #define CHECKSUM_GEN_TCP                1
  /* CHECKSUM_CHECK_IP==1: Check checksums in software for incoming IP packets.*/
  #define CHECKSUM_CHECK_IP               1
  /* CHECKSUM_CHECK_UDP==1: Check checksums in software for incoming UDP packets.*/
  #define CHECKSUM_CHECK_UDP              1
  /* CHECKSUM_CHECK_TCP==1: Check checksums in software for incoming TCP packets.*/
  #define CHECKSUM_CHECK_TCP              1
  /* CHECKSUM_CHECK_ICMP==1: Check checksums by hardware for incoming ICMP packets.*/
  #define CHECKSUM_GEN_ICMP               1
#endif


/*
   ----------------------------------------------
   ---------- Sequential layer options ----------
   ----------------------------------------------
*/
/**
 * LWIP_NETCONN==1: Enable Netconn API (require to use api_lib.c)
 */
#define LWIP_NETCONN                    1

/*
   ------------------------------------
   ---------- Socket options ----------
   ------------------------------------
*/
/**
 * LWIP_SOCKET==1: Enable Socket API (require to use sockets.c)
 */
#define LWIP_SOCKET                     0

/*
   -----------------------------------
   ---------- DEBUG options ----------
   -----------------------------------
*/

#define LWIP_DEBUG                      0


/*
   ---------------------------------
   ---------- OS options ----------
   ---------------------------------
*/

#define TCPIP_THREAD_NAME              "TCP/IP"
#define TCPIP_THREAD_STACKSIZE          1000
#define TCPIP_MBOX_SIZE                 5
#define DEFAULT_UDP_RECVMBOX_SIZE       2000
#define DEFAULT_TCP_RECVMBOX_SIZE       2000
#define DEFAULT_ACCEPTMBOX_SIZE         2000
#define DEFAULT_THREAD_STACKSIZE        500
#define TCPIP_THREAD_PRIO               3 

#define LWIP_COMPAT_MUTEX 1

#endif /* __LWIPOPTS_H__ */

3、编译

在上述工作都完成后,可以修改应用的CMakeLists.txt进行编译了,由于之前已经完成了驱动的添加,我们现在只需要添加协议栈相关的源码和头文件。

[1]添加LWIP的源码和头文件

#LWIP
file(GLOB Src_LWIP_API 
    "${PATH_WORKSPACE_ROOT}/lwip/lwip-2.1.3/src/api/*.c"
)

file(GLOB Src_LWIP_CORE_ROOT 
    "${PATH_WORKSPACE_ROOT}/lwip/lwip-2.1.3/src/core/*.c"
)

file(GLOB Src_LWIP_CORE_IPV4 
    "${PATH_WORKSPACE_ROOT}/lwip/lwip-2.1.3/src/core/ipv4/*.c"
)

file(GLOB Src_LWIP_NETIF
    "${PATH_WORKSPACE_ROOT}/lwip/lwip-2.1.3/src/netif/*.c"
)

set(Src_LWIP
    ${Src_LWIP_API}
    ${Src_LWIP_CORE_ROOT}
    ${Src_LWIP_CORE_IPV4}
    ${Src_LWIP_NETIF}
    ${PATH_WORKSPACE_ROOT}/lwip/lwip_adapter/freertos/sys_arch.c
    ${PATH_WORKSPACE_ROOT}/lwip/lwip_adapter/netif_eth/netif_eth.c
    
)
set(Inc_LWIP
    ${PATH_WORKSPACE_ROOT}/lwip/lwip-2.1.3/src/include  
    ${PATH_WORKSPACE_ROOT}/lwip/lwip-2.1.3/src/compat 
    ${PATH_WORKSPACE_ROOT}/lwip/lwip-2.1.3/src/include/lwip
    ${PATH_WORKSPACE_ROOT}/lwip/lwip-2.1.3/src/include/lwip/apps
    ${PATH_WORKSPACE_ROOT}/lwip/lwip-2.1.3/src/include/lwip/priv
    ${PATH_WORKSPACE_ROOT}/lwip/lwip-2.1.3/src/include/lwip/prot
    ${PATH_WORKSPACE_ROOT}/lwip/lwip-2.1.3/src/include/netif
    ${PATH_WORKSPACE_ROOT}/lwip/lwip_adapter
    ${PATH_WORKSPACE_ROOT}/lwip/lwip_adapter/freertos/include/
    ${PATH_WORKSPACE_ROOT}/lwip/lwip_adapter/freertos/include/arch
)

补充完整之前的phy检测任务,在stm32f4_eth_bsp.c中,添加 头文件"netif.h"

将网卡的netif extern 过来,如下图所示

2026-01-26T16:12:31.png
2026-01-26T16:12:31.png

在link up时调用接口

netif_set_link_up(&xnetif);

在link up时调用接口

netif_set_link_down(&xnetif);
2026-01-26T16:12:22.png
2026-01-26T16:12:22.png

修改应用

#include <FreeRTOS.h>
#include <stm32f4xx.h>
#include <misc.h>
#include "lwip/api.h"
#include "lwip/opt.h"
#include <string.h>
#include <stdio.h>
#include <FreeRTOS.h>
#include <stdio.h> 
#include "misc.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include <stdint.h>
#include <stm32f4xx.h>
#include "stm32f4x7_eth.h"
#include "lwip/mem.h"
#include "lwip/memp.h"
#include "lwip/dhcp.h"
#include "tcpip.h"
#include "lwip/init.h"
#include "lwip/netif.h"
#include "lwip/ip_addr.h"
#include "lwip/dhcp.h"
extern void Debug_UartInit(void);
extern void ETH_BSP_Config();

/*Static IP ADDRESS*/
#define IP_ADDR0   192
#define IP_ADDR1   168
#define IP_ADDR2   137
#define IP_ADDR3   10
   
/*NETMASK*/
#define NETMASK_ADDR0   255
#define NETMASK_ADDR1   255
#define NETMASK_ADDR2   255
#define NETMASK_ADDR3   0

/*Gateway Address*/
#define GW_ADDR0   192
#define GW_ADDR1   168
#define GW_ADDR2   137
#define GW_ADDR3   1  

extern err_t ethernetif_init(struct netif *netif);
struct netif xnetif; /* network interface structure */
void MAINTask(void *parm)
{
    ip_addr_t ipaddr;
    ip_addr_t netmask;
    ip_addr_t gw;
    IP4_ADDR(&ipaddr, IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3);
    IP4_ADDR(&netmask, NETMASK_ADDR0, NETMASK_ADDR1 , NETMASK_ADDR2, NETMASK_ADDR3);
    IP4_ADDR(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3);
    ETH_BSP_Config(); 
    tcpip_init( NULL, NULL );    
    netif_add(&xnetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &tcpip_input);
    netif_set_default(&xnetif);
    netif_set_up(&xnetif);
    for( ; ; )
    {
      vTaskDelay(1000);
      /* Should not reach here. */
    }
}

void main()
{
    Debug_UartInit();
    printf("Application Start!\r\n");
    static StaticTask_t MAINTaskTCB;
    static StackType_t MAINTaskStack[ 1024 ];
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);


        //静态创建任务
    ( void ) xTaskCreateStatic( MAINTask,
                                "main_task",
                                1024,
                                NULL,
                                configMAX_PRIORITIES - 1U,
                                &( MAINTaskStack[ 0 ] ),
                                &( MAINTaskTCB ) );

    vTaskStartScheduler();
    return 0;
}

[3]编译测试

将LWIP的源码和头文件加入到工程编译

2026-01-26T15:38:14.png
2026-01-26T15:38:14.png

编译完成后如下所示

2026-01-26T15:39:19.png
2026-01-26T15:39:19.png

将固件下载到板卡中,并将PC的网络适配器IPV4地址配置到192.168.137.0/24这个网段。

等待板卡link up后,使用ping命令

2026-01-26T16:06:59.png
2026-01-26T16:06:59.png
2026-01-26T16:08:21.png
2026-01-26T16:08:21.png

至此,LWIP的移植完成了,在后续的章节中,我们再来探讨协议栈相关的更多话题。