FreeRTOS-任务管理

目录

一、任务功能

二、顶级任务状态-简单理解

 三、创建任务原型

 xTaskCreate() API函数原型

四、简单创建实例

      4.1 简单实例1:创建2个任务

  4.2 简单实例2:使用任务的参数

五、任务优先级

六、时间测量和滴答中断

 七、扩展“未运行”状态

7.1 阻塞状态

7.2 挂起状态

7.3 准备就绪状态

7.4 状态转换图

7.5 实例

1.阻塞实例


一、任务功能

        每个任务本身就是一个小程序。它有一个入口点,通常会在无限循环中永远运行,并且不会退出。FreeRTOS任务必须不允许以任何方式从它们实现的函数中返回——它们必须包含' return '语句,并且必须允许在函数结束后执行。如果不再需要某个任务,则应该显式地删除它。单个任务函数定义可用于创建任意数量的任务—每个创建的任务都是一个单独的执行实例,具有自己的堆栈和任务本身中定义的任何自动(堆栈)变量的副本。一个典型任务的结构如下:

void ATaskFunction( void *pvParameters ) 
{ 
/*变量可以像普通函数一样声明*/
    for( ;; ) 
    { 
        /*实现任务功能的代码放在这里*/
    } 
/*如果任务实现脱离了上述循环,则必须在到达其实现函数的末尾之前删除任务。
传递给vTaskDelete() API函数的NULL参数表示要删除的任务是调用(此)任务。*/
    vTaskDelete( NULL ); 
} 

二、顶级任务状态-简单理解

        一个应用程序可以包含许多任务。如果运行应用程序的处理器包含单个核心,那么在任何给定时间只能执行一个任务。这意味着任务可以以两种状态之一存在:运行和未运行。其实“未运行状态”实际包含了许多子状态,如图1所示。

        当任务处于运行状态时,处理器正在执行任务的代码。当任务处于“未运行”状态时,任务处于休眠状态,其状态已被保存,以便在调度程序决定下次进入“运行”状态时恢复执行。当任务恢复执行时,它会从上次离开“运行”状态之前将要执行的指令执行

        从非运行状态过渡到运行状态的任务被称为“交换入”或“交换入”。相反,从运行状态转换到非运行状态的任务被称为“切换出”或“交换出”。FreeRTOS调度器是唯一可以切换任务进出的实体。

图1  顶级任务状态和转换

 三、创建任务原型

 xTaskCreate() API函数原型

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

参数名称/返回值

描述
pvTaskCode 任务只是永远不会退出的C函数,因此,通常以无限循环的形式实现。pvTaskCode参数只是一个指针,指向实现任务的函数(实际上,只是函数的名称)。
pcName

任务的描述性名称。FreeRTOS不以任何方式使用它。它纯粹是作为调试辅助工具包含的。通过人类可读的名称识别任务比通过句柄识别任务要简单得多。

应用程序定义的常量configMAX_TASK_NAME_LEN定义了任务名可以使用的最大长度,包括NULL终止符。提供超过此最大值的字符串将导致字符串被静默截断。

usStackDepth 

每个任务都有自己唯一的堆栈,在任务创建时由内核分配给该任务。usStackDepth值告诉内核堆栈的大小。该值指定堆栈可以容纳的字数,而不是字节数。例如,如果堆栈是32位宽,usStackDepth作为100传入,那么将分配400字节的堆栈空间(100 * 4字节)。堆栈深度乘以堆栈宽度不能超过uint16_t类型变量所能包含的最大值。空闲任务使用的堆栈大小由应用程序定义的常量configMINIMAL_STACK_SIZE定义。

pvParameters 任务函数接受指向void (void*)的指针类型的形参。分配给pvParameters的值是传递给任务的值。
uxPriority 定义任务执行的优先级。优先级可以从0(最低优先级)分配到(configMAX_PRIORITIES - 1)(最高优先级)。configMAX_PRIORITIES是一个用户定义的常量。
pxCreatedTask 

pxCreatedTask可用于向正在创建的任务传递句柄。然后,这个句柄可以用于在API调用中引用任务,例如,更改任务优先级或删除任务。

如果应用程序不需要任务句柄,则可以将pxCreatedTask设置为NULL。

Returned value

有两种可能的返回值:

1. pdPASS (1):任务创建成功

2. pdFAIL (0):这表明任务还没有创建,因为FreeRTOS没有足够的堆内存来分配足够的RAM来保存任务数据结构和堆栈。

四、简单创建实例

      4.1 简单实例1:创建2个任务

         示例演示创建两个简单任务所需的步骤,然后开始执行任务。任务只是将同一个标志位置0或者置1。这两个任务都是以相同的优先级创建的,除了标志位外,其他都是相同的。需要用到的宏定义如下:

#define TASK1_SIZE         128 //任务1
#define TASK1_PRIORITY     3
TaskHandle_t task1_handle;
void task1(void *pvparameter);

#define TASK2_SIZE         128 //任务2
#define TASK2_PRIORITY     3
TaskHandle_t task2_handle;
void task2(void *pvparameter);

        然后在main函数里创建两个任务

xTaskCreate((TaskFunction_t) task1,
		       (const char * ) "TASK1",
		       (uint16_t) TASK1_SIZE,
		       (void *)  NULL,
		       (UBaseType_t) TASK1_PRIORITY,
		       (TaskHandle_t *)&task1_handle ) ;
	
xTaskCreate((TaskFunction_t) task2,
		       (const char * ) "TASK2",
		       (uint16_t) TASK2_SIZE,
		       (void *) NULL,
		       (UBaseType_t) TASK2_PRIORITY,
		       (TaskHandle_t *)&task2_handle ) ;

        两个任务功能编写,任务1将FLAG置0,任务2将FLAG置1,2个任务都是无限循环且不会退出。

void task1(void *pvparameter)//任务1
{
	while(1)
	{
		taskflagrun=0;//这是一个全局变量
	}
	
}
void task2(void *pvparameter)//任务2
{
	while(1)
	{	
		taskflagrun=1;
	}
}

        通过keil的逻辑分析仪仿真运行结果如图2所示,从图2可以看出,即使在死循环的情况下2个任务也是轮流执行,将标志位置0置1,且每个任务运行的时间相同。

图2 标志位变化 

  4.2 简单实例2:使用任务的参数

        和实例1一样,不过唯一改变的是利用的任务的参数进行赋值,首先定义2个变量,分别为10和20,然后修改2个任务函数的标志位赋值,以下程序为主要程序,其他参考实例1。

int a=10;
int b=20;
const int  *pcTextForTask1 = &a; //任务的参数
const int  *pcTextForTask2 = &b; 
xTaskCreate((TaskFunction_t) task1,
		       (const char * ) "TASK1",
		       (uint16_t) TASK1_SIZE,
		       (void *)  pcTextForTask1,//原来为NULL
		       (UBaseType_t) TASK1_PRIORITY,
		       (TaskHandle_t *)&task1_handle ) ;
	
    xTaskCreate((TaskFunction_t) task2,
		       (const char * ) "TASK2",
		       (uint16_t) TASK2_SIZE,
		       (void *) pcTextForTask2,//原来为NULL
		       (UBaseType_t) TASK2_PRIORITY,
		       (TaskHandle_t *)&task2_handle ) ;
	vTaskDelete(start_handle);
void task1(void *pvparameter)
{
	while(1)
	{
		taskflagrun=*(int *)pcTextForTask1;//使用任务的参数
	}
	
}

void task2(void *pvparameter)
{
	while(1)
	{	
		taskflagrun=*(int *)pcTextForTask2;
	}
}

        keil的逻辑分析仿真结果如图3所示。标志位轮流被置10和20。

图3 标志位变化

五、任务优先级

        xTaskCreate() API函数的uxPriority参数为正在创建的任务分配初始优先级。通过使用vTaskPrioritySet() API函数,可以在调度程序启动后更改优先级。

        可用优先级的最大数量由FreeRTOSConfig.h中应用程序定义的configMAX_PRIORITIES编译时配置常量设置。数字优先级值表示优先级任务,优先级0是可能的最低优先级。因此,可用的优先级范围是0到(configMAX_PRIORITIES - 1)。任何数量的任务都可以共享相同的优先级,从而确保最大的设计灵活性。

        FreeRTOS由两种方法可以切换到下一个需要运行的任务,一个是通用的方法,另外一个是特殊的方法,也就是硬件方法,使用MCU自带的硬件指令实现。宏定义configUSE_PORT_OPTIMISED_TASK_SELECTION为0的时候使用的就是第一种通用方法;定义为1的时候使用的则是第二种特殊方法;STM32有计算前导零的指令,所以是可以使用第二种方法。

六、时间测量和滴答中断

        每个任务在一个“时间片”中执行,在一个时间片开始时进入运行状态,在一个时间片结束时退出运行状态。为了能够选择要运行的下一个任务,调度器本身必须在每个时间片结束时执行周期性中断,称为“滴答中断”,用于此目的。时间片的长度有效地由tick中断频率设置,该频率由FreeRTOSConfig.h中应用程序定义的configTICK_RATE_HZ编译时间配置常数配置。例如,如果configTICK_RATE_HZ设置为100 (Hz),那么时间片将是10毫秒。两次滴答中断之间的时间称为“滴答周期”。一个时间片等于一个滴答周期。如图4所示,其中顶线显示调度程序执行的时间,细箭头显示从一个任务到计时中断,然后从计时中断返回到另一个任务的执行顺序。

图4  执行序列展开以显示滴答中断正在执行

        宏pdMS_TO_TICKS()可用于将以毫秒为单位指定的时间转换为以ticks为单位指定的时间。例如,调用vTaskDelay(pdMS_TO_TICKS(100))将使任务保持阻塞状态100毫秒。 

注意:不建议在应用程序中直接指定以刻度为单位的时间,而是使用pdMS_TO_TICKS()宏来指定以毫秒为单位的时间,这样做可以确保即使刻度频率发生变化,应用程序中指定的时间也不会改变。

 七、扩展“未运行”状态

        前面所讲的只有运行和未运行2种状态,但其实“未运行”状态也可以分为3种状态。

7.1 阻塞状态

        等待事件的任务被称为“Blocked”状态,这是Not Running状态的一个子状态。任务可以进入Blocked状态来等待两种不同类型的事件:

1. 时间(与时间相关)事件—事件可能是即将到期的延迟时间,也可能是达到的绝对时间。例如,一个任务可能会进入阻塞状态,等待10毫秒通过。

2. 同步事件——事件起源于另一个任务或中断。例如,任务可能进入阻塞状态以等待数据到达队列。同步事件涵盖了广泛的事件类型。FreeRTOS队列、二进制信号量、计数信号量、互斥锁、递归互斥锁、事件组和直接到任务通知都可以用来创建同步事件。

        任务可以用超时阻塞同步事件,从而有效地同时阻塞两种类型的事件。例如,一个任务可以选择等待最多10毫秒的数据到达队列。如果数据在10毫秒内到达,或者10毫秒内没有数据到达,任务将离开Blocked状态。

7.2 挂起状态

       “挂起”也是“不运行”的子状态。处于“挂起”状态的任务对调度程序不可用。进入Suspended状态的唯一方法是通过调用vTaskSuspend() API函数,唯一的出路是通过调用vTaskResume()或xTaskResumeFromISR() API函数。大多数应用程序不使用挂起状态。 

7.3 准备就绪状态

        处于“未运行”状态但未被阻塞或挂起的任务称为处于“就绪”状态。它们能够运行,因此“准备好”运行,但当前不处于运行状态。

7.4 状态转换图

        图5扩展了前面过度简化的状态图,包括所有Not Running子状态。到目前为止,在示例中创建的任务还没有使用阻塞或挂起状态;它们只是在就绪状态和运行状态之间进行了转换,如图5中的粗体所示。

 图5 全任务状态机

7.5 实例

1.阻塞实例

        使用vTaskDelay()的调用,将任务置于Blocked状态,直到延迟期过期。具体程序参考4.1简单实例1,唯一修改的是task2任务函数,添加vTaskDelay()函数使task2进入阻塞状态,修改程序如下:

void task2(void *pvparameter)
{
	while(1)
	{	
		taskflagrun=1;
		vTaskDelay(5);//使task2任务进入阻塞状态5ms
	}
}

        通过keil逻辑分析仪进行仿真,仿真结果如图6。

图6 阻塞状态 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值