FreeRTOS任务基础

本文介绍了FreeRTOS任务相关知识。为解决单片机程序实时性差等问题引入操作系统,阐述了任务特性、状态、优先级、堆栈、控制块及列表和列表项。还介绍了任务创建和删除函数,包括动态和静态内存分配方法,并给出示例,最后进行下载测试,展示了任务运行和删除效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. FreeRTOS任务

在接触操作系统前,单片机程序的编写采用的是在一个while死循环里面反复的执行,在循环中调用相应的函数来完成对应的操作。在循环中各个函数按照排队顺序轮流执行,不管这个功能模块有多紧急,没轮到就只能等着,因此整个系统的实时性差。对于稍大一点的嵌入式应用程序,显然是不适用的。除实时性受限制之外,各个功能模块的先后顺序安排也增加了程序设计的复杂度和不可预见性。

为了解决系统的固有问题,引入了操作系统。几乎所有的操作系统都采用多任务系统,FreeRTOS也不例外。在多任务系统中,多个任务并不在同一时间执行,每个任务执行的时间都很短,且CPU的执行速度很快,看起来像在同一时刻执行了很多个任务一样。哪个任务先执行,是由操作系统进行调度的。FreeRTOS支持抢占式调度、时间片调度和合作式调度。抢占式多任务系统执行过程如图所示。

 在抢占式多任务系统中,高优先级任务可以打断低优先级任务获得CPU使用权,而更高优先级任务又可以打断高优先级任务,这样就保证了紧急任务的优先执行。同时,中断可以打断所有优先级的任务,在中断返回时,会选择最高优先级任务来运行。一般情况下,具有高优先级的任务在事务处理完成后,会调用一个等待下一个事件发生的阻塞函数,主动让出CPU 使用权,让优先级比它低的任务有被执行的机会。

1.1 任务的特性

每个实时应用都可作为FreeRTOS的一个独立任务,每个任务都有自己的运行环境,为了能在任务之间进行切换,需要使用堆栈来保存任务的上下文环境,以便调度器在恢复任务时能使该任务从被切出的地方继续执行。一般情况下,任务具有以下特性。

  1. 简单,每个任务尽可能完成简单的操作。
  2. 没有使用限制,每个任务都有可能被调度器选中运行。
  3. 支持优先级,不同任务可以设置不同的优先级。
  4. 支持抢占,高优先级任务能抢占(打断)低优先级任务。
  5. 每个任务都有堆栈,将导致RAM使用量增大。

1.2 任务的状态

 FreeRTOS的任务有4种状态,分别是运行态、就绪态、阻塞态和挂起态。
运行态:若一个任务正在运行,就说这个任务处于运行态。处于运行态的任务就是当前正在使用处理器的任务。如果使用的是单核处理器,那么无论在什么时候都只有一个任务处于运行态。
就绪态:处于就绪态的任务是指那些已经准备就绪,可以运行的任务,但当前未被调度器选中从而未运行,因为有其他优先级更高的任务正在运行。
阻塞态:若一个任务当前正在等待某个外部事件发生,就说这个任务处于阻塞态。任务进入阻塞态会有一个超时时间,如果超过这个超时时间,任务就会退出阻塞态,即使所等待的事件还没有发生。
挂起态:挂起态类似于阻塞态,但任务进入挂起态后不能被调度器选中从而进入运行态,而且进入挂起态的任务没有超时时间。
FreeRTOS的任务永远处于这4种状态之一,各状态之间可以在等待事件或API函数调用中进行转换。

1.3 任务的优先级

 任务可以设置不同的优先级,FreeRTOS对任务的优先级数量没有限制。优先级值越大,代表优先级越高这与使用ARM Cortex-M内核的STM32微控制器的中断优先级正好相反,务必高度重视。FreeRTOS 调度器能确保处于就绪态或运行态的高优先级任务获得处理器使用权。当多个任务的优先级相同时,FreeRTOS 将使用时间片调度方式,使处于就绪态且优先级相同的任务共享处理器的执行时间,前提是在配置文件中使能了时间片调度功能。

1.4 任务堆栈

任务堆栈是任务的重要组成部分。FreeRTOS之所以能正确恢复一个任务的运行就是因为有任务堆栈保驾护航。所谓堆栈,是指在存储器中按照“后进先出”的原则组织的连续数据存储空间。调度器在进行任务切换时会将任务的上下文环境(CPU寄存器值、任务中的局部变量等)保存在此任务的任务堆栈中,等到该任务下次恢复运行时就会用任务堆栈中保存的数据来恢复现场,确保任务能从上次中断的地方继续正确运行。
任务堆栈是在创建任务的时候自动创建或指定的。对于动态任务,任务堆栈由动态任务创建函数xTaskCreat()自动创建,但要传入指定任务堆栈大小的参数。动态任务创建函数xTaskCreat()的第3个参数就指明了要自动创建的任务堆栈大小,其单位为word(4B)。



 BaseType_t xTaskCreate(    TaskFunction_t pvTaskCode,   /*任务函数*/
                            const char * const pcName,    /*任务名*/    
                            configSTACK_DEPTH_TYPE usStackDepth,    /*任务堆栈大小*/
                            void *pvParameters,        /*任务参数*/
                            UBaseType_t uxPriority,    /*任务优先级*/
                            TaskHandle_t *pxCreatedTask    /*任务句柄*/
                          );

如果采用静态任务创建函数xTaskCreateStati(),就要由用户定义任务堆栈,然后将堆栈首地址作为实参传入xTaskCreateStatic()函数。 

 TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode, /*任务函数*/
                                 const char * const pcName, /*任务名*/    
                                 const uint32_t ulStackDepth,  /*任务堆栈大小*/
                                 void * const pvParameters,   /*任务参数*/
                                 UBaseType_t uxPriority, /*任务优先级*/
                                 StackType_t * const puxStackBuffer, /*任务堆栈首地址*/
                                 StaticTask_t * const pxTaskBuffer ); /*任务句柄*/

 注意,任务堆栈的类型为StackType_t,在文件portmacro.h中定义,实际为uint32_t类型,即任务堆栈大小的单位为word(4B)。至于任务创建函数的使用方法,会在后续的章节中详细介绍。

1.5 任务控制块

FreeRTOS 用来记录任务堆栈指针、任务当前状态、任务优先级等一些与任务管理相关的属性表叫作任务控制块(TCB)。
任务控制块靠一个结构体类型TCB_t实现,这是新版本中的命名,在旧版本中叫
tskTCB,两者的实质一样,在文件task.c中定义。tskTCB结构体如下:

typedef struct tskTaskControlBlock 		
{
	volatile StackType_t	*pxTopOfStack;	/*任务堆栈栈顶*/

	ListItem_t			xStateListItem;	    /*任务状态列表项 */
	ListItem_t			xEventListItem;		/*事件列表项*/
	UBaseType_t			uxPriority;			/*任务优先级*/
	StackType_t			*pxStack;			/*任务堆栈首地址 */
	char				pcTaskName[ configMAX_TASK_NAME_LEN ];  /*任务名 */


	#if ( portCRITICAL_NESTING_IN_TCB == 1 )
		UBaseType_t		uxCriticalNesting;	/*临界段嵌套深度 */
	#endif

	#if ( configUSE_TRACE_FACILITY == 1 )
		UBaseType_t		uxTCBNumber;		/**/
		UBaseType_t		uxTaskNumber;		/*< Stores a number specifically for use by third party trace code. */
	#endif

	#if ( configUSE_MUTEXES == 1 )
		UBaseType_t		uxBasePriority;		/*任务原始优先级 */
		UBaseType_t		uxMutexesHeld;      /*任务获取到的互斥信号量个数 */
	#endif

	#if ( configUSE_APPLICATION_TASK_TAG == 1 )
		TaskHookFunction_t pxTaskTag;
	#endif

	#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
		void			*pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
	#endif

	#if( configGENERATE_RUN_TIME_STATS == 1 )
		uint32_t		ulRunTimeCounter;	/*任务运行时间统计 */
	#endif

	#if ( configUSE_NEWLIB_REENTRANT == 1 )
		struct	_reent xNewLib_reent;
	#endif

	#if( configUSE_TASK_NOTIFICATIONS == 1 )
		volatile uint32_t ulNotifiedValue;      /*任务通知值 */
		volatile uint8_t ucNotifyState;         /*任务通知状态 */
	#endif


	#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e731 !e9029 Macro has been consolidated for readability reasons. */
		uint8_t	ucStaticallyAllocated; 		/*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */
	#endif

	#if( INCLUDE_xTaskAbortDelay == 1 )
		uint8_t ucDelayAborted;
	#endif

	#if( configUSE_POSIX_ERRNO == 1 )
		int iTaskErrno;
	#endif

} tskTCB;

上面的tskTCB结构体类型定义中省略了部分条件编译代码,这些条件编译代码与FreeRTOS 的裁剪有关。是否使用互斥信号量、任务运行时间统计、任务通知等功能,在tskTCB 结构体类型定义中均做成条件编译代码。
任务控制块在创建任务时初始化,若任务创建成功,则任务控制块中的 pxTopOfStack指向任务堆栈栈顶,该指针随着出入栈操作不断更新,而pxStack指向任务堆栈首地址。FreeRTOS 使用列表来处理就绪、挂起、延时等任务,通过任务控制块中的任务状态列表项xStateListItem 事件列表项xEventListItem 挂接到不同的列表中来实现。例如,当某个任务处于就绪态时,调度器就会将任务状态列表项挂接到就绪列表中。事件列表项与之类似,如当在队列满的情况下任务因入队操作而阻塞时,调度器就会将事件列表项挂接到队列的等待入队列表。

1.6 列表和列表项

列表和列表项是FreeRTOS中定义的数据结构,FrccRTOS列表使用指针指向列表项。一个列表下面可能有很多个列表项,每个列表项都有一个指针指向列表。列表和列表项的定义及代码实现在文件 list.c和list.h中。

1.6.1 列表

列表用于跟踪任务。处于就绪态、挂起态、阻塞态的任务,都会被挂接到各自的列表中。列表类型名为List_t,列表在文件 list.h中的定义如下。

typedef struct xLIST
{
	listFIRST_LIST_INTEGRITY_CHECK_VALUE				/*完整性检查 */
	volatile UBaseType_t uxNumberOfItems;               /*记录列表项数量 */
	ListItem_t * configLIST_VOLATILE pxIndex;			/*指向某一个列表项的指针 */
	MiniListItem_t xListEnd;							/*迷你列表项*/
	listSECOND_LIST_INTEGRITY_CHECK_VALUE				/*完整性检查 */
} List_t;

列表结构体中的第一个和最后一个成员是用来检查列表完整性的。在配置文件中将宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1才可开启完整性检查,默认不开启。去掉第一个和最后一个成员后,在列表结构中,uxNumberOfltems用来记录这个列表中有多少个列表项;pxIndex指向列表项,用来遍历列表;xListEnd是一个迷你列表项,用来挂接列表项。

1.6.2 列表项

 列表项就是存放在列表中的具体项目。FreeRTOS 提供了两种列表项:全功能列表项(以下简称列表项)和迷你列表项。列表项在文件list.h中的定义如下。

struct xLIST_ITEM
{
	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE			/*完整性检查*/
	configLIST_VOLATILE TickType_t xItemValue;			/*列表项值*/
	struct xLIST_ITEM * configLIST_VOLATILE pxNext;		/*指向下一个列表项*/
	struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;	/*指向上一个列表项*/
	void * pvOwner;										/*记录列表项属于哪个任务控制块*/
	struct xLIST * configLIST_VOLATILE pxContainer;		/*记录列表项属于哪个列表*/
	listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE			/*完整性检查*/
};
typedef struct xLIST_ITEM ListItem_t;	                /*取另一个别名*/

与列表结构体一样,列表项结构体中第一个和最后一个成员是用来检查列表项完整性。在列表项结构中,xItemValue用来记录列表项值,当在列表中插入列表项时,按列表项值从小到大的顺序插入;pxNext和pxPrevious 分别用来指向下一个列表项和上一个列表项,实现类似双向链表的功能;pvOwner记录列表项归谁所有,通常指向某个任务控制块;pxContainer用来记录列表项属于哪个列表。


除了列表项,FreeRTOS还提供了迷你列表项。

struct xMINI_LIST_ITEM
{
	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE			/*完整性检查 */
	configLIST_VOLATILE TickType_t xItemValue;          /*列表项值*/
	struct xLIST_ITEM * configLIST_VOLATILE pxNext;     /*指向下一个列表项*/
	struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; /*指向上一个列表项*/
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;

 迷你列表项除完整性检查这个成员变量以外,只保留了列表项中最核心的3个成员,

在列表中,最后一个成员就是迷你列表项(去掉完整性检查成员),刚创建的列表用它表示列表的结束,随着列表项的插入,列表项就被挂接在这个迷你列表项之下。FreeRTOS 使用不同的列表来表示任务的不同状态。例如,用一个就绪列表来跟踪所有已经准备好运行的任务,因为每个优先级下都可能有就绪任务,FreeRTOS使用就绪列表数组来实现所有优先级就绪任务的管理。

static List_t pxReadyTasksLists [configMAX_PRIORITIES];

pxReadyTasksLists[0]是所有准备好的优先级为0的任务列表,pxReadyTasksLists[1]是所有准备好的优先级为1的任务列表,以此类推,直到pxReadyTasksLists[configMAX PRIORITIES-1]。每个优先级的任务就绪列表下挂接不同任务的列表项,其中列表项的成员pvOwner 指向所属任务的任务控制块,当前任务的任务控制块保存在全局变量pxCurentTCB中。

2  任务创建和删除

任务管理是FreeRTOS的核心功能,涉及任务创建、任务删除、任务挂起、任务恢复和任务调度等内容。任务句柄用来标识一个任务,其类型名为TaskHandle_t,指向任务控制块。

2.1 任务函数

无论采用何种方法创建任务,均需要用到任务函数。FreeRTOS 规定任务函数的返回值必须为void,而且带有一个void型指针参数。任务函数的一般形式如下。

static void TaskFunction(void *pvParameters)
{
	while(1)		/*FreeRTOS任务是一个死循环*/
	{
		/*完成任务功能的代码放在这里*/

	}
}

FreeRTOS任务不允许以任何方式从实现函数中返回,即不能有一条retumn语句,也不能执行到函数末尾。如果一个任务不再被需要,可以显式地将其删除。

static void TaskFunction(void *pvParameters)
{
	int iVaribleExample = 0;
	/*可以像普通函数一样定义变量。用这个函数创建的每个任务实例都有一个属于自己的iVarialbleExample变量。但iVariableExample 被定义为static时除外,所有的任务实例将共享这个变量*/
	while(1)		/*FreeRTOS任务是一个死循环*/
	{
		/*完成任务功能的代码放在这里*/

	}
	/*如果任务的具体实现会跳出上面的死循环,则此任务必须在函数运行完之前删除。传入NULL参数表示删除的是当前任务*/
	vTaskDelete(NULL);
}

一个任务函数可以创建若干个任务,创建的任务都是独立执行的实列,拥有属于自己的栈空间,一以及属于自己的自动变量,即任务函数本身定义的变量(static修饰的变量除外)。 

2.2 任务的创建与删除函数

FreeRTOS 提供的任务创建函数有4个,以Static 结尾表示在创建任务时使用静态内存分配方法,带有Restricted 限定词表示使用MPU进行保护限制。任务创建和删除函数如下所示:

xTaskCreate()   					 //用动态内存方式创建任务
xTaskCreateStatic()					 //用静态内存方式创建任务
xTaskCreateRestricted()				 //创建MPU内存保护任务,任务堆栈使用静态内存分配,任务控制块使用动态内存分配
xTaskCreateRestrictedStatic()		 //创建MPU内存保护任务,任务堆栈和任务控制块使用静态内存分配
vTaskDelete()						 //删除任务

2.3 用动态内存分配方法创建任务

xTaskCrcate()函数用于使用动态内存分配方法创建任务并将其加入就绪任务列表。用这个函数,要求在FreeRTOSConfig.h头文件中将宏 configSUPPORT_DYNAMI ALLOCATION设置为1,或者取消该宏的定义(在这种情况下,它将默认为1)。

xTaskCreate()函数原型如下。

BaseType_t xTaskCreate(    TaskFunction_t pvTaskCode,                  /*任务函数*/
                            const char * const pcName,                /*任务名*/
                            configSTACK_DEPTH_TYPE usStackDepth,        /*任务堆栈大小*/
                            void * const pvParameters,                /* 任务参数 */
                            UBaseType_t uxPriority,                    /*任务优先级*/
                            TaskHandle_t *pxCreatedTask            /*任务句柄*/
                          );

pxTaskCode: 指向任务入口函数的指针,实参在形式上仅表现为一个函数名。任务名,一般用于跟踪和调试,也可用于获取任务句柄。任务名的长度不能超过FrceRTOSConfig.h头文件中宏configMAX_TASK_NAMELEN所定义的大小。

pcName : 任务堆栈大小,在32位系统中,其单位为word(4B)。

pvParameters :传递给任务函数的参数。

uxPriority :    任务优先级,范围为0~configMAX_PRIORITIES-1。
pxCreatedTask : 任务句柄,任务创建成功后会返回此任务的任务句柄,用于标识这个。

如果任务创建成功,则返回pdPASS(一个定义为1的宏);如果任务创建失败,则返回 errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY,原因是堆内存不足,不能进行动态内存分配。

2.4 用静态内存分配方法创建任务

xTaskCreateStatic()函数也用于创建任务并将其加入就绪任务列表,但创建任务所需的内存不采用动态内存分配,而需要由用户指定。使用这个函数,要求在FreeRTOSConfig.h头文件中将宏configSUPPORT_STATIC_ALLOCATION设置为1。

xTaskCreateStatic()函数原型如下。

BaseType_t xTaskCreateStatic(
	TaskFunction_t pxTaskCode,				/*任务函数 */
	const char *const pcName,/*任务名*/
	const uint32_t ulStackDepth,/*任务堆栈大小*/
	void * const pvParameters,/*任务参数 */
	UBaseType_t uxPriority,/*任务优先级*/
	StackType_t * const puxStackBuffer,/*任务堆栈*/
	StaticTaskt * const pxTaskBuffer )/*任务控制块*/

pxTaskCode:指向任务入口函数的指针,实参在形式上仅表现为一个函数名。

pcName:任务名,一般用于跟踪和调试,也可用于获取任务句柄。任务名的长度不能超过FreeRTOSConfig.h 头文件中宏configMAX_TASK NAME_LEN所定义的大小。

ulStackDepth:任务堆栈大小,在32位系统中,其单位为word(4B)。

pvParameters:传递给任务函数的参数。

uxPriority:任务优先级,范围为0~configMAX_PRIORITIES-1。

puxSTackBuffer:任务堆栈,一般是一个数组,数组类型为StackType_t,在32位架构系统中为uint32_t,需要用户事先分配好内存。

pxTaskBuffer:任务控制块,需要用户事先分配好内存。

如果任务创建成功,则返回任务句柄;如果任务创建失败,则返回NULL,失败的原因往往是用户没有为任务堆栈或任务控制块事先分配内存,即puxStackBuffer或pxTaskBuffer为NULL。
用静态内存分配方法创建任务,除要指定待创建任务的任务堆栈任务控制块内存以外,还要实现 vApplicationGetldleTaskMemory()和 vApplicationGetTimerTaskMemory()这两个函数,作用是给空闲任务和软件定时器服务任务分配任务堆栈和任务控制块所需内存,过程比较烦琐。在实际使用中,用动态内存分配方法创建任务方便得多。

2.5 任务的删除

vTaskDelete()函数用于删除一个已创建好的任务,包括就绪态、阻塞态或挂起念
务,也可在任务运行时删除任务本身。任务删除后,不能再使用该任务的任务句柄,因为该任务已经不存在了。如果被删除的任务是用动态内存分配方法创建的,那么分配给该任务的任务堆栈和任务控制块内存会在空闲任务中自动释放。因此,在调用vTaskDelete()函数删除任务之后,要给空闲任务运行的机会,用于回收资源。空闲任务一般设定为最低优先级,在所有用户任务不运行的时候运行。空闲任务运行时间是衡量操作系统任务设计是否合理的一个重要指标。

用静态内存分配方法创建任务时分配的任务堆栈和任务控制块内存,以及由用户自行分配给任务的内存,需要由用户程序进行释放,这些内存不会在空闲任务中自动释放。任务删除函数如下。

void vTaskDelete(TaskHandle_t xTask);

xTask: 要删除的任务句柄,为NULL时表示删除任务本身。

返回值:无。

使用vTaskDelete()函数需要在FreeRTOSConfig.h头文件中将宏INCLUDE_vTaskDelete设置为1。如果在调用此函数时传入的参数为NULL,即数值0,那么删除的就是当前正在运行的任务,此任务被删除后,FreeRTOS会切换到任务就绪列表中下一个要运行的最高优先级的任务。

3 任务创建与删除示例

本示例通过appStartTas()函数创建两个 FreeRTOS 任务。任务 1的任务函数为Led0Task(),其功能是使LED0每秒闪烁1次,在完成5次闪烁后删除任务2。任务2的任务函数是Led1Task(),其功能是使LED1每秒闪烁2次。

3.1 组织代码

采用一对文件的方式来组织代码,先在工程根目录中建立appTask文件夹,用来存放与任务相关的文件,再编写appTask.c及appTask.h这一对文件。

3.2 编写LED0的任务函数和LED1的任务函数

/**********************************************************************
函 数 名:Led0Task
功能说明:LED0每秒闪烁1次,闪烁5次后删除任务2
形    参:pvParameters 是在创建该任务时传递的参数
返 回 值:无
优 先 级:	4
**********************************************************************/
static void Led0Task(void *pvParameters)
{
	uint16_t cnt = 0;			//检测灯闪烁次数的局部变量
	while(1)
	{
		GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_4)));
		vTaskDelay(500);
		printf("LED0  1秒为周期闪烁   \r\n");
		if(++cnt >=10)
		{
				if(eTaskGetState(Led1TaskHandle) != eDeleted) //如果任务1没有被删除
				{
					cnt = 0;
					vTaskDelete(Led1TaskHandle);
					printf("LED1 被删除 \r\n");
				}
		}
	}
	//如果在任务的具体实现中会跳出while死循环,则这个任务必须在函数运行完之前删除它。
	//传入NULL参数表示删除的是当前任务。
	vTaskDelete(NULL);
}
/**********************************************************************
函 数 名:Led1Task
功能说明:LED1每秒闪烁2次
形    参:pvParameters 是在创建该任务时传递的参数
返 回 值:无
优 先 级:	4
**********************************************************************/
static void Led1Task(void *pvParameters)
{
	
	while(1)
	{
		GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_5)));
		vTaskDelay(250);
		printf("LED1  LED0  0.5秒为周期闪烁  \r\n");

	}
	//如果在任务的具体实现中会跳出while死循环,则这个任务必须在函数运行完之前删除它。
	//传入NULL参数表示删除的是当前任务。
	vTaskDelete(NULL);
}

3.3 创建任务

 任务的创建在appStartTask()函数中完成,先进入临界段,调用xTaskCreate()函数创建LED0闪烁及LED1闪烁任务,在创建任务之前,要先定义这两个任务的任务句柄。appStartTask()函数在appTask.h文件中声明,在appTask.c文件中实现,代码如下。

static TaskHandle_t Led0TaskHandle = NULL;//任务LED0任务句柄
static TaskHandle_t Led1TaskHandle = NULL;//任务LED1任务句柄
/**********************************************************************
函 数 名:appStartTask
功能说明:任务开始函数,用于创建其他函数并且开启调度器
形    参:pvParameters 是在创建该任务时传递的参数
返 回 值:无
**********************************************************************/
void appStartTask(void)
{
		taskENTER_CRITICAL();   /*进入临界段,关中断*/
		xTaskCreate(Led0Task,"Led0Task",128,NULL,4,&Led0TaskHandle);
		xTaskCreate(Led1Task,"Led1Task",128,NULL,4,&Led1TaskHandle);
		taskEXIT_CRITICAL(); 	/*退出临界段,关中断*/
		vTaskStartScheduler();/*开启调度器*/
}

4 下载测试

打开 串口助手,可以看到LED0以每秒1次的频率闪烁,LED1以每秒2次的频率闪烁,LED0闪烁5次后,LED1停止闪烁,因此LED1的闪烁任务被删除。

5 总结

FreeRTOS任务有运行态,就绪态,阻塞态和挂起态4中状态,调度器会选择优先级最高的就绪任务来运行。FreeRTOS通过任务控制块来操作任务,涉及列表和列表项两个重要的数据结构,任务堆栈可以确保任务之间能正确切换,在操作系统的调度下看起来就像所以任务同时运行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值