LWIP移植


前言

  本文主要对LWIP简单介绍,并对LWIP进行基于UCOS操作系统的移植,以及对LWIP相关编程接口作简单介绍

一、以太网协议简介

1.1 TCP/IP协议简介

  TCP/IP 中文名为传输控制协议/因特网互联协议,又名网络通讯协议,是 Internet 最基本的协议、Internet 国际互联网络的基础,由网络层的 IP 协议和传输层的 TCP 协议组成。TCP/IP 协议不是 TCP 和 IP 这两个协议的合称,而是指因特网整个 TCP/IP 协议族。从协议分层模型方面来讲,TCP/IP 由四个层次组成:网络接口层、网络层、传输层、应用层。OSI 是传统的开放式系统互连参考模型,该模型将 TCP/IP 分为七层:物理层、数据链路层(网络接口层)、网络层(网络层)、传输层(传输层)、会话层、表示层和应用层(应用层)。TCP/IP 模型与 OSI模型对比如表所示
在这里插入图片描述
  在LWIP实验中 PHY 层芯片 LAN8720 相当于物理层,STM32F407 自带的 MAC 层相当于数据链路层,而 LWIP 提供的就是网络层、传输层的功能,应用层是需要用户自己根据自己想要的功能去实现的。

1.2 STM32的ETH外设

  STM32F407 芯片自带以太网模块,该模块包括带专用 DMA 控制器的 MAC 802.3(介质访问控制)控制器,支持介质独立接口 (MII) 和简化介质独立接口 (RMII),并自带了一个用于外部 PHY 通信的 SMI 接口,通过一组配置寄存器,用户可以为 MAC 控制器和 DMA 控制器选择所需模式和功能。
在这里插入图片描述

1.2.1 MAC子层

  MAC 子层是属于数据链路层的下半部分,它主要负责与物理层进行数据交接,如是否可以发送数据,发送的数据是否正确,对数据流进行控制等。它自动对来自上层的数据包加上一些控制信号,交给物理层。接收方得到正常数据时,自动去除 MAC 控制信号,把该数据包交给上层
  IEEE 对以太网上传输的数据包格式也进行了统一规定,MAC 数据包由前导字段、帧起始定界符、目标地址、源地址、数据包类型、数据域、填充域、校验和域组成。
在这里插入图片描述

1.2.2 SMI站管理接口

  站管理接口(SMI) 允许应用程序通过 2 条线:时钟(MDC)和数据线(MDIO)访问任意 PHY 寄存器。该接口支持访问多达 32 个 PHY。应用程序可以从 32 个 PHY 中选择一个 PHY,然后从任意 PHY 包含的 32 个寄存器中选择一个寄存器,发送控制数据或接收状态信息。任意给定时间内只能对一个 PHY 中的一个寄存器进行寻址。在 MAC 对 PHY 进行读写操作的时候,应用程序不能修改 MII 的地址寄存器和 MII 的数据寄存器。在此期间对 MII 地址寄存器或 MII 数据寄存器执行的写操作将会被忽略。

1.2.3 MII和RMII接口

  介质独立接口(MII)用于MAC层与PHY层进行数据传输。而精简介质独立接口(RMII) 规范降低 10/100 Mbit/s 下微控制器以太网外设与外部 PHY 间的引脚数。根据 IEEE 802.3u 标准,MII 包括 16 个数据和控制信号的引脚。RMII 规范将引脚数减少为 7 个。
  因为 RMII 相比 MII,其发送和接收都少了两条线。因此要达到 10Mbit/s 的速度,其时钟频率应为 5MHZ,同理要达到100Mbit/s 的速度其时钟频率应为 50MHz,而 MII 接口分别为 2.5MHZ 和 25MHZ。STM32F407 开发板采用 RMII 接口连接 LAN8720示意图:
在这里插入图片描述

1.3 外部PHY芯片LAN8720

  LAN8720 是低功耗的 10/100M 以太网 PHY 层芯片,I/O 引脚电压符合 IEEE802.3-2005 标准。LAN8720 支持通过 RMII 接口与以太网 MAC 层通信,内置 10-BASE-T/100BASE-TX 全双工传输模块,支持 10Mbps 和 100Mbps。LAN8720 可以通过自协商的方式与目的主机最佳的连接方式(速度和双工模式)。支持 HP Auto-MDIX 自动翻转功能,无需更换网线即可将连接更改为直连或交叉连接。LAN8720 功能框图如图所示:
在这里插入图片描述

1.3.1 LAN8720 中断管理

  当一个中断事件发生并且相应事件的中断位使能,LAN8720 就会在 nINT(14 脚)产生一个低电平有效的中断信号。LAN8720的中断系统提供两种中断模式:主中断模式和复用中断模式。主中断模式是默认中断模式,LAN8720 上电或复位后就工作在主中断模式,当模式控制/状态寄存器(十进制地址为 17)的ALTINT 为 0 是 LAN8720 工作在主模式,当 ALTINT 为 1 时工作在复用中断模式。可以不使用用到中断功能

1.3.2 PHY 地址设置

  MAC 层通过 SMI 总线对 PHY 进行读写操作,SMI 可以控制 32 个 PHY 芯片,通过不同的PHY 芯片地址来对不同的 PHY 操作。LAN8720 通过设置 RXER/PHYAD0 引脚来设置其 PHY地址,默认情况下为 0,在系统上电时会检测该引脚获取得到 LAN8720A的地址为 0 或者 1,并保存在特殊模式寄存器(R18)的 PHYAD 位中,该寄存器的 PHYAD有 5 个位,在需要超过 2 个 LAN8720A 时可以通过软件设置不同 SMI 通信地址。PHYAD[0]是与 RXER 引脚共用。

1.3.3 nINT/REFCLKO 配置

  nINTSEL引脚(2 号引脚)用于设置 nINT/REFCLKO 引脚(14 号引脚)的功能。当工作在 REF_CLK In 模式时,50MHz 的外部时钟信号应接到 LAN8720 的 XTAL1/CKIN引脚(5 号引脚)和 STM32F407 的 RMII_REF_CLK(PA1)引脚上;为了降低成本,LAN8720 可以从外部的 25MHz 的晶振中产生 REF_CLK 时钟。到要使用此功能时应工作在 REF_CLK Out 模式时。
在这里插入图片描述

1.3.4 LAN8720 内部寄存器

  PHY 是由 IEEE 802.3 定义的,一般通过 SMI 对 PHY 进行管理和控制,也就是读写 PHY 内部寄存器。PHY 寄存器的地址空间为 5 位,可以定义 0 ~ 31 共 32 个寄存器,IEEE 802.3 定义了 0~ 15 这 16 个寄存器的功能,16~31 寄存器由芯片制造商自由定义。但是随之 PHY 芯片功能的增加,很多 PHY 芯片采用分页技术来扩展地址空间,定义更多的寄存器,在这里我们只介绍几个用到的寄存器(括号内为寄存器地址,此处使用十进制表示):BCR(0),BSR(1),PHY 特殊功能寄存器(31)这三个寄存器。

  1. BCR寄存器
    在这里插入图片描述
      我们配置 LAN8720 其实就是配置 BCR 寄存器,我们通过调用 ST 官方的以太网库的ETH_ReadPHYRegister 和 ETH_WritePHYRegister 函数来完成对 PHY 芯片寄存器的读写操作。在 ST 官方以太网库的 stm32f4x7_eth.h 中已经定义了 BCR 和 BSR 寄存器。在以后要讲的 LAN8720 初始化的中并没有看到我们直接操作 PHY 的寄存器,原因就在这里当我们调用 ETH_Init()函数以后就会根据我们输入的参数配置 LAN8720 的相应寄存器。

  2. BSR 寄存器
    在这里插入图片描述

  3. LAN8720 特殊功能寄存器
    在这里插入图片描述
      特殊功能寄存器中我们关心的是 bit2~bit4 这三位,因为通过这 3 位来确定连接的状态和速度。在 ETH_Init 函数中通过读取这 3 位的值来设置 BCR 寄存器的 bit8 和 bit13。由于特殊功能寄存器不属于 IEEE802.3 规定的前 16 个寄存器,所以每个厂家的可能不同,这个需要用户根自己实际使用的 PHY 芯片去修改,在 stm32f4x7_eth_conf.h 文件中定义

1.4 LWIP 简介

  LwIP全名:Light weight IP,意思是轻量化的 TCP/IP 协议,是瑞典计算机科学院(SICS)的 Adam Dunkels 开发的一个小型开源的 TCP/IP 协议栈。LwIP 的设计初衷是:用少量的资源消耗实现一个较为完整的 TCP/IP 协议栈,其中“完整”主要指的是 TCP 协议的完整性,实现的重点是在保持 TCP 协议主要功能的基础上减少对 RAM 的占用。此外 LwIP既可以移植到操作系统上运行,也可以在无操作系统的情况下独立运行。其与TCP/IP的区别如下:
在这里插入图片描述
  LwIP下载网址:http://download-mirror.savannah.gnu.org/releases/lwip/
  下载两个包:lwip-2.1.2.zip(源码包)和 contrib-2.1.0.zip(contrib 包)。解压以后会得到两个文件夹。打开LWIP1.4.1 其中包括 doc,src 和 test 三个文件夹和 5 个其他文件。doc 文件夹下包含了几个与协议栈使用相关的文本文档,doc 文件夹里面有两个比较重要的文档:rawapi.txt 和 sys_arch.txt。
在这里插入图片描述
在这里插入图片描述

  rawapi.txt 告诉读者怎么使用 raw/callback API 进行编程,sys_arch.txt 包含了移植说明,在移植的时候会用到。src 文件夹是我们的重点,里面包含了 LWIP 的源码。test 是 LWIP 提供的一些测试程序。src 文件夹由 4 个文件夹组成:api、core、include、netif 四个文件夹。api 文件夹里面是 LWIP的 sequential API(Netconn)和 socket API 两种接口函数的源码,要使用这两种 API 需要操作系统支持。 core 文件夹是 LWIP 内核源码,include 文件夹里面是 LWIP 使用到的头文件,netif 文件夹里面是与网络底层接口有关的文件。
  LwIP 提供了三种编程接口,分别为 RAW/Callback API、NETCONN API、SOCKETAPI。它们的易用性从左到右依次提高,而执行效率从左到右依次降低,用户可以根据实际情况,平衡利弊,选择合适的 API 进行网络应用程序的开发。以下内容将分别介绍这三种 API。

二、带操作系统的移植

  首先LWIP也可以实现无操作系统移植,不过无操作系统只能使用RAW API 编程接口,虽然可以带来更高的运行效率,但是学习成本增大,且很多应用场景并不适合

2.1 移植准备工作

  需要三个官方文件lwip-1.4.1、contrib-1.4.1和STM32F4x7_ETH_LwIP_V1.1.0,以及一个移植好UCOS的工程。其中lwip文件夹是关于以太网协议的实现,contrib是以太网顶层应用层的实现,STM32F4x7_ETH_LwIP文件是官方驱动实现,最后UCOS的工程移植不介绍,具体见下链接:https://blog.csdn.net/weixin_44567668/article/details/131056991
  所需文件下载见下:https://download.csdn.net/download/weixin_44567668/87919549

  当然也可以在其他操作系统上移植,本文不做过多介绍

2.2 文件移植与工程添加

  1. 官方驱动移植
    将STM32F4x7_ETH_LwIP_V1.1.0\Libraries里的STM32F4x7_ETH_Driver文件夹复制进工程里的FWLIB中
    在这里插入图片描述

  2. LWIP文件移植
    在工程里新建LWIP文件夹,然后将lwip-1.4.1\src里的文件复制进来,然后在LWIP文件夹里建立arch与apps文件夹
    在这里插入图片描述

  3. arch框架移植
    将contrib-1.4.1\contrib-1.4.1\ports\unix文件夹里的cc.h、cpu.h、lwipopts.h、perf.h、sys_arch.c、sys_arch.h这6个文件复制进上面新建的arch文件夹里
    在这里插入图片描述

  4. 在keil工程里添加,结果如下
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

2.3 修改以太网驱动

2.3.1 stm32f4x7_eth_conf.h修改

  将stm32f4x7_eth_conf_template.h重命名为stm32f4x7_eth_conf.h,里面定义了一些关于操作 PHY 芯片的信息,根据 LAN8720 做一定的修改

#ifndef __STM32F4x7_ETH_CONF_H
#define __STM32F4x7_ETH_CONF_H
#include "stm32f4xx.h"

#define USE_ENHANCED_DMA_DESCRIPTORS

//如果使用自己定义的延时函数的话就注销掉下面一行代码,否则使用
//默认的低精度延时函数

//#define USE_Delay    	//使用默认延时函数,因此注销掉
#ifdef USE_Delay
	#include "main.h"               
	#define _eth_delay_    Delay     //Delay为用户自己提供的高精度延时函数
                                    
#else
	#define _eth_delay_    ETH_Delay //默认的_eth_delay功能函数延时精度差
#endif

#ifdef  CUSTOM_DRIVER_BUFFERS_CONFIG
	//重新定义以太网接收和发送缓冲区的大小和数量
	#define ETH_RX_BUF_SIZE    ETH_MAX_PACKET_SIZE //接收缓冲区的大小
	#define ETH_TX_BUF_SIZE    ETH_MAX_PACKET_SIZE //发送缓冲区的大小
	#define ETH_RXBUFNB        20                  //接收缓冲区数量
	#define ETH_TXBUFNB        5                   //发送缓冲区数量
#endif

//*******************PHY配置块*******************
#ifdef USE_Delay
	#define PHY_RESET_DELAY    ((uint32_t)0x000000FF)  	//PHY复位延时
	#define PHY_CONFIG_DELAY   ((uint32_t)0x00000FFF) 	//PHY配置延时
	#define ETH_REG_WRITE_DELAY ((uint32_t)0x00000001)	//向以太网寄存器写数据时的延时
#else
	#define PHY_RESET_DELAY    ((uint32_t)0x000FFFFF)	//PHY复位延时
	#define PHY_CONFIG_DELAY   ((uint32_t)0x00FFFFFF)	//PHY配置延时
	#define ETH_REG_WRITE_DELAY ((uint32_t)0x0000FFFF)	//向以太网寄存器写数据时的延时
#endif

//LAN8720 PHY芯片的状态寄存器
#define PHY_SR				((uint16_t)31) 		//LAN8720的PHY状态寄存器地址
#define PHY_SPEED_STATUS    ((uint16_t)0x0004) 	//LAN8720 PHY速度值掩码
#define PHY_DUPLEX_STATUS   ((uint16_t)0x00010) //LAN8720 PHY连接状态值掩码  
#endif 

2.3.2 修改 stm32f4x7_eth.c 文件

将下面四个数组:Rx_Buff[]、Tx_Buff[]、DMARxDscrTab[]和 DMATxDscrTab[]屏蔽,这四个数组占用了大量的 RAM,后面采用动态内存分配

//#if defined   (__CC_ARM) /*!< ARM Compiler */
//__align(4) 
//ETH_DMADESCTypeDef  DMARxDscrTab[ETH_RXBUFNB];/* Ethernet Rx MA Descriptor */
//__align(4) 
//ETH_DMADESCTypeDef  DMATxDscrTab[ETH_TXBUFNB];/* Ethernet Tx DMA Descriptor */
//__align(4) 
//uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE]; /* Ethernet Receive Buffer */
//__align(4) 
//uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE]; /* Ethernet Transmit Buffer */

//#elif defined ( __ICCARM__ ) /*!< IAR Compiler */
//#pragma data_alignment=4
//ETH_DMADESCTypeDef  DMARxDscrTab[ETH_RXBUFNB];/* Ethernet Rx MA Descriptor */
//#pragma data_alignment=4
//ETH_DMADESCTypeDef  DMATxDscrTab[ETH_TXBUFNB];/* Ethernet Tx DMA Descriptor */
//#pragma data_alignment=4
//uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE]; /* Ethernet Receive Buffer */
//#pragma data_alignment=4
//uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE]; /* Ethernet Transmit Buffer */

//#elif defined (__GNUC__) /*!< GNU Compiler */
//ETH_DMADESCTypeDef  DMARxDscrTab[ETH_RXBUFNB] __attribute__ ((aligned (4))); /* Ethernet Rx DMA Descriptor */
//ETH_DMADESCTypeDef  DMATxDscrTab[ETH_TXBUFNB] __attribute__ ((aligned (4))); /* Ethernet Tx DMA Descriptor */
//uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE] __attribute__ ((aligned (4))); /* Ethernet Receive Buffer */
//uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE] __attribute__ ((aligned (4))); /* Ethernet Transmit Buffer */

//#elif defined  (__TASKING__) /*!< TASKING Compiler */
//__align(4) 
//ETH_DMADESCTypeDef  DMARxDscrTab[ETH_RXBUFNB];/* Ethernet Rx MA Descriptor */
//__align(4) 
//ETH_DMADESCTypeDef  DMATxDscrTab[ETH_TXBUFNB];/* Ethernet Tx DMA Descriptor */
//__align(4) 
//uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE]; /* Ethernet Receive Buffer */
//__align(4) 
//uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE]; /* Ethernet Transmit Buffer */

//#endif /* __CC_ARM */

2.3.3 添加 LAN8720 和 MAC /DMA 驱动

  1. lan8720.h头文件
#ifndef __LAN8720_H
#define __LAN8720_H
#include "sys.h"
#include "stm32f4x7_eth.h"

#define LAN8720_PHY_ADDRESS  	0x00				//LAN8720 PHY芯片地址.
#define LAN8720_RST 		   	PDout(3) 			//LAN8720复位引脚	 

extern ETH_DMADESCTypeDef *DMARxDscrTab;			//以太网DMA接收描述符数据结构体指针
extern ETH_DMADESCTypeDef *DMATxDscrTab;			//以太网DMA发送描述符数据结构体指针 
extern uint8_t *Rx_Buff; 							//以太网底层驱动接收buffers指针 
extern uint8_t *Tx_Buff; 							//以太网底层驱动发送buffers指针
extern ETH_DMADESCTypeDef  *DMATxDescToSet;			//DMA发送描述符追踪指针
extern ETH_DMADESCTypeDef  *DMARxDescToGet; 		//DMA接收描述符追踪指针 
extern ETH_DMA_Rx_Frame_infos *DMA_RX_FRAME_infos;	//DMA最后接收到的帧信息指针

u8 LAN8720_Init(void);
u8 LAN8720_Get_Speed(void);
u8 ETH_MACDMA_Config(void);
FrameTypeDef ETH_Rx_Packet(void);
u8 ETH_Tx_Packet(u16 FrameLength);
u32 ETH_GetCurrentTxBuffer(void);
u8 ETH_Mem_Malloc(void);
void ETH_Mem_Free(void);
#endif 
  1. lan8720.c文件
#include "lan8720.h"
#include "stm32f4x7_eth.h"
#include "usart.h" 
#include "delay.h"
#include "malloc.h" 


ETH_DMADESCTypeDef *DMARxDscrTab;	//以太网DMA接收描述符数据结构体指针
ETH_DMADESCTypeDef *DMATxDscrTab;	//以太网DMA发送描述符数据结构体指针 
uint8_t *Rx_Buff; 					//以太网底层驱动接收buffers指针 
uint8_t *Tx_Buff; 					//以太网底层驱动发送buffers指针
  
static void ETHERNET_NVICConfiguration(void);
//LAN8720初始化
//返回值:0,成功;
//    其他,失败
u8 LAN8720_Init(void)
{
	u8 rval=0;
	GPIO_InitTypeDef GPIO_InitStructure;
  
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOC|RCC_AHB1Periph_GPIOG|RCC_AHB1Periph_GPIOD, ENABLE);//使能GPIO时钟 RMII接口
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);   //使能SYSCFG时钟
  
	SYSCFG_ETH_MediaInterfaceConfig(SYSCFG_ETH_MediaInterface_RMII); //MAC和PHY之间使用RMII接口

	/*网络引脚设置 RMII接口
	  ETH_MDIO -------------------------> PA2
	  ETH_MDC --------------------------> PC1
	  ETH_RMII_REF_CLK------------------> PA1
	  ETH_RMII_CRS_DV ------------------> PA7
	  ETH_RMII_RXD0 --------------------> PC4
	  ETH_RMII_RXD1 --------------------> PC5
	  ETH_RMII_TX_EN -------------------> PG11
	  ETH_RMII_TXD0 --------------------> PG13
	  ETH_RMII_TXD1 --------------------> PG14
	  ETH_RESET-------------------------> PD3*/
					
	  //配置PA1 PA2 PA7
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_7;
	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_NOPULL ;  
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_ETH); //引脚复用到网络接口上
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_ETH);
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_ETH);

	//配置PC1,PC4 and PC5
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_4 | GPIO_Pin_5;
	GPIO_Init(GPIOC, &GPIO_InitStructure);
	GPIO_PinAFConfig(GPIOC, GPIO_PinSource1, GPIO_AF_ETH); //引脚复用到网络接口上
	GPIO_PinAFConfig(GPIOC, GPIO_PinSource4, GPIO_AF_ETH);
	GPIO_PinAFConfig(GPIOC, GPIO_PinSource5, GPIO_AF_ETH);
                                
	//配置PG11, PG14 and PG13 
	GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_11 | GPIO_Pin_13 | GPIO_Pin_14;
	GPIO_Init(GPIOG, &GPIO_InitStructure);
	GPIO_PinAFConfig(GPIOG, GPIO_PinSource11, GPIO_AF_ETH);
	GPIO_PinAFConfig(GPIOG, GPIO_PinSource13, GPIO_AF_ETH);
	GPIO_PinAFConfig(GPIOG, GPIO_PinSource14, GPIO_AF_ETH);
	
	//配置PD3为推完输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;	//推完输出
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;  
	GPIO_Init(GPIOD, &GPIO_InitStructure);
	
	LAN8720_RST=0;					//硬件复位LAN8720
	delay_ms(50);	
	LAN8720_RST=1;				 	//复位结束 
	ETHERNET_NVICConfiguration();	//设置中断优先级
	rval=ETH_MACDMA_Config();		//配置MAC及DMA
	return !rval;					//ETH的规则为:0,失败;1,成功;所以要取反一下 
}

//以太网中断分组配置
void ETHERNET_NVICConfiguration(void)
{
	NVIC_InitTypeDef NVIC_InitStructure;
	
	NVIC_InitStructure.NVIC_IRQChannel = ETH_IRQn;  //以太网中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0X00;  //中断寄存器组2最高优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0X00;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
}


//得到8720的速度模式
//返回值:
//001:10M半双工
//101:10M全双工
//010:100M半双工
//110:100M全双工
//其他:错误.
u8 LAN8720_Get_Speed(void)
{
	u8 speed;
	speed=((ETH_ReadPHYRegister(0x00,31)&0x1C)>>2); //从LAN8720的31号寄存器中读取网络速度和双工模式
	return speed;
}
/
//以下部分为STM32F407网卡配置/接口函数.

//初始化ETH MAC层及DMA配置
//返回值:ETH_ERROR,发送失败(0)
//		ETH_SUCCESS,发送成功(1)
u8 ETH_MACDMA_Config(void)
{
	u8 rval;
	ETH_InitTypeDef ETH_InitStructure; 
	
	//使能以太网MAC以及MAC接收和发送时钟
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_ETH_MAC | RCC_AHB1Periph_ETH_MAC_Tx |RCC_AHB1Periph_ETH_MAC_Rx, ENABLE);
                        
	ETH_DeInit();  								//AHB总线重启以太网
	ETH_SoftwareReset();  						//软件重启网络
	while (ETH_GetSoftwareResetStatus() == SET);//等待软件重启网络完成 
	ETH_StructInit(&ETH_InitStructure); 	 	//初始化网络为默认值  

	///网络MAC参数设置 
	ETH_InitStructure.ETH_AutoNegotiation = ETH_AutoNegotiation_Enable;   			//开启网络自适应功能
	ETH_InitStructure.ETH_LoopbackMode = ETH_LoopbackMode_Disable;					//关闭反馈
	ETH_InitStructure.ETH_RetryTransmission = ETH_RetryTransmission_Disable; 		//关闭重传功能
	ETH_InitStructure.ETH_AutomaticPadCRCStrip = ETH_AutomaticPadCRCStrip_Disable; 	//关闭自动去除PDA/CRC功能 
	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; 			//开启ipv4和TCP/UDP/ICMP的帧校验和卸载   
#endif
	//当我们使用帧校验和卸载功能的时候,一定要使能存储转发模式,存储转发模式中要保证整个帧存储在FIFO中,
	//这样MAC能插入/识别出帧校验值,当真校验正确的时候DMA就可以处理帧,否则就丢弃掉该帧
	ETH_InitStructure.ETH_DropTCPIPChecksumErrorFrame = ETH_DropTCPIPChecksumErrorFrame_Enable; //开启丢弃TCP/IP错误帧
	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;  	//开启DMA传输的地址对齐功能
	ETH_InitStructure.ETH_FixedBurst = ETH_FixedBurst_Enable;            			//开启固定突发功能    
	ETH_InitStructure.ETH_RxDMABurstLength = ETH_RxDMABurstLength_32Beat;     		//DMA发送的最大突发长度为32个节拍   
	ETH_InitStructure.ETH_TxDMABurstLength = ETH_TxDMABurstLength_32Beat;			//DMA接收的最大突发长度为32个节拍
	ETH_InitStructure.ETH_DMAArbitration = ETH_DMAArbitration_RoundRobin_RxTx_2_1;
	rval=ETH_Init(&ETH_InitStructure,LAN8720_PHY_ADDRESS);		//配置ETH
	if(rval==ETH_SUCCESS)//配置成功
	{
		ETH_DMAITConfig(ETH_DMA_IT_NIS|ETH_DMA_IT_R,ENABLE);  	//使能以太网接收中断	
	}
	return rval;
}

extern void lwip_pkt_handle(void);		//在lwip_comm.c里面定义
//以太网DMA接收中断服务函数
void ETH_IRQHandler(void)
{
	while(ETH_GetRxPktSize(DMARxDescToGet)!=0) 	//检测是否收到数据包
	{ 
		lwip_pkt_handle();		
	}
	ETH_DMAClearITPendingBit(ETH_DMA_IT_R); 	//清除DMA中断标志位
	ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS);	//清除DMA接收中断标志位
}  
//接收一个网卡数据包
//返回值:网络数据包帧结构体
FrameTypeDef ETH_Rx_Packet(void)
{ 
	u32 framelength=0;
	FrameTypeDef frame={0,0};   
	//检查当前描述符,是否属于ETHERNET DMA(设置的时候)/CPU(复位的时候)
	if((DMARxDescToGet->Status&ETH_DMARxDesc_OWN)!=(u32)RESET)
	{	
		frame.length=ETH_ERROR; 
		if ((ETH->DMASR&ETH_DMASR_RBUS)!=(u32)RESET)  
		{ 
			ETH->DMASR = ETH_DMASR_RBUS;//清除ETH DMA的RBUS位 
			ETH->DMARPDR=0;//恢复DMA接收
		}
		return frame;//错误,OWN位被设置了
	}  
	if(((DMARxDescToGet->Status&ETH_DMARxDesc_ES)==(u32)RESET)&& 
	((DMARxDescToGet->Status & ETH_DMARxDesc_LS)!=(u32)RESET)&&  
	((DMARxDescToGet->Status & ETH_DMARxDesc_FS)!=(u32)RESET))  
	{       
		framelength=((DMARxDescToGet->Status&ETH_DMARxDesc_FL)>>ETH_DMARxDesc_FrameLengthShift)-4;//得到接收包帧长度(不包含4字节CRC)
 		frame.buffer = DMARxDescToGet->Buffer1Addr;//得到包数据所在的位置
	}else framelength=ETH_ERROR;//错误  
	frame.length=framelength; 
	frame.descriptor=DMARxDescToGet;  
	//更新ETH DMA全局Rx描述符为下一个Rx描述符
	//为下一次buffer读取设置下一个DMA Rx描述符
	DMARxDescToGet=(ETH_DMADESCTypeDef*)(DMARxDescToGet->Buffer2NextDescAddr);   
	return frame;  
}
//发送一个网卡数据包
//FrameLength:数据包长度
//返回值:ETH_ERROR,发送失败(0)
//		ETH_SUCCESS,发送成功(1)
u8 ETH_Tx_Packet(u16 FrameLength)
{   
	//检查当前描述符,是否属于ETHERNET DMA(设置的时候)/CPU(复位的时候)
	if((DMATxDescToSet->Status&ETH_DMATxDesc_OWN)!=(u32)RESET)return ETH_ERROR;//错误,OWN位被设置了 
 	DMATxDescToSet->ControlBufferSize=(FrameLength&ETH_DMATxDesc_TBS1);//设置帧长度,bits[12:0]
	DMATxDescToSet->Status|=ETH_DMATxDesc_LS|ETH_DMATxDesc_FS;//设置最后一个和第一个位段置位(1个描述符传输一帧)
  	DMATxDescToSet->Status|=ETH_DMATxDesc_OWN;//设置Tx描述符的OWN位,buffer重归ETH DMA
	if((ETH->DMASR&ETH_DMASR_TBUS)!=(u32)RESET)//当Tx Buffer不可用位(TBUS)被设置的时候,重置它.恢复传输
	{ 
		ETH->DMASR=ETH_DMASR_TBUS;//重置ETH DMA TBUS位 
		ETH->DMATPDR=0;//恢复DMA发送
	} 
	//更新ETH DMA全局Tx描述符为下一个Tx描述符
	//为下一次buffer发送设置下一个DMA Tx描述符 
	DMATxDescToSet=(ETH_DMADESCTypeDef*)(DMATxDescToSet->Buffer2NextDescAddr);    
	return ETH_SUCCESS;   
}
//得到当前描述符的Tx buffer地址
//返回值:Tx buffer地址
u32 ETH_GetCurrentTxBuffer(void)
{  
  return DMATxDescToSet->Buffer1Addr;//返回Tx buffer地址  
}

//为ETH底层驱动申请内存
//返回值:0,正常
//    其他,失败
u8 ETH_Mem_Malloc(void)
{ 
	DMARxDscrTab=mymalloc(SRAMIN,ETH_RXBUFNB*sizeof(ETH_DMADESCTypeDef));//申请内存
	DMATxDscrTab=mymalloc(SRAMIN,ETH_TXBUFNB*sizeof(ETH_DMADESCTypeDef));//申请内存  
	Rx_Buff=mymalloc(SRAMIN,ETH_RX_BUF_SIZE*ETH_RXBUFNB);	//申请内存
	Tx_Buff=mymalloc(SRAMIN,ETH_TX_BUF_SIZE*ETH_TXBUFNB);	//申请内存
	if(!DMARxDscrTab||!DMATxDscrTab||!Rx_Buff||!Tx_Buff)
	{
		ETH_Mem_Free();
		return 1;	//申请失败
	}	
	return 0;		//申请成功
}

//释放ETH 底层驱动申请的内存
void ETH_Mem_Free(void)
{ 
	myfree(SRAMIN,DMARxDscrTab);//释放内存
	myfree(SRAMIN,DMATxDscrTab);//释放内存
	myfree(SRAMIN,Rx_Buff);		//释放内存
	myfree(SRAMIN,Tx_Buff);		//释放内存  
}

2.4 LwIP的网络接口修改

2.4.1 添加ethernetif.h文件

  将STM32F4x7_ETH_LwIP_V1.1.0\Utilities\Third_Party\lwip-1.4.1\port\STM32F4x7\Standalone里的ethernetif.h添加到我们的LWIP移植\LWIP\include\netif里
在这里插入图片描述

2.4.2 修改ethernetif.c文件

#include "netif/ethernetif.h" 
#include "lan8720.h"  
#include "lwip_comm.h" 
#include "netif/etharp.h"  
#include "string.h"  

//由ethernetif_init()调用用于初始化硬件
//netif:网卡结构体指针 
//返回值:ERR_OK,正常
//       其他,失败
static err_t low_level_init(struct netif *netif)
{
#ifdef CHECKSUM_BY_HARDWARE
	int i; 
#endif 
	netif->hwaddr_len = ETHARP_HWADDR_LEN; //设置MAC地址长度,为6个字节
	//初始化MAC地址,设置什么地址由用户自己设置,但是不能与网络中其他设备MAC地址重复
	netif->hwaddr[0]=lwipdev.mac[0]; 
	netif->hwaddr[1]=lwipdev.mac[1]; 
	netif->hwaddr[2]=lwipdev.mac[2];
	netif->hwaddr[3]=lwipdev.mac[3];
	netif->hwaddr[4]=lwipdev.mac[4];
	netif->hwaddr[5]=lwipdev.mac[5];
	netif->mtu=1500; //最大允许传输单元,允许该网卡广播和ARP功能
	//并且该网卡允许有硬件链路连接
	netif->flags = NETIF_FLAG_BROADCAST|NETIF_FLAG_ETHARP|NETIF_FLAG_LINK_UP;
	
	ETH_MACAddressConfig(ETH_MAC_Address0, netif->hwaddr); //向STM32F4的MAC地址寄存器中写入MAC地址
	ETH_DMATxDescChainInit(DMATxDscrTab, Tx_Buff, ETH_TXBUFNB);
	ETH_DMARxDescChainInit(DMARxDscrTab, Rx_Buff, ETH_RXBUFNB);
#ifdef CHECKSUM_BY_HARDWARE 	//使用硬件帧校验
	for(i=0;i<ETH_TXBUFNB;i++)	//使能TCP,UDP和ICMP的发送帧校验,TCP,UDP和ICMP的接收帧校验在DMA中配置了
	{
		ETH_DMATxDescChecksumInsertionConfig(&DMATxDscrTab[i], ETH_DMATxDesc_ChecksumTCPUDPICMPFull);
	}
#endif
	ETH_Start(); //开启MAC和DMA				
	return ERR_OK;
} 
//用于发送数据包的最底层函数(lwip通过netif->linkoutput指向该函数)
//netif:网卡结构体指针
//p:pbuf数据结构体指针
//返回值:ERR_OK,发送正常
//       ERR_MEM,发送失败
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
	u8 res;
	struct pbuf *q;
	int l = 0;
	u8 *buffer=(u8 *)ETH_GetCurrentTxBuffer(); 
	for(q=p;q!=NULL;q=q->next) 
	{
		memcpy((u8_t*)&buffer[l], q->payload, q->len);
		l=l+q->len;
	} 
	res=ETH_Tx_Packet(l); 
	if(res==ETH_ERROR)return ERR_MEM;//返回错误状态
	return ERR_OK;
} 
//用于接收数据包的最底层函数
//neitif:网卡结构体指针
//返回值:pbuf数据结构体指针
static struct pbuf * low_level_input(struct netif *netif)
{  
	struct pbuf *p, *q;
	u16_t len;
	int l =0;
	FrameTypeDef frame;
	u8 *buffer;
	p = NULL;
	frame=ETH_Rx_Packet();
	len=frame.length;//得到包大小
	buffer=(u8 *)frame.buffer;//得到包数据地址 
	p=pbuf_alloc(PBUF_RAW,len,PBUF_POOL);//pbufs内存池分配pbuf
	if(p!=NULL)
	{
		for(q=p;q!=NULL;q=q->next)
		{
			memcpy((u8_t*)q->payload,(u8_t*)&buffer[l], q->len);
			l=l+q->len;
		}    
	}
	frame.descriptor->Status=ETH_DMARxDesc_OWN;//设置Rx描述符OWN位,buffer重归ETH DMA 
	if((ETH->DMASR&ETH_DMASR_RBUS)!=(u32)RESET)//当Rx Buffer不可用位(RBUS)被设置的时候,重置它.恢复传输
	{ 
		ETH->DMASR=ETH_DMASR_RBUS;//重置ETH DMA RBUS位 
		ETH->DMARPDR=0;//恢复DMA接收
	}
	return p;
} 
//网卡接收数据(lwip直接调用)
//netif:网卡结构体指针
//返回值:ERR_OK,发送正常
//       ERR_MEM,发送失败
err_t ethernetif_input(struct netif *netif)
{
	err_t err;
	struct pbuf *p;
	p=low_level_input(netif);
	if(p==NULL) return ERR_MEM;
	err=netif->input(p, netif);
	if(err!=ERR_OK)
	{
		LWIP_DEBUGF(NETIF_DEBUG,("ethernetif_input: IP input error\n"));
		pbuf_free(p);
		p = NULL;
	} 
	return err;
} 
//使用low_level_init()函数来初始化网络
//netif:网卡结构体指针
//返回值:ERR_OK,正常
//       其他,失败
err_t ethernetif_init(struct netif *netif)
{
	LWIP_ASSERT("netif!=NULL",(netif!=NULL));
#if LWIP_NETIF_HOSTNAME			//LWIP_NETIF_HOSTNAME 
	netif->hostname="lwip";  	//初始化名称
#endif 
	netif->name[0]=IFNAME0; 	//初始化变量netif的name字段
	netif->name[1]=IFNAME1; 	//在文件外定义这里不用关心具体值
	netif->output=etharp_output;//IP层发送数据包函数
	netif->linkoutput=low_level_output;//ARP模块发送数据包函数
	low_level_init(netif); 		//底层硬件初始化函数
	return ERR_OK;
}

2.4.3 修改mem.c 和 memp.c文件

:可自行选择内存管理函数
   LWIP 有一个内存堆 ram_heap 和内存池 memp_memory,这两个是 LWIP 的内存来源。这两个分别在 mem.c 和 memp.c 中,我们将这两个数组改用 ALIENTEK 的内存分配函数对其进行内存分配。在 mem.c 文件中我们将ram_heap 数组注销掉,定义为指向 u8_t 的指针
在这里插入图片描述
   将 memp.c 文件中将 memp_memory 数组屏蔽掉改为指针
在这里插入图片描述
   在 memp.c 文件中添加 memp_get_memorysize()函数
在这里插入图片描述
   在 memp.c 文件中添加 memp_get_memorysize()函数

//得到memp_memory数组大小
u32 memp_get_memorysize(void)
{
	u32 length=0;
	length=(
			MEM_ALIGNMENT-1 //全局型数组 为所有POOL分配的内存空间
			#define LWIP_MEMPOOL(name,num,size,desc)+((num)*(MEMP_SIZE+MEMP_ALIGN_SIZE(size)))//MEMP_SIZE表示需要在每个POOL头部预留的空间  MEMP_SIZE = 0
			#include "lwip/memp_std.h"
			);
	return length;
}

2.4.5 修改icmp.c 文件

  修改 icmp.c 文档使其支持硬件帧校验,在195行注释掉上面,添加下面代码

//#if CHECKSUM_GEN_ICMP
//    /* adjust the checksum */
//    if (iecho->chksum >= PP_HTONS(0xffffU - (ICMP_ECHO << 8))) {
//      iecho->chksum += PP_HTONS(ICMP_ECHO << 8) + 1;
//    } else {
//      iecho->chksum += PP_HTONS(ICMP_ECHO << 8);
//    }
//#else /* CHECKSUM_GEN_ICMP */
//    iecho->chksum = 0;
//#endif /* CHECKSUM_GEN_ICMP */
/* This part of code has been modified by ST's MCD Application Team */
/* To use the Checksum Offload Engine for the putgoing ICMP packets,
   the ICMP checksum field should be set to 0, this is required only for Tx ICMP*/
#ifdef CHECKSUM_BY_HARDWARE
    iecho->chksum = 0;
#else
	/* adjust the checksum */
    if (iecho->chksum >= htons(0xffff - (ICMP_ECHO << 8))) {
      iecho->chksum += htons(ICMP_ECHO << 8) + 1;
    } else {
      iecho->chksum += htons(ICMP_ECHO << 8);
    }	
#endif

2.5 修改LWIP与UCOS接口文件

2.5.1 修改LWIP源文件名字

  1. sys.c 和 sys.h文件名修改
      我们按路径:lwip-1.4.1->src->core 可以发现在 core 文件下有一个 sys.c 文件,按路径lwip-1.4.1->src->include->lwip 可以发现有一个 sys.h 文件。sys.c 和 sys.h 这两个文件和我们的SYSTEM 文件中的 sys.c 和 sys.h 重名,因此我们将 LWIP 中 sys.c 和 sys.h 改为 lwip_sys.c 和lwip_sys.h,然后在工程中将 LWIP 源码里面的#include “sys.h”代码也要改掉,更改为#include “lwip_sys.h”。
  2. cpu.h文件名修改
      UCOSIII 和 LWIP 中都有个 cpu.h 文件,因此其中的某一个必须改名。这里我们将 LWIP 中的 cpu.h 文件名字修改为 lwip_cpu.h,直接在模板工程中修改。文件路径为:LWIP->arch->cpu.h。cpu.h 用来定义 CPU 的大小端模式,因为 STM32 是小端模式
    在这里插入图片描述
#ifndef __CPU_H__
#define __CPU_H__
#define BYTE_ORDER LITTLE_ENDIAN //小端模式
#endif

2.5.2 修改头文件

  1. 修改cc.h文件
      在arch文件夹里cc.h主要完成了协议栈内部使用的数据类型的定义。在 LWIP 中支持针对关键代码的保护,比如申请内存等,而我们知道在 UCOS II 有临界区保护,因此我们就可以使用 UCOS II 中的临界区保护函数。在 cc.h 问文件中我们使用了宏定义来实现这一功能
#ifndef __CC_H__
#define __CC_H__

#include "cpu.h"
#include "stdio.h"
#include "includes.h"  //使用UCOS要添加

/*-------------data type------------------------------------------------------*/

typedef unsigned   char    u8_t;    /* Unsigned 8 bit quantity         */
typedef signed     char    s8_t;    /* Signed    8 bit quantity        */
typedef unsigned   short   u16_t;   /* Unsigned 16 bit quantity        */
typedef signed     short   s16_t;   /* Signed   16 bit quantity        */
typedef unsigned   long    u32_t;   /* Unsigned 32 bit quantity        */
typedef signed     long    s32_t;   /* Signed   32 bit quantity        */
typedef u32_t mem_ptr_t;            /* Unsigned 32 bit quantity        */
typedef int sys_prot_t;

/*-------------critical region protection (depends on uC/OS-II setting)-------*/
//使用操作系统时的临界区保护,这里以 UCOS III 为例
//当定义了 OS_CRITICAL_METHOD 时就说明使用了 UCOS III
#if CPU_CFG_CRITICAL_METHOD == 1
#define SYS_ARCH_DECL_PROTECT(lev)
#define SYS_ARCH_PROTECT(lev) CPU_INT_DIS()
#define SYS_ARCH_UNPROTECT(lev) CPU_INT_EN()
#endif
#if CPU_CFG_CRITICAL_METHOD == 3 
#define SYS_ARCH_DECL_PROTECT(lev) u32_t lev
#define SYS_ARCH_PROTECT(lev) lev = CPU_SR_Save() //进入临界区
#define SYS_ARCH_UNPROTECT(lev) CPU_SR_Restore(lev) //退出临界区
#endif

/*----------------------------------------------------------------------------*/

/* define compiler specific symbols */
#if defined (__ICCARM__)

#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT 
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#define PACK_STRUCT_USE_INCLUDES

#elif defined (__CC_ARM)

#define PACK_STRUCT_BEGIN __packed
#define PACK_STRUCT_STRUCT 
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x

#elif defined (__GNUC__)

#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT __attribute__ ((__packed__))
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x

#elif defined (__TASKING__)

#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x

#endif

/*---define (sn)printf formatters for these lwip types, for lwip DEBUG/STATS--*/

#define U16_F "4d"
#define S16_F "4d"
#define X16_F "4x"
#define U32_F "8ld"
#define S32_F "8ld"
#define X32_F "8lx"

/*--------------macros--------------------------------------------------------*/
#ifndef LWIP_PLATFORM_ASSERT
#define LWIP_PLATFORM_ASSERT(x) \
    do \
    {   printf("Assertion \"%s\" failed at line %d in %s\r\n", x, __LINE__, __FILE__); \
    } while(0)
#endif

#ifndef LWIP_PLATFORM_DIAG
#define LWIP_PLATFORM_DIAG(x) do {printf x;} while(0)
#endif

#endif /* __CC_H__ */
  1. 修改perf.h文件
      perf.h是和系统测量与统计相关的文件,我们不使用任何的测量和统计
#ifndef __PERF_H__
#define __PERF_H__
#define PERF_START //空定义
#define PERF_STOP(x) //空定义
#endif

2.5.3 修改 lwipopts.h配置文件

  lwipopts.h 是用来裁剪和配置 LWIP 的,那么我们要使用操作系统的话就需要对其进行相应的配置

#ifndef __LWIPOPTS_H__
#define __LWIPOPTS_H__

//线程优先级
#ifndef TCPIP_THREAD_PRIO
#define TCPIP_THREAD_PRIO		5	//定义内核任务的优先级为5
#endif
#undef  DEFAULT_THREAD_PRIO
#define DEFAULT_THREAD_PRIO		2


#define SYS_LIGHTWEIGHT_PROT    1		//为1时使用实时操作系统的轻量级保护,保护关键代码不被中断打断
#define NO_SYS                  0  		//使用UCOS操作系统
#define MEM_ALIGNMENT           4  		//使用4字节对齐模式
#define MEM_SIZE                16000 	//内存堆heap大小
#define MEMP_NUM_PBUF           20 		//MEMP_NUM_PBUF:memp结构的pbuf数量,如果应用从ROM或者静态存储区发送大量数据时,这个值应该设置大一点
#define MEMP_NUM_UDP_PCB        6		//MEMP_NUM_UDP_PCB:UDP协议控制块(PCB)数量.每个活动的UDP"连接"需要一个PCB.
#define MEMP_NUM_TCP_PCB        10		//MEMP_NUM_TCP_PCB:同时建立激活的TCP数量
#define MEMP_NUM_TCP_PCB_LISTEN 6		//MEMP_NUM_TCP_PCB_LISTEN:能够监听的TCP连接数量
#define MEMP_NUM_TCP_SEG        15		//MEMP_NUM_TCP_SEG:最多同时在队列中的TCP段数量
#define MEMP_NUM_SYS_TIMEOUT    8		//MEMP_NUM_SYS_TIMEOUT:能够同时激活的timeout个数

//pbuf选项
#define PBUF_POOL_SIZE          20		//PBUF_POOL_SIZE:pbuf内存池个数
#define PBUF_POOL_BUFSIZE       512		//PBUF_POOL_BUFSIZE:每个pbuf内存池大小

#define LWIP_TCP                1  		//使用TCP
#define TCP_TTL                 255		//生存时间

#undef TCP_QUEUE_OOSEQ
#define TCP_QUEUE_OOSEQ         0 		//当TCP的数据段超出队列时的控制位,当设备的内存过小的时候此项应为0

#undef TCPIP_MBOX_SIZE
#define TCPIP_MBOX_SIZE         MAX_QUEUE_ENTRIES   //tcpip创建主线程时的消息邮箱大小

#undef DEFAULT_TCP_RECVMBOX_SIZE
#define DEFAULT_TCP_RECVMBOX_SIZE       MAX_QUEUE_ENTRIES  

#undef DEFAULT_ACCEPTMBOX_SIZE
#define DEFAULT_ACCEPTMBOX_SIZE         MAX_QUEUE_ENTRIES  


#define TCP_MSS                 (1500 - 40)	  	//最大TCP分段,TCP_MSS = (MTU - IP报头大小 - TCP报头大小
#define TCP_SND_BUF             (4*TCP_MSS)		//TCP发送缓冲区大小(bytes).
#define TCP_SND_QUEUELEN        (2* TCP_SND_BUF/TCP_MSS)	//TCP_SND_QUEUELEN: TCP发送缓冲区大小(pbuf).这个值最小为(2 * TCP_SND_BUF/TCP_MSS)
#define TCP_WND                 (2*TCP_MSS)		//TCP发送窗口
#define LWIP_ICMP               1 	//使用ICMP协议
#define LWIP_DHCP               1	//使用DHCP
#define LWIP_UDP                1 	//使用UDP服务
#define UDP_TTL                 255 //UDP数据包生存时间
#define LWIP_STATS 0
#define LWIP_PROVIDE_ERRNO 1


//帧校验和选项,STM32F4x7允许通过硬件识别和计算IP,UDP和ICMP的帧校验和
#define CHECKSUM_BY_HARDWARE //定义CHECKSUM_BY_HARDWARE,使用硬件帧校验
#ifdef CHECKSUM_BY_HARDWARE
  //CHECKSUM_GEN_IP==0: 硬件生成IP数据包的帧校验和
  #define CHECKSUM_GEN_IP                 0
  //CHECKSUM_GEN_UDP==0: 硬件生成UDP数据包的帧校验和
  #define CHECKSUM_GEN_UDP                0
  //CHECKSUM_GEN_TCP==0: 硬件生成TCP数据包的帧校验和
  #define CHECKSUM_GEN_TCP                0 
  //CHECKSUM_CHECK_IP==0: 硬件检查输入的IP数据包帧校验和
  #define CHECKSUM_CHECK_IP               0
  //CHECKSUM_CHECK_UDP==0: 硬件检查输入的UDP数据包帧校验和
  #define CHECKSUM_CHECK_UDP              0
  //CHECKSUM_CHECK_TCP==0: 硬件检查输入的TCP数据包帧校验和
  #define CHECKSUM_CHECK_TCP              0
#else
  //CHECKSUM_GEN_IP==1: 软件生成IP数据包帧校验和
  #define CHECKSUM_GEN_IP                 1
  // CHECKSUM_GEN_UDP==1: 软件生成UDOP数据包帧校验和
  #define CHECKSUM_GEN_UDP                1
  //CHECKSUM_GEN_TCP==1: 软件生成TCP数据包帧校验和
  #define CHECKSUM_GEN_TCP                1
  // CHECKSUM_CHECK_IP==1: 软件检查输入的IP数据包帧校验和
  #define CHECKSUM_CHECK_IP               1
  // CHECKSUM_CHECK_UDP==1: 软件检查输入的UDP数据包帧校验和
  #define CHECKSUM_CHECK_UDP              1
  //CHECKSUM_CHECK_TCP==1: 软件检查输入的TCP数据包帧校验和
  #define CHECKSUM_CHECK_TCP              1
#endif



#define LWIP_NETCONN                    1 	//LWIP_NETCONN==1:使能NETCON函数(要求使用api_lib.c)
#define LWIP_SOCKET                     1	//LWIP_SOCKET==1:使能Sicket API(要求使用sockets.c)
#define LWIP_COMPAT_MUTEX               1
#define LWIP_SO_RCVTIMEO                1 	//通过定义LWIP_SO_RCVTIMEO使能netconn结构体中recv_timeout,使用recv_timeout可以避免阻塞线程

//有关系统的选项
#define TCPIP_THREAD_STACKSIZE          1000	//内核任务堆栈大小
#define DEFAULT_UDP_RECVMBOX_SIZE       2000
#define DEFAULT_THREAD_STACKSIZE        512

//LWIP调试选项
#define LWIP_DEBUG                    	 0	 //关闭DEBUG选项
#define ICMP_DEBUG                      LWIP_DBG_OFF //开启/关闭ICMPdebug

#endif /* __LWIPOPTS_H__ */

2.5.4 修改 sys_arch.h 和 sys.arch.c 文件

  1. 修改 sys_arch.h 头文件
      在 sys_arch.h 中定义了消息邮箱的数量和每个消息邮箱的大小,在这个文件中还定义了 4中数据类型:sys_sem_t、sys_mutex_t、sys_mbox_t 和 sys_thread_t。分别为信号量、互斥信号量、消息邮箱和线程 ID。这里不能直接使用 UCOS 的消息邮箱,使用的是消息队列
#ifndef __ARCH_SYS_ARCH_H__
#define __ARCH_SYS_ARCH_H__ 
#include <includes.h>
#include "arch/cc.h"
#include "includes.h"

#ifdef SYS_ARCH_GLOBALS
#define SYS_ARCH_EXT
#else
#define SYS_ARCH_EXT extern
#endif

#define MAX_QUEUES OS_CFG_MSG_POOL_SIZE// 消息邮箱的数量
#define MAX_QUEUE_ENTRIES 20 // 每个消息邮箱的大小

typedef OS_SEM sys_sem_t; //LWIP 使用的信号量
typedef OS_MUTEX sys_mutex_t; //LWIP 使用的互斥信号量
typedef OS_Q sys_mbox_t; //LWIP 使用的消息邮箱,其实就是 UCOS 中的消息队列
typedef CPU_INT08U sys_thread_t; //线程 ID,也就是任务优先级

#endif
  1. 修改 sys_arch.c 文件
      sys_arch.c 文件是非常重要的一个文件,在这个文件中定义了 LWIP 使用到的关于信号量、消息邮箱的操作函数。与邮箱有关的函数要使用操作系统中的消息队列,有关于消息邮箱的这些函数功能来自于移植手册 sys_arch.txt
#define SYS_ARCH_GLOBALS

/* lwIP includes. */
#include "lwip/debug.h"
#include "lwip/def.h"
#include "lwip/lwip_sys.h"
#include "lwip/mem.h"
#include "includes.h"
#include "delay.h"
#include "arch/sys_arch.h"
#include "malloc.h"
#include "os_cfg_app.h"


//当消息指针为空时,指向一个常量pvNullPointer所指向的值.
//在UCOS中如果OSQPost()中的msg==NULL会返回一条OS_ERR_POST_NULL
//错误,而在lwip中会调用sys_mbox_post(mbox,NULL)发送一条空消息,我们
//在本函数中把NULL变成一个常量指针0Xffffffff
const void * const pvNullPointer = (mem_ptr_t*)0xffffffff;
 
/*************************消息邮箱函数*********************/
//创建一个消息邮箱
//*mbox:消息邮箱
//size:邮箱大小
//返回值:ERR_OK,创建成功
//         其他,创建失败
err_t sys_mbox_new( sys_mbox_t *mbox, int size)
{
	OS_ERR err;
	//消息队列最多容纳 MAX_QUEUE_ENTRIES 消息数目
	if(size>MAX_QUEUE_ENTRIES)size=MAX_QUEUE_ENTRIES;
	OSQCreate((OS_Q* )mbox, //消息队列
 			(CPU_CHAR* )"LWIP Quiue", //消息队列名称
 			(OS_MSG_QTY )size, //消息队列长度
 			(OS_ERR* )&err); //错误码
	if(err==OS_ERR_NONE) return ERR_OK;
	return ERR_MEM;
} 
//释放并删除一个消息邮箱
//*mbox:要删除的消息邮箱
void sys_mbox_free(sys_mbox_t * mbox)
{
	OS_ERR err;
#if OS_CFG_Q_FLUSH_EN > 0u 
	OSQFlush(mbox,&err);
#endif
	OSQDel((OS_Q* )mbox,
			(OS_OPT )OS_OPT_DEL_ALWAYS,
 			(OS_ERR* )&err);
	LWIP_ASSERT( "OSQDel ",err == OS_ERR_NONE );
}
//向消息邮箱中发送一条消息(必须发送成功)
//*mbox:消息邮箱
//*msg:要发送的消息
void sys_mbox_post(sys_mbox_t *mbox,void *msg)
{    
	OS_ERR err;
	CPU_INT08U i=0;
	//当 msg 为空时 msg 等于 pvNullPointer 指向的值
	if(msg==NULL)msg=(void*)&pvNullPointer;
	//发送消息
 	while(i<10) //试 10 次 
 	{
		OSQPost((OS_Q* )mbox,
				(void* )msg,
				(OS_MSG_SIZE )strlen(msg),
				(OS_OPT )OS_OPT_POST_FIFO,
 				(OS_ERR* )&err);
		if(err==OS_ERR_NONE) break;
		i++;
		OSTimeDlyHMSM(0,0,0,5,OS_OPT_TIME_HMSM_STRICT,&err); //延时 5ms
	}
	LWIP_ASSERT( "sys_mbox_post error!\n", i !=10 );
}
//尝试向一个消息邮箱发送消息
//此函数相对于sys_mbox_post函数只发送一次消息,
//发送失败后不会尝试第二次发送
//*mbox:消息邮箱
//*msg:要发送的消息
//返回值:ERR_OK,发送OK
// 	     ERR_MEM,发送失败
err_t sys_mbox_trypost(sys_mbox_t *mbox, void *msg)
{ 
	OS_ERR err;
	//当 msg 为空时 msg 等于 pvNullPointer 指向的值
	if(msg==NULL)msg=(void*)&pvNullPointer;
	OSQPost((OS_Q* )mbox,
			(void* )msg,
			(OS_MSG_SIZE )sizeof(msg),
			(OS_OPT )OS_OPT_POST_FIFO,
			(OS_ERR* )&err);
	if(err!=OS_ERR_NONE) return ERR_MEM;
	return ERR_OK;
}

//等待邮箱中的消息
//*mbox:消息邮箱
//*msg:消息
//timeout:超时时间,如果timeout为0的话,就一直等待
//返回值:当timeout不为0时如果成功的话就返回等待的时间,
//		失败的话就返回超时SYS_ARCH_TIMEOUT
u32_t sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout)
{ 
	OS_ERR err;
	OS_MSG_SIZE size;
	u32_t ucos_timeout,timeout_new;
	void *temp;
	if(timeout!=0)
	{
		//转换为节拍数,因为 UCOS 延时使用的是节拍数,而 LWIP 是用 ms
		ucos_timeout=(timeout*OS_CFG_TICK_RATE_HZ)/1000;
		if(ucos_timeout<1)ucos_timeout=1;//至少 1 个节拍
	}else ucos_timeout = 0; 
	timeout = OSTimeGet(&err); //获取系统时间
	//请求消息
	temp=OSQPend((OS_Q* )mbox, 
				(OS_TICK )ucos_timeout,
				(OS_OPT )OS_OPT_PEND_BLOCKING,
				(OS_MSG_SIZE* )&size,
 				(CPU_TS* )0,
 				(OS_ERR* )&err);
	if(msg!=NULL)
	{
		//因为 lwip 发送空消息的时候我们使用了 pvNullPointer 指针,所以判断 pvNullPointer
		//指向的值就可知道请求到的消息是否有效。
		if(temp==(void*)&pvNullPointer)*msg = NULL;
		else *msg=temp;
	} 
	if(err==OS_ERR_TIMEOUT)timeout=SYS_ARCH_TIMEOUT; //请求超时
	else
	{
		LWIP_ASSERT("OSQPend ",err==OS_ERR_NONE); 
		timeout_new=OSTimeGet(&err);
		//算出请求消息或使用的时间
		if (timeout_new>=timeout) timeout_new = timeout_new - timeout;
		else timeout_new = 0xffffffff - timeout + timeout_new; 
		timeout=timeout_new*1000/OS_CFG_TICK_RATE_HZ + 1; 
	}
	return timeout;
}
//尝试获取消息
//*mbox:消息邮箱
//*msg:消息
//返回值:等待消息所用的时间/SYS_ARCH_TIMEOUT
u32_t sys_arch_mbox_tryfetch(sys_mbox_t *mbox, void **msg)
{
	return sys_arch_mbox_fetch(mbox,msg,1);//尝试获取一个消息
}
//检查一个消息邮箱是否有效
//*mbox:消息邮箱
//返回值:1,有效.
//      0,无效
int sys_mbox_valid(sys_mbox_t *mbox)
{  
	if(mbox->NamePtr) 
		return (strcmp(mbox->NamePtr,"?Q"))? 1:0;
	else
		return 0;
} 
//设置一个消息邮箱为无效
//*mbox:消息邮箱
void sys_mbox_set_invalid(sys_mbox_t *mbox)
{
	if(sys_mbox_valid(mbox))
	sys_mbox_free(mbox);
} 

/********************信号量相关函数************************/
//创建一个信号量
//*sem:创建的信号量
//count:信号量值
//返回值:ERR_OK,创建OK
// 	     ERR_MEM,创建失败
err_t sys_sem_new(sys_sem_t * sem, u8_t count)
{  
	OS_ERR err;
	OSSemCreate ((OS_SEM* )sem,
 				(CPU_CHAR* )"LWIP Sem",
 				(OS_SEM_CTR )count,
 				(OS_ERR* )&err);
	if(err!=OS_ERR_NONE)return ERR_MEM; 
	LWIP_ASSERT("OSSemCreate ",sem != NULL );
	return ERR_OK;
} 
//等待一个信号量
//*sem:要等待的信号量
//timeout:超时时间
//返回值:当timeout不为0时如果成功的话就返回等待的时间,
//		失败的话就返回超时SYS_ARCH_TIMEOUT
u32_t sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout)
{ 
	OS_ERR err;
	u32_t ucos_timeout, timeout_new; 
	if( timeout!=0) 
	{
		//转换为节拍数,因为 UCOS 延时使用的是节拍数,而 LWIP 是用 ms
		ucos_timeout = (timeout * OS_CFG_TICK_RATE_HZ) / 1000;
		if(ucos_timeout < 1)
		ucos_timeout = 1;
	}else ucos_timeout = 0; 
	timeout = OSTimeGet(&err); 
	OSSemPend(sem,ucos_timeout,OS_OPT_PEND_BLOCKING,0,&err); //请求信号量
	if(err == OS_ERR_TIMEOUT)timeout=SYS_ARCH_TIMEOUT; //请求超时
	else
	{ 
		timeout_new = OSTimeGet(&err); 
		if (timeout_new>=timeout) timeout_new = timeout_new - timeout;
		else timeout_new = 0xffffffff - timeout + timeout_new;
		//算出请求消息或使用的时间(ms)
		timeout = (timeout_new*1000/OS_CFG_TICK_RATE_HZ + 1);
	}
	return timeout;
}
//发送一个信号量
//sem:信号量指针
void sys_sem_signal(sys_sem_t *sem)
{
	OS_ERR err;
	OSSemPost(sem,OS_OPT_POST_1,&err);//发送信号量
	LWIP_ASSERT("OSSemPost ",err == OS_ERR_NONE );
}
//释放并删除一个信号量
//sem:信号量指针
void sys_sem_free(sys_sem_t *sem)
{
	OS_ERR err;
	OSSemDel(sem,OS_OPT_DEL_ALWAYS,&err);
	LWIP_ASSERT("OSSemDel ",err==OS_ERR_NONE);
	sem = NULL;
} 
//查询一个信号量的状态,无效或有效
//sem:信号量指针
//返回值:1,有效.
//      0,无效
int sys_sem_valid(sys_sem_t *sem)
{
	if(sem->NamePtr)
		return (strcmp(sem->NamePtr,"?SEM"))? 1:0;
	else
		return 0;           
} 
//设置一个信号量无效
//sem:信号量指针
void sys_sem_set_invalid(sys_sem_t *sem)
{
	if(sys_sem_valid(sem))
	sys_sem_free(sem);
} 
//arch初始化
void sys_init(void)
{ 
    //这里,我们在该函数,不做任何事情
} 

/****************创建新进程****************/
extern CPU_STK * TCPIP_THREAD_TASK_STK;//TCP IP 内核任务堆栈,在lwip_comm函数定义
//LWIP 内核任务的任务控制块
OS_TCB TcpipthreadTaskTCB;
//创建一个新进程
//*name:进程名称
//thred:进程任务函数
//*arg:进程任务函数的参数
//stacksize:进程任务的堆栈大小
//prio:进程任务的优先级
sys_thread_t sys_thread_new(const char *name, lwip_thread_fn thread, void *arg, int stacksize, int prio)
{
	OS_ERR err;
	CPU_SR_ALLOC();
	if(strcmp(name,TCPIP_THREAD_NAME)==0)//创建 TCP IP 内核任务
	{
		OS_CRITICAL_ENTER(); //进入临界区
		//创建开始任务
		OSTaskCreate((OS_TCB* )&TcpipthreadTaskTCB,//任务控制块
					(CPU_CHAR* )"TCPIPThread task", //任务名字
					(OS_TASK_PTR )thread, //任务函数
					(void* )0, //传递给任务函数的参数
					(OS_PRIO )prio, //任务优先级
					(CPU_STK* )&TCPIP_THREAD_TASK_STK[0],
					(CPU_STK_SIZE )stacksize/10, //任务堆栈深度限位
					(CPU_STK_SIZE )stacksize, //任务堆栈大小
					(OS_MSG_QTY )0,
					(OS_TICK )0,
					(void* )0, //用户补充的存储区
					(OS_OPT )OS_OPT_TASK_STK_CHK|\ //任务选项
							OS_OPT_TASK_STK_CLR, 
					(OS_ERR* )&err); //存放该函数错误时的返回值
		OS_CRITICAL_EXIT(); //退出临界区
	} 
	return 0;
} 
//lwip延时函数
//ms:要延时的ms数
void sys_msleep(u32_t ms)
{
	delay_ms(ms);
}
//获取系统时间,LWIP1.4.1增加的函数
//返回值:当前系统时间(单位:毫秒)
u32_t sys_now(void)
{
	OS_ERR err;
	u32_t ucos_time, lwip_time;
	ucos_time=OSTimeGet(&err); //获取当前系统时间 得到的是 UCSO 的节拍数
	lwip_time=(ucos_time*1000/OS_CFG_TICK_RATE_HZ+1);//将节拍数转换为 LWIP 的时间MS
	return lwip_time; //返回 lwip_time;
}

  最后我们还实现了 sys_msleep 和 sys_now 这两个函数,他们分别为 LWIP 使用到的延时函数和获取系统时间函数。sys_msleep 函数很简单,sys_now 函数是先获取到 UCOS 的时间节拍,然后将其转换为 LWIP 使用的 ms,并返回这个时间值。

2.5.5 添加LWIP通用文件

  lwip_comm.c 和 lwip_comm.h 是将 LWIP 源码和前面的以太网驱动库结合起来的桥梁,在apps里添加lwip_comm文件夹,在里面添加lwip_comm.c 和 lwip_comm.h两个文件

  1. 添加lwip_comm.h 文件
#ifndef _LWIP_COMM_H
#define _LWIP_COMM_H 
#include "lan8720.h" 

#define LWIP_MAX_DHCP_TRIES		4   //DHCP服务器最大重试次数 

//lwip控制结构体
typedef struct  
{
	u8 mac[6];      //MAC地址
	u8 remoteip[4];	//远端主机IP地址 
	u8 ip[4];       //本机IP地址
	u8 netmask[4]; 	//子网掩码
	u8 gateway[4]; 	//默认网关的IP地址
	
	vu8 dhcpstatus;	//dhcp状态 
					//0,未获取DHCP地址;
					//1,进入DHCP获取状态
					//2,成功获取DHCP地址
					//0XFF,获取失败.
}__lwip_dev;
extern __lwip_dev lwipdev;	//lwip控制结构体
 
void lwip_pkt_handle(void);
void lwip_comm_default_ip_set(__lwip_dev *lwipx);
u8 lwip_comm_mem_malloc(void);
void lwip_comm_mem_free(void);
u8 lwip_comm_init(void);
void lwip_comm_dhcp_creat(void);
void lwip_comm_dhcp_delete(void);
void lwip_comm_destroy(void);
void lwip_comm_delete_next_timeout(void);

#endif
  1. 添加lwip_comm.c 文件
#include "lwip_comm.h" 
#include "netif/etharp.h"
#include "lwip/dhcp.h"
#include "ethernetif.h" 
#include "lwip/timers.h"
#include "lwip/tcp_impl.h"
#include "lwip/ip_frag.h"
#include "lwip/tcpip.h" 
#include "malloc.h"
#include "delay.h"
#include "usart.h"  
#include <stdio.h>

  
__lwip_dev lwipdev;						//lwip控制结构体 
struct netif lwip_netif;				//定义一个全局的网络接口

extern u32 memp_get_memorysize(void);	//在memp.c里面定义
extern u8_t *memp_memory;				//在memp.c里面定义.
extern u8_t *ram_heap;					//在mem.c里面定义.



//lwip两个任务定义(内核任务和DHCP任务)

//lwip内核任务堆栈(优先级和堆栈大小在lwipopts.h定义了) 
CPU_STK * TCPIP_THREAD_TASK_STK;

//lwip DHCP任务
//设置任务优先级
#define LWIP_DHCP_TASK_PRIO       		7
//设置任务堆栈大小
#define LWIP_DHCP_STK_SIZE  		    256
//任务控制块
OS_TCB LwipdhcpTaskTCB;
//任务堆栈,采用内存管理的方式控制申请
CPU_STK * LWIP_DHCP_TASK_STK;
//任务函数
void lwip_dhcp_task(void *pdata); 


//用于以太网中断调用
void lwip_pkt_handle(void)
{
	ethernetif_input(&lwip_netif);
}
//lwip内核部分,内存申请
//返回值:0,成功;
//    其他,失败
u8 lwip_comm_mem_malloc(void)
{
	u32 mempsize;
	u32 ramheapsize; 
	mempsize=memp_get_memorysize();			//得到memp_memory数组大小
	memp_memory=mymalloc(SRAMIN,mempsize);	//为memp_memory申请内存
	ramheapsize=LWIP_MEM_ALIGN_SIZE(MEM_SIZE)+2*LWIP_MEM_ALIGN_SIZE(4*3)+MEM_ALIGNMENT;//得到ram heap大小
	ram_heap=mymalloc(SRAMIN,ramheapsize);	//为ram_heap申请内存 
	TCPIP_THREAD_TASK_STK=mymalloc(SRAMIN,TCPIP_THREAD_STACKSIZE*4);//给内核任务申请堆栈 
	LWIP_DHCP_TASK_STK=mymalloc(SRAMIN,LWIP_DHCP_STK_SIZE*4);		//给dhcp任务堆栈申请内存空间
	if(!memp_memory||!ram_heap||!TCPIP_THREAD_TASK_STK||!LWIP_DHCP_TASK_STK)//有申请失败的
	{
		lwip_comm_mem_free();
		return 1;
	}
	return 0;	
}
//lwip内核部分,内存释放
void lwip_comm_mem_free(void)
{ 	
	myfree(SRAMIN,memp_memory);
	myfree(SRAMIN,ram_heap);
	myfree(SRAMIN,TCPIP_THREAD_TASK_STK);
	myfree(SRAMIN,LWIP_DHCP_TASK_STK);
}
//lwip 默认IP设置
//lwipx:lwip控制结构体指针
void lwip_comm_default_ip_set(__lwip_dev *lwipx)
{
	u32 sn0;
	sn0=*(vu32*)(0x1FFF7A10);//获取STM32的唯一ID的前24位作为MAC地址后三字节
	//默认远端IP为:192.168.1.100
	lwipx->remoteip[0]=192;	
	lwipx->remoteip[1]=168;
	lwipx->remoteip[2]=1;
	lwipx->remoteip[3]=11;
	//MAC地址设置(高三字节固定为:2.0.0,低三字节用STM32唯一ID)
	lwipx->mac[0]=2;//高三字节(IEEE称之为组织唯一ID,OUI)地址固定为:2.0.0
	lwipx->mac[1]=0;
	lwipx->mac[2]=0;
	lwipx->mac[3]=(sn0>>16)&0XFF;//低三字节用STM32的唯一ID
	lwipx->mac[4]=(sn0>>8)&0XFFF;;
	lwipx->mac[5]=sn0&0XFF; 
	//默认本地IP为:192.168.1.30
	lwipx->ip[0]=192;	
	lwipx->ip[1]=168;
	lwipx->ip[2]=1;
	lwipx->ip[3]=30;
	//默认子网掩码:255.255.255.0
	lwipx->netmask[0]=255;	
	lwipx->netmask[1]=255;
	lwipx->netmask[2]=255;
	lwipx->netmask[3]=0;
	//默认网关:192.168.1.1
	lwipx->gateway[0]=192;	
	lwipx->gateway[1]=168;
	lwipx->gateway[2]=1;
	lwipx->gateway[3]=1;	
	lwipx->dhcpstatus=0;//没有DHCP	
} 

//LWIP初始化(LWIP启动的时候使用)
//返回值:0,成功
//      1,内存错误
//      2,LAN8720初始化失败
//      3,网卡添加失败.
u8 lwip_comm_init(void)
{
	OS_CPU_SR cpu_sr;
	struct netif *Netif_Init_Flag;		//调用netif_add()函数时的返回值,用于判断网络初始化是否成功
	struct ip_addr ipaddr;  			//ip地址
	struct ip_addr netmask; 			//子网掩码
	struct ip_addr gw;      			//默认网关 
	if(ETH_Mem_Malloc())return 1;		//内存申请失败
	if(lwip_comm_mem_malloc())return 1;	//内存申请失败
	if(LAN8720_Init())return 2;			//初始化LAN8720失败 
	tcpip_init(NULL,NULL);				//初始化tcp ip内核,该函数里面会创建tcpip_thread内核任务
	lwip_comm_default_ip_set(&lwipdev);	//设置默认IP等信息
#if LWIP_DHCP		//使用动态IP
	ipaddr.addr = 0;
	netmask.addr = 0;
	gw.addr = 0;
#else				//使用静态IP
	IP4_ADDR(&ipaddr,lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);
	IP4_ADDR(&netmask,lwipdev.netmask[0],lwipdev.netmask[1] ,lwipdev.netmask[2],lwipdev.netmask[3]);
	IP4_ADDR(&gw,lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]);
	printf("网卡en的MAC地址为:................%d.%d.%d.%d.%d.%d\r\n",lwipdev.mac[0],lwipdev.mac[1],lwipdev.mac[2],lwipdev.mac[3],lwipdev.mac[4],lwipdev.mac[5]);
	printf("静态IP地址........................%d.%d.%d.%d\r\n",lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);
	printf("子网掩码..........................%d.%d.%d.%d\r\n",lwipdev.netmask[0],lwipdev.netmask[1],lwipdev.netmask[2],lwipdev.netmask[3]);
	printf("默认网关..........................%d.%d.%d.%d\r\n",lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]);
#endif
	OS_ENTER_CRITICAL();  //进入临界区
	Netif_Init_Flag=netif_add(&lwip_netif,&ipaddr,&netmask,&gw,NULL,&ethernetif_init,&tcpip_input);//向网卡列表中添加一个网口
	OS_EXIT_CRITICAL();  //退出临界区
	if(Netif_Init_Flag==NULL)return 3;//网卡添加失败 
	else//网口添加成功后,设置netif为默认值,并且打开netif网口
	{
		netif_set_default(&lwip_netif); //设置netif为默认网口
		netif_set_up(&lwip_netif);		//打开netif网口
	}
	return 0;//操作OK.
}   
//如果使能了DHCP
#if LWIP_DHCP
//创建DHCP任务
void lwip_comm_dhcp_creat(void)
{
	OS_CPU_SR cpu_sr;
	OS_ENTER_CRITICAL();  //进入临界区
	OSTaskCreate(lwip_dhcp_task,(void*)0,(OS_STK*)&LWIP_DHCP_TASK_STK[LWIP_DHCP_STK_SIZE-1],LWIP_DHCP_TASK_PRIO);//创建DHCP任务 
	OS_EXIT_CRITICAL();  //退出临界区
}
//删除DHCP任务
void lwip_comm_dhcp_delete(void)
{
	dhcp_stop(&lwip_netif); 		//关闭DHCP
	OSTaskDel(LWIP_DHCP_TASK_PRIO);	//删除DHCP任务
}
//DHCP处理任务
void lwip_dhcp_task(void *pdata)
{
	u32 ip=0,netmask=0,gw=0;
	dhcp_start(&lwip_netif);//开启DHCP 
	lwipdev.dhcpstatus=0;	//正在DHCP
	printf("正在查找DHCP服务器,请稍等...........\r\n");   
	while(1)
	{ 
		printf("正在获取地址...\r\n");
		ip=lwip_netif.ip_addr.addr;		//读取新IP地址
		netmask=lwip_netif.netmask.addr;//读取子网掩码
		gw=lwip_netif.gw.addr;			//读取默认网关 
		if(ip!=0)   					//当正确读取到IP地址的时候
		{
			lwipdev.dhcpstatus=2;	//DHCP成功
 			printf("网卡en的MAC地址为:................%d.%d.%d.%d.%d.%d\r\n",lwipdev.mac[0],lwipdev.mac[1],lwipdev.mac[2],lwipdev.mac[3],lwipdev.mac[4],lwipdev.mac[5]);
			//解析出通过DHCP获取到的IP地址
			lwipdev.ip[3]=(uint8_t)(ip>>24); 
			lwipdev.ip[2]=(uint8_t)(ip>>16);
			lwipdev.ip[1]=(uint8_t)(ip>>8);
			lwipdev.ip[0]=(uint8_t)(ip);
			printf("通过DHCP获取到IP地址..............%d.%d.%d.%d\r\n",lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);
			//解析通过DHCP获取到的子网掩码地址
			lwipdev.netmask[3]=(uint8_t)(netmask>>24);
			lwipdev.netmask[2]=(uint8_t)(netmask>>16);
			lwipdev.netmask[1]=(uint8_t)(netmask>>8);
			lwipdev.netmask[0]=(uint8_t)(netmask);
			printf("通过DHCP获取到子网掩码............%d.%d.%d.%d\r\n",lwipdev.netmask[0],lwipdev.netmask[1],lwipdev.netmask[2],lwipdev.netmask[3]);
			//解析出通过DHCP获取到的默认网关
			lwipdev.gateway[3]=(uint8_t)(gw>>24);
			lwipdev.gateway[2]=(uint8_t)(gw>>16);
			lwipdev.gateway[1]=(uint8_t)(gw>>8);
			lwipdev.gateway[0]=(uint8_t)(gw);
			printf("通过DHCP获取到的默认网关..........%d.%d.%d.%d\r\n",lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]);
			break;
		}else if(lwip_netif.dhcp->tries>LWIP_MAX_DHCP_TRIES) //通过DHCP服务获取IP地址失败,且超过最大尝试次数
		{  
			lwipdev.dhcpstatus=0XFF;//DHCP失败.
			//使用静态IP地址
			IP4_ADDR(&(lwip_netif.ip_addr),lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);
			IP4_ADDR(&(lwip_netif.netmask),lwipdev.netmask[0],lwipdev.netmask[1],lwipdev.netmask[2],lwipdev.netmask[3]);
			IP4_ADDR(&(lwip_netif.gw),lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]);
			printf("DHCP服务超时,使用静态IP地址!\r\n");
			printf("网卡en的MAC地址为:................%d.%d.%d.%d.%d.%d\r\n",lwipdev.mac[0],lwipdev.mac[1],lwipdev.mac[2],lwipdev.mac[3],lwipdev.mac[4],lwipdev.mac[5]);
			printf("静态IP地址........................%d.%d.%d.%d\r\n",lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);
			printf("子网掩码..........................%d.%d.%d.%d\r\n",lwipdev.netmask[0],lwipdev.netmask[1],lwipdev.netmask[2],lwipdev.netmask[3]);
			printf("默认网关..........................%d.%d.%d.%d\r\n",lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]);
			break;
		}  
		delay_ms(250); //延时250ms
	}
	lwip_comm_dhcp_delete();//删除DHCP任务 
}
#endif 

在 lwip_comm.c 中有一下 7 个函数:

①lwip_comm_mem_malloc()函数完成了对 mem.c 和memp.c 中内存堆 ram_heap 和内存池 memp_memory 的内存分配
②lwip_comm_mem_free()函数用来释放内存堆 ram_heap 和内存池 memp_memory 的内存
③lwip_comm_default_ip_set()函数用来设置默认地址
④lwip_comm_init 函数主要完成 LWIP 内核初始化、设置默认网卡并且打开指定的网卡
⑤在中断服务函数中我们调用了lwip_pkt_handle()函数来接收数据
⑥lwip_periodic_handle()函数就可以完成对 LWIP 内核的定时处理函数的周期性调用
⑦lwip_dhcp_process_handle()函数为 DHCP 处理函数

2.6 测试函数编写

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "lwip_comm.h"
#include "LAN8720.h"
#include "usmart.h"
#include "timer.h"
#include "lcd.h"
#include "sram.h"
#include "malloc.h"
#include "lwip_comm.h"
#include "includes.h"
#include "lwipopts.h"


//在LCD上显示地址信息任务
//任务优先级
#define DISPLAY_TASK_PRIO	8
//任务堆栈大小
#define DISPLAY_STK_SIZE	128
//任务堆栈
OS_STK	DISPLAY_TASK_STK[DISPLAY_STK_SIZE];
//任务函数
void display_task(void *pdata);


//LED任务
//任务优先级
#define LED_TASK_PRIO		9
//任务堆栈大小
#define LED_STK_SIZE		64
//任务堆栈
OS_STK	LED_TASK_STK[LED_STK_SIZE];
//任务函数
void led_task(void *pdata);  


//START任务
//任务优先级
#define START_TASK_PRIO		10
//任务堆栈大小
#define START_STK_SIZE		128
//任务堆栈
OS_STK START_TASK_STK[START_STK_SIZE];
//任务函数
void start_task(void *pdata); 

//在LCD上显示地址信息
//mode:1 显示DHCP获取到的地址
//	  其他 显示静态地址
void show_address(u8 mode)
{
	u8 buf[30];
	if(mode==2)
	{
		sprintf((char*)buf,"DHCP IP :%d.%d.%d.%d",lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);						//打印动态IP地址
		LCD_ShowString(30,130,210,16,16,buf); 
		sprintf((char*)buf,"DHCP GW :%d.%d.%d.%d",lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]);	//打印网关地址
		LCD_ShowString(30,150,210,16,16,buf); 
		sprintf((char*)buf,"NET MASK:%d.%d.%d.%d",lwipdev.netmask[0],lwipdev.netmask[1],lwipdev.netmask[2],lwipdev.netmask[3]);	//打印子网掩码地址
		LCD_ShowString(30,170,210,16,16,buf); 
	}
	else 
	{
		sprintf((char*)buf,"Static IP:%d.%d.%d.%d",lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);						//打印动态IP地址
		LCD_ShowString(30,130,210,16,16,buf); 
		sprintf((char*)buf,"Static GW:%d.%d.%d.%d",lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]);	//打印网关地址
		LCD_ShowString(30,150,210,16,16,buf); 
		sprintf((char*)buf,"NET MASK :%d.%d.%d.%d",lwipdev.netmask[0],lwipdev.netmask[1],lwipdev.netmask[2],lwipdev.netmask[3]);	//打印子网掩码地址
		LCD_ShowString(30,170,210,16,16,buf); 
	}	
}

int main(void)
{
	delay_init(168);       	//延时初始化
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	//中断分组配置
	uart_init(115200);    	//串口波特率设置
	usmart_dev.init(84); 	//初始化USMART
	LED_Init();  			//LED初始化
	KEY_Init();  			//按键初始化
	LCD_Init();  			//LCD初始化
	
	FSMC_SRAM_Init();		//SRAM初始化
	
	mymem_init(SRAMIN);  	//初始化内部内存池
	mymem_init(SRAMEX);  	//初始化外部内存池
	mymem_init(SRAMCCM); 	//初始化CCM内存池
	
	POINT_COLOR = RED; 		//红色字体
	LCD_ShowString(30,30,200,20,16,"Explorer STM32F4");
	LCD_ShowString(30,50,200,20,16,"LWIP+UCOS Test");
	LCD_ShowString(30,70,200,20,16,"ATOM@ALIENTEK");
	LCD_ShowString(30,90,200,20,16,"2014/9/1");

	OSInit(); 					//UCOS初始化
	while(lwip_comm_init()) 	//lwip初始化
	{
		LCD_ShowString(30,110,200,20,16,"Lwip Init failed!"); 	//lwip初始化失败
		delay_ms(500);
		LCD_Fill(30,110,230,150,WHITE);
		delay_ms(500);
	}
	LCD_ShowString(30,110,200,20,16,"Lwip Init Success!"); 		//lwip初始化成功
	OSTaskCreate(start_task,(void*)0,(OS_STK*)&START_TASK_STK[START_STK_SIZE-1],START_TASK_PRIO);
	OSStart(); //开启UCOS
}

//start任务
void start_task(void *pdata)
{
	OS_CPU_SR cpu_sr;
	pdata = pdata ;
	
	OSStatInit();  			//初始化统计任务
	OS_ENTER_CRITICAL();  	//关中断
#if LWIP_DHCP
	lwip_comm_dhcp_creat(); //创建DHCP任务
#endif
	
	OSTaskCreate(led_task,(void*)0,(OS_STK*)&LED_TASK_STK[LED_STK_SIZE-1],LED_TASK_PRIO);//创建LED任务
	OSTaskCreate(display_task,(void*)0,(OS_STK*)&DISPLAY_TASK_STK[DISPLAY_STK_SIZE-1],DISPLAY_TASK_PRIO); //显示任务
	OSTaskSuspend(OS_PRIO_SELF); //挂起start_task任务
	OS_EXIT_CRITICAL();  		//开中断
}

//显示地址等信息
void display_task(void *pdata)
{
	while(1)
	{ 
#if LWIP_DHCP									//当开启DHCP的时候
		if(lwipdev.dhcpstatus != 0) 			//开启DHCP
		{
			show_address(lwipdev.dhcpstatus );	//显示地址信息
			OSTaskSuspend(OS_PRIO_SELF); 		//显示完地址信息后挂起自身任务
		}
#else
		show_address(0); 						//显示静态地址
		OSTaskSuspend(OS_PRIO_SELF); 			//显示完地址信息后挂起自身任务
#endif //LWIP_DHCP
		OSTimeDlyHMSM(0,0,0,500);
	}
}

//led任务
void led_task(void *pdata)
{
	while(1)
	{
		LED0 = !LED0;
		OSTimeDlyHMSM(0,0,0,500);  //延时500ms
 	}
}

三、NETCONN接口编程

  使用 NETCONN API 时需要有操作系统的支持

3.1 pbuf结构

  lwIP使用 pbuf 对数据进行发送与接收,灵活的 pbuf 结构体使得数据在不同层之间传输时可以减少内存的开销以及减少内存复制所占用的时间,一切都是为了节约内存,提高数据在不同层之间传递的速度。lwIP 源码中的 pbuf.c 和 pbuf.h 这两个文件就是关于 pbuf 的,pbuf 结构如下源码所示:

struct pbuf {
 	/* pbuf 链表中指向下一个 pbuf 结构 */
 	struct pbuf *next;
 	/* 数据指针,指向该 pbuf 所记录的数据区域 */
 	void *payload;
 	/* 当前 pbuf 及后续所有 pbuf 中所包含的数据总长度 */
 	u16_t tot_len;
 	/* 当前 pbuf 中数据的长度 */
 	u16_t len;
 	/* 当前 pbuf 的类型 */
 	u8_t type;
 	/* 状态位未用到 */
 	u8_t flags;
 	/* 指向该 pbuf 的指针数,即该 pbuf 被引用的次数 */
 	LWIP_PBUF_REF_T ref;
 	/* 对于传入的数据包,它包含输入 netif 的索引 */
	u8_t if_idx;
};

pbuf 结构体具有多个字段,这些字段的作用如下所示:
在这里插入图片描述

3.2 netbuf数据缓冲区

  在前面我们讲过了描述数据包的 pbuf 结构,netbuf 是 NETCONN 编程 API 接口使用的描述数据包的结构,我们可以使用 netbuf 来管理发送数据、接收数据的缓冲区。有关 netbuf 的详细描述在netbuf.c 和 netbuf.h 这两个文件中,netbuf 是一个结构体,它在 netbuf.h 中定义了这个结构体,代码如下:

/* 网络缓冲区包含数据和寻址信息 */
struct netbuf {
	/* p 字段的指针指向 pbuf 链表 */
 	struct pbuf *p, *ptr;
 	ip_addr_t addr; /* 记录了数据发送方的 IP 地址 */
 	u16_t port; /* 记录了数据发送方的端口号 */
};

  其中 p 和 ptr 都指向 pbuf 链表,不同的是 p 一直指向 pbuf 链表的第一个 pbuf 结构,而 ptr 可能指向链表中其他的位置,lwIP 提供的 netbuf_next 和 netbuf_first 函数都是操作 ptr 字段的。addr 和 port 字段用来记录数据发送方的 IP 地址和端口号,lwIP 提供的netbuf_fromaddr 和 netbuf_fromport 函数用于返回 addr 和 port 这两个字段。netbuf和pbuf之间的关系如图所示:
在这里插入图片描述
  不管是 TCP 连接还是 UDP 连接,接收到数据包后会将数据封装在一个 netbuf 中,然后将这个 netbuf 交给应用程序去处理。在数据发送时,根据不同的连接有不同的处理:对于 TCP连接,用户只需要提供待发送数据的起始地址和长度,内核会根据实际情况将数据封装在合适大小的数据包中,并放入发送队列中,对于 UDP 来说,用户需要自行将数据封装在 netbuf 结构中,当发送函数被调用的时候内核直接将数据包中的数据发送出去。
  在 netbuf.c 中提供了几个操作 netbuf 的函数,如下表所示:
在这里插入图片描述
注意:用户使用 NETCONN 编程 API 接口时必须在 lwipopts.h 把 LWIP_NETCONN 配置项设置为 1 启动 NETCONN 编程 API 接口。

3.3 netconn连接结构

  NETCONN 对于UDP 和 TCP这两种连接提供了统一的编程接口,用于使用同一的连接结构和编程函数,在 api.h 中定了 netcon 结构体,代码如下:

/* netconn 描述符 */
struct netconn {
 	/* 连接类型,TCP UDP 或者 RAW */
 	enum netconn_type type;
 	/* 当前连接状态 */
 	enum netconn_state state;
 	/* 内核中与连接相关的控制块指针 */
 	union {
 		struct ip_pcb *ip; /* IP 控制块 */
 		struct tcp_pcb *tcp; /* TCP 控制块 */
 		struct udp_pcb *udp; /* UDP 控制块 */
 		struct raw_pcb *raw; /* RAW 控制块 */
	} pcb;
	/* 这个 netconn 最后一个异步未报告的错误 */
 	err_t pending_err;
#if !LWIP_NETCONN_SEM_PER_THREAD
 	/* 用于两部分 API 同步的信号量 */
 	sys_sem_t op_completed;
#endif
 	/* 接收数据的邮箱 */
 	sys_mbox_t recvmbox;
#if LWIP_TCP
 	/* 用于 TCP 服务器端,连接请求的缓冲队列*/
 	sys_mbox_t acceptmbox;
#endif /* LWIP_TCP */
 	/* Socket 描述符,用于 Socket API */
#if LWIP_SOCKET
 int Socket;
#endif /* LWIP_SOCKET */
#if LWIP_SO_RCVTIMEO
 	/* 接收数据时的超时时间*/
 	u32_t recv_timeout;
#endif /* LWIP_SO_RCVTIMEO */
 	/* 标识符 */
 	u8_t flags;
#if LWIP_TCP
 /* TCP:当传递到 netconn_write 的数据不适合发送缓冲区时,
 	这将临时存储消息。
 	也用于连接和关闭。 */
 	struct api_msg *current_msg;
#endif /* LWIP_TCP */
 	/* 连接相关回调函数,实现 Socket API 时使用 */
 	netconn_callback callback;
};

  在 api.h 文件中还定义了连接状态和连接类型,这两个都是枚举类型

/* 枚举类型,用于描述连接类型 */
enum netconn_type {
 	NETCONN_INVALID = 0, /* 无效类型 */
 	NETCONN_TCP = 0x10, /* TCP */
 	NETCONN_UDP = 0x20, /* UDP */
 	NETCONN_UDPLITE = 0x21, /* UDPLite */
 	NETCONN_UDPNOCHKSUM = 0x22, /* 无校验 UDP */
 	NETCONN_RAW = 0x40 /* 原始链接 */
};
/* 枚举类型,用于描述连接状态,主要用于 TCP 连接中 */
enum netconn_state
{
 	NETCONN_NONE, /* 不处于任何状态 */
 	NETCONN_WRITE, /* 正在发送数据 */
 	NETCONN_LISTEN, /* 侦听状态 */
 	NETCONN_CONNECT, /* 连接状态 */
 	NETCONN_CLOSE /* 关闭状态 */
};

3.4 netconn编程API函数

  本节我们就讲解一下 NETCONN 编程的 API 函数,这些函数在 api_lib.c 文件中实现的,其中这个文件包含了很多 netconn 接口的函数,它们大部分是 lwIP 内部调用的,少部分是给用户使用的,用户能使用的函数如下表所示:

函数 描述
netconn_new() 这个函数其实就是一个宏定义,用来为新连接申请一个连接结构netconn 空间。
netconn_delete() 该函数的功能是删除一个 netconn 连接结构。
netconn_getaddr() 该函数用来获取一个 netconn 连接结构的源 IP 地址和源端口号或者目的 IP 地址和目的端口号。
netconn_bind() 将一个连接结构与本地 IP 地址和端口号进行绑定。
netconn_connect() 将一个连接结构与目的 IP 地址和端口号进行绑定。
netconn_disconnect() 只能用在 UDP 连接结构中,用来断开与服务器的连接。
netconn_listen() 这个函数就是一个宏定义,只在 TCP 服务器程序中使用,用来将连接结构 netconn 置为侦听状态。
netconn_accept() 这个函数也只用与 TCP 服务器程序中,服务器调用此函数可以获得一个新的连接。
netconn_recv() 从连接的 recvmbox 邮箱中接收数据包,可用于 TCP 连接,也可用于UDP 连接。
netconn_send() 用于在已建立的 UDP 连接上发送数据。
netconn_write() 用于在稳定的 TCP 连接上发送数据。
netconn_close() 关闭一个 TCP 连接。
  1. netconn_new()函数
      netconn_new()函数是函数 netconn_new_with_proto_and_callback()的宏定义,此函数用来为新连接申请一个 netconn 空间,参数为新连接的类型,连接类型在上一节已经讲过了,常用的值是 NETCONN_UDP 和 NETCONN_TCP,分别代表 UDP 连接和 TCP 连接。
      系统对 conn 申请内存,如果内存申请成功,则系统构建 API 消息。所谓API 消息,其实就是两个 API 部分的交互的消息,它是由用户的调用 API 函数为起点,使用IPC 通信机制告诉内核需要执行那个部分的 API 函数,netconn_new 函数交互流程如下图所示:
    在这里插入图片描述

  2. netconn_delete()函数
      netconn_delete()函数用来删除一个 netconn 连接结构,如果函数调用时双方仍然处于连接状态,则相应连接将被关闭:对于 UDP 连接,连接立即被关闭,UDP 控制块被删除;对于 TCP连接,则函数执行主动关闭,内核完成剩余的断开握手过程。

  3. netconn_getaddr()函数
      netconn_getaddr()函数用来获取一个 netconn 连接结构的源 IP 地址和源端口号或者目的 IP地址和目的端口号,IP 地址保存在 addr 中,端口信息保存在 port 中,参数 local 指明是获取源地址还是目的地址,当 local 为 1 时表示本地地址,此函数原型如下

err_t netconn_getaddr(struct netconn *conn, ip_addr_t *addr,u16_t *port, u8_t local);
  1. netconn_bind()函数
      netconn_bind()函数将一个连接结构与本地 IP 地址 addr 和端口号 port 进行绑定,服务器端程序必须执行这一步,服务器必须与指定的端口号绑定才能结接受客户端的连接请求,函数原型如下
err_t netconn_bind(struct netconn *conn, ip_addr_t *addr, u16_t port);
  1. netconn_connect()函数
      netconn_connect()函数的功能是连接服务器,将指定的连接结构与目的 IP 地址 addr 和目的端口号 port 进行绑定,当作为 TCP 客户端程序时,调用此函数会产生握手过程,函数原型如下
err_t netconn_connect(struct netconn *conn, ip_addr_t *addr, u16_t port);
  1. netconn_disconnect()函数
      netconn_disconnect()函数只能使用在 UDP 连接中,功能是断开与服务器的连接。对于 UDP连接来说就是将 UDP 控制块中的 remote_ip 和 remote_port 字段值清零,函数原型如下
err_t netconn_disconnect (struct netconn *conn);
  1. netconn_listen()函数
      netconn_listen()函数只有在 TCP 服务器程序中使用,将一个连接结构 netconn 设置为侦听状态,既将 TCP 控制块的状态设置为 LISTEN 状态。
  2. netconn_accept()函数
      netconn_accept()函数也只用于 TCP 服务器程序,服务器调用此函数可以从 acceptmbox 邮箱中获取一个新建立的连接,若邮箱为空,则函数会一直阻塞,直到新连接的到来。服务器端调用此函数前必须先调用 netconn_listen()函数将连接设置为侦听状态,函数原型如下
err_t netconn_accept(struct netconn *conn, struct netconn **new_conn);
  1. netconn_recv()函数
       netconn_recv()函数是从连接的 recvmbox 邮箱中接收数据包,可用于 TDP 连接,也可用于UDP 连接,函数会一直阻塞,直到从邮箱中获得数据消息,数据被封装在 netbuf 中。如果从邮箱中接收到一条空消息,表示对方已经关闭当前的连接,应用程序也应该关闭这个无效的连接,函数原型如下
err_t netconn_recv(struct netconn *conn, struct netbuf **new_buf);
  1. netconn_send()函数
      netconn_send()函数用于在 UDP 连接上发送数据,参数 conn 指出了要操作的连接,参数 buf为要发送的数据,数据被封装在 netbuf 中。如果 IP 层分片功能未使能,则 netbuf 中的数据不能太长,不能超过 MTU 的值,最好不要超过 1000 字节。如果 IP 层分片功能使能的情况下就可以忽略此细节,函数原型如下:
err_t netconn_send(struct netconn *conn, struct netbuf *buf);
  1. netconn_write()函数
      netconn_write()函数用于在稳定的 TCP 连接上发送数据,参数 dataptr 和 size 分别指出了待发送数据的起始地址和长度,函数并不要求用户将数据封装在 netbuf 中,对于数据长度也没有限制,内核会直接处理这些数据,将他们封装在 pbuf 中,并挂接到 TCP 的发送队列中
  2. netconn_close()函数
      netconn_close()函数用来关闭一个 TCP 连接,该函数会产生一个 FIN 握手包的发送,成功后函数便返回,而后剩余的断开握手操作由内核自动完成,用户程序不用关心,该函数只是断开一个连接,但不会删除连接结构 netconn,用户需要调用 netconn_delete()函数来删除连接结构,否则会造成内存泄漏,函数原型如下
err_t netconn_close(struct netconn *conn);

四、Socket接口编程

  lwIP设计了三种应用程序编程接口:RAW 编程接口、NETCONN编程接口和Socket 编程接口。RAW编程接口只能在无操作系统环境下运行,NETCONN编程接口在上面链接里有介绍。而Socket编程接口是由NETCONN编程接口封装而成,下面简单介绍一下lwIP Socket编程接口常用的API函数。

  1. socket函数
#define socket(domain,type,protocol) lwip_socket(domain,type,protocol)

  该函数的原型,如上源码所示。向内核申请一个套接字,本质上该函数调用了函数 lwip_socket,该函数的参数如下表所示
在这里插入图片描述

  1. bind函数
#define bind(s,name,namelen) lwip_bind(s,name,namelen)
int bind(int s, const struct sockaddr *name, socklen_t namelen)

  如上源码所示,该函数与netconn_bind函数一样,用于服务器端绑定套接字与网卡信息,本质上就是对函数netconn_bind再一次封装,从上述源码可以知道参数

  • name指向一个sockaddr结构体,它包含了本地IP地址和端口号等信息;
  • namelen指出结构体的长度。

  结构体sockaddr 定义如下源码所示:

struct sockaddr {
	u8_t sa_len; /* 长度 */
	sa_family_t sa_family; /* 协议簇 */
	char sa_data[14]; /* 连续的 14 字节信息 */
};
struct sockaddr_in {
	u8_t sin_len; /* 长度 */
	u8_t sin_family; /* 协议簇 */
	u16_t sin_port; /* 端口号 */
	struct in_addr sin_addr; /* IP 地址 */
	char sin_zero[8];
};

  可以看出,lwIP 作者定义了两个结构体,结构体sockaddr中的 sa_family 指向该套接字所使用的协议簇,本地 IP 地址和端口号等信息在 sa_data 数组里面定义,这里暂未用到。由于sa_data 以连续空间的方式存在,所以用户要填写其中的 IP 字段和端口 port 字段,这样会比较麻烦,因此 lwIP 定义了另一个结构体sockaddr_in,它与 sockaddr 结构对等,只是从中抽出 IP地址和端口号 port,方便于用于的编程操作。

  1. connect函数
#define connect(s,name,namelen) lwip_connect(s,name,namelen)
int lwip_connect(int s, const struct sockaddr *name, socklen_t namelen);

  该函数与netconn接口的netconn_connect函数作用是一样的,因此它是被 netconn_connect函数封装了,该函数的作用是将Socket与远程IP地址和端口号绑定,如果开发板作为客户端,通常使用这个函数来绑定服务器的IP地址和端口号,对于TCP连接,调用这个函数会使客户端与服务器之间发生连接握手过程,并建立稳定的连接;如果是UDP连接,该函数调用不会有任何数据包被发送,只是在连接结构中记录下服务器的地址信息。当调用成功时,函数返回0;否则返回-1。

  1. listen函数
#define listen(s,backlog) lwip_listen(s,backlog)
int lwip_listen(int s, int backlog);

  该函数和netconn的函数netconn_listen作用一样,它是由函数netconn_listen封装得来的,内核同时接收到多个连接请求时,需要对这些请求进行排队处理,参数backlog指明了该套接字上连接请求队列的最大长度。当调用成功时,函数返回0;否则返回-1。该函数作用于TCP服务器程序

  1. accept函数
#define accept(s,addr,addrlen) lwip_accept(s,addr,addrlen)
int lwip_accept(int s, struct sockaddr *addr, socklen_t *addrlen);

  该函数与netconn_accept作用是一样的,当接收到新连接后,连接另一端(客户端)的地址信息会被填入到地址结构addr中,而对应地址信息的长度被记录到addrlen中。函数返回新连接的套接字描述符,若调用失败,函数返回-1。该函数作用于TCP服务器程序

  1. send()/sendto()函数
#define send(s,dataptr,size,flags) lwip_send(s,dataptr,size,flags)
#define sendto(s,dataptr,size,flags,to,tolen) lwip_sendto(s,dataptr,size,flags,to,tolen)
ssize_t lwip_send(int s, const void *dataptr, size_t size, int flags);
ssize_t lwip_sendto(int s, const void *dataptr, size_t size, int flags, 
					const struct sockaddr *to, socklen_t tolen);

  该函数是被netconn_send封装的,其作用是向另一端发送 UDP 报文,这两个函数的原型如上源码所示。可以看出,函数sendto比函数send多了两个参数,该函数如下表所示:
在这里插入图片描述

  1. write函数
      该函数用于在一条已经建立的连接上发送数据,通常使用在 TCP 程序中,但在 UDP 程序中也能使用。该函数本质上是基于前面介绍的 send 函数来实现的,其参数的意义与 send 也相同。当函数调用成功时,返回成功发送的字节数;否则返回-1。

  2. read()/recv()/recvfrom()函数

#define recv(s,mem,len,flags) lwip_recv(s,mem,len,flags)
#define recvfrom(s,mem,len,flags,from,fromlen) lwip_recvfrom(s,mem,len,flags,from,fromlen)
ssize_t lwip_readv(int s, const struct iovec *iov, int iovcnt);
ssize_t lwip_recvfrom(int s, void *mem, size_t len, int flags,
						struct sockaddr *from, socklen_t *fromlen);
#define read(s,mem,len) lwip_read(s,mem,len)
ssize_t lwip_read(int s, void *mem, size_t len);

  函数recvfrom和recv用来从一个套接字中接收数据,该函数可以在UDP程序使用,也可在TCP程序中使用。该函数本质上是被函数netconn_recv的封装,其参数与函数sendto的参数完全相似,如下表所示,数据发送方的地址信息会被填写到from中,fromlen指明了缓存from的长度;mem和len分别记录了接收数据的缓存起始地址和缓存长度,flags指明用户控制接收的方式,通常设置为0。两个函数的原型如上源码所示
在这里插入图片描述

  1. close函数
#define close(s) lwip_close(s)
int lwip_close(int s);

  函数close作用是关闭套接字,对应的套接字描述符不再有效,与描述符对应的内核结构lwip_socket 也将被全部复位。该函数本质上是被 netconn_delete 的封装,对于 TCP 连接来说,该函数将导致断开握手过程的发生。若调用成功,该函数返回 0;否则返回-1。该函数的原型如上源码所示

  socket建立连接流程:
在这里插入图片描述

相关推荐

  1. lwip问题

    2024-06-15 05:38:03       13 阅读
  2. TCP/IP LWIP FPGA 笔记

    2024-06-15 05:38:03       29 阅读
  3. Lwip之TCP客户端示例记录

    2024-06-15 05:38:03       19 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-15 05:38:03       10 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-15 05:38:03       12 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-15 05:38:03       11 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-15 05:38:03       13 阅读

热门阅读

  1. 以太坊私有链 —— POW搭建

    2024-06-15 05:38:03       5 阅读
  2. 6.11 c语言

    2024-06-15 05:38:03       3 阅读
  3. DP专项训练

    2024-06-15 05:38:03       4 阅读
  4. uniapp面试题

    2024-06-15 05:38:03       5 阅读
  5. 【lesson3】服务端Json工具类的设计和实现

    2024-06-15 05:38:03       8 阅读
  6. 力扣475.供暖器

    2024-06-15 05:38:03       5 阅读
  7. 图片based64编码解码python代码

    2024-06-15 05:38:03       5 阅读