STM32之LWIP网络协议栈移植

时间:2022-08-17 01:07:49

1.LWIP介绍

  lwip是瑞典计算机科学院网络嵌入式系统小组(SICS)的Adam Dunkels(亚当·邓克尔) 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持 TCP 协议主要功能的基础上减少对RAM的占用。

 LwIP是Light Weight(轻型)IP 协议,有无操作系统的支持都可以运行。LwIP 实现的重点是在保持TCP协议 主要功能的基础上减少对RAM的占用,它只需十几KB的RAM和 40K左右的ROM就可以运行,这使LwIP协议栈适合在低端的嵌入式系统中使用。lwip提供三种API:

   RAW API

   (NETCONN)lwip API

   BSD API


  •  RAW API

RAW API把协议栈和应用程序放到一个进程里边,该接口基于函数回调技术,使用该接口的应用程序可以不用进行连续操作。不过,这会使应用程序编写难度加大且代码不易被理解。为了接收数据,应用程序会向协议栈注册一个回调函数。该回调函数与特定的连接相关联,当该关联的连接到达一个信息包,该回调函数就会被协议栈调用。这既有优点也有缺点。优点是既然应用程序和TCP/IP协议栈驻留在同一个进程中,那么发送和接收数据就不再产生进程切换。主要缺点是应用程序不能使自己陷入长期的连续运算中,这样会导致通讯性能下降,原因是TCP/IP处理与连续运算是不能并行发生的。这个缺点可以通过把应用程序分为两部分来克服,一部分处理通讯,一部分处理运算。

  • lwip API

lwip API把接收与处理放在一个线程里面。这样只要处理流程稍微被延迟,接收就会被阻塞,直接造成频繁丢包、响应不及时等严重问题。因此,接收与协议处理必须分开。LwIP的作者显然已经考虑到了这一点,他为我们提供了 tcpip_input() 函数来处理这个问题, 虽然他并没有在 rawapi 一文中说明。 讲到这里,读者应该知道tcpip_input()函数投递的消息从哪里来的答案了吧,没错,它们来自于由底层网络驱动组成的接收线程。我们在编写网络驱动时, 其接收部分以任务的形式创建。 数据包到达后, 去掉以太网包头得到IP包, 然后直接调用tcpip_input()函数将其投递到mbox邮箱。投递结束,接收任务继续下一个数据包的接收,而被投递得IP包将由TCPIP线程继续处理。这样,即使某个IP包的处理时间过长也不会造成频繁丢包现象的发生。这就是lwip API。

  • BSD API

BSD API提供了基于open-read-write-close模型的UNIX标准API,它的最大特点是使应用程序移植到其它系统时比较容易,但用在嵌入式系统中效率比较低,占用资源多。

2.lwip特性

(1)支持多网络接口下的IP转发;

(2)支持ICMP协议;

(3)包括实验性扩展的UDP(用户数据报协议);

(4)包括阻塞控制、RTT估算、快速恢复和快速转发的TCP(传输控制协议);

(5)提供专门的内部回调接口(Raw API),用于提高应用程序性能;

(6)可选择的Berkeley接口API (在多线程情况下使用) 。

(7)在最新的版本中支持ppp

(8) 新版本中增加了的IP fragment的支持.

(9) 支持DHCP协议,动态分配ip地址.

3.Lwip协议移植

移植平台:STM32F10ZE

网卡驱动:DM9000

编译环境:KEIL5

DM9000驱动示例:https://blog.51cto.com/u_15688123/6153339

1.源码下载地址:LWIP源码

STM32之LWIP网络协议栈移植

2.解压文件

STM32之LWIP网络协议栈移植

3.打开已完成DM9000驱动的工程,在工程中创建lwip文件夹,在lwip文件夹中创建src文件夹和lwip1.4_config文件夹:

STM32之LWIP网络协议栈移植

4.将lwip-1.4.1源码中的src中所有文件复制到用户创建的src中:

STM32之LWIP网络协议栈移植

5.复制contrib中文件

STM32之LWIP网络协议栈移植

6.删除不必要文件

STM32之LWIP网络协议栈移植


STM32之LWIP网络协议栈移植

STM32之LWIP网络协议栈移植

7.打开工程,添加.c文件到工程中

STM32之LWIP网络协议栈移植


STM32之LWIP网络协议栈移植

8.添加.h文件路径

STM32之LWIP网络协议栈移植

9.编译工程

STM32之LWIP网络协议栈移植

10.修改sys_arch.c文件,只保留下面函数,其它全部删除。

STM32之LWIP网络协议栈移植

11.修改sys_arch.c文件,只保留下面函数,其它全部删除。

STM32之LWIP网络协议栈移植


STM32之LWIP网络协议栈移植

12.修改ethernetif.c文件

STM32之LWIP网络协议栈移植


STM32之LWIP网络协议栈移植


STM32之LWIP网络协议栈移植


STM32之LWIP网络协议栈移植

4.LWIP协议栈使用示例之 — 获取动态IP

  在LWIP_confg目录下创建lwip_config.c和lwip_config.h文件
  lwip_config.c文件:

#include "lwip_config.h"
#include "lwip/ip_addr.h"
#include "lwip/init.h"
#include "lwip/netif.h"
#include "netif/etharp.h"
#include "lwip/dhcp.h"
#include "lwip/tcp_impl.h"
extern err_t ethernetif_init(struct netif *netif);//网卡初始化函数,原型在ethernetif.c中
extern void  ethernetif_input(struct netif *netif);
/**************LWIP协议栈初始化***********/
struct netif lwip_netif; //结构体原型在lwip/netif.h中
void LWIP_Config_Init(void)
{
	ip_addr_t ip_addr={0};//IP地址
	ip_addr_t netmask={0};//子网掩码
	ip_addr_t gw={0};//网关
	/*静态分配IP*/
//	IP4_ADDR(&ip_addr,192,168,12,23);//设置静态IP
//	IP4_ADDR(&netmask,255,255,255,0);//子网掩码
//	IP4_ADDR(&netmask,192,168,12,1);//网关	
	/*1.LWIP协议栈初始化*/
	lwip_init();	
	/*2.添加新的网卡设备到LWIP协议栈中*/
	/*err_t ethernet_input(struct pbuf *p, struct netif *netif)处理已经读取从硬件接口发来的一个数据包*/
	netif_add(&lwip_netif,&ip_addr,&netmask,&gw,NULL,ethernetif_init,ethernet_input);
	/*3.设置网卡为LWIP协议栈默认设备*/
	netif_set_default(&lwip_netif);
	/*4.注册网卡设备为默认接口*/
	netif_set_up(&lwip_netif);
	/*5.动态分配IP*/
	dhcp_start(&lwip_netif);
}
/*****DHCP定期数据处理函数(轮询方式实现数据更新)****/

u32 LWIP_TCP_TIME_CNT=0;
u32 LWIP_ARP_TIME_CNT=0;
u32 LWIP_DHCP_TIME_CNT=0;
u32 LWIP_UPDATE_DHCP_TIME_CNT=0;
u8 lwip_dhcp_stat=0;
void LWIP_DataUpdata(void)
{
	/*250MS 更新一次*/
	if(LWIP_TCP_TIME_CNT>=TCP_TMR_INTERVAL)
	{
		LWIP_TCP_TIME_CNT=0;
		tcp_tmr(); //对 TCP 数据进行解析
	}	
	/*5000ms 更新一次*/
	if(LWIP_ARP_TIME_CNT>=ARP_TMR_INTERVAL)
	{
		LWIP_ARP_TIME_CNT=0;
		etharp_tmr();//清理 ARP 缓存表
	}	
	//500ms获取一次
	if(LWIP_DHCP_TIME_CNT>=DHCP_FINE_TIMER_MSECS )
	{
		LWIP_DHCP_TIME_CNT=0;
		dhcp_fine_tmr(); //解析 DHCP 请求,判断 IP 地址是否获取成功
	}
	/*60S 更新一次*/
	if(LWIP_UPDATE_DHCP_TIME_CNT>=DHCP_COARSE_TIMER_MSECS)
	{
		LWIP_UPDATE_DHCP_TIME_CNT=0;
		dhcp_coarse_tmr(); //更新检查 DHCP 的租约时间
	}	
	ethernetif_input(&lwip_netif); //读取网卡的数据进行上报
	if(lwip_dhcp_stat==0)
	{
		LWIP_GetDHCP_Addr();
	}
}
/******获取动态分配的IP地址*************/
void LWIP_GetDHCP_Addr(void)
{
	char buff[200];
	u32 ip_addr;//IP地址
	u32 netmask;//子网掩码
	u32 gw;//网关
	ip_addr=lwip_netif.ip_addr.addr;//IP地址
	netmask=lwip_netif.netmask.addr;//子网掩码
	gw=lwip_netif.gw.addr;
	if(ip_addr!=0)
	{
		lwip_dhcp_stat=1;//成功获取到IP地址
		printf("IP地址:%d.%d.%d.%d\r\n",(ip_addr>>0)&0xff,(ip_addr>>8)&0xff,(ip_addr>>16)&0xff,(ip_addr>>24)&0xff);
		snprintf(buff,sizeof(buff),"IP地址:%d.%d.%d.%d",(ip_addr>>0)&0xff,(ip_addr>>8)&0xff,(ip_addr>>16)&0xff,(ip_addr>>24)&0xff);
		LCD_ShowStr(30,30+20*4,16,(u8 *)buff);//IP地址
		printf("子网掩码:%d.%d.%d.%d\r\n",(netmask>>0)&0xff,(netmask>>8)&0xff,(netmask>>16)&0xff,(netmask>>24)&0xff);
		snprintf(buff,sizeof(buff),"子网掩码:%d.%d.%d.%d\r\n",(netmask>>0)&0xff,(netmask>>8)&0xff,(netmask>>16)&0xff,(netmask>>24)&0xff);
		LCD_ShowStr(30,30+20*5,16,(u8 *)buff);//子网掩码
		printf("网关:%d.%d.%d.%d\r\n",(gw>>0)&0xff,(gw>>8)&0xff,(gw>>16)&0xff,(gw>>24)&0xff);	
		snprintf(buff,sizeof(buff),"网关:%d.%d.%d.%d\r\n",(gw>>0)&0xff,(gw>>8)&0xff,(gw>>16)&0xff,(gw>>24)&0xff);	
		LCD_ShowStr(30,30+20*6,16,(u8 *)buff);//子网掩码
	}
}

5.动态分配IP

#include "dm9000.h"
#include "lwip_config.h"
u8 dm9000_tx_buff[64]={0x11,0x22,0x33,0x44,0x55};
u8 dm9000_rx_buff[1024];
int main()
{
	char buff[200];
	u8 stat;
	Beep_Init();
	Led_Init();
	Key_Init();
	W25Q64_Init();
	Usartx_Init(USART1,115200,72);
	TIMx_Init(TIM2,72,20*1000);
	IIC_Init();
	printf("初始化完成\r\n");
	NT35310_Init();
	/*DM9000初始化*/
	LCD_ShowStr(30,30,16,"DM9000初始化中。。。");//显示字符串
	if(DM9000_Init()==0)
	{
		printf("DM9000初始化成功\r\n");
		LCD_ShowStr(30,30+20,16,"DM9000\t OK!");//显示字符串
	}
	else 
	{
		printf("DM9000初始化失败\r\n");
		LCD_ShowStr(30,30+20,16,"DM9000\t ERR!");//显示字符串
	}
	/*获取DM9000工作模式*/
	LCD_ShowStr(128,30+20*2,16,"网卡信息");//显示字符串
	stat=DM9000_Get_SpeedAndDuplex();//获取连接状态和工作方式
	if(stat!=0xff)
	{
		printf("网卡速度:%d Mbps 模式:%s\r\n",(stat&0x02)?10:100,(stat&0x01)?"全双工":"半双工");
		snprintf(buff,sizeof(buff),"网卡速度:%d MHZ\t %s",(stat&0x02)?10:100,(stat&0x01)?"全双工":"半双工");
		LCD_ShowStr(30,30+20*3,16,(u8 *)buff);//网卡速度
	}
	else 
	{
		printf("DM9000网卡状态信息获取失败!\r\n");
		LCD_ShowStr(30,30+20*3,16,(u8 *)"获取网卡信息失败!");//网卡速度
	}
	LWIP_Config_Init();//LWIP协议栈初始化
	TIMx_Init(TIM6,72,1000);
	TIM6->CR1|=1<<0;
	while(1)
	{
		LWIP_DataUpdata();
	}
}

6.运行效果

STM32之LWIP网络协议栈移植

STM32之LWIP网络协议栈移植