基于FREERTOS系统的LWIP协议移植(STM32F1战舰版)


本次项目开发平台为正点原子的STM32F1战舰版
(本次博客也是对上一博客的具体说明)

参考文献

1.正点原子-FREERTOS的开发教程
2.正点原子-基于ucos系统的LWIP协议移植开发教程
3.STM32嵌入式开发教程指南—[李老师]

前言

FREERTOS是由safeRTOS衍生的一套操作系统,由Richard Barry于2002年开发完成的,具有源代码公开、可移植、易裁剪且功能全面的特点,能移植到很多内核中,它要求的配置低,但运行效率高。
LWIP是一款由瑞士计算机科学家的Adam等设计研发的轻量级TCP/IP协议栈,具备TCP/IP的主要功能,主要优点是内存使用率低、代码空间小,适用于资源紧张的嵌入式系统中使用。
本课题主要是在FREERTOS系统的上移植LWIP协议,并对其进行分析,最终测试PC端能否与开发板进行通信。

源码链接

https://github.com/zht1217/FREERTOS-and-LWIP

FREERTOS系统介绍

FREERTOS作为一个轻量级嵌入式操作系统,提供了一个高层次的可信任代码。源代码以C开发,系统实现的任务数量没有限制,FREERTOS内核支持优先级调度算法,每个任务可根据重要程度的不同赋予一定的优先级,CPU总是让处于就绪态的、优先级最高的任务先运行。FREERTOS内核同时支持轮换调度算法,系统允许不同的任务使用相同的优先级,在没有更高优先级任务就绪的情况下,同一优先级的任务共享CPU的使用时间。
此外,FREERTOS还具有强大的执行跟踪功能、堆栈溢出检测、互斥信号量、优先级继承权等特点,在嵌入式操作系统中是为数不多的同时具有实时性、开源性、可靠性、易用性、多平台支持等特点的嵌入式操作系统。
FREERTOS提供的功能包括:任务管理、时间管理、消息队列、内存管理、记录功能等,可基本满足较小系统的需要。

FREERTOS系统之API函数

本章节主要介绍FREERTOS中常用的几个API函数,读者不必深究函数的细节,只需要知道参数对任务行为的影响即可,即可满足平时的开发。

1.创建任务函数xTaskCreate()

	BaseType_t xTaskCreate(	TaskFunction_t pxTaskCode,
							const char * const pcName,
							const uint16_t usStackDepth,
							void * const pvParameters,
							UBaseType_t uxPriority,
							TaskHandle_t * const pxCreatedTask )

FREERTOS采用xTaskCreate()来进行任务的创建。函数原型如上图所示,因为是永不退出的c函数,以死循环实现。参数pxTaskCode为指向任务的实现函数的指针,与函数名相同;pcName为任务名称,该参数对任务没影响;内核在创建任务时为其分配唯一的堆栈空间,usStackDepth指示内核为其分配空间容量,需要注意的是,该参数单位为字(4字节),例如,此处为10,那么实际分配的堆栈空间为40字节;pvParameters为传递任务函数的参数;uxPriority为任务执行的优先级,取值范围为0-configMAX_PRIORITIES-1);pxCreateTask为该任务的句柄,其他的API可以通过该句柄对该任务进行引用,例如改变任务优先级或删除任务。

2.删除任务函数xTaskDelete()

void vTaskDelete( TaskHandle_t xTaskToDelete )

函数原型如上图,被删除的任务不再存在,也就是说不再进入运行态。任务被删除后就不能再使用此任务句柄;此外,只有内核为任务分配的内存空间才会在任务被删除后自动回收。任务自己占用的内存或资源需要由应用程序自己显示的释放。

3.创建二值信号量函数xSemaphoreCreateBinary()

#define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )

函数原型如上图,新版本采用上述函数来创建,此函数默认创建的二值信号量为空,可以看到,此函数也是一个宏。

4.获取信号量函数xSemaphoreTake()


#define xSemaphoreTake( xSemaphore, xBlockTime )		xQueueGenericReceive( ( QueueHandle_t ) ( xSemaphore ), NULL, ( xBlockTime ), pdFALSE )

函数原型如上图,可以看到此函数也是一个宏,xSemaphore为要获取的信号量句柄,xBlockTime为阻塞时间。

5.释放信号量函数xSemaphoreGive()、xSemaphoreGiveISR()

#define xSemaphoreGive( xSemaphore )		xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )

此函数用于释放二值信号量、计数、互斥信号量等,xSemaphore为要释放的句柄。

#define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken )	xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ), ( pxHigherPriorityTaskWoken ) )

此函数为上面函数的特殊形式,只用于中断例程中,xSemaphore为要释放的信号量;对于信号量来说,可能有不只一个任务处于阻塞状态,调用此函数会让信号量有效,所以会让其中一个等待任务切换切出阻塞态。如果调用此函数使一个任务解除阻塞态,并且此任务优先级高于当前任务(被中断的任务),那么pxHigherPriorityTaskWoken会设为pdTURE,如果已经设为pdTURE,则中断退出前应当进行一次上下文切换,这样才能保证中断直接返回就绪态任务中优先级最高的任务。

6.创建互斥信号量函数xSemaphoreCreateMutex()

#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )

函数原型如上图,互斥量是一种特殊的二值信号量,用于控制在两个或多个任务间访问共享资源。用于互斥的信号量用完之后必须归还,而二值信号量用于同步之后便丢弃。返回值为NULL,表示创建失败,原因是内存空间不足,返回非NULL创建成功。

FREERTOS系统移植

FREERTOS的实现主要由list.c、queue.c、croutine.c和task.c4个文件来完成。List.c是一个链表的实现,主要供给内核调度器使用;queue.c是一个队列的实现,支持中断环境和信号量控制;croutine.c和task.c是两种任务的组织实现。
因此,FREERTOS在STM32上的移植主要在三个文件实现,一个portmacro.h文件定义编译器相关的数据类型和中断处理的宏定义;一个port.c文件市场实现任务的堆栈初始化、系统心跳的管理和任务请求;一个portasm.s实现具体的任务切换。此移植具体教程不是本手册重点,具体移植过程请参考正点原子教程。

LWIP协议介绍

LWIP是瑞典计算机学院的Adam等开发的一个小型的TCP/IP协议栈。有无操作系统的支持都可以运行,LWIP协议在保证TCP协议主要功能的基础上减少对RAM的占用,它只需要十几KB的RAM和40K左右的ROM就可以运行,因此其适合在低端的嵌入式系统中使用。
LWIP在设计之初,设计者无法预测LWIP运行的环境是怎么样的,而且世界上操作系统那么多,根本没法统一,而如果 LWIP要运行在操作系统环境中,那么就必须产生依赖,即 LWIP需要依赖操作系统自身的通信机制,如信号量、互斥量、消息队列(邮箱)等,所以LWIP设计者在设计的时候就提供一套与操作系统相关的接口,由用户根据操作系统的不同进行移植,这样子就能降低耦合度,让 LWIP内核不受其运行的环境影响,因为往往用户并不能完全了解内核的运作,所以只需要用户在移植的时候对LWIP提供的接口根据不同操作系统进行完善即可。
LWIP协议提供了三种应用程序的API接口,即RAW接口,RAW API是LWIP的一大特色, 在没有操作系统支持的裸机环境中,只能使用这种 API 进行开发,同时这种 API 也可以用在操作系统环境中;NETCONN API 是基于操作系统的 IPC 机制(即信号量和邮箱机制)实现的,它的设计将LWIP内核代码和网络应用程序分离成了独立的线程;SOCKET API,即套接字,它对网络连接进行了高级的抽象,使得用户可以像操作文件一样操作网络连接,LWIP协议的SOCKET API是基于NETCONN API的。因此,本次开发中,会应用NETCONN API来进行协议的移植实现。

LWIP协议移植

本次LWIP协议移植分为两部分,即以太网接口ethernetif.c的移植和操作系统模拟层sys_arch.c的移植。
先看重头戏,sys_arch.c的移植,在LWIP协议源码\lwip-1.4.1\doc目录下的sys_arch.txt文档对相关的接口已经做出了说明解释。

1. SYS_ARCH.C文档讲解

现在,我们先看看具体的函数功能如何实现。具体函数功能如下:

  1. 创建新的消息邮箱在这里插入图片描述

在sys_arch.txt中是这么说的,如上,我们需要为最大的元素size创建一个空邮箱,元素作为一个指针类型存储在邮箱中,在FREERTOS系统中没有邮箱这个概念,所以用消息队列替代,其实本质都一样,这里我们定义了邮箱大小为MAX_QUEUE_ENTRIES,如果邮箱被创建,那么会返回ERR_OK。直接看代码实现,比较容易理解。

err_t  sys_mbox_new(sys_mbox_t *mbox,int size)
{
    
	if(size>MAX_QUEUE_ENTRIES)size=MAX_QUEUE_ENTRIES;		//消息队列最多容纳MAX_QUEUE_ENTRIES消息数目
 	mbox->xQueue = xQueueCreate(size, sizeof(void *));  		//创建消息队列,该消息队列存放指针
	LWIP_ASSERT("OSQCreate",mbox->xQueue!=NULL); 
	if(mbox->xQueue!=NULL)return ERR_OK;  //返回ERR_OK,表示消息队列创建成功 ERR_OK=0
	else 
		return ERR_MEM;  				//消息队列创建错误
} 

2.等待邮箱中的消息,文档说明如下

在这里插入图片描述

从消息队列取出一条消息,该函数是一个阻塞函数。调用该函数的线程若未取到消息,则形参timeout所指顶的时间内,该线程被阻塞。当超时timeout所指定的时间后,该线程恢复至就绪态。若timeout为0,则调用该函数的线程一直被阻塞,直到收到消息。
(这里对FREERTOS中的几种任务状态简单的说明一下,首先运行态-----顾名思义,当前这个任务处于运行状态,单核情况下,当前是我在使用处理器,没你们的事。接着是就绪态-------表面意思,万事俱备只欠东风,就差cpu了,为啥没运行呢,前面有个比我NB的在运行呢(也就是优先级高或同一优先级),没轮到我。然后是阻塞态----就是这个任务正在等待某个事件,比如一个线程调用了阻塞式的I/O方法,调用了某个对象的wait()方法,或调用等,都会使线程进入阻塞状态,任务进入阻塞态以等待两种不同的事件即定时和同步。最后是挂起态-----显而易见,就是不做任何处理,除非其他任务或中断唤醒,当然,大部分应用程序不会用到挂起态。),具体的函数实现如下:

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->xQueue,&(*msg),timeout/portTICK_RATE_MS))
        {
            EndTime = xTaskGetTickCount();
            Elapsed = (EndTime - StartTime) * portTICK_RATE_MS;
            return Elapsed;
        }
        else  //超时就退出
        {
            *msg = NULL;
            return SYS_ARCH_TIMEOUT;
        }
        
    }
    else
    {
        while (pdTRUE != xQueueReceive(mbox->xQueue, &(*msg),portMAX_DELAY))
        {
        }
        EndTime = xTaskGetTickCount();
        Elapsed = (EndTime - StartTime) * portTICK_RATE_MS;
        return Elapsed;
    }
}
    

3.尝试获取消息
在这里插入图片描述

从消息队列尝试取出一条消息,该函数是一个非阻塞函数,当取到消息返回成功,否则立即退出,返回队列为空。实现如下:

u32_t sys_arch_mbox_tryfetch(sys_mbox_t *mbox, void **msg)
{
    void* dummyptr;
    if (msg == NULL)
    {
        msg = &dummyptr;
    }
     if (pdTRUE == xQueueReceive(mbox->xQueue,&(*msg),0))
     {
         return ERR_OK;
     }
     else
     {
         return SYS_MBOX_EMPTY;
     }
}

4.创建信号量
在这里插入图片描述

创建一个信号量,创建成功,返回ERR_OK,否则返回 ERR_MEM,函数实现如下:

err_t sys_sem_new(sys_sem_t* sem, u8_t count)
{  

    if (count <= 1)
    {
        *sem = xSemaphoreCreateBinary();
        if (count == 1)
        {
            sys_sem_signal(sem);
        }
        
    }
    else
    {
        *sem= xSemaphoreCreateCounting(count,count);
    }
        if (*sem == NULL)
        {
      
        return ERR_MEM;
        }
        else 
        {
        return ERR_OK;
        }    
        
} 

5.等待一个信息量,说明文档如下:
在这里插入图片描述

该函数是一个阻塞函数。调用该函数的线程在形参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;
        }
        else
        {
            return SYS_ARCH_TIMEOUT;
        }
    }
    else
    {
        while (xSemaphoreTake(*sem, portMAX_DELAY) != pdTRUE)
        {
        }
        EndTime = xTaskGetTickCount(); 
        Elapsed = (EndTime - StartTime) * portTICK_RATE_MS;
        return Elapsed;
    }
}

6.释放信号量,以及后面的删除等比较简单,故不在作说明。

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

7.删除信号量,函数功能如下:

void sys_sem_free(sys_sem_t *sem)
{
	vSemaphoreDelete(*sem); 
	*sem = NULL;
}

8.查询一个信号量的状态,无效或有效,函数功能如下:

int sys_sem_valid(sys_sem_t *sem)
{
	if(*sem != NULL)
    return 1;
  else
    return 0;		
} 

9.设置一个信号量无效,实现如下:

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

10.系统初始化函数:

void sys_init(void)
{     
} 

11.接下来是将LWIP单独作为一个线程,创建一个新线程:

在这里插入图片描述
其中形参name指定线程的名字,thread对应的该线程的函数,args为线程的形参,stacksize为该线程对应的堆栈的大小,prio对应的该线程的优先级。

TaskHandle_t LWIP_ThreadHandler;
sys_thread_t sys_thread_new(const char *name,  void (* thread)(void *arg), void *arg, int stacksize, int prio)
{
	taskENTER_CRITICAL();  //进入临界区 
	xTaskCreate((TaskFunction_t)thread,
						name,
						(uint16_t     )stacksize,
						(void*        )NULL,
						(UBaseType_t  )prio,
						(TaskHandle_t*)&LWIP_ThreadHandler);//创建TCP IP内核任务 
	taskEXIT_CRITICAL();  //退出临界区
	return 0;
} 

12.获取系统的时间:

u32_t sys_now(void)
{
	u32_t lwip_time;
	lwip_time=(xTaskGetTickCount()*1000/configTICK_RATE_HZ+1);//将节拍数转换为LWIP的时间MS
	return lwip_time; 		//返回lwip_time;
}

13.保护临界区资源及访问临界区资源。
在这里插入图片描述
临界区资源就是指会被多个任务访问到的公共资源,各进程采取互斥的方式,实现共享的资源。,而临界区就是进程中访问临界资源的那段代码,也即是在 taskENTER_CRITICAL()和taskEXIT_CRITICAL()之间的代码,每次只允许一个进程进入临界区,无论硬件还是软件资源都必须互斥的进行访问。
函数实现如下:

sys_prot_t sys_arch_protect(void)
{
    vPortEnterCritical();
    return 1;
}
void sys_arch_unprotect(sys_prot_t pval)
{
    (void) pval;
    vPortExitCritical();
}

到这里,关于sys_arch.c文件内容全部完成。

2.dm9000.c文档讲解

这里不在赘述了,可参照正点原子教程在相应位置添加信号量,FREERTOS系统互斥信号量的添加在下方贴出。
在这里插入图片描述

Dm9000的中断处理函数也是参照正点原子在相应的位置修改即可,修改为的内容在下方已贴出。
在这里插入图片描述

3.ethernetif.c文档讲解

Ethnernetif.c是LWIP协议栈和STM32F103网络驱动程序之间的接口,它主要包含ethernetif_init、ethernetif_input、low_level_input、low_level_output等函数,接下来会逐渐介绍。

1.ethernetif_init函数
这个函数是LWIP底层网络接口的初始化函数,指定了网络接口netif对应的主机名及网卡描述,并指定了该网卡的MAC地址,同时,该函数还指定了netif的发送数据报文函数,并调用了网络底层驱动初始化函数low_level_init对网络底层进行初始化。源代码如下:

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.low_level_init函数
这个函数是网卡初始化函数,设定网卡的物理地址以及每帧最大传输字节数据等。源码如下:

static err_t low_level_init(struct netif *netif)
{
	//INT8U err;
	netif->hwaddr_len = ETHARP_HWADDR_LEN; //设置MAC地址长度,为6个字节
	//初始化MAC地址,设置什么地址由用户自己设置,但是不能与网络中其他设备MAC地址重复
	netif->hwaddr[0]=lwipdev.mac[0]; 
	netif->hwaddr[1]=lwipdev.mac[1]; 
	netif->hwaddr[2]=lwipdev.mac[2];
	netif->hwaddr[3]=lwipdev.mac[3];
	netif->hwaddr[4]=lwipdev.mac[4];
	netif->hwaddr[5]=lwipdev.mac[5];
	netif->mtu=1500; //最大允许传输单元,允许该网卡广播和ARP功能
	netif->flags = NETIF_FLAG_BROADCAST|NETIF_FLAG_ETHARP|NETIF_FLAG_LINK_UP; 
	return ERR_OK;
} 

3.ethernetif_input函数
因为使用了操作系统,我们在此处将接收数据函数独立成为一个网卡接收线程,这样子在收到数据时候采取处理数据,然后递交给内核线程。需要注意的是,网卡接收线程是需要通过信号量机制去接收数据的,一般来说我们都是使用中断的方式去获取网络数据包,当产生中断的时候,我们不会在中断中处理数据,一般在中断中打个标志位,告诉对应的线程去处理,也就是我们的网卡接收线程去处理数据,那么会通过信号量进行同步,当网卡收到数据就会产生中断然后释放信号量,然后线程从阻塞中恢复,从网卡中读取数据,向上递交。因此,该函数用于从底层物理网卡读取报文,并将报文上传LWIP协议栈函数ethernet_input进行处理,该函数会请求信号量dm900input,一旦请求到该信号量那么说明有数据收到,则会调用low_level_input()进行数据接收。
函数源码如下:

err_t ethernetif_input(struct netif *netif)
{
	unsigned char _err;
	err_t err;
	struct pbuf *p;
	while(1)
	{
        if(dm9000input!=NULL)
        {
            xSemaphoreTake(dm9000input,portMAX_DELAY);//死等dm9000input信号
        }
        else
        {
            vTaskDelay(100);
        }
			while(1)
			{
				p=low_level_input(netif);   //调用low_level_input函数接收数据
				if(p!=NULL)
				{
					err=netif->input(p, netif); //调用netif结构体中的input字段(一个函数)来处理数据包
					if(err!=ERR_OK)
					{
						LWIP_DEBUGF(NETIF_DEBUG,("ethernetif_input: IP input error\n"));
						pbuf_free(p);
						p = NULL;
					} 
				}else break; 
			}
	}
} 

4.low_level_input()函数
此函数主要是从DM9000中接受数据,通过DM9000_Receive_Packet()函数来实现。

static struct pbuf * low_level_input(struct netif *netif)
{  
	struct pbuf *p;
	p=DM9000_Receive_Packet();
	return p;
}

5.low_level_output()函数
用发送数据,通过DM9000发送数据。

static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
	DM9000_SendPacket(p);//发送数据
	return ERR_OK;
}

4.lwip_comm.c文档讲解

  1. 创建一个任务来接收数据如下:
    在这里插入图片描述
    2.接下来是lwip_comm_init()初始化函数:
    参照正点原子教程在相应的位置添加如下:

在这里插入图片描述

创建DM9000任务:

在这里插入图片描述
最后创建lwip_comm_dhcp任务:

在这里插入图片描述

至此移植已经全部完成。

5.Main函数实现

最后,完成main函数的编写,主要完成亮灯任务和运行lwip获取地址任务。实现代码部分如下:

int main(void)
{ 

   delay_init();    //延时函数初始化	  
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); 	//设置NVIC中断分组
    uart_init(115200);  //串口初始化为115200
    LED_Init();     //LED端口初始化
    LCD_Init(); //初始化LCD
    KEY_Init(); //初始化按键
   // usmart_dev.init(72);    //初始化USMART		
    FSMC_SRAM_Init();//初始化外部SRAM
    //DM9000_Init();
    my_mem_init(SRAMIN);        //初始化内部内存池
    my_mem_init(SRAMEX);        //初始化外部内存池
   
    //创建开始任务
    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();          //开启任务调度
}

//开始任务任务函数
void start_task(void *pvParameters)
{
     lwip_comm_init();    //lwip初始化
    
    taskENTER_CRITICAL();           //进入临界区

    #if LWIP_DHCP
    lwip_comm_dhcp_creat(); //创建DHCP任务
    #endif
              //创建led任务
    xTaskCreate((TaskFunction_t )led_task,             
                (const char*    )"led_task",           
                (uint16_t       )LED_STK_SIZE,        
                (void*          )NULL,                  
                (UBaseType_t    )LED_TASK_PRIO,        
                (TaskHandle_t*  )&LedTask_Handler); 
    //创建display任务
    xTaskCreate((TaskFunction_t )display_task,             
                (const char*    )"display_task",           
                (uint16_t       )DISPALY_STK_SIZE,        
                (void*          )NULL,                  
                (UBaseType_t    )DISPALY_TASK_PRIO,        
                (TaskHandle_t*  )&DisplayTask_Handler);   

      
    vTaskDelete(StartTask_Handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
               
}
xSemaphoreCreateMutex()

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

//led任务
void led_task(void *pdata)
{
    while(1)
    {
        LED0 = !LED0;
        vTaskDelay(1000);
        LED1 =!LED1;
        vTaskDelay(1000);
      
    }
}

最终测试结果

串口打印:
在这里插入图片描述
Pc端ping开发板如下:

在这里插入图片描述
同一网络内可以ping通,移植成功。
至此全篇结束。

  • 16
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值