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

2023-10-29


本次项目开发平台为正点原子的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通,移植成功。
至此全篇结束。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • 手臂 g++ 中缺少一些东西

    我安装了 CodeSourcery g 工具链并尝试编译一个简单的 hello world 程序 include
  • ARM 汇编 SOS 中的 64 位除法

    我正在计算 16 个 64 位数字相加的平均值 我认为我已经正确完成了所有加法 但现在我需要弄清楚如何将 64 位数字除以 16 但我被困住了 任何帮助都会非常感谢你 到目前为止 这是我的代码 tableSize EQU 16 sum EQ
  • arm64 汇编:LDP 与 LD4 执行时间

    假设我想用连续内存位置的值加载四个连续的 aarch64 向量寄存器 一种方法是 ldp q0 q1 x0 ldp q2 q3 x0 32 根据ARM优化指南 https static docs arm com uan0016 a cort
  • 如何在 ARM 架构上从 RAM 运行代码

    我正在对 ARM Cortex R4 进行编程 并且有一些二进制文件 我想从 TCRAM 执行它们 只是为了看看性能的提升是否足够好 我知道我必须编写一个函数来将二进制文件复制到 RAM 这可以通过链接器脚本来完成 并且知道二进制文件的大小
  • 警告:可加载部分“my_section”位于 ELF 段之外

    我使用 Cortex R4 的 Arm Compiler v6 9 构建了一个 axf elf 文件 但是 当我使用 Arm MCU Eclipse J link GDB 插件将其加载到目标时 它无法加载我的段的初始化数据 如果我使用 Se
  • 适用于arm(cortex-m3)的位置独立可执行文件(-pie)

    我正在使用codesourcery g lite 基于gcc4 7 2版本 为stm32 Cortex m3 编程 我希望动态加载可执行文件 我知道我有两个选择 1 可重定位的elf 需要一个elf解析器 2 具有全局偏移寄存器的位置无关代
  • 如何使用 gcc 编译代码和 ARM Cortex A8 目标进行调用图分析?

    我对这个已经咬牙切齿了 我需要在 ARM 板上进行分析并需要查看调用图 我尝试使用 OProfile Kernel perf 和 Google 性能工具 一切正常 但不输出任何调用图信息 这使我得出结论 我没有正确编译代码 我在编译 C 代
  • ARM Cortex A8 PMNC 读取在启用后也给出 0.. 有什么想法/建议吗?

    MODULE LICENSE GPL MODULE DESCRIPTION user mode access to performance registers int init arm init void unsigned int valu
  • ARM 中只有两个操作数的 ADD 或 SUB

    我正在学习ARM汇编语言 我读过 ADD 应该有 3 个操作数 然而 我见过很多案例 现实中只有两种 例如 STR R1 SP 0x20 var 1C LDR R1 a lua 0x1DE4E6 MOVS R0 R4 haystack AD
  • 读取和打印手臂组件中的字符串

    我正在使用 ARMSim 刚刚开始学习汇编 所以如果我看起来一无所知 请原谅我 但我正在尝试从输入文件中读取字符串 然后将其打印到输出屏幕 到目前为止我有 equ SWI Open 0x66 open a file equ SWI Clos
  • 交叉编译 Qt 4.7 时出现“非法指令”

    我已经在这个问题上苦苦挣扎了一个多星期了 但仍然找不到解决方案 我正在尝试为 ARM 设备交叉编译 Qt 4 7 嵌入式开源版本 构建过程本身可以顺利完成 但生成的二进制文件似乎包含处理器无法理解的指令 构建主机是 i386 上的 Debi
  • 移动数组中的元素

    我需要一点帮助 我想将数组中的元素向上移动一个元素 以便新位置 1 包含位置 1 中的旧值 new 2 包含 old 1 依此类推 旧的最后一个值被丢弃 第一个位置的新值是我每秒给出的新值 我使用大小为 10 的数组 uint32 t TE
  • 在 Intel 机器上构建 Apple Silicon 二进制文件

    如何在 macOS 11 Intel 上编译 C 项目以在 Silicon 上运行 我当前的构建脚本很简单 configure make sudo make install 我尝试过使用 host and target标志与aarch64
  • 了解 U-Boot 内存占用

    我不明白加载 U Boot 时 RAM 中发生了什么 我正在开发 Xilinx Zynq ZC702 评估套件 并尝试使用 U Boot 在其上加载 Linux 内核 于是我使用Xilinx工具Vivado和SDK生成了一个BOOT bin
  • AOSP 的“午餐”组合是什么意思?我需要选择什么?

    我是 Android 设备 ROM 开发的新手 无论如何 我现在正在为具有 64 位处理器的中国设备构建 AOSP 我按照 source android com 上的菜单进行操作 当我运行 午餐 命令时 终端显示 午餐菜单 选择一个组合 我
  • saber sd 如何在没有 SPL 的情况下直接从 uboot 启动

    sabre sd 基于 imx 6 最大内部 RAM 约为 150Kb 然而 uboot 足够大 可以容纳在这个空间中 在这个场景中事情是如何进行的 https community freescale com docs DOC 95015
  • 为什么 GCC 交叉编译不构建“crti.o”?

    在尝试为arm构建gcc 4 x x交叉编译器时 我陷入了缺失的困境crti o文件在 BUILD DIR gcc子目录 An strace在顶层Makefile表明编译后的xgcc正在调用交联器ld with crti o 作为一个论点
  • GCC C++ (ARM) 和指向结构体字段的 const 指针

    假设有一个简单的测试代码 typedef struct int first int second int third type t define ADDRESS 0x12345678 define REGISTER type t ADDRE
  • 如何获取结构体中任意成员的位位置

    如何获取结构体中任意成员的位位置 在示例中 gt typedef struct BitExamStruct unsigned int v1 3 unsigned int v2 4 unsigned int v3 5 unsigned int
  • 设备树不匹配:.probe 从未被调用

    我无法理解设备树的工作原理 或者具体来说为什么该驱动程序无法初始化 这是在 android 版本 3 10 的 rockchip 供应商内核中 驱动程序 看门狗 rk29 wdt c 为了可读性而减少 static const struct

随机推荐

  • 树莓派不能上网解决方案

    判断自己的树莓派能不能上网 用这条命令试试 ping www baidu com ping www baidu com Temporary failure in name resolution 出现了以上错误 说明树莓派不能上网 解决思路
  • css3实现hover颜色,背景色,宽度等平滑变动(transition)

  • webpack 和html-webpack-plugin版本对应问题

    为了实现功能 配置生成预览页面 以前是 要实现的效果是 直接打开设置的首页 这里由于版本对应问题 一直报错 当前版本 devDependencies html webpack plugin 2 30 1 webpack 3 6 0 webp
  • idea使用lombok插件不能生效的原因

    要成功的使用lombok插件 需要3个步骤 一 需要先在idea中下载Lombok plugin 点击File gt settings gt plugins gt 然后点击以下图中所示 接着 在输入框输入lombok进行搜索 之后点击安装便
  • 粤嵌GEC6818-学习笔记2-屏幕相关及音频播放

    这里写目录标题 LCD屏幕 简介 操作 打开屏幕 映射 如何让plcd指向屏幕首地址 BMP图片的解析 把一张BMP格式的图片显示在我们的开发板上 触摸板的相关操作 练习 获取屏幕坐标 线程进程 练习 创建广告播放的一个线程 音频播放 播放
  • STM32——GPIO输入——按键检测

    硬件介绍 当按键置空时 IO接地 按键按下之后 IO口接通3 3V高电压 电流比较大 为了避免损坏IO 这里需要加装一个限流电阻 可以看到IO口是默认低电平 按键按下后产生一个上升沿 和平常的电路设计不太一样 这是因为PA0还具有一种自动唤
  • centos7网卡配置参数详细

    CentOS 7 中的网卡配置参数通常位于 etc sysconfig network scripts ifcfg
  • Python爬虫从入门到精通:(1)爬虫基础简介_Python涛哥

    第一章 爬虫基础简介 爬虫概述 前戏 你是否在夜深人静的时候 想看一些会让你更睡不着的图片 你是否在考试或者面试前夕 想看一些具有针对性的题目和面试题 你是否想在杂乱的网络世界获取你想要的数据 爬虫的价值 实际应用 就业 什么是爬虫 通过编
  • TensorFlow学习(4) 学习率调度 & 正则化

    1 学习率调度 恒定高学习率训练可能会发散 低学习率会收敛到最优解但是会花费大量时间 1 1 常用的学习率调度及其概念 幂调度 指数调度 分段调度 性能调度 1 2 实现幂调度 在创建优化器时 设置超参数decay 使用示例 optimiz
  • Python 面向对象程序设计类的使用、继承等

    这个实验主要通过了解对象 类 封装 继承 方法 构造函数和析构函数等面向对象的程序设计的基本概念 掌握 Python 类的定义 类的方法 类的继承等 在做实验时要注意 init 应该是4个下划线 前后各两个 也要注意自己的属性条件 并且也可
  • 对 tcp out-of-window 的安全建议

    TCP 收到一个 out of window 报文后会立即回复一个 ack 这是 RFC793 中 SEGMENT ARRIVES 段的要求 但这是为什么 难道不是默默丢弃才对吗 对 oow 报文回复 ack 岂不是把正确的 ack 号回过
  • L2-041 插松枝

    include
  • 复习1: 深度学习优化算法 SGD -> SGDM -> NAG ->AdaGrad -> AdaDelta -> Adam -> Nadam 详细解释 + 如何选择优化算法

    深度学习优化算法经历了 SGD gt SGDM gt NAG gt AdaGrad gt AdaDelta gt Adam gt Nadam 这样的发展历程 优化器其实就是采用何种方式对损失函数进行迭代优化 也就是有一个卷积参数我们初始化了
  • 无向图染色

    无向图染色 给一个无向图染色 可以填红黑两种颜色 必须保证相邻两个节点不能同时为红色 输出有多少种不同的染色方案 输入描述 第 行输入M 图中节点数 N 边数 后续N行格式为 V1V2表示一个V1到V2的边 数据范围 1 lt M lt 1
  • 研发工具链介绍

    本节课程为 研发工具链介绍 我们将主要学习三个工具 项目管理工具 iCafe 代码管理工具 iCode 交付平台 iPipe 此外我们知道 管理实践具有以下三个特点 用 精益 指引产品规划 用 敏捷 加速迭代开发 用 数据 驱动持续改进 而
  • 那些在一个公司死磕5年以上的测试,最后都怎么样了?

    2023年的测试市场是崩溃的 即使是老员工 也要面对裁员 降薪 外包化 没前途 薪资不过20k 没有面试 找不到工作 确实都客观存在 但与此同时 也有不少卷赢同行拿高薪的案例 因为只要互联网存在 测试就是刚需 只是需要更卷一些了 这里我准备
  • MSRA实习申请经验分享

    MSRA实习申请经验分享 自我介绍 简历投递 面试 成败关键点 自我介绍 博主目前大四 因为大四下没啥事想申请到MSRA实习半年 不久前成功申请到了MSRA的实习 这里简单分享一下经验 首先自我介绍一下 本人本科是国内某top10的985高
  • springboot简单整合logback日志框架

    引入依赖 实际上我们只需要引入springboot的的web依赖就可以了 springboot是默认整合logback的依赖的 编写xml文件 xml文件默认叫做logback xml 放在resource目录下就可以
  • python画桃心表白

    python用turtle画简单图案比较方便 大一学python的turtle模块时 记得要画各种图案 如国旗 桃心等等图案 期末课程设计时有可能还会遇到画54张扑克牌 当初室友就被迫选了这道题 下面是程序 import turtle im
  • 基于FREERTOS系统的LWIP协议移植(STM32F1战舰版)

    文章目录 参考文献 前言 源码链接 FREERTOS系统介绍 FREERTOS系统之API函数 1 创建任务函数xTaskCreate 2 删除任务函数xTaskDelete 3 创建二值信号量函数xSemaphoreCreateBinar