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),应用于多种领域,例如交换机、网络接口卡等。

主要特性
- 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 -------------------------> PA2ETH_MDC --------------------------> PC1ETH_RMII_REF_CLK------------------> PA1ETH_RMII_CRS_DV ------------------> PA7ETH_RMII_RXD0 --------------------> PC4ETH_RMII_RXD1 --------------------> PC5ETH_RMII_TX_EN -------------------> PG11ETH_RMII_TXD0 --------------------> PG13ETH_RMII_TXD1 --------------------> PG14ETH_RESET-------------------------> PD3*///配置PA1 PA2 PA7GPIO_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 PC5GPIO_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; //硬件复位LAN8720delay_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_DHCPlwip_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;elsereturn 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 6static 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_STATSlwip_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 fullresult = ERR_MEM;#if SYS_STATSlwip_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 arbitraryEndTime = 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;elsereturn 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;elsereturn 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 listfor(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 createds_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);}}// Errorreturn 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 deleteds_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 debuggingprintf(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.
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
