基于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战舰版) 的相关文章

  • 源和目标具有不同的 EABI 版本

    我正在尝试使用 ARM 工具链编译 so 文件 但是我不断收到这个错误 错误 源对象的 EABI 版本为 0 但目标对象的 EABI 版本为 5 我无法更改工具链中的任何内容 因为我必须使用给定的工具链 我以前从未见过这个错误 我使用了这个
  • 为 ARM 交叉编译 zlib

    我尝试为arm poky linux gnueabi交叉编译zlib 但启动 make 时出现错误 zlib 1 2 11 AR HOST ar CC HOST gcc RANLIB HOST ranlib configure prefix
  • ARM + gcc:不要使用一大块 .rodata 部分

    我想使用 gcc 编译一个程序 并针对 ARM 处理器进行链接时间优化 当我在没有 LTO 的情况下编译时 系统会被编译 当我启用 LTO 时 使用 flto 我收到以下汇编错误 错误 无效的文字常量 池需要更近 环顾网络 我发现这与我系统
  • 移动数组中的元素

    我需要一点帮助 我想将数组中的元素向上移动一个元素 以便新位置 1 包含位置 1 中的旧值 new 2 包含 old 1 依此类推 旧的最后一个值被丢弃 第一个位置的新值是我每秒给出的新值 我使用大小为 10 的数组 uint32 t TE
  • STM32 传输结束时,循环 DMA 外设到存储器的行为如何?

    我想问一下 在以下情况下 STM32 中的 DMA SPI rx 会如何表现 我有一个指定的 例如 96 字节数组 名为 A 用于存储从 SPI 接收到的数据 我打开循环 SPI DMA 它对每个字节进行操作 配置为 96 字节 是否有可能
  • Beaglebone Black 的 U-boot 无法构建 - 目标 CPU 不支持 THUMB 指令

    我正在尝试按照 Chris Simmonds 的 掌握嵌入式 Linux 编程 中的说明为 Beagle Bone Black 构建 u boot 我已经构建了交叉工具链 现在正在尝试使用该工具链构建 Das U boot 但由于不支持 T
  • 使用 NEON 优化 Cortex-A8 颜色转换

    我目前正在执行颜色转换例程 以便从 YUY2 转换为 NV12 我有一个相当快的函数 但没有我预期的那么快 主要是由于缓存未命中 void convert hd uint8 t orig uint8 t result uint32 t wi
  • AOSP 的“午餐”组合是什么意思?我需要选择什么?

    我是 Android 设备 ROM 开发的新手 无论如何 我现在正在为具有 64 位处理器的中国设备构建 AOSP 我按照 source android com 上的菜单进行操作 当我运行 午餐 命令时 终端显示 午餐菜单 选择一个组合 我
  • arm-linux-gnueabi 编译器选项

    我在用 ARM Linux gnueabi gcc在 Linux 中为 ARM 处理器编译 C 程序 但是 我不确定它编译的默认 ARM 模式是什么 例如 对于 C 代码 test c unsigned int main return 0x
  • 如何模拟ARM处理器运行环境并加载Linux内核模块?

    我尝试加载我的vmlinux into gdb并使用 ARM 内核模拟器 但我不明白为什么我会得到Undefined target command sim 这是外壳输出 arm eabi gdb vmlinux GNU gdb GDB 7
  • iPhone 3GS 上的 ARM 与 Thumb 性能比较,非浮点代码

    我想知道是否有人有关于 iPhone 3GS 上 ARM 与 Thumb 代码性能的硬性数据 特别是对于非浮点 VFP 或 NEON 代码 我知道 Thumb 模式下的浮点性能问题 更大的 ARM 指令的额外代码大小是否会在某个时刻成为性能
  • 为什么前向引用 ADR 指令在 Thumb 代码中以偶数偏移进行汇编?

    To bx对于 Thumb 函数 需要设置地址的最低有效位 GNU 作为文档states https sourceware org binutils docs as ARM Opcodes html当地址是从一个生成时这是如何工作的adr伪
  • .ko 文件是如何构建的

    我正在尝试将我自己的驱动程序移植到Beagle 板 xm arm cortex A8 在移植时我试图弄清楚如何 ko文件实际构建 在我们的Makefile我们只有一个命令来构建 o file 怎样是一个 ko文件已建立 使用Linux 2
  • aarch64 Linux 硬浮点或软浮点

    linux系统有arm64 有arm架构armv8 a 如何知道 Debian 运行的是硬浮动还是软浮动 符合 AAPCS64 GNU GCC for armv8仅提供硬浮动aarch64工具链 这与 armv7 a 的 GCC 不同 后者
  • 已编译 LKM 的互换性

    是否可以使用可加载内核模块 编译为3 0 8 mod unload ARMv5 我自制的内核 在具有版本的内核中3 0 31 gd5a18e0 SMP preempt mod unload ARMv7 安卓股票内核 该模块本身几乎不包含任何
  • Linux 内核使用的设备树文件 (dtb) 可视化工具? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我正在寻找一个可以图形化表示Linux内核中使用的硬件设备树的工具 我正在尝试了解特定 Arm 芯片组
  • 读取STM32 MCU SPI数据寄存器的值

    有很多类似的问题 但似乎没有一个问题完全相同 我正在将 STML4 MCU 连接到 6 轴传感器 LSM6DS3 我已经成功地在 I2C 中实现了所有内容 但想要 SPI 的额外速度 和 DMA 如果我能让这些第一步工作起来的话 因此 第一
  • STM32F4 定时器 - 计算周期和预分频,以生成 1 ms 延迟

    我在用STM32F407VGT6 with CubeMX 因此 我从通用定时器开始 但我被预分频值和周期值所困扰 基本上我想每隔一段时间生成一个定时器中断n 其中 n 1 2 3 ms 并执行一些任务 计算周期和预分频值的公式有很多变化 公
  • 尝试了解 ARM 二进制映像中的加载内存地址 (LMA) 和二进制文件偏移量

    我在一家ARM 皮质 M4 STM32F4xxxx 我试图了解二进制文件 elf and bin 在内存中构建并闪存 特别是关于内存位置 具体来说 我不明白的是LMA从实际的二进制文件偏移量进行 翻译 让我用一个例子来解释一下 我有一个 e
  • 在 Intel 机器 (Mac) 上构建 ARM 架构的 Docker 镜像

    我希望能够从我的 Mac 构建 ARM 的 Docker 映像 我知道我可以使用 QEMU 在 Mac 上运行 ARM 容器 但我不知道如何为 ARM 构建 现在这有点复杂 我相信在不久的将来 docker 会让这一切变得更容易 基本上 您

随机推荐

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

    判断自己的树莓派能不能上网 用这条命令试试 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