基于STM32F103的FreeRTOS系列(八)·任务管理API详细解析

目录

1.  简介

1.1  任务的简介

1.2  任务调度器

1.3  任务状态

1.4  任务状态转换

2.  常用任务API函数

2.1  任务的挂起函数

2.1.1  挂起指定任务vTaskSuspend()

2.1.2  挂起全部任务vTaskSuspendALL()

2.2  任务的恢复函数

2.2.1  恢复指定任务vTaskResume()

2.2.2  恢复全部任务vTaskResumeALL()

2.2.3  中断恢复任务Type_t xTaskResumeFromISR(TaskHandle_t xTaskToResume)

2.3  删除任务函数 vTaskDelete()

2.4  任务的延时函数

2.4.1  相对延时函数 vTaskDelay()

2.4.2  绝对延时函数 vTaskDelayUntil()

3.  代码编写

3.1  初始化按键

3.2  主函数任务

3.3  完整代码


1.  简介

1.1  任务的简介

        在裸机系统中,系统的主体就是 main 函数里面顺序执行无限循环,这个无限循环里面 CPU 按照顺序完成各种事情。
        在FreeRTOS中,根据功能的不同,把整个系统分割成一个个独立无限循环无法返回的函数,这个函数就称为任务

1.2  任务调度器

        在FreeRTOS中任何时刻,只有一个任务得到运行,对于哪一个任务先执行是由FreeRTOS调度器决定的。调度器会不断的启动、停止每一个任务,宏观看上去所有的任务都在同时在执行。
        FreeRTOS 中的任务是抢占式调度机制,优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度。

1.3  任务状态

        任务的状态通常分为以下四种状态:就绪(Ready)、运行(Running)、阻塞(Blocked)、挂起态(Suspended)。

就绪态 (ready):该任务在就绪列表中,就绪的任务已经具备执行的能力,只等待调度器进行调度,新创建的任务会初始化为就绪态。

运行态 (running):该状态表明任务正在执行,此时它占用处理器,FreeRTOS调度器选择运行的永远是处于最高优先级的就绪态任务,当任务被运行的一刻,它的任务状态就变成了运行态。

阻塞态 (blocked):正在运行的任务发生阻塞 (延时、读信号量等待、读写队列或者等待读写事件) 时,该任务会从就绪列表中删除,任务状态由运行态变成阻塞态。这种状态下,进程或线程不会主动进行CPU调度,但是它仍然处于调度器的管理下,并且可以在事件发生后立即被重新调度到运行态。

挂起态 (suspended):处于挂起态的任务对调度器而言是不可见的,让一个任务进入挂起状态的唯一办法就是调用 vTaskSuspend() 函数;而把一个挂起状态的任务恢复 的唯一途径就是调用 vTaskResume() 或 vTaskResumeFromISR() 函数。

挂起态阻塞态的区别:
        当任务有较长的时间不允许运行的时候,我们可以挂起任务,这样调度器就不会管这个任务的任何信息,直到我们调用恢复任务的 API 函数;
        而任务处于阻塞态的时候,系统还需要判断阻塞态的任务是否超时,是否可以解除阻塞。

        简单来说,

        挂起态:任务的挂起和恢复,需要主动调用相关API,一旦挂起程序就不在管这个任务,直到主动调取API恢复。

        阻塞态:阻塞态是由于某些事件的等待而被动进入的,进入后程序还要时不时查看一下他(调度器可以追踪其状态),并在事件发生时尽快唤醒。

1.4  任务状态转换

① 创建任务→就绪态 (ready):任务创建完成后进入就绪态,表明任务已准备就绪,随时可以运行,只等待调度器进行调度。
② 就绪态→运行态 (running):发生任务切换时,就绪列表中最高优先级的任务被执行,从而进入运行态。
③ 运行态→就绪态:更高优先级任务创建或者恢复后,会发生任务调度,此刻就绪列表中最高优先级任务变为运行态,那么原先运行的任务由运行态变为就绪态,依然在就绪列表中,等待最高优先级的任务运行完毕,继续运行原来的任务 (此处可以看做是CPU使用权被更高优先级的任务抢占了)。
④ 运行态→阻塞态 (blocked):正在运行的任务发生阻塞 (延时、读信号量等待) 时,该任务会从就绪列表删除,任务状态由运行态变成阻塞态,然后发生任务切换,运行就绪列表中当前最高优先级任务。
⑤ 阻塞态→就绪态:阻塞的任务被恢复后 (任务恢复、延时时间超时、读信号量超时 或读到信号量等),此时被恢复的任务会被加入就绪列表,从而由阻塞态变成就绪态;如果此时被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换,将该任务将再次转换任务状态,由就绪态变成运行态。
⑥⑦⑧ 就绪态、阻塞态、运行态→挂起态 (suspended):任务可以通过调用 vTaskSuspend() API 函数都可以将处于任何状态的任务挂起,被挂起的任务得不到CPU的使用权,也不会参与调度,除非它从挂起态中解除。
⑨ 挂起态→就绪态:把一个挂起状态的任务恢复的唯一途径就是调用 vTaskResume() 或 vTaskResumeFromISR() API 函数,如果此时被恢复任务的优先级高于正在运行任务的优先级,则会发生 任务切换,将该任务将再次转换任务状态,由就绪态变成运行态。

2.  常用任务API函数

2.1  任务的挂起函数

2.1.1  挂起指定任务vTaskSuspend()

void vTaskSuspend(TaskHandle_t xTaskToSuspend)
参数:xTaskToSuspend

要挂起的任务的任务句柄,创建任务的时候会为每个任务分配一个任务句柄。如果使用函数xTaskCreate()创建任务的话那么函数的参数pxCreatedTask就是此任务的任务句柄,如果使用函数 xTaskCreateStatic()创建任务的话那么函数的返回值就是此任务的任务句柄。也可以通过函数xTaskGetHandle()来根据任务名字来获取某个任务的任务句柄。

注意!如果参数为NULL的话表示挂起任务自己。

返回值

        任务可以通过调用 vTaskSuspend() 函数都可以将处于任何状态的任务挂起,被挂起的任务得不到 CPU 的使用权,也不会参与调度,它相对于调度器而言是不可见的,除非它从挂起态中解除。

//KEY任务函数
void key_task(void *pvParameters)
{
    u8 key=0;
	
	while(1)
    {
		key=KEY_Scan(0);
		if(key==KEY_UP_PRESS)
		{
			printf("挂起LED任务!\n");
			vTaskSuspend(LED2Task_Handler);/* 挂起LED任务 */
			printf("挂起LED任务成功!\n");
		}
		else if(key==KEY1_PRESS)
		{
			printf("恢复LED任务!\n");
			vTaskResume(LED2Task_Handler);/* 恢复LED任务!*/
			printf("恢复LED任务成功!\n");
		}
		vTaskDelay(20);
    }
}

        记住是任何状态,其中也包括自己本身,但是在挂起自身的时候会进行一次任务上下文切换,需要挂起自身就将 xTaskToSuspend 设置为NULL传递进来即可。

 static TaskHandle_t LED_Task_Handle = NULL;	/*LED任务句柄*/
 
 static void KEY_Task(void* parameter)
 {
     while (1) {
         if ( Key_Scan(KEY1_GPIO_PORT, KEY1_GPIO_PIN) == KEY_ON ) {
             /*K1被按下*/
             printf("挂起 LED 任务!\n");
             vTaskSuspend(LED_Task_Handle);	/*挂起LED任务*/
         }
         vTaskDelay(20);			/*延时20个tick*/
     }
 }

2.1.2  挂起全部任务vTaskSuspendALL()

        挂起全部任务,实际上就是挂起任务调度器。需要搭配vTaskResumeALL()一起使用,vTaskSuspendALL()被调用多少次,vTaskResumeALL()就要调用多少次。

void vTaskSuspendALL(void)
{
    ++uxSchedulerSuspended;
}

2.2  任务的恢复函数

2.2.1  恢复指定任务vTaskResume()

void vTaskResume(TaskHandle_t xTaskToResume)
参数:xTaskToResume要恢复任务的任务句柄
返回值:

        任务恢复就是让挂起的任务重新进入就绪状态,恢复的任务会保留挂起前的状态信息,在恢复的时候根据挂起时的状态,继续运行。
        如果被恢复任务在所有就绪态任务中,处于最高优先级列表的第一位,那么系统将进行任务上下文的切换。

//KEY任务函数
void key_task(void *pvParameters)
{
    u8 key=0;
	
	while(1)
    {
		key=KEY_Scan(0);
		if(key==KEY_UP_PRESS)
		{
			printf("挂起LED任务!\n");
			vTaskSuspend(LED2Task_Handler);/* 挂起LED任务 */
			printf("挂起LED任务成功!\n");
		}
		else if(key==KEY1_PRESS)
		{
			printf("恢复LED任务!\n");
			vTaskResume(LED2Task_Handler);/* 恢复LED任务!*/
			printf("恢复LED任务成功!\n");
		}
		vTaskDelay(20);
    }
}

2.2.2  恢复全部任务vTaskResumeALL()

        需要搭配vTaskSuspendALL()使用,vTaskSuspendALL()调用多少次,vTaskResumeALL()就要调用多少次。

2.2.3  中断恢复任务Type_t xTaskResumeFromISR(TaskHandle_t xTaskToResume)

Type_t xTaskResumeFromISR(TaskHandle_t xTaskToResume)
参数:xTaskToResume要恢复任务的任务句柄
返回值:

pdTRUE:恢复运行的任务的任务优先级等于或者高于正在运行的任务(被中断打断的任务),这意味着在退出中断服务函数以后必须进行一次上下文切换。

pdFALSE:恢复运行的任务的任务优先级低于当前正在运行的任务(被中断打断的

任务),这意味着在退出中断服务函数的以后不需要进行上下文切换。

2.3  删除任务函数 vTaskDelete()

对于删除任务函数:

(1) 当一个任务删除另外一个任务时,形参为 要删除任务 创建时返回的任务句柄;
(2) 如果是删除自身, 则形参为 NULL。


/*  删除任务本身 */
vTaskDelete( NULL );

/*  在其他任务删除 DeleteTask 任务 */
vTaskDelete( DeleteHandle );

2.4  任务的延时函数

2.4.1  相对延时函数 vTaskDelay()

        阻塞延时的阻塞,是指任务调用该延时函数后,任务会被剥离 CPU 使用权,然后进入阻塞状态,直到延时结束,任务重新获取 CPU 使用权才可以继续运行。在任务阻塞的这段时间,CPU可以去执行其它的任务,如果其它的任务也在延时状态,那么 CPU就将运行空闲任务。

void vTaskDelay(const TickType_t xTicksToDelay)

        延时的时长由形参xTicksToDelay决定,单位为系统节拍周期, 比如系统的时钟节拍周期为 1ms,那么调用 vTaskDelay(1) 的延时时间则为 1ms。

        相对延时的意思是:想要延时100ms,那么这100ms是调用vTaskDelay() 之后开始计时的,经过100个系统节拍周期完成计时。因此,vTaskDelay() 并不适用与周期性执行任务的场合。

        除此之外,其它任务和中断活动,也会影响到 vTaskDelay() 的调用(比如调用前,高优先级任务抢占了当前任务),进而影响到任务的下一次执行的时间。

void vTaskA( void * pvParameters )
 {
     while (1) {
       // ...
       // 这里为任务主体代码
       // ...

       /*  调用相对延时函数, 阻塞 1000 个 个 tick */
       vTaskDelay( 1000 );
     }
 }

2.4.2  绝对延时函数 vTaskDelayUntil()

        绝对延时函数,常用于较精确的周期运行任务,比如我有一个任务,希望它以固定频率定期执行,而不受外部的影响,任务从上一次运行开始到下一次运行开始的时间间隔是绝对的,而不是相对的。

void vTaskDelayUntil(TickType_t* const pxPreviousWakeTime,const TickType_t xTimeIncrement);
void vTaskA( void * pvParameters )
 {
   /*  用于保存上次时间。调用后系统自动更新 */
   static portTickType PreviousWakeTime;
   /*  设置延时时间,将时间转为节拍数 */
   const portTickType TimeIncrement = pdMS_TO_TICKS(1000);

   /*  获取当前系统时间 */
   PreviousWakeTime = xTaskGetTickCount();

   while (1) {
     /*  调用绝对延时函数, 任务时间间隔为 1000 个 个 tick */
     vTaskDelayUntil( &PreviousWakeTime ,TimeIncrement );

     // ...
     // 这里为任务主体代码
     // ...
   }
}

3.  代码编写

        我们不在一步一步移植了,前面有移植过程:

江协科技/江科大STM32代码移植FreeRTOS实时操作系统-CSDN博客

基于STM32F103的FreeRTOS系列(九)·任务创建函数的使用·静态方法和动态方法-CSDN博客

        这里直接使用移植好的动态任务文件:

基于STM32F103C8T6的FreeRTOS任务创建·动态任务资源-CSDN文库

3.1  初始化按键

key.c文件:

#include "key.h"
#include "SysTick.h"
#include "FreeRTOS.h"
#include "task.h"

/*******************************************************************************
* 函 数 名         : KEY_Init
* 函数功能		   : 按键初始化
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
void KEY_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);		//开启GPIOB的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);						//将PB1和PB11引脚初始化为上拉输入
}


/**
  * 函    数:按键获取键码
  * 参    数:无
  * 返 回 值:按下按键的键码值,范围:0~2,返回0代表没有按键按下
  * 注意事项:此函数是阻塞式操作,当按键按住不放时,函数会卡住,直到按键松手
  */
uint8_t Key_GetNum(void)
{
	uint8_t KeyNum = 0;		//定义变量,默认键码值为0
	
	if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)			//读PB1输入寄存器的状态,如果为0,则代表按键1按下
	{
		while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0);	//等待按键松手
		KeyNum = 1;												//置键码为1
	}
	
	if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0)			//读PB11输入寄存器的状态,如果为0,则代表按键2按下
	{
		while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0);	//等待按键松手
		KeyNum = 2;												//置键码为2
	}
	
	return KeyNum;			//返回键码值,如果没有按键按下,所有if都不成立,则键码为默认值0
}

key.h文件:

#ifndef _key_H
#define _key_H

#include "system.h"
 
void KEY_Init(void);
uint8_t Key_GetNum(void);

#endif

3.2  主函数任务

        按键1按下实现任务2的挂起,按键2按下实现任务2的恢复:

//任务3函数
void key_task(void *pvParameters)
{
		uint8_t KeyNum;
	
	  while(1)
    {
			KeyNum = Key_GetNum();
			if(KeyNum==1)
			{
				printf("挂起LED任务!\n");
				vTaskSuspend(LED2Task_Handler);/* 挂起LED任务 */
				printf("挂起LED任务成功!\n");								
			}
			else if(KeyNum==2)
			{
				printf("恢复LED任务!\n");
				vTaskResume(LED2Task_Handler);/* 恢复LED任务!*/
				printf("恢复LED任务成功!\n");
			}	
			vTaskDelay(20);			
    }
}

        宏定义声明:

//任务优先级
#define KEY_TASK_PRIO		4
//任务堆栈大小	
#define KEY_STK_SIZE 		50  
//任务句柄
TaskHandle_t KEYTask_Handler;
//任务函数
void key_task(void *pvParameters);

        再开始任务中创建key任务:

	//创建key任务
    xTaskCreate((TaskFunction_t )key_task,     
                (const char*    )"key_task",   
                (uint16_t       )KEY_STK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )KEY_TASK_PRIO,
                (TaskHandle_t*  )&KEYTask_Handler); 	

         运行结果:开始时led1和led2闪烁,按键1(PB1)按下,任务2挂起,led2保持当前状态(亮或者灭),按键2(PB11)按下,任务2恢复,led2恢复正常运行。

3.3  完整代码

基于STM32F103C8T6的FreeRTOS的任务挂起资源-CSDN文库

FreeRTOS_时光の尘的博客-CSDN博客

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

时光の尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值