STM32F4+FreeRTOS+LWIP移植

目录

  • 以太网(ETH)简介
    • 主要特性
  • LWIP协议栈
    • LWIP在STM32上的源代码移植
      • 1、core文件:LWIP内核源代码
      • 2、core/ipv4文件:IPv4标准与IP层数据包相关代码
      • 3、api文件:包含sequential API和socket API
      • 4、netif文件:包含与底层网络接口相关的文件
    • 修改代码部分
      • 1、初始化部分
      • 2、sys_arch.c文件
      • 3、UDP通信线程
      • 4、以太网配置

STM32F407平台实现以太网LWIP+FreeRTOS移植。


以太网(ETH)简介

借助以太网外设,STM32F4xx 可以通过以太网按照 IEEE 802.3-2002 标准发送和接收数据。以太网提供了可配置、灵活的外设,用以满足客户的各种应用需求。它支持与外部物理层(PHY) 相连的两个工业标准接口:默认情况下使用的介质独立接口 (MII)(在 IEEE 802.3 规范中定义)和简化介质独立接口 (RMII),应用于多种领域,例如交换机、网络接口卡等。
STM32F4+FreeRTOS+LWIP移植_第1张图片

主要特性

  • MAC 模块对以下系列的系统使用 LAN CSMA/CD 子层:数据速率为 10 Mbit/s 和 100 Mbit/s 的基带系统和宽带系统。支持半双工和全双工工作模式。冲突检测访问方法仅适用于半双工工作模式。支持 MAC 控制帧子层。
  • 符合IEEE 802.3的MII和RMII接口
  • 使用MDIO接口配置和管理最多32个PHY设备
  • 可编程帧长度,支持高达 16 KB 的巨型帧
  • 可编程帧间隔(40-96 位时间,以 8 为步长)
  • 支持多种灵活的地址过滤模式
  • 支持以太网帧时间戳(请参见 IEEE 1588-2008)。每个帧的发送或接收状态下给出 64 位时间戳
  • DMA 具有独立的发送和接收引擎以及相应的 CSR(控制和状态寄存器)空间。发送引擎将数据从系统存储器传送到 Tx FIFO,而接收引擎将数据从 Rx FIFO 传送到系统存储器。DMA可以在 CPU 完全不干预的情况下,通过描述符有效地将数据从源传送到目标。DMA 专为面向包的数据传送(如以太网中的帧)而设计。该控制器经过编程后,可在完成帧发送和接收传送操作时以及其它正常/错误条件下产生 CPU 中断。

LWIP协议栈

LWIP是TCP/IP协议栈的一个开放源代码实现,它由瑞士计算机科学院的Adam Dunkels等开发,目的是减少内存使用率和代码空间大小,因此LWIP适用于运行在资源受限的嵌入式系统环境中。LWIP可以在几百字节或几十KB的RAM空间运行。LWIP既可以移植到操作系统上运行,也可以在无操作系统下独立运行。

LWIP在STM32上的源代码移植

1、core文件:LWIP内核源代码

文件 说明
dhcp.c 包含DHCP(动态主机配置协议)客户端的实现代码
dns.c 包含DNS(域名系统)客户端的实现代码
init.c 包含了LWIP协议栈初始化密切相关的函数,以及一些协议栈配置信息的检查和输出
mem.c 协议栈内存堆管理函数的实现代码
memp.c 协议栈内存池管理函数的实现代码
netif.c 包含协议栈网络接口管理的相关函数,协议栈每个接口用一个相对应的结构进行描述
pbuf.c 包含协议栈内核使用的数据包管理函数,用于协议栈层次间的数据传递,避免数据拷贝
raw.c 原始套接字的实现代码,可以通过该文件中的函数直接操纵IP层数据包
stats.c 包含协议栈内部数据统计与显示的函数,比如内存使用状况、邮箱、信号量等信息
sys.c 实现对操作系统模拟层的封装,为协议栈提供统一的邮箱、 信号量操作函数。如果开发者需要使用协议栈的sequential API和socket API, 则必须使用底层操作系统提供的邮箱与信号量机制,这时内核要求移植者提供一个称为sys_ arch. c的操作系统模拟层文件,这个文件主要完成对操作系统中邮箱与信号量函数的封装。而sys. c文件的功能是将sys_ arch. c中的函数再次封装,以得到具有协议栈特色的邮箱、信号量操作函数。所谓特色,就是在这些还数中加入一种机制,以实现协议栈中各个定时事件的正确处理。不使用sequential API和socket API时,LWIP 的编程方式是基于回调机制的,因此不需要任何邮箱和信号量机制。
tcp.c 包含TCP控制块操作的函数,也包含了TCP定时处理函数
tcp_in.c 包含TCP协议数据接收、处理相关的函数,以及最重要的TCP状态机函数
tcp_out.c 包含TCP协议数据发送相关函数,例如数据包发送,超时重传函数等
udp.c 包含实现UDP协议的相关函数,包括控制块管理、数据包发送函数、数据包接收函数等

2、core/ipv4文件:IPv4标准与IP层数据包相关代码

文件 说明
autoip.c 包含IP地址自动配置相关函数,若主机从DHCF服务器处获职IP地址失败,则此时主机可以选择启动AUTOIP功能来配置自身的IP地址。AUTOIP将主机配置为169.254.0.0/16中的某个地址,并提供一套完整的机制来避免IP地址冲突
icmp.c 包含ICMP (网际控制报文协议) 协议的实现代码。ICMP协议为IE数据包传递过程中的差错报告、差错纠正以及目的地址可达性测试提供了支持,常见的Ping命令就属于ICMP应用中的一种。
igmp.c 包含IGMP (网络组管理协议)协议的实现代码。IGMP为网络中的多播数据传输提供了支持,主机加入某个多播组后,可以接收到改组的UDP多播数据。
inet.c 包含I层使用到的一-些功能函数的定义,如r地址的转换、网络字节序与主机字节序转换等
inet_chksum.c 包含对IP数据包校验相关的函数
ip.c 包含IPv4协议实现的相关函数,如数据包的接收、递交、发送等
ip_ addr.c 包含-个判断IP地址是否为广播地址的函数
ip_frag.c 提供了IP层数据包分片与重组相关的函数

3、api文件:包含sequential API和socket API

文件 说明
api_lib.c 包含sequential API函数的实现,主要包含预留给用户的编程接口
api.msg.c 包含sequential API函数的实现,主要包含API消息的封装和处理函数
netbuf.c 包含上层数据包管理函数的实现
netdb.c 包含与主机名字转换相关的函数,主要在socket中被使用到
netlfapi.c 包含上层网络接口管理函数的实现
sockets.c 包含socket API函数的实现
tcpip.c 提供了上层API与协议栈内核交互的函徽,它是整个上层API功能得以实现的一个枢纽,其实现的功能可以理解为:从API函数处接收消息,然后将消息递交给内核函数,内核函数根据消息做出相应的处理。

4、netif文件:包含与底层网络接口相关的文件

文件 说明
etharp.c 包含ARP协议的实现代码。主要用来实现主机以太网物理地址到IP地址的映射。
ethernetif.c 包含了与以太网网卡密切相关的初始化、发送、接收等函数的实现。这个文件夹中的函数并不能使用,它们都是一个框架性的结构,移植者需要根据自己使用的网卡特性来完成这些函数。
loopif.c 为协议栈提供回环网络接口功能。使用这个接口可以实现本地两个进程之间的数据传递。
slipif.c SLIP (串行链路IP) ,提供一种在串行链路上传送IP数据包的函数定义,移植者需要根据自己使用的串行线路特性来实现这些函数。

修改代码部分

本项目参考自正点原子探索者开发板的网络实验7 NETCONN_UDP实验工程,原工程使用的是ucos_ii+lwip的工程,这里更改为FreeRTOS+lwip的工程并修改。

1、初始化部分

因为原来的工程是初始化时候必须插网线,不插网线初始化就会失败,本工程是把lwip初始化分为两部分,首先是初始化对应的GPIO口和时钟,这部分完成后才可以读取PHY的基本状态寄存器的值。

//初始化接口并使能时钟
void LAN8720_Port_Init(void)
{
	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;				 	//复位结束 
	
	//使能以太网MAC以及MAC接收和发送时钟
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_ETH_MAC | RCC_AHB1Periph_ETH_MAC_Tx |RCC_AHB1Periph_ETH_MAC_Rx, ENABLE); 
}

然后在创建的lwip_task任务中查询PHY的BSR寄存器的Link Status状态位,当检测到网络连接时再进行tcpip内核初始化和添加网口等操作,如果开启了DHCP服务则在初始化完成后创建dhcp_task的任务。一旦DHCP成功之后,除非系统复位重新初始化,否则不需要重新开启DHCP,即使网线拔下再插入也不会影响继续通信。

//lwip任务
void lwip_task(void *pvParameters)
{
	while(1)
	{
		//查询网络连接状态
		if(Check_Network_connent())
		{
			if(lwip_comm_init())	//有网络连接则初始化lwip
			{
				LCD_ShowString(30,210,200,16,16,"LWIP Init Falied!");   //lwip初始化失败
			}
#if LWIP_DHCP
			lwip_comm_dhcp_creat(); 	//创建DHCP任务
#endif						
			vTaskSuspend(LWIP_Task_Handler);	//初始化完成后挂起自己
		}
		LED1 = !LED1;
		vTaskDelay(200);  //延时200ms
	}
}

//检测网络连接状态
//返回值:0,无网络连接
//       1,有网络连接
u8 Check_Network_connent(void)
{
	if (ETH_ReadPHYRegister(LAN8720_PHY_ADDRESS, PHY_BSR) & PHY_Linked_Status)
		return 1;
	else
		return 0;	
}

2、sys_arch.c文件

操作系统模拟层是LWIP协议栈的一部分,它存在的目的是方便将LWIP移植到不同的操作系统上,它为操作系统和LWIP协议栈之间提供一个接口桥梁,当用户移植到一个新的操作系统的时候,只需要修改操作系统模拟层的各函数即可。Sys_arch.txt文件给出了详细的说明。总的来说,操作系统模拟层主要完成了与信号量、消息邮箱机制、线程相关的功能。
typedef xSemaphoreHandle sys_sem_t; //LWIP信号量类型定义
typedef xQueueHandle sys_mbox_t; //LWIP消息队列类型定义
typedef xTaskHandle sys_thread_t; //LWIP线程类型定义

/*这个文件是操作系统模拟层,是lwip协议栈的一部分。方便将lwip移植到各种不同的操作系统上,为操作系统和lwip协议栈之间
提供一个接口桥梁,当用户移植lwip到一个新的操作系统的时候,只需要修改操作系统模拟层内的各函数即可。lwip使用的消息邮箱
就FreeRTOS中的消息队列这个c文件是信号量、消息队列机制、线程相关的功能的实现代码*/
/* lwIP includes. */
#include "lwip/debug.h"
#include "lwip/def.h"
#include "lwip/lwip_sys.h"
#include "lwip/mem.h"
#include "lwip/stats.h"
#include "FreeRTOS.h"
#include "task.h"
#include "lwip/lwip_timers.h"

xTaskHandle xTaskGetCurrentTaskHandle( void ) PRIVILEGED_FUNCTION;

struct timeoutlist
{
	struct sys_timeouts timeouts;
	xTaskHandle pid;
};

/* This is the number of threads that can be started with sys_thread_new() */
#define SYS_THREAD_MAX 6

static struct timeoutlist s_timeoutlist[SYS_THREAD_MAX];
static u16_t s_nextthread = 0;

/*-----------------------------------------------------------------------------------*/
//  Creates an empty mailbox.
//使用FreeRTOS提供的消息队列机制创建一个空的消息队列(函数xQueueCreate)
err_t sys_mbox_new(sys_mbox_t *mbox, int size)
{
	(void ) size;
	/*创建队列*/
	*mbox = xQueueCreate( archMESG_QUEUE_LENGTH, sizeof( void * ) );

#if SYS_STATS
      ++lwip_stats.sys.mbox.used;
      if (lwip_stats.sys.mbox.max < lwip_stats.sys.mbox.used) {
         lwip_stats.sys.mbox.max = lwip_stats.sys.mbox.used;
	  }
#endif /* SYS_STATS */
 if (*mbox == NULL)
  return ERR_MEM;
 
 return ERR_OK;
}

/*-----------------------------------------------------------------------------------*/
//与sys_mbox_new相反,这个函数用于删除一个队列,当该队列中还有未被取出的消息时,该函数应当报错,并通知应用程序
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 */
}

/*-----------------------------------------------------------------------------------*/
//   Posts the "msg" to the mailbox.
//该函数用于将消息发送至消息队列中(必须发送成功),是一个阻塞函数。当消息被发送至队列后,该函数才会退出阻塞状态。
void sys_mbox_post(sys_mbox_t *mbox, void *data)
{
	while ( xQueueSendToBack(*mbox, &data, portMAX_DELAY ) != pdTRUE ){}
}


/*-----------------------------------------------------------------------------------*/
//   Try to post the "msg" to the mailbox.
//该函数用于尝试将某个消息发送至消息队列中,当消息被成功投递后,则返回成功,否则返回失败
err_t sys_mbox_trypost(sys_mbox_t *mbox, void *msg)
{
err_t result;

   if ( xQueueSendFromISR( *mbox, &msg, 0 ) == pdPASS )
   {
      result = ERR_OK;
   }
   else {
      // could not post, queue must be full
      result = ERR_MEM;
			
#if SYS_STATS
      lwip_stats.sys.mbox.err++;
#endif /* SYS_STATS */
			
   }

   return result;
}

/*-----------------------------------------------------------------------------------*/
/*该函数用于从消息队列中取出一条消息。(等待邮箱中的消息)该函数也是个阻塞函数。调用该函数的线程若未取到消息,则在
形参timeout所指定的时间内,该线程被阻塞。当超过timeout所指定的时间后,该线程恢复至就绪状态。若timeout为0,则调用
该函数的线程一直被阻塞,直到收到消息。*/
u32_t sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout)
{
void *dummyptr;
portTickType StartTime, EndTime, Elapsed;

	StartTime = xTaskGetTickCount();

	if ( msg == NULL )
	{
		msg = &dummyptr;
	}
		
	if ( timeout != 0 )
	{
		if ( pdTRUE == xQueueReceive( *mbox, &(*msg), timeout / portTICK_RATE_MS ) )
		{
			EndTime = xTaskGetTickCount();
			Elapsed = (EndTime - StartTime) * portTICK_RATE_MS;
			
			return ( Elapsed );
		}
		else // timed out blocking for message
		{
			*msg = NULL;
			
			return SYS_ARCH_TIMEOUT;
		}
	}
	else // block forever for a message.
	{
		while( pdTRUE != xQueueReceive( *mbox, &(*msg), portMAX_DELAY ) ){} // time is arbitrary
		EndTime = xTaskGetTickCount();
		Elapsed = (EndTime - StartTime) * portTICK_RATE_MS;
		
		return ( Elapsed ); // return time blocked TODO test	
	}
}

/*-----------------------------------------------------------------------------------*/
/*该函数尝试从消息队列中取出消息,它是一个非阻塞函数。当取到消息时,则返回成功,否则立即退出,返回“队列空” */
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;
   }
}
/*----------------------------------------------------------------------------------*/
//设置一个消息邮箱为有效
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;                      
}                                             

/*-----------------------------------------------------------------------------------*/
//  Creates a new semaphore. The "count" argument specifies
//  the initial state of the semaphore.
//该函数用于创建一个信号量,其中形参count指明了当前信号量的状态。若count为0,则该信号量在创建时就被
//”取走“了
err_t sys_sem_new(sys_sem_t *sem, u8_t count)
{
	//创建二进制信号量
	vSemaphoreCreateBinary(*sem );
	if(*sem == NULL)
	{
#if SYS_STATS
      ++lwip_stats.sys.sem.err;
#endif /* SYS_STATS */	
		return ERR_MEM;
	}
	
	if(count == 0)	// Means it can't be taken
	{
		xSemaphoreTake(*sem,1);
	}

#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 */
		
	return ERR_OK;
}

/*-----------------------------------------------------------------------------------*/
*/该函数是一个阻塞函数,(等待一个信号量)调用该函数的线程在形参timeout指定的时间内被阻塞。若timeout为0,则调用
该函数的线程将一直被阻塞,直到等待的信号量被释放。当该函数取到信号量时,它将返回取得该信号量所占用的时间。*/
u32_t sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout)
{
portTickType StartTime, EndTime, Elapsed;

	StartTime = xTaskGetTickCount();

	if(	timeout != 0)
	{
		if( xSemaphoreTake( *sem, timeout / portTICK_RATE_MS ) == pdTRUE )
		{
			EndTime = xTaskGetTickCount();
			Elapsed = (EndTime - StartTime) * portTICK_RATE_MS;
			
			return (Elapsed); // return time blocked TODO test	
		}
		else
		{
			return SYS_ARCH_TIMEOUT;
		}
	}
	else // must block without a timeout
	{
		while( xSemaphoreTake(*sem, portMAX_DELAY) != pdTRUE){}
		EndTime = xTaskGetTickCount();
		Elapsed = (EndTime - StartTime) * portTICK_RATE_MS;

		return ( Elapsed ); // return time blocked	
		
	}
}

/*-----------------------------------------------------------------------------------*/
// Signals a semaphore
//该函数用于发送一个信号量
void sys_sem_signal(sys_sem_t *sem)
{
	xSemaphoreGive(*sem);
}

/*-----------------------------------------------------------------------------------*/
// Deallocates a semaphore
//该函数用于释放并删除一个信号量
void sys_sem_free(sys_sem_t *sem)
{
#if SYS_STATS
      --lwip_stats.sys.sem.used;
#endif /* SYS_STATS */
			
	vQueueDelete(*sem);
}
/*-----------------------------------------------------------------------------------*/
//查询一个信号量的状态,无效或有效
int sys_sem_valid(sys_sem_t *sem)                                               
{
  if (*sem == SYS_SEM_NULL)
    return 0;
  else
    return 1;                                       
}

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

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

/*-----------------------------------------------------------------------------------*/
// Initialize sys arch
//该函数是操作系统模拟层的初始化函数。它主要对定时器管理数组进行了初始化。(可能用不到)
void sys_init(void)
{
	int i;

	// Initialize the the per-thread sys_timeouts structures
	// make sure there are no valid pids in the list
	for(i = 0; i < SYS_THREAD_MAX; i++)
	{
		s_timeoutlist[i].pid = 0;
		s_timeoutlist[i].timeouts.next = NULL;
	}
	
	// keep track of how many threads have been created
	s_nextthread = 0;
}

/*-----------------------------------------------------------------------------------*/
/*该函数用于返回当前任务的定时器管理链表首地址。*/
struct sys_timeouts *sys_arch_timeouts(void)
{
int i;
xTaskHandle pid;
struct timeoutlist *tl;

	pid =  xTaskGetCurrentTaskHandle();
              

	for(i = 0; i < s_nextthread; i++)
	{
		tl = &(s_timeoutlist[i]);
		if(tl->pid == pid)
		{
			return &(tl->timeouts);
		}
	}

	// Error
	return NULL;
}

/*-----------------------------------------------------------------------------------*/
                                      /* Mutexes*/
/*-----------------------------------------------------------------------------------*/
/*-----------------------------------------------------------------------------------*/
#if LWIP_COMPAT_MUTEX == 0
/* Create a new mutex*/
err_t sys_mutex_new(sys_mutex_t *mutex) {

  *mutex = xSemaphoreCreateMutex();
		if(*mutex == NULL)
	{
#if SYS_STATS
      ++lwip_stats.sys.mutex.err;
#endif /* SYS_STATS */	
		return ERR_MEM;
	}

#if SYS_STATS
	++lwip_stats.sys.mutex.used;
 	if (lwip_stats.sys.mutex.max < lwip_stats.sys.mutex.used) {
		lwip_stats.sys.mutex.max = lwip_stats.sys.mutex.used;
	}
#endif /* SYS_STATS */
        return ERR_OK;
}
/*-----------------------------------------------------------------------------------*/
/* Deallocate a mutex*/
void sys_mutex_free(sys_mutex_t *mutex)
{
#if SYS_STATS
      --lwip_stats.sys.mutex.used;
#endif /* SYS_STATS */
			
	vQueueDelete(*mutex);
}
/*-----------------------------------------------------------------------------------*/
/* Lock a mutex*/
void sys_mutex_lock(sys_mutex_t *mutex)
{
	sys_arch_sem_wait(*mutex, 0);
}

/*-----------------------------------------------------------------------------------*/
/* Unlock a mutex*/
void sys_mutex_unlock(sys_mutex_t *mutex)
{
	xSemaphoreGive(*mutex);
}
#endif /*LWIP_COMPAT_MUTEX*/
/*-----------------------------------------------------------------------------------*/
// TODO
/*-----------------------------------------------------------------------------------*/
/*该函数用于创建一个新的线程。其中,形参name指定了该线程的名称,thread是该线程对应的函数,arg是该线程的
形参,stacksize指定了该线程对应的堆栈大小,prio则指定了该线程的优先级。 (一般用来创建一个内核任务)*/
sys_thread_t sys_thread_new(const char *name, lwip_thread_fn thread , void *arg, int stacksize, int prio)
{
xTaskHandle CreatedTask;
int result;

   if ( s_nextthread < SYS_THREAD_MAX )
   {
      result = xTaskCreate( thread, ( portCHAR * ) name, stacksize, arg, prio, &CreatedTask );

	   // For each task created, store the task handle (pid) in the timers array.
	   // This scheme doesn't allow for threads to be deleted
	   s_timeoutlist[s_nextthread++].pid = CreatedTask;

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

/*该函数用于保护临界区资源。*/
sys_prot_t sys_arch_protect(void)
{
	//vPortEnterCritical();
	taskENTER_CRITICAL_FROM_ISR();//中断进入临界保护
	return 1;
}

/*该函数在访问临界区资源时使用,它必须与sys_arch_protect函数成对使用。*/
void sys_arch_unprotect(sys_prot_t pval)
{
//	( void ) pval;
//	vPortExitCritical();
	taskEXIT_CRITICAL_FROM_ISR(pval);//中断退出临界保护
}

/*
 * Prints an assertion messages and aborts execution.
 */
void sys_assert( const char *msg )
{	
	( void ) msg;
	/*FSL:only needed for debugging
	printf(msg);
	printf("\n\r");
	*/
    vPortEnterCritical(  );
    for(;;)
    ;
}

3、UDP通信线程

//UDP客户端任务
#define UDP_TASK_PRIO 		6
//任务堆栈大小
#define UDP_STK_SIZE		300	
//任务句柄
TaskHandle_t UDP_Task_Handler;
//任务函数
void udp_thread(void *pdata);  

u8 udp_demo_recvbuf[UDP_DEMO_RX_BUFSIZE];	//UDP接收数据缓冲区
//UDP发送数据内容
const u8 *udp_demo_sendbuf="Explorer STM32F407 NETCONN UDP demo send data\r\n";
u8 udp_flag;							//UDP数据发送标志位

//udp任务函数
void udp_thread(void *pdata)
{
	err_t err;
	static struct netconn *udpconn;
	static struct netbuf  *recvbuf;
	static struct netbuf  *sentbuf;
	struct ip_addr destipaddr;
	u32 data_len = 0;
	struct pbuf *q;
	
	LWIP_UNUSED_ARG(pdata);
	udpconn = netconn_new(NETCONN_UDP);  //创建一个UDP链接
	udpconn->recv_timeout = 10;  		
	
	if(udpconn != NULL)  //创建UDP连接成功
	{
		err = netconn_bind(udpconn,IP_ADDR_ANY,UDP_DEMO_PORT); 
		IP4_ADDR(&destipaddr,lwipdev.remoteip[0],lwipdev.remoteip[1], lwipdev.remoteip[2],lwipdev.remoteip[3]); //构造目的IP地址
		netconn_connect(udpconn,&destipaddr,UDP_DEMO_PORT); 	//连接到远端主机
		if(err == ERR_OK)//绑定完成
		{
			while(1)
			{
				if((udp_flag & LWIP_SEND_DATA) == LWIP_SEND_DATA) //有数据要发送
				{
					sentbuf = netbuf_new();
					netbuf_alloc(sentbuf,strlen((char *)udp_demo_sendbuf));
					memcpy(sentbuf->p->payload,(void*)udp_demo_sendbuf,strlen((char*)udp_demo_sendbuf));
					err = netconn_send(udpconn,sentbuf);  	//将netbuf中的数据发送出去
					if(err != ERR_OK)
					{
						printf("发送失败\r\n");
						netbuf_delete(sentbuf);      //删除buf
					}
					udp_flag &= ~LWIP_SEND_DATA;	//清除数据发送标志
					netbuf_delete(sentbuf);      	//删除buf
				}	
				
				netconn_recv(udpconn,&recvbuf); //接收数据
				if(recvbuf != NULL)          //接收到数据
				{ 
					taskENTER_CRITICAL();    //进入临界区
					memset(udp_demo_recvbuf,0,UDP_DEMO_RX_BUFSIZE);  //数据接收缓冲区清零
					for(q=recvbuf->p;q!=NULL;q=q->next)  //遍历完整个pbuf链表
					{
						//判断要拷贝到UDP_DEMO_RX_BUFSIZE中的数据是否大于UDP_DEMO_RX_BUFSIZE的剩余空间,如果大于
						//的话就只拷贝UDP_DEMO_RX_BUFSIZE中剩余长度的数据,否则的话就拷贝所有的数据
						if(q->len > (UDP_DEMO_RX_BUFSIZE-data_len)) memcpy(udp_demo_recvbuf+data_len,q->payload,(UDP_DEMO_RX_BUFSIZE-data_len));//拷贝数据
						else memcpy(udp_demo_recvbuf+data_len,q->payload,q->len);
						data_len += q->len;  	
						if(data_len > UDP_DEMO_RX_BUFSIZE) break; //超出TCP客户端接收数组,跳出	
					}
					taskEXIT_CRITICAL();      //退出临界区
					data_len=0;  //复制完成后data_len要清零。
					printf("%s\r\n",udp_demo_recvbuf);  //打印接收到的数据
					netbuf_delete(recvbuf);      //删除buf
				}else vTaskDelay(5);  //延时5ms
			}
		}else printf("UDP绑定失败\r\n");
	}else printf("UDP连接创建失败\r\n");
}


//创建UDP线程
//返回值:0 UDP创建成功
//		其他 UDP创建失败
void udp_demo_init(void)
{
	taskENTER_CRITICAL();    //进入临界区

	//创建UDP线程
	xTaskCreate((TaskFunction_t )udp_thread,  		//任务函数
              (const char*    )"UDP_Thread",        //任务名称
              (uint16_t       )UDP_STK_SIZE,      	//任务堆栈大小
              (void*          )NULL,                //传递给任务函数的参数
              (UBaseType_t    )UDP_TASK_PRIO,    	//任务优先级
              (TaskHandle_t*  )&UDP_Task_Handler);	//任务句柄 
	taskEXIT_CRITICAL();     //退出临界区
}

4、以太网配置

因为原来的以太网中断设置为最高优先级0,而我们给FreeRTOS系统可管理的最高中断优先级设置的是5,会导致port.c文件的configASSERT调用断言,需要对其进行更改。

代码下载链接: STM32F4+FreeRTOS+LwIP.zip.

你可能感兴趣的:(LWIP,stm32,stm32,freertos,网络协议,tcpip)