基于FreeRTOS的LWIP移植

目录

  • 前言
  • 一、移植准备工作
  • 二、以太网固件库与驱动
    • [2.1 固件库文件添加](#2.1 固件库文件添加)
    • [2.2 库文件修改](#2.2 库文件修改)
    • [2.3 添加网卡驱动](#2.3 添加网卡驱动)
  • [三、LWIP 数据包和网络接口管理](#三、LWIP 数据包和网络接口管理)
    • [3.1 添加LWIP源文件](#3.1 添加LWIP源文件)
    • [3.2 Lwip文件修改](#3.2 Lwip文件修改)
      • [3.2.1 修改cc.h](#3.2.1 修改cc.h)
      • [3.2.2 修改lwipopts.h](#3.2.2 修改lwipopts.h)
      • [3.2.3 修改icmp.c](#3.2.3 修改icmp.c)
      • [3.2.4 修改sys_arch.h和sys_arch.c](#3.2.4 修改sys_arch.h和sys_arch.c)
      • [3.2.5 修改ethernetif.h和ethernetif.c](#3.2.5 修改ethernetif.h和ethernetif.c)
  • 四、添加应用程序
    • [4.1 添加Lwip应用](#4.1 添加Lwip应用)
    • [4.2 添加FreeRTOS应用](#4.2 添加FreeRTOS应用)

前言

介绍一下基于FreeRTOS的LWIP移植,平台为STM32F407标准库的Keil环境。LWIP的相关介绍见以下链接:https://blog.csdn.net/weixin_44567668/article/details/139619797

一、移植准备工作

移植文件需要LWIP源码FreeRTOS标准库工程

  1. FreeRTOS标准库工程

    移植需要一个已经移植好FreeRTOS的STM32标准库工程,大概如下图所示

    FreeRTOS的移植参考以下链接:https://blog.csdn.net/weixin_44567668/article/details/135419275

  2. Lwip源码

    Lwip源码可以从官网获取,LWIP官网链接:http://savannah.nongnu.org/projects/lwip/

    点击Download Area 就可以下载源码,我们选择contrib-1.4.1.ziplwip-1.4.1.zip ,不用最新版本是因为标准库缺少对应最新版本的以太网句柄ETH_HandleTypeDef,如果是HAL库版本可以用最新版本的Lwip源码

    此时由于一些配置和移植需要,还要下载一下ST官方移植示例,ST官方参考工程链接:https://www.st.com/en/embedded-software/stsw-stm32070.html

二、以太网固件库与驱动

2.1 固件库文件添加

从ST例程里找到以太网库STM32F4x7_ETH_Driver ,并将其复制到我们工程文件里

然后在keil工程里添加进来stm32f4x7_eth.cstm32f4xx_syscfg.c

2.2 库文件修改

  1. stm32f4x7_eth_conf.h的修改
      在STM32F4x7_ETH_Driver\inc 很容易找到stm32f4x7_eth_conf_template.h,里面定义了一些关于操作PHY芯片的信息,有关PHY芯片LAN8720的相关介绍见前言链接。此时我们将stm32f4x7_eth_conf_template.h重命名为stm32f4x7_eth_conf.h,然后将里面改成LAN8720对应的配置
c 复制代码
#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 
  1. stm32f4x7_eth.c的修改
      在 stm32f4x7_eth.c 文件中针对不同的平台定义了四个数组:Rx_Buff[]、Tx_Buff[]、DMARxDscrTab[]和 DMATxDscrTab[],这四个数组占用了大量的 RAM。我们在这里将这四个变量屏蔽掉,如图所示

2.3 添加网卡驱动

网卡驱动主要是对以太网的一些配置,主要是一些初始化工作,这里新建两个文件为ethernet.hethernet.c

  1. ethernet.h头文件
c 复制代码
#ifndef __LAN8720_H
#define __LAN8720_H

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


/* MAC ADDRESS*/
#define MAC_ADDR0   02
#define MAC_ADDR1   00
#define MAC_ADDR2   00
#define MAC_ADDR3   00
#define MAC_ADDR4   00
#define MAC_ADDR5   00
 
/*Static IP ADDRESS*/
#define IP_ADDR0   192
#define IP_ADDR1   168
#define IP_ADDR2   0
#define IP_ADDR3   10
   
/*NETMASK*/
#define NETMASK_ADDR0   255
#define NETMASK_ADDR1   255
#define NETMASK_ADDR2   255
#define NETMASK_ADDR3   0

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


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 ethernet_chip_get_speed(void);
u8 ETH_MACDMA_Config(void);
FrameTypeDef ETH_Rx_Packet(void);
u8 ETH_Tx_Packet(u16 FrameLength);

uint8_t ethernet_mem_malloc(void);
void ETH_Mem_Free(void);
#endif 
  1. ethernet.c
      在ethernet.c中下面几个函数,如表所示。
函数 说明
LAN8720_Init() LAN8720 初始化函数
ETHERNET_NVICConfiguration() 以太网 DMA 中断优先级配置
ethernet_chip_get_speed() 获取当前连接速度和双工状态
ETH_MACDMA_Config() 以太网 MAC 和 DMA 配置函数
ETH_IRQHandler() 以太网 DMA 接收中断服务函数
ETH_Rx_Packet() 从网卡中接收数据包
ETH_Tx_Packet() 从网卡发送数据包
ETH_Mem_Malloc() Rx_Buff[]、Tx_Buff[]、DMARxDscrTab[]和DMATxDscrTab[]这四个数组申请内存
ETH_Mem_Free() 释 放 Rx_Buff[] 、Tx_Buff[] 、DMARxDscrTab[]和DMATxDscrTab[]这四个数组的内存

其中ETH_Rx_Packet()ETH_Tx_Packet()主要替代库函数ETH_Prepare_Transmit_Descriptors()ETH_Get_Received_Frame_interrupt(),同样是为了减小内存开销。全部代码如下

c 复制代码
#include "ethernet.h"
#include "stm32f4x7_eth.h"
#include "usart.h" 
#include "delay.h"
#include "malloc.h" 
#include "FreeRTOS.h"
#include "semphr.h"
#include "task.h"

ETH_DMADESCTypeDef *DMARxDscrTab;	//以太网DMA接收描述符数据结构体指针
ETH_DMADESCTypeDef *DMATxDscrTab;	//以太网DMA发送描述符数据结构体指针 
uint8_t *Rx_Buff; 					//以太网底层驱动接收buffers指针 
uint8_t *Tx_Buff; 					//以太网底层驱动发送buffers指针


extern xSemaphoreHandle s_xSemaphore;
  
static void ETHERNET_NVICConfiguration(void);

//------------------------------- BSP -------------------------------------//
//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 = 0X06;  //中断寄存器组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 ethernet_chip_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; 
	
	//使能以太网时钟
	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;
}


//以太网中断服务函数
void ETH_IRQHandler(void)
{
	portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
	
//	if ( ETH_GetDMAFlagStatus(ETH_DMA_FLAG_R) == SET) 
	if(ETH_GetRxPktSize(DMARxDescToGet)!=0) 	//检测是否收到数据包
	{ 
		xSemaphoreGiveFromISR( s_xSemaphore, &xHigherPriorityTaskWoken );   	
	}
	
	ETH_DMAClearITPendingBit(ETH_DMA_IT_R);
	ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS);
	
	/* Switch tasks if necessary. */	
  if( xHigherPriorityTaskWoken != pdFALSE )
  {
    portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
  }
}  

//----------------------------- 库函数重定义 ----------------------------------//
//接收一个网卡数据包
//返回值:网络数据包帧结构体
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;   
}

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


//为ETH底层驱动申请内存
//返回值:0,正常
//    其他,失败
uint8_t 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;		//申请成功
}

此时编译会有一个报错,提示未定义变量s_xSemaphore,这是后面Lwip移植ethernetif.c时定义的一个信号量,这时候可以不用管,也可以先屏蔽

三、LWIP 数据包和网络接口管理

3.1 添加LWIP源文件

在工程目录下新建文件夹LWIP ,然后把下载的lwip-1.4.1\src 文件夹复制进去,然后再添加arch文件夹

然后我们需要将中间文件添加到arch 文件夹,总共5个文件cc.hlwipopts.hperf.hsys_arch.hsys_arch.c。这些都可以在ST参考例程STM32F4x7_ETH_LwIP_V1.1.0 里找到,由于都需要修改,建议直接复制附件工程或者在Keil里新建

然后将src\api 文件夹里所有文件,src\core 里的c文件和ipv4 里的所有文件,以及src\netif 里的ethernetif.cetharp.c添加到Keil工程


以及sys_arch.c,添加进来如下

最后将头文件加进来

3.2 Lwip文件修改

3.2.1 修改cc.h

cc.h 主要完成了协议栈内部使用的数据类型的定义,如果使用操作系统的话还有临界代码区保护等等

c 复制代码
#ifndef __CC_H__
#define __CC_H__

//#include "cpu.h"
#include "stdio.h"

/*-------------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)-------*/

#define BYTE_ORDER LITTLE_ENDIAN  //小端模式

#if OS_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 OS_CRITICAL_METHOD == 3  //method 3 is used in this port.
#define SYS_ARCH_DECL_PROTECT(lev)	u32_t lev
#define SYS_ARCH_PROTECT(lev)		lev = OS_CPU_SR_Save()
#define SYS_ARCH_UNPROTECT(lev)		OS_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__ */

3.2.2 修改lwipopts.h

在 LWIP 的源码中有个opt.h的文件,这个文件是裁剪和配置 LWIP 的,不过我们最好不要直接在 opt.h 里面做修改,而是在lwipopts.h里进行来裁剪与配置

c 复制代码
#ifndef __LWIPOPTS_H__
#define __LWIPOPTS_H__

#define SYS_LIGHTWEIGHT_PROT    1

//NO_SYS==1:不使用操作系统
#define NO_SYS                  0  //不使用操作系统

//使用4字节对齐模式
#define MEM_ALIGNMENT           4  

//MEM_SIZE:heap内存的大小,如果在应用中有大量数据发送的话这个值最好设置大一点 
#define MEM_SIZE                16000 //内存堆大小

//MEMP_NUM_PBUF:memp结构的pbuf数量,如果应用从ROM或者静态存储区发送大量数据时,这个值应该设置大一点
#define MEMP_NUM_PBUF           10

//MEMP_NUM_UDP_PCB:UDP协议控制块(PCB)数量.每个活动的UDP"连接"需要一个PCB.
#define MEMP_NUM_UDP_PCB        6

//MEMP_NUM_TCP_PCB:同时建立激活的TCP数量
#define MEMP_NUM_TCP_PCB        10

//MEMP_NUM_TCP_PCB_LISTEN:能够监听的TCP连接数量
#define MEMP_NUM_TCP_PCB_LISTEN 6

//MEMP_NUM_TCP_SEG:最多同时在队列中的TCP段数量
#define MEMP_NUM_TCP_SEG        15

//MEMP_NUM_SYS_TIMEOUT:能够同时激活的timeout个数
#define MEMP_NUM_SYS_TIMEOUT    8


/* ---------- Pbuf选项---------- */
//PBUF_POOL_SIZE:pbuf内存池个数. 
#define PBUF_POOL_SIZE          20

//PBUF_POOL_BUFSIZE:每个pbuf内存池大小. 
#define PBUF_POOL_BUFSIZE       512


/* ---------- TCP选项---------- */
#define LWIP_TCP                1  //为1是使用TCP
#define TCP_TTL                 255//生存时间

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

//最大TCP分段
#define TCP_MSS                 (1500 - 40)	  //TCP_MSS = (MTU - IP报头大小 - TCP报头大小

//TCP发送缓冲区大小(bytes).
#define TCP_SND_BUF             (4*TCP_MSS)

//TCP_SND_QUEUELEN: TCP发送缓冲区大小(pbuf).这个值最小为(2 * TCP_SND_BUF/TCP_MSS) 
#define TCP_SND_QUEUELEN        (2* TCP_SND_BUF/TCP_MSS)

//TCP发送窗口
#define TCP_WND                 (2*TCP_MSS)


/* ---------- ICMP选项---------- */
#define LWIP_ICMP                 1 //使用ICMP协议

/* ---------- DHCP选项---------- */
//当使用DHCP时此位应该为1,LwIP 0.5.1版本中没有DHCP服务.
#define LWIP_DHCP               1

/* ---------- UDP选项 ---------- */ 
#define LWIP_UDP                1 //使用UDP服务
#define UDP_TTL                 255 //UDP数据包生存时间


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


/* ---------- 链接回调选项 ---------- */
/* WIP_NETIF_LINK_CALLBACK==1:支持来自接口的回调函数
   每当链接改变(例如,向下链接)
 */
#define LWIP_NETIF_LINK_CALLBACK        0


//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


/*
   ----------------------------------------------
   ---------- SequentialAPI选项----------
   ----------------------------------------------
*/

//LWIP_NETCONN==1:使能NETCON函数(要求使用api_lib.c)
#define LWIP_NETCONN                    1

/*
   ------------------------------------
   ---------- Socket API选项----------
   ------------------------------------
*/
//LWIP_SOCKET==1:使能Socket API(要求使用sockets.c)
#define LWIP_SOCKET                     1

#define LWIP_COMPAT_MUTEX               1

#define LWIP_SO_RCVTIMEO                1 //通过定义LWIP_SO_RCVTIMEO使能netconn结构体中recv_timeout,使用recv_timeout可以避免阻塞线程


/*
   ----------------------------------------
   ---------- Lwip调试选项----------
   ----------------------------------------
*/
//#define LWIP_DEBUG                     1 //开启DEBUG选项

#define ICMP_DEBUG                      LWIP_DBG_OFF //开启/关闭ICMPdebug

/*
   ------------------------------------
   ---------- httpd options ----------
   ------------------------------------
*/
/** Set this to 1 to include "fsdata_custom.c" instead of "fsdata.c" for the
 * file system (to prevent changing the file included in CVS) */
#define HTTPD_USE_CUSTOM_FSDATA         1

#endif /* __LWIPOPTS_H__ */

3.2.3 修改icmp.c

我们需要修改 icmp.c 文档使其支持硬件帧校验,修改部分如图所示。图中红框部分为 icmp.c 的源码,我们将其注销掉,蓝框部分是我们需要添加进去的代码,这部分代码是由 ST提供的。

c 复制代码
#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

3.2.4 修改sys_arch.h和sys_arch.c

这两个文件主要为协议栈提供邮箱、信号量等机制,实际上就是对RTOS的机制进行一次封装

  1. sys_arch.h
c 复制代码
#ifndef __ARCH_SYS_ARCH_H__
#define __ARCH_SYS_ARCH_H__ 

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"

void sys_arch_msleep(uint32_t delay_ms);
#define sys_msleep(ms) sys_arch_msleep(ms)

#define SYS_MBOX_NULL (xQueueHandle)0
#define SYS_SEM_NULL  (xSemaphoreHandle)0

typedef xSemaphoreHandle sys_sem_t;
typedef xSemaphoreHandle sys_mutex_t;
typedef xQueueHandle sys_mbox_t;
typedef xTaskHandle sys_thread_t;

#endif 
  1. sys_arch.c
c 复制代码
#include "lwip/debug.h"
#include "lwip/def.h"
#include "lwip/sys.h"
#include "lwip/mem.h"
#include "lwip/stats.h"
#include "FreeRTOS.h"
#include "task.h"


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

//static u16_t s_nextthread = 0;

/* Initialize this module (see description in sys.h) */
void sys_init(void)
{
	// keep track of how many threads have been created
//  s_nextthread = 0;
}


#if SYS_LIGHTWEIGHT_PROT

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

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

#endif /* SYS_LIGHTWEIGHT_PROT */



void sys_sem_signal(sys_sem_t *sem)
{
  xSemaphoreGive(*sem);
}

err_t sys_sem_new(sys_sem_t *sem, u8_t count)
{
  /* 创建 sem */
  if(count <= 1)
  {    
    *sem = xSemaphoreCreateBinary();
    if(count == 1)
    {
      sys_sem_signal(sem);
    }
		else
		{
			xSemaphoreTake(*sem,1);
		}
  }
  else
    *sem = xSemaphoreCreateCounting(count,count);
	
#if SYS_STATS
	++lwip_stats.sys.sem.used;
 	if (lwip_stats.sys.sem.max < lwip_stats.sys.sem.used) {
		lwip_stats.sys.sem.max = lwip_stats.sys.sem.used;
	}
#endif /* SYS_STATS */
  
  if(*sem != SYS_SEM_NULL)
    return ERR_OK;
  else
  {
#if SYS_STATS
    ++lwip_stats.sys.sem.err;
#endif /* SYS_STATS */
    printf("[sys_arch]:new sem fail!\n");
    return ERR_MEM;
  }
}

u32_t sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout)
{
  BaseType_t ret;
	portTickType StartTime;
	
	StartTime = xTaskGetTickCount();

  if(!timeout) {
    /* wait infinite */
    ret = xSemaphoreTake(*sem, portMAX_DELAY);
  } else {
    ret = xSemaphoreTake(*sem, timeout / portTICK_RATE_MS);
  }
	
	if (ret == pdTRUE)
	{
		return ((xTaskGetTickCount()-StartTime)*portTICK_RATE_MS);
	}
	else
	{
		/* timed out */
		return SYS_ARCH_TIMEOUT;
  }
}

void sys_sem_free(sys_sem_t *sem)
{
#if SYS_STATS
   --lwip_stats.sys.sem.used;
#endif /* SYS_STATS */
  /* 删除 sem */
  vSemaphoreDelete(*sem);
  *sem = SYS_SEM_NULL;
}

int sys_sem_valid(sys_sem_t *sem)                                               
{
  return (*sem != SYS_SEM_NULL);                                    
}

void sys_sem_set_invalid(sys_sem_t *sem)
{
  *sem = SYS_SEM_NULL;
}


err_t sys_mbox_new(sys_mbox_t *mbox, int size)
{
	(void ) size;
	
  *mbox = xQueueCreate((UBaseType_t)size, sizeof(void *));
	
  if(*mbox == NULL) {
    return ERR_MEM;
  }

  return ERR_OK;
}



void sys_mbox_post(sys_mbox_t *mbox, void *msg)
{
  while(xQueueSendToBack(*mbox, &msg, portMAX_DELAY) != pdTRUE);
}


//   Try to post the "msg" to the mailbox.
err_t sys_mbox_trypost(sys_mbox_t *mbox, void *msg)
{
  if(xQueueSend(*mbox,&msg,0) == pdPASS)  
    return ERR_OK;
  else
    return ERR_MEM;
}


u32_t sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout)
{
  BaseType_t ret;
  void *msg_dummy;
	portTickType StartTime;
	
	StartTime = xTaskGetTickCount();

  if (!msg) {
    msg = &msg_dummy;
  }

  if (!timeout) {
    /* wait infinite */
    ret = xQueueReceive(*mbox, &(*msg), portMAX_DELAY);
  } else {
    ret = xQueueReceive(*mbox, &(*msg), timeout / portTICK_RATE_MS);
  }
	
	if (ret == pdTRUE)
	{
		return ((xTaskGetTickCount() - StartTime)*portTICK_PERIOD_MS);
	}
	else
	{
		/* timed out */
		*msg = NULL;
		return SYS_ARCH_TIMEOUT;
	}
}

u32_t sys_arch_mbox_tryfetch(sys_mbox_t *mbox, void **msg)
{
  void *dummyptr;

	if ( msg == NULL )
	{
		msg = &dummyptr;
	}

	if ( pdTRUE == xQueueReceive( *mbox, &(*msg), 0 ) )
	{
		return ERR_OK;
	}
	else
	{
		return SYS_MBOX_EMPTY;
	}
}

void sys_mbox_free(sys_mbox_t *mbox)
{
  if( uxQueueMessagesWaiting( *mbox ) )
	{
		/* Line for breakpoint.  Should never break here! */
		portNOP();
#if SYS_STATS
	    lwip_stats.sys.mbox.err++;
#endif /* SYS_STATS */
			
		// TODO notify the user of failure.
	}

  vQueueDelete(*mbox);

#if SYS_STATS
     --lwip_stats.sys.mbox.used;
#endif /* SYS_STATS */
}

int sys_mbox_valid(sys_mbox_t *mbox)          
{      
  if (*mbox == SYS_MBOX_NULL) 
    return 0;
  else
    return 1;
}   

void sys_mbox_set_invalid(sys_mbox_t *mbox)
{
  *mbox = SYS_MBOX_NULL; 
}

sys_thread_t sys_thread_new(const char *name, lwip_thread_fn thread, void *arg, int stacksize, int prio)
{
  xTaskHandle rtos_task;
  BaseType_t ret;

  ret = xTaskCreate(thread, name, stacksize, arg, prio, &rtos_task);

  if(ret != pdPASS)
	{
		return NULL;
	}
		
  return rtos_task;
}

void sys_arch_msleep(uint32_t delay_ms)
{
  TickType_t delay_ticks = delay_ms / portTICK_RATE_MS;
  vTaskDelay(delay_ticks);
}

//为LWIP提供计时
u32_t sys_now(void){
	return xTaskGetTickCount() * portTICK_PERIOD_MS;
}

函数功能如下表

函数名称 功能
sys_mbox_new 创建消息邮箱
sys_mbox_free 删除一个邮箱
sys_mbox_post 向邮箱投递消息,阻塞
sys_mbox_trypost 尝试向邮箱投递消息,不阻塞
sys_arch_mbox_fetch 获取消息,阻塞
sys_arch_mbox_tryfetch 尝试获取消息,不阻塞
sys_mbox_valid 检查一个邮箱是否有效
sys_mbox_set_invalid 设置一个邮箱无效
sys_sem_new 创建一个信号量
sys_arch_sem_wait 等待一个信号量
sys_sem_signal 释放一个信号量
sys_sem_free 删除一个信号量
sys_sem_valid 查询一个信号量是否有效
sys_sem_set_invalid 设置一个信号量无效
sys_thread_new 创建进程
sys_init 初始化操作系统模拟层
sys_msleep LWIP 延时函数
sys_now 获取当前系统时间

3.2.5 修改ethernetif.h和ethernetif.c

  1. ethernetif.h
c 复制代码
#ifndef __ETHERNETIF_H__
#define __ETHERNETIF_H__

#include "lwip/err.h"
#include "lwip/netif.h"
 
#define NETIF_IN_TASK_STACK_SIZE    ( 1024 )
#define NETIF_IN_TASK_PRIORITY      ( 2 )
 
//网卡的名字
#define IFNAME0 'e'
#define IFNAME1 'n'
 

err_t ethernetif_init(struct netif *netif);

#endif
  1. ethernetif.c
      该文件为网卡驱动文件,用来描述硬件网卡的收发函数及相关信息。该文件定义的函数如下表所示:
函数 描述
low_level_init() 底层初始化
low_level_output() 数据包发送函数
low_level_input() 获取数据报函数
ethernetif_input() 处理数据包函数
ethernetif_init() 网卡初始化
c 复制代码
#include "netif/ethernetif.h" 
#include "ethernet.h"  
#include "lwip_app.h" 
#include "netif/etharp.h" 
#include "lwip/sys.h"
#include "string.h"  
#include "FreeRTOS.h"
#include "semphr.h"
#include "task.h"


/* 定义一个信号量 */
xSemaphoreHandle s_xSemaphore = NULL;


/* Forward declarations. */
void  ethernetif_input(void *pParams);


//由ethernetif_init()调用用于初始化硬件
//netif:网卡结构体指针 
//返回值:ERR_OK,正常
//       其他,失败
static void 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]=MAC_ADDR0; 
	netif->hwaddr[1]=MAC_ADDR1;
	netif->hwaddr[2]=MAC_ADDR2;
	netif->hwaddr[3]=MAC_ADDR3;
	netif->hwaddr[4]=MAC_ADDR4;
	netif->hwaddr[5]=MAC_ADDR5;
	
	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
	
	/* 创建一个信号量 */
	if(s_xSemaphore == NULL)
	{
		s_xSemaphore = xSemaphoreCreateBinary();
		xSemaphoreTake( s_xSemaphore, 0);
	}
	
	/* 创建处理ETH_MAC的任务 */
	sys_thread_new("eth_thread",
								 ethernetif_input,        /* 任务入口函数 */
								 netif,                   /* 任务入口函数参数 */
								 NETIF_IN_TASK_STACK_SIZE,/* 任务栈大小 */
								 NETIF_IN_TASK_PRIORITY); /* 任务的优先级 */
	
	ETH_Start(); //开启MAC和DMA				
} 
//用于发送数据包的最底层函数(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 *)(DMATxDescToSet->Buffer1Addr); //获取当前要发送的DMA描述符中的缓冲区地址
	
	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); //调用ETH_Tx_Packet函数发送数据
	if(res==ETH_ERROR)return ERR_MEM;//返回错误状态
	return ERR_OK;
} 

///用于接收数据包的最底层函数
//neitif:网卡结构体指针
//返回值:pbuf数据结构体指针
static struct pbuf * low_level_input(struct netif *netif)
{  
	struct pbuf *p= NULL, *q;
	u16_t len;
	int l =0;
	FrameTypeDef frame;
	u8 *buffer;
	
	frame=ETH_Rx_Packet();
	len=frame.length;//得到包大小
	buffer=(u8 *)frame.buffer;//得到包数据地址 
	
	if(len > 0)
	{
		p=pbuf_alloc(PBUF_RAW,len,PBUF_POOL);//pbufs内存池分配pbuf
	}
	
	if(p!=NULL)
	{
		/* 将接收描述符中Rx Buffer的数据拷贝到pbuf中 */
		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;
}


void ethernetif_input(void *pParams)
{
	struct netif *netif;
	struct pbuf *p = NULL;
	netif = (struct netif*) pParams;
  LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
	
  while(1)
  {
    if (xSemaphoreTake( s_xSemaphore, portMAX_DELAY)==pdTRUE)
    {
			/* move received packet into a new pbuf */
      taskENTER_CRITICAL();
TRY_GET_NEXT_FRAME:
      p = low_level_input( netif );//调用low_level_input函数接收数据
	taskEXIT_CRITICAL();
			
      if(p != NULL)
      {
		taskENTER_CRITICAL();
				
        if (ERR_OK != netif->input( p, netif))//调用netif结构体中的input字段(一个函数)来处理数据包
        {
          LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
          pbuf_free(p);
          p = NULL;
        }
        else
        {
			xSemaphoreTake( s_xSemaphore, 0);
          goto TRY_GET_NEXT_FRAME;
        }	
		taskEXIT_CRITICAL();
      }
    }
  }
} 
//使用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.3小节报错未定义变量xSemaphoreHandle s_xSemaphore = NULL 在这里定义了。此时再编译发现两个报警,不用管

四、添加应用程序

4.1 添加Lwip应用

  1. lwip_app.h
c 复制代码
#ifndef _LWIP_COMM_H
#define _LWIP_COMM_H 
#include "ethernet.h" 


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

typedef void (*display_fn)(uint8_t index);
   
//lwip控制结构体
typedef struct  
{
	uint8_t mac[6];                 /* MAC地址 */
	uint8_t remoteip[4];            /* 远端主机IP地址 */ 
	uint8_t ip[4];                  /* 本机IP地址 */
	uint8_t netmask[4];             /* 子网掩码 */
	uint8_t gateway[4];             /* 默认网关的IP地址 */
	uint8_t dhcpstatus;             /* dhcp状态 */
									/* 0, 未获取DHCP地址;*/
									/* 1, 进入DHCP获取状态*/
									/* 2, 成功获取DHCP地址*/
									/* 0XFF,获取失败 */
	uint8_t link_status;            /* 连接状态 */
	display_fn lwip_display_fn;     /* 显示函数指针 */
}__lwip_dev;
extern __lwip_dev g_lwipdev;	//lwip控制结构体

uint8_t lwip_comm_init(void);

#endif
  1. lwip_app.c
      主要是初始化和开启DHCP
c 复制代码
#include "lwip_app.h" 
#include "netif/etharp.h"
#include "lwip/dhcp.h"
#include "lwip/mem.h"
#include "lwip/memp.h"
#include "lwip/init.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 g_lwipdev;						//lwip控制结构体 
struct netif g_lwip_netif;				//定义一个全局的网络接口


/* DHCP线程配置 */
#define LWIP_DHCP_TASK_PRIO             4                   /* 任务优先级 */
#define LWIP_DHCP_STK_SIZE              128 * 2             /* 任务堆栈大小 */
void lwip_periodic_handle(void *argument);                  /* DHCP线程 */

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

u32 TCPTimer=0;			//TCP查询计时器
u32 ARPTimer=0;			//ARP查询计时器
u32 lwip_localtime;		//lwip本地时间计数器,单位:ms

#if LWIP_DHCP
u32 DHCPfineTimer=0;	//DHCP精细处理计时器
u32 DHCPcoarseTimer=0;	//DHCP粗糙处理计时器
#endif



//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]=104;
	//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	
} 



/* 如果使能DHCP */
#if LWIP_DHCP

/**
 * @breif       DHCP进程
 * @param       argument:传入的形参
 * @retval      无
 */
void lwip_periodic_handle(void *argument)
{
  u32 ip=0,netmask=0,gw=0;
	dhcp_start(&g_lwip_netif);//开启DHCP 
	g_lwipdev.dhcpstatus=0;	//正在DHCP
	printf("正在查找DHCP服务器,请稍等...........\r\n");   
	while(1)
	{ 
		printf("正在获取地址...\r\n");
		ip=g_lwip_netif.ip_addr.addr;		//读取新IP地址
		netmask=g_lwip_netif.netmask.addr;//读取子网掩码
		gw=g_lwip_netif.gw.addr;			//读取默认网关 
		if(ip!=0)   					//当正确读取到IP地址的时候
		{
			g_lwipdev.dhcpstatus=2;	//DHCP成功
 			printf("网卡en的MAC地址为:................%d.%d.%d.%d.%d.%d\r\n",g_lwipdev.mac[0],g_lwipdev.mac[1],g_lwipdev.mac[2],g_lwipdev.mac[3],g_lwipdev.mac[4],g_lwipdev.mac[5]);
			//解析出通过DHCP获取到的IP地址
			g_lwipdev.ip[3]=(uint8_t)(ip>>24); 
			g_lwipdev.ip[2]=(uint8_t)(ip>>16);
			g_lwipdev.ip[1]=(uint8_t)(ip>>8);
			g_lwipdev.ip[0]=(uint8_t)(ip);
			printf("通过DHCP获取到IP地址..............%d.%d.%d.%d\r\n",g_lwipdev.ip[0],g_lwipdev.ip[1],g_lwipdev.ip[2],g_lwipdev.ip[3]);
			//解析通过DHCP获取到的子网掩码地址
			g_lwipdev.netmask[3]=(uint8_t)(netmask>>24);
			g_lwipdev.netmask[2]=(uint8_t)(netmask>>16);
			g_lwipdev.netmask[1]=(uint8_t)(netmask>>8);
			g_lwipdev.netmask[0]=(uint8_t)(netmask);
			printf("通过DHCP获取到子网掩码............%d.%d.%d.%d\r\n",g_lwipdev.netmask[0],g_lwipdev.netmask[1],g_lwipdev.netmask[2],g_lwipdev.netmask[3]);
			//解析出通过DHCP获取到的默认网关
			g_lwipdev.gateway[3]=(uint8_t)(gw>>24);
			g_lwipdev.gateway[2]=(uint8_t)(gw>>16);
			g_lwipdev.gateway[1]=(uint8_t)(gw>>8);
			g_lwipdev.gateway[0]=(uint8_t)(gw);
			printf("通过DHCP获取到的默认网关..........%d.%d.%d.%d\r\n",g_lwipdev.gateway[0],g_lwipdev.gateway[1],g_lwipdev.gateway[2],g_lwipdev.gateway[3]);
			break;
		}else if(g_lwip_netif.dhcp->tries>LWIP_MAX_DHCP_TRIES) //通过DHCP服务获取IP地址失败,且超过最大尝试次数
		{  
			g_lwipdev.dhcpstatus=0XFF;//DHCP失败.
			//使用静态IP地址
			IP4_ADDR(&(g_lwip_netif.ip_addr),g_lwipdev.ip[0],g_lwipdev.ip[1],g_lwipdev.ip[2],g_lwipdev.ip[3]);
			IP4_ADDR(&(g_lwip_netif.netmask),g_lwipdev.netmask[0],g_lwipdev.netmask[1],g_lwipdev.netmask[2],g_lwipdev.netmask[3]);
			IP4_ADDR(&(g_lwip_netif.gw),g_lwipdev.gateway[0],g_lwipdev.gateway[1],g_lwipdev.gateway[2],g_lwipdev.gateway[3]);
			printf("DHCP服务超时,使用静态IP地址!\r\n");
			printf("网卡en的MAC地址为:................%d.%d.%d.%d.%d.%d\r\n",g_lwipdev.mac[0],g_lwipdev.mac[1],g_lwipdev.mac[2],g_lwipdev.mac[3],g_lwipdev.mac[4],g_lwipdev.mac[5]);
			printf("静态IP地址........................%d.%d.%d.%d\r\n",g_lwipdev.ip[0],g_lwipdev.ip[1],g_lwipdev.ip[2],g_lwipdev.ip[3]);
			printf("子网掩码..........................%d.%d.%d.%d\r\n",g_lwipdev.netmask[0],g_lwipdev.netmask[1],g_lwipdev.netmask[2],g_lwipdev.netmask[3]);
			printf("默认网关..........................%d.%d.%d.%d\r\n",g_lwipdev.gateway[0],g_lwipdev.gateway[1],g_lwipdev.gateway[2],g_lwipdev.gateway[3]);
			break;
		}  
		delay_ms(250); //延时250ms
	}
	dhcp_stop(&g_lwip_netif); 		//关闭DHCP
}
#endif

//LWIP初始化(LWIP启动的时候使用)
//返回值:0,成功
//      1,内存错误
//      2,LAN8720初始化失败
//      3,网卡添加失败.
u8 lwip_comm_init(void)
{
	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(LAN8720_Init())return 2;			//初始化LAN8720失败 
	tcpip_init(NULL,NULL);				//初始化tcp ip内核,该函数里面会创建tcpip_thread内核任务
	lwip_comm_default_ip_set(&g_lwipdev);	//设置默认IP等信息

#if LWIP_DHCP		//使用动态IP
	ipaddr.addr = 0;
	netmask.addr = 0;
	gw.addr = 0;
#else				//使用静态IP
	IP4_ADDR(&ipaddr,g_lwipdev.ip[0],g_lwipdev.ip[1],g_lwipdev.ip[2],g_lwipdev.ip[3]);
	IP4_ADDR(&netmask,g_lwipdev.netmask[0],g_lwipdev.netmask[1] ,g_lwipdev.netmask[2],g_lwipdev.netmask[3]);
	IP4_ADDR(&gw,g_lwipdev.gateway[0],g_lwipdev.gateway[1],g_lwipdev.gateway[2],g_lwipdev.gateway[3]);
	printf("网卡en的MAC地址为:................%d.%d.%d.%d.%d.%d\r\n",g_lwipdev.mac[0],g_lwipdev.mac[1],g_lwipdev.mac[2],g_lwipdev.mac[3],g_lwipdev.mac[4],g_lwipdev.mac[5]);
	printf("静态IP地址........................%d.%d.%d.%d\r\n",g_lwipdev.ip[0],g_lwipdev.ip[1],g_lwipdev.ip[2],g_lwipdev.ip[3]);
	printf("子网掩码..........................%d.%d.%d.%d\r\n",g_lwipdev.netmask[0],g_lwipdev.netmask[1],g_lwipdev.netmask[2],g_lwipdev.netmask[3]);
	printf("默认网关..........................%d.%d.%d.%d\r\n",g_lwipdev.gateway[0],g_lwipdev.gateway[1],g_lwipdev.gateway[2],g_lwipdev.gateway[3]);
#endif
	netif_init_flag=netif_add(&g_lwip_netif,&ipaddr,&netmask,&gw,NULL,&ethernetif_init,&tcpip_input);//向网卡列表中添加一个网口
	
#if LWIP_DHCP			//如果使用DHCP的话
	g_lwipdev.dhcpstatus=0;	//DHCP标记为0
	dhcp_start(&g_lwip_netif);	//开启DHCP服务
#endif
	
	if(netif_init_flag==NULL)return 3;//网卡添加失败 
	else//网口添加成功后,设置netif为默认值,并且打开netif网口
	{
		netif_set_default(&g_lwip_netif); //设置netif为默认网口
		
		if (netif_is_link_up(&g_lwip_netif))
		{
				netif_set_up(&g_lwip_netif);      /* 打开netif网口 */
		}
		else
		{
				netif_set_down(&g_lwip_netif);
		}
	}
	
#if LWIP_DHCP                                       /* 如果使用DHCP的话 */
    g_lwipdev.dhcpstatus = 0;                       /* DHCP标记为0 */
    /* DHCP轮询任务 */
    sys_thread_new("eth_dhcp",
                   lwip_periodic_handle,            /* 任务入口函数 */
                   &g_lwip_netif,                   /* 任务入口函数参数 */
                   LWIP_DHCP_STK_SIZE,              /* 任务栈大小 */
                   LWIP_DHCP_TASK_PRIO);            /* 任务的优先级 */
#endif	
	
	return 0;//操作OK.
}   

4.2 添加FreeRTOS应用

在主函数main.c里创建FreeRTOS任务

c 复制代码
#include "main.h"
#include "malloc.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "led.h" 
#include "lwip_app.h"

//------------------------ 任务相关定义 ---------------------//
//任务优先级
#define START_TASK_PRIO		5
//任务堆栈大小	
#define START_STK_SIZE 		128  
//任务句柄
TaskHandle_t StartTask_Handler;


//任务优先级
#define KEY_TASK_PRIO		10
//任务堆栈大小	
#define KEY_STK_SIZE 		128  
//任务句柄
TaskHandle_t KeyTask_Handler;


//任务优先级
#define TASK1_TASK_PRIO		11
//任务堆栈大小	
#define TASK1_STK_SIZE 		128  
//任务句柄
TaskHandle_t LedTask_Handler;


//------------------------ 任务函数 ---------------------//

//led任务函数
void led_task(void *pvParameters)
{
	u8 task1_num=0;
	
	while(1)
	{
		task1_num++;	//任务执1行次数加1 注意task1_num1加到255的时候会清零!!
		LED0=!LED0;
		printf("任务1已经执行:%d次\r\n",task1_num);

    vTaskDelay(1000);                           //延时1s,也就是1000个时钟节拍	
	}
}


//key任务函数
void key_task(void *pvParameters)
{
	u8 key;
	while(1)
	{
		key=KEY_Scan(0);
		switch(key)
		{
			case KEY0_PRES:
				vTaskSuspend(LedTask_Handler);//挂起任务1
				printf("挂起任务1的运行!\r\n");
				break;
			case KEY1_PRES:
				vTaskResume(LedTask_Handler);	//恢复任务1
				printf("恢复任务1的运行!\r\n");
				break;
		}
		vTaskDelay(10);			//延时10ms 
	}
}

//开始任务任务函数
void start_task(void *pvParameters)
{
	uint8_t speed;
	
	speed = ethernet_chip_get_speed();      /* 得到网速 */
	printf("speed:%d",speed);
	
	while (lwip_comm_init() != 0)
	{
		delay_ms(500);
	}
	
	taskENTER_CRITICAL();           //进入临界区
	//创建KEY任务
	xTaskCreate((TaskFunction_t )key_task,             
							(const char*    )"key_task",           
							(uint16_t       )KEY_STK_SIZE,        
							(void*          )NULL,                  
							(UBaseType_t    )KEY_TASK_PRIO,        
							(TaskHandle_t*  )&KeyTask_Handler);  
	//创建TASK1任务
	xTaskCreate((TaskFunction_t )led_task,             
							(const char*    )"task1_task",           
							(uint16_t       )TASK1_STK_SIZE,        
							(void*          )NULL,                  
							(UBaseType_t    )TASK1_TASK_PRIO,        
							(TaskHandle_t*  )&LedTask_Handler);   
	vTaskDelete(StartTask_Handler); //删除开始任务
	taskEXIT_CRITICAL();            //退出临界区
}

int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
	uart_init(115200);
	delay_init(168);
	KEY_Init();
	LED_Init();
	
	my_mem_init(SRAMIN);  	//初始化内部内存池
	my_mem_init(SRAMCCM); 	//初始化CCM内存池
	
	//创建开始任务
	xTaskCreate((TaskFunction_t )start_task,            //任务函数
							(const char*    )"start_task",          //任务名称
							(uint16_t       )START_STK_SIZE,        //任务堆栈大小
							(void*          )NULL,                  //传递给任务函数的参数
							(UBaseType_t    )START_TASK_PRIO,       //任务优先级
							(TaskHandle_t*  )&StartTask_Handler);   //任务句柄              
	vTaskStartScheduler();          //开启任务调度
}
相关推荐
BreezeJuvenile1 小时前
STM32总体架构简单介绍
stm32·单片机·嵌入式硬件
scgg2 小时前
STM32抢占优先级不生效
stm32·疑问
可乐鸡翅好好吃3 小时前
STM32的中断(什么是外部中断和其他中断以及中断号是什么)
c语言·stm32·单片机·嵌入式硬件·mcu·物联网·51单片机
嵌入式大圣5 小时前
单片机在电路板中的应用
单片机·嵌入式硬件
小A1597 小时前
STM32完全学习——使用标准库完成PWM输出
stm32·单片机·学习
小A1597 小时前
STM32完全学习——使用标准库完成定时器中断
stm32·单片机·学习
美式小田7 小时前
单片机学习笔记 10. 中断系统(理论)
笔记·单片机·嵌入式硬件·学习
我不是码农 嘤嘤嘤7 小时前
单片机GPIO的8种工作模式
单片机·嵌入式硬件
相醉为友8 小时前
006 单片机嵌入式中的C语言与代码风格规范——常识
c语言·单片机·嵌入式硬件
likkoliu9 小时前
FreeRTOS LIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 存在的意义以及高于它的中断不能调用 safe freertos api
freertos·中断·safe freertos api·library_max_syscall_interrupt_priority