STM32F407平台实现以太网LWIP+FreeRTOS移植。
借助以太网外设,STM32F4xx 可以通过以太网按照 IEEE 802.3-2002 标准发送和接收数据。以太网提供了可配置、灵活的外设,用以满足客户的各种应用需求。它支持与外部物理层(PHY) 相连的两个工业标准接口:默认情况下使用的介质独立接口 (MII)(在 IEEE 802.3 规范中定义)和简化介质独立接口 (RMII),应用于多种领域,例如交换机、网络接口卡等。
LWIP是TCP/IP协议栈的一个开放源代码实现,它由瑞士计算机科学院的Adam Dunkels等开发,目的是减少内存使用率和代码空间大小,因此LWIP适用于运行在资源受限的嵌入式系统环境中。LWIP可以在几百字节或几十KB的RAM空间运行。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协议的相关函数,包括控制块管理、数据包发送函数、数据包接收函数等 |
文件 | 说明 |
---|---|
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层数据包分片与重组相关的函数 |
文件 | 说明 |
---|---|
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函数处接收消息,然后将消息递交给内核函数,内核函数根据消息做出相应的处理。 |
文件 | 说明 |
---|---|
etharp.c | 包含ARP协议的实现代码。主要用来实现主机以太网物理地址到IP地址的映射。 |
ethernetif.c | 包含了与以太网网卡密切相关的初始化、发送、接收等函数的实现。这个文件夹中的函数并不能使用,它们都是一个框架性的结构,移植者需要根据自己使用的网卡特性来完成这些函数。 |
loopif.c | 为协议栈提供回环网络接口功能。使用这个接口可以实现本地两个进程之间的数据传递。 |
slipif.c | SLIP (串行链路IP) ,提供一种在串行链路上传送IP数据包的函数定义,移植者需要根据自己使用的串行线路特性来实现这些函数。 |
本项目参考自正点原子探索者开发板的网络实验7 NETCONN_UDP实验工程,原工程使用的是ucos_ii+lwip的工程,这里更改为FreeRTOS+lwip的工程并修改。
因为原来的工程是初始化时候必须插网线,不插网线初始化就会失败,本工程是把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;
}
操作系统模拟层是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(;;)
;
}
//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(); //退出临界区
}
因为原来的以太网中断设置为最高优先级0,而我们给FreeRTOS系统可管理的最高中断优先级设置的是5,会导致port.c文件的configASSERT调用断言,需要对其进行更改。
代码下载链接: STM32F4+FreeRTOS+LwIP.zip.