LWIP介绍
LWIP是使用非常广泛的一个小型开源TCP/IP协议栈,非常适合用于小型嵌入式系统,主要特点如下:
- 支持协议非常广泛,IP,IPv6,ICMP,ND,MLD,UDP,TCP,IGMP,ARP,PPPoS,PPPoE,DHCP客户端,DNS客户端(包括mDNS主机名解析器),AutoIP / APIPA(Zeroconf),SNMP代理等等
- 稳定性好
- 提供了类BSD UNIX标准socket API,大大降低了学习难度。
- 最新版本里提供了一些高级应用,MQTT、HTTP、TFTP等等
- 多网口提供IP转发
LWIP移植
lwip的官方网站是http://savannah.nongnu.org/projects/lwip/,这里有关于LWIP的介绍以及下载链接
1、下载源码
源码下植地址为http://download.savannah.nongnu.org/releases/lwip/
版本选择最新的稳定版2.1.2,下载完成后解压可以看到如下目录结构
其中,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并修改,如下:
修改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, ðernetif_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工作正常,移植完成。
附本节源码列表,需要的同鞋请私信: