[STM32F4][步步深入学网络004]LWIP移植

LWIP介绍

LWIP是使用非常广泛的一个小型开源TCP/IP协议栈,非常适合用于小型嵌入式系统,主要特点如下:

  1. 支持协议非常广泛,IP,IPv6,ICMP,ND,MLD,UDP,TCP,IGMP,ARP,PPPoS,PPPoE,DHCP客户端,DNS客户端(包括mDNS主机名解析器),AutoIP / APIPA(Zeroconf),SNMP代理等等
  2. 稳定性好
  3. 提供了类BSD UNIX标准socket API,大大降低了学习难度。
  4. 最新版本里提供了一些高级应用,MQTT、HTTP、TFTP等等
  5. 多网口提供IP转发

LWIP移植

lwip的官方网站是http://savannah.nongnu.org/projects/lwip/,这里有关于LWIP的介绍以及下载链接

1、下载源码
源码下植地址为http://download.savannah.nongnu.org/releases/lwip/
版本选择最新的稳定版2.1.2,下载完成后解压可以看到如下目录结构
LWIP源码
其中,src目录下为源代码,doc文件夹为文档目录,test文件夹包含了单元测试和其它测试代码,CMakeList.txt为cmake构建文件(MDK中不会使用到),其它文件均为文档文件。

2、添加源码到工程
将解压缩后的lwip-2.1.2目录整个移动到上一节的工程中,如图:
在这里插入图片描述
然后添加文件到MDK工程:
(1)添加lwip-2.1.2/src/api下所有文件
(2)添加/lwip-2.1.2/src/core目录下除ipv6外所有文件
(3)添加lwip-2.1.2/src/core/ipv4目录下所有文件
(4)添加lwip-2.1.2/src/netif目录下的ethernet.c文件

这里核心源码就添加完成了。

3、配置LWIP
在application目录下建立一个lwipopts.h文件,用于配置LWIP,参考配置如下:

#ifndef __LWIPOPTS_H__
#define __LWIPOPTS_H__

/* IP 配置*/
#define IP_SOF_BROADCAST                1 //IP广播包
#define IP_SOF_BROADCAST_RECV           1//IP接收广播包
#define IP_NAT                          0//IP路由
#define LWIP_IPV4                       1//IPV4
#define LWIP_IPV6                       0//IPV6
#define IP_FORWARD                      0 //IP转发
#define IP_REASSEMBLY                   0 //IP重组
#define IP_FRAG                         0 //IP分片


/* NETIF */
#define LWIP_NETIF_STATUS_CALLBACK      1 //网卡状态回调
#define LWIP_NETIF_LINK_CALLBACK        1 //网卡连接回调
#define LWIP_NETIF_LOOPBACK             0 //网卡回环
#define LWIP_HAVE_LOOPIF                0 //回环网卡
#define LWIP_NETIF_API                  1 //网卡API


/* SOCKET */
#define LWIP_SOCKET                     1 //soket api
#define LWIP_NETCONN                    1 
#define SO_REUSE                        1  //socket 重用
#define LWIP_SO_RCVTIMEO                1  //socket 发送超时时间
#define LWIP_SO_SNDTIMEO                1 //socket 接收时间
#define LWIP_SO_RCVBUF                  1
#define LWIP_SOCKET_SELECT              1 //socket select
#define LWIP_SOCKET_POLL                1 //socket poll
#define LWIP_COMPAT_SOCKETS             1 //api函数名字兼容bsd socket api
#define RECV_BUFSIZE_DEFAULT            8192 //接收buffer
#define LWIP_POSIX_SOCKETS_IO_NAMES     0 // socket IO 名字 

/* IGMP */
#define LWIP_IGMP                       0  //igmp

/* ICMP */
#define LWIP_ICMP                       1  //icmp
#define ICMP_TTL                        255 //ttl

/* SNMP */
#define LWIP_SNMP                       0 //snmp

/* DNS */
#define LWIP_DNS                        1  //dns 客户端

/* DEBUG */
#define LWIP_DEBUG							//调试
#define SYS_DEBUG                       LWIP_DBG_ON  
#define ETHARP_DEBUG                    LWIP_DBG_OFF
#define PPP_DEBUG                       LWIP_DBG_OFF
#define MEM_DEBUG                       LWIP_DBG_OFF
#define MEMP_DEBUG                      LWIP_DBG_OFF
#define PBUF_DEBUG                      LWIP_DBG_OFF
#define API_LIB_DEBUG                   LWIP_DBG_OFF
#define API_MSG_DEBUG                   LWIP_DBG_OFF
#define TCPIP_DEBUG                     LWIP_DBG_OFF
#define NETIF_DEBUG                     LWIP_DBG_OFF
#define SOCKETS_DEBUG                   LWIP_DBG_OFF
#define DNS_DEBUG                       LWIP_DBG_OFF
#define AUTOIP_DEBUG                    LWIP_DBG_OFF
#define DHCP_DEBUG                      LWIP_DBG_OFF
#define IP_DEBUG                        LWIP_DBG_OFF
#define IP_REASS_DEBUG                  LWIP_DBG_OFF
#define ICMP_DEBUG                      LWIP_DBG_OFF
#define IGMP_DEBUG                      LWIP_DBG_OFF
#define UDP_DEBUG                       LWIP_DBG_OFF
#define TCP_DEBUG                       LWIP_DBG_OFF
#define TCP_INPUT_DEBUG                 LWIP_DBG_OFF
#define TCP_OUTPUT_DEBUG                LWIP_DBG_OFF
#define TCP_RTO_DEBUG                   LWIP_DBG_OFF
#define TCP_CWND_DEBUG                  LWIP_DBG_OFF
#define TCP_WND_DEBUG                   LWIP_DBG_OFF
#define TCP_FR_DEBUG                    LWIP_DBG_OFF
#define TCP_QLEN_DEBUG                  LWIP_DBG_OFF
#define TCP_RST_DEBUG                   LWIP_DBG_OFF
#define LWIP_DBG_TYPES_ON               (LWIP_DBG_ON|LWIP_DBG_TRACE|LWIP_DBG_STATE|LWIP_DBG_FRESH|LWIP_DBG_HALT)


/* MEM / MEMP */
#define MEM_ALIGNMENT                   4   //mem 对齐为4字节
#define MEMP_OVERFLOW_CHECK             1    //mem pool 溢出检测
#define LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT 1   
#define MEMP_MEM_MALLOC                 0   //mem代替mem pool
#define MEM_LIBC_MALLOC                 0  //标准库的malloc
#define MEMP_NUM_PBUF                   32  //mem pool 内型的pbuf个数
#define MEMP_NUM_NETCONN                16 //时序API类型连接的pbuf个数
#define MEMP_NUM_RAW_PCB                4 //raw pcb个数
#define MEMP_NUM_UDP_PCB                4 //udp pcb个数
#define MEMP_NUM_TCP_PCB                16//tcpp pcb个数
#define MEMP_NUM_TCP_SEG                40 //tcp分段数量
#define PBUF_POOL_SIZE                  16
#define PBUF_LINK_HLEN                  16

/* TCP */
#define LWIP_TCP                        1   //开启tcp
#define TCP_TTL                         255  //TTL
#define TCP_QUEUE_OOSEQ                 1    //重组段
#define TCP_MSS                         1460 //mss
#define TCP_SND_BUF                     8196 //buffer
#define TCP_SND_QUEUELEN                (4 * TCP_SND_BUF/TCP_MSS) //发送缓冲个数
#define TCP_SNDLOWAT                    (TCP_SND_BUF/2)
#define TCP_SNDQUEUELOWAT               TCP_SND_QUEUELEN/2
#define TCP_WND                         8196  //tcp窗口
#define TCP_MAXRTX                      12  //重传次数
#define TCP_SYNMAXRTX                   4   //syn 重传次数
#define LWIP_TCP_KEEPALIVE              1   //保活

/* ARP */
#define LWIP_ARP                        1  //开启ARP
#define ARP_TABLE_SIZE                  10 //arp缓存表大小
#define ARP_QUEUEING                    1


/* CHECKSUM */
#ifdef LWIP_USING_HW_CHECKSUM             //是否计算校验
#define CHECKSUM_GEN_IP                 0
#define CHECKSUM_GEN_UDP                0
#define CHECKSUM_GEN_TCP                0
#define CHECKSUM_GEN_ICMP               0
#define CHECKSUM_CHECK_IP               0
#define CHECKSUM_CHECK_UDP              0
#define CHECKSUM_CHECK_TCP              0
#define CHECKSUM_CHECK_ICMP             0
#endif

/* DHCP */
#define LWIP_DHCP                       1  //dhcp 客户端
#define DHCP_DOES_ARP_CHECK             (LWIP_DHCP)

/* AUTOIP */
#define LWIP_AUTOIP                     0  //autoip
#define LWIP_DHCP_AUTOIP_COOP           (LWIP_DHCP && LWIP_AUTOIP)

/* UDP */
#define LWIP_UDP                        1 //udp
#define LWIP_UDPLITE                    0
#define UDP_TTL                         255
#define DEFAULT_UDP_RECVMBOX_SIZE       1

/* RAW */
#define LWIP_RAW                        1  //raw api
#define DEFAULT_RAW_RECVMBOX_SIZE       1
#define DEFAULT_ACCEPTMBOX_SIZE         10


/* LWIP_STATS */
#define LWIP_STATS                      1   //lwip 状态统计
#define LWIP_STATS_DISPLAY              1  //状态统计输出

#if LWIP_STATS								//lwip 状态统计
#define LINK_STATS                      1
#define IP_STATS                        1
#define ICMP_STATS                      1
#define IGMP_STATS                      1
#define IPFRAG_STATS                    1
#define UDP_STATS                       1
#define TCP_STATS                       1
#define MEM_STATS                       1
#define MEMP_STATS                      1
#define PBUF_STATS                      1
#define SYS_STATS                       1
#define MIB2_STATS                      1
#endif /* LWIP_STATS */

/* PPP */
#define PPP_SUPPORT                     0      /* PPP */


/* SYS */
#define NO_SYS                          0    //是否不含OS 
#define BYTE_ORDER                      LITTLE_ENDIAN  //计算机字节序
#define SYS_LIGHTWEIGHT_PROT            (NO_SYS==0)   
#define TCPIP_MBOX_SIZE                 20    //tcp邮箱大小
#define TCPIP_THREAD_PRIO               5    //tcpip主任务优先级
#define TCPIP_THREAD_STACKSIZE          4096 //tcpip主任务堆栈
#define TCPIP_THREAD_NAME               "tcpip" //tcpip主任务名称
#define DEFAULT_TCP_RECVMBOX_SIZE       10  //默认邮箱大小
#define LWIP_PROVIDE_ERRNO                  //使用lwip提供的errno,而不使用系统提供的

#endif


4、添加移植文件夹
在lwip-2.1.2/src目录下建立一个port目录,port目录统共需要添加入下文件及文件夹,用于存放自定义的移植文件,目录结构如下:

port
│  ethernetif.c
│  sys_arch.c
│  
└─arch
        bpstruct.h
        cc.h
        cpu.h
        epstruct.h
        perf.h
        sys_arch.h

后续将说明各文件的内容

5、最关键的部分来了,添加lwip的os接口。

在新建的port目录下建立arch文件夹,在arch文件夹下建立sys_arch.h文件,主要定义lwip信号量、邮箱、任务等等结构体,如下:

#ifndef __SYS_ARCH_H__
#define __SYS_ARCH_H__

#include "lwip/opt.h"
#include "arch/cc.h"
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"
#include "semphr.h"

typedef SemaphoreHandle_t sys_sem_t;
typedef QueueHandle_t sys_mutex_t;
typedef QueueHandle_t sys_mbox_t;
typedef TaskHandle_t sys_thread_t;

#define SYS_MBOX_NULL ((void*)0)
#define SYS_SEM_NULL  ((void*)0)
    
#endif /* __SYS_ARCH_H__ */


sys_arch.h文件的存放位置一定要在port/arch/ 目录下,否则lwip源文件会找不到。其它几个文件也是一样,bpstruct.h,cc.h,cpu.h,epstruct.h,perf.h也一样,这几个文件比较简单,是关于编译器以及CPU的配置,详见代码。

lwip-2.1.2/src/port目录下建立sys_arch.c文件,主要定义如下函数:
a、信号量的创建、销毁、挂起、释放、查询函数
sys_sem_new 创建信号量
sys_sem_free 销毁信号量
sys_sem_signal 发送信号量
sys_arch_sem_wait 等待信号量
sys_sem_valid 查询是否可用
sys_sem_set_invalid 设置为不可用
参考代码如下:

err_t sys_sem_new(sys_sem_t *sem, u8_t count)
{
   *sem = xSemaphoreCreateCounting (255,count);
    
    if(*sem )
        return ERR_OK;
    else
        return ERR_MEM;
}


/*
 * Deallocate a semaphore
 */
void sys_sem_free(sys_sem_t* sem)
{
    vSemaphoreDelete(*sem);
}

/*
 * Signal a semaphore
 */
void sys_sem_signal(sys_sem_t* sem )
{
    BaseType_t xHigherPriorityTaskWoken;
    if((SCB->ICSR &0xff)==0)
    {
        xSemaphoreGive(*sem);
    }
    else
    {
        xSemaphoreGiveFromISR( *sem,&xHigherPriorityTaskWoken);
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
}

u32_t sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout)
{

    u32_t tick;
    BaseType_t ret;
    
    tick = HAL_GetTick();
    if(timeout>0)
    {
        ret = xSemaphoreTake(*sem,timeout);
    }
    else
    {
        ret = xSemaphoreTake(*sem,portMAX_DELAY);
    }
    
    if(ret == pdPASS)
    {
        tick = HAL_GetTick() - tick;
    }
    else
    {
        tick = SYS_ARCH_TIMEOUT;
    }

    return tick;
}


int sys_sem_valid(sys_sem_t *sem)
{
    return (*sem != NULL);
}
void sys_sem_set_invalid(sys_sem_t *sem)
{
 	*sem = NULL;
}

b、互斥量的创建、销毁、挂起、释放、查询函数
这里互斥量仅用到了二值信号量,与信号量类似,不在赘述。

c、邮箱相关操作函数

d、任务创建函数,参考如下:

typedef void (*lwip_thread_fn)(void *arg);

sys_thread_t sys_thread_new(const char    *name,
                            lwip_thread_fn thread,
                            void          *arg,
                            int            stacksize,
                            int            prio)
{

    TaskHandle_t CreatedTask;
    int result;

    result = xTaskCreate( thread, ( portCHAR * ) name, stacksize, arg, prio, &CreatedTask );

   if(result == pdPASS)
   {
       return CreatedTask;
   }
   else
   {
       return NULL;
   }
   
}

e、临界区保护函数参考如下:

sys_prot_t sys_arch_protect(void)
{
    vPortEnterCritical();
    return 1;
}

void sys_arch_unprotect(sys_prot_t pval)
{
    vPortExitCritical();
    return;
}

f、os初始化函数,为空即可

void sys_init(void)
{
}

g、断言函数,为死循环即可

void sys_arch_assert(const char *file, int line)
{
	while(1){};
}

h、上电开始到此时的滴答函数

u32_t sys_jiffies(void)
{
    return xTaskGetTickCount();
}

i、获取当前时间的函数,单位为ms

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

j、内存初始化、分配、回收函数
另外为了在工程中不添加mem.c,可将mem.c文件中的其它函数一并复制出来。

void mem_init(void)
{
}

void *mem_calloc(mem_size_t count, mem_size_t size)
{
    return pvPortMalloc(count* size);
}

void *mem_trim(void *mem, mem_size_t size)
{
    /* not support trim yet */
    return mem;
}

void *mem_malloc(mem_size_t size)
{
    return pvPortMalloc(size);
}

void  mem_free(void *mem)
{
    vPortFree(mem);
}

#if MEM_OVERFLOW_CHECK || MEMP_OVERFLOW_CHECK
/**
 * Check if a mep element was victim of an overflow or underflow
 * (e.g. the restricted area after/before it has been altered)
 *
 * @param p the mem element to check
 * @param size allocated size of the element
 * @param descr1 description of the element source shown on error
 * @param descr2 description of the element source shown on error
 */
void
mem_overflow_check_raw(void *p, size_t size, const char *descr1, const char *descr2)
{
#if MEM_SANITY_REGION_AFTER_ALIGNED || MEM_SANITY_REGION_BEFORE_ALIGNED
  u16_t k;
  u8_t *m;

#if MEM_SANITY_REGION_AFTER_ALIGNED > 0
  m = (u8_t *)p + size;
  for (k = 0; k < MEM_SANITY_REGION_AFTER_ALIGNED; k++) {
    if (m[k] != 0xcd) {
      char errstr[128];
      snprintf(errstr, sizeof(errstr), "detected mem overflow in %s%s", descr1, descr2);
      LWIP_ASSERT(errstr, 0);
    }
  }
#endif /* MEM_SANITY_REGION_AFTER_ALIGNED > 0 */

#if MEM_SANITY_REGION_BEFORE_ALIGNED > 0
  m = (u8_t *)p - MEM_SANITY_REGION_BEFORE_ALIGNED;
  for (k = 0; k < MEM_SANITY_REGION_BEFORE_ALIGNED; k++) {
    if (m[k] != 0xcd) {
      char errstr[128];
      snprintf(errstr, sizeof(errstr), "detected mem underflow in %s%s", descr1, descr2);
      LWIP_ASSERT(errstr, 0);
    }
  }
#endif /* MEM_SANITY_REGION_BEFORE_ALIGNED > 0 */
#else
  LWIP_UNUSED_ARG(p);
  LWIP_UNUSED_ARG(desc);
  LWIP_UNUSED_ARG(descr);
#endif
}

/**
 * Initialize the restricted area of a mem element.
 */
void
mem_overflow_init_raw(void *p, size_t size)
{
#if MEM_SANITY_REGION_BEFORE_ALIGNED > 0 || MEM_SANITY_REGION_AFTER_ALIGNED > 0
  u8_t *m;
#if MEM_SANITY_REGION_BEFORE_ALIGNED > 0
  m = (u8_t *)p - MEM_SANITY_REGION_BEFORE_ALIGNED;
  memset(m, 0xcd, MEM_SANITY_REGION_BEFORE_ALIGNED);
#endif
#if MEM_SANITY_REGION_AFTER_ALIGNED > 0
  m = (u8_t *)p + size;
  memset(m, 0xcd, MEM_SANITY_REGION_AFTER_ALIGNED);
#endif
#else /* MEM_SANITY_REGION_BEFORE_ALIGNED > 0 || MEM_SANITY_REGION_AFTER_ALIGNED > 0 */
  LWIP_UNUSED_ARG(p);
  LWIP_UNUSED_ARG(desc);
#endif /* MEM_SANITY_REGION_BEFORE_ALIGNED > 0 || MEM_SANITY_REGION_AFTER_ALIGNED > 0 */
}
#endif /* MEM_OVERFLOW_CHECK || MEMP_OVERFLOW_CHECK */

这样就可以在工程中删除mem.c文件了,如图
在这里插入图片描述

至此,关于LWIP的OS支持就到此结束了。

6、添加硬件底层收发驱动函数

这里使用了CubeMX生成了一个带OS以及LWIP的模板,从中拷贝其驱动文件ethernetif.c并修改,如下:
1
修改ethernetif.c文件,有是就将其关于CMSIS OS的接口修改为FreeRTOS接口即可,这里不再赘述,详见代码。

如果自己动手写驱动函数,则主要完成以下几个函数:

a、硬件接收函数,从网卡中接收一包数据,并将数据转换为一个pbuf的返回,原型如下:

 struct pbuf * hw_input(struct netif *netif);

b、将上一步接收的pbuf使用netif->input函数调用传入tcp/ip协议栈,对于os版本的netif->input,在初始化的时候会初始化为tcpip_input,此函数定义在tcpip.c中。

c、硬件发送函数,选定一张网卡,并将数据发送出去,原型如下:

 err_t hw_output(struct netif *netif, struct pbuf *p);

此函数不会由用户直接调用,而是在初始化的时候注册到netif->linkoutput中去,此后,就会由协议栈来调用

d、初始化网卡函数,包括硬件初始化,发送函数的注册等等,此函数在添加网卡的时候(netif_add函数)会自动调用,参考代码如下:

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;
  /* We directly use etharp_output() here to save a function call.
   * You can instead declare your own function an call etharp_output()
   * from it if you have to do some checks before sending (e.g. if link
   * is available...) */

#if LWIP_IPV4
#if LWIP_ARP || LWIP_ETHERNET
#if LWIP_ARP
  netif->output = etharp_output;
#else
  /* The user should write ist own code in low_level_output_arp_off function */
  netif->output = low_level_output_arp_off;
#endif /* LWIP_ARP */
#endif /* LWIP_ARP || LWIP_ETHERNET */
#endif /* LWIP_IPV4 */
 
#if LWIP_IPV6
  netif->output_ip6 = ethip6_output;
#endif /* LWIP_IPV6 */

  netif->linkoutput = low_level_output;

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

  return ERR_OK;
}

至此,完成收发驱动函数了,整个协议栈移植就完成了。

7、测试

添加应用代码测试一下,参考代码如下:

struct netif gnetif;
ip4_addr_t ipaddr;
ip4_addr_t netmask;
ip4_addr_t gw;

void SetupLwip(void)
{
  /* Initilialize the LwIP stack with RTOS */
  tcpip_init( NULL, NULL );

  /* IP addresses initialization with DHCP (IPv4) */
  ipaddr.addr = 0;
  netmask.addr = 0;
  gw.addr = 0;

  /* 添加网卡 */
  netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &tcpip_input);

  /* 设为默认网卡 */
  netif_set_default(&gnetif);

 /* 启停网卡 */
  if (netif_is_link_up(&gnetif))
  {
    /* When the netif is fully configured this function must be called */
    netif_set_up(&gnetif);
  }
  else
  {
    /* When the netif link is down this function must be called */
    netif_set_down(&gnetif);
  }

  /* 启动dhcp */
  dhcp_start(&gnetif);

  /* 等待3s左右,dhcp完成 */
  vTaskDelay(3000);
  
  /* 打印网卡信息 */
  list_if();
}

上述代码添加了一个网卡并启用它,ip初始设置为空,后续由路由器dhcp自动分配。
在代码最后,list_if用于打印网卡的所有信息。

将SetupLwip函数加入到初始化任务中去,如下:
在这里插入图片描述
主板插上网线连接路由器(计算机也在同一路由器下),最后运行结果如下:
在这里插入图片描述
可见,主板从路由器DHCP成功获得了IP 192.168.3.30。使用ping命令测试一下:
在这里插入图片描述
由此可见LWIP工作正常,移植完成。

附本节源码列表,需要的同鞋请私信:
在这里插入图片描述

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值