Bootstrap

FreeRTOS的任务操作

3 FreeRTOS任务操作

3.1 创建task

​ 任务的创建不代表任务的执行,任务创建后还需要使用调度器来让任务执行。即vTaskStartScheduler();同时创建任务函数,不等于任务处理函数。他只是创建了任务,但是没有规定任务要做什么。

有动态创建和静态创建两种创建方式:

  • 动态分配:只需要我们提供任务的堆栈大小和返回句柄,TCB任务块,任务堆栈的大小是动态分配的,系统会使用malloc等函数分配的,内存在堆区,基本方式是使用xTaskCreate()函数动态分配,这也是我们常用的分配方式,使用动态分配时,会自己自动实现malloc和free功能。
  • 静态分配:提供任务的堆栈大小和返回句柄的同时,还需要提供堆栈空间和任务控制块,,基本使用方式是使用xTaskCreateStatic()函数动态分配。
/*			
		@brief	动态分配内存创建任务函数
		@retval pdTRUE:创建成功,errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY:内存不足创建失败 
		@param  pvTaskCode:是一个函数指针,用来指向任务处理函数
            pcName:任务的名字
            usStackDepth:给这个任务分配的栈的深度,注意单位是word,1表示4个字节,而不是1个字节。
            pvParameters:调用任务处理函数时传入的参数
            uxPriority:这个任务的优先级别
            pxCreatedTask:任务句柄,之后对这个任务的操作都通过他来操作,类似于文件描述符。句柄类型是TaskHandle_t类型。
*/
BaseType_t xTaskCreate(	TaskFunction_t pxTaskCode,
											const char * const pcName,
											const uint16_t usStackDepth,
											void * const pvParameters,
											UBaseType_t uxPriority,
											TaskHandle_t * const pxCreatedTask );

/*
		@brief  静态分配内存创建任务函数
		@retval 创建成功的任务句柄
		@param pvTaskCode:任务函数
					 pcName:任务名称
  				 usStackDepth:任务栈深度,单位为字(word)
  				 pvParameters:任务参数
  				 uxPriority:任务优先级
  				 puxStackBuffer:任务栈空间数组,创建任务的堆栈首地址
  				 pxTaskBuffer:任务控制块存储空间
*/
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 );


//创建一个任务处理函数,即任务创建后,被调度器执行的时候做什么事情。函数原型如下:
//TaskFunction_t是一个函数指针,指向void FunctionName (void*)这样的函数
typedef void (* TaskFunction_t)( void * );
//所以我们对应的函数应该长这个样子:
void TaskFunctionName( void *pvParameters );

ps:任务控制块地址和任务句柄并不相同,我们操控任务是通过任务句柄操作的,任务句柄最终指向任务控制块。

根据任务处理函数的原型,我们可以知道,规定任务处理函数时,需要有以下特点:

  1. 这个函数不能有返回值
  2. 他的参数void *pvParameters 是通过创建任务函数xTaskCreate的参数传递进来的。
  3. 可以用同一个任务处理函数创建多个任务,也就是说,多个任务执行同一个事情。

ps:因为每次创建任务的时候,都会分配栈空间, 这就意味着任务1的局部变量存储在任务1的栈空间中,而任务2的局部变量存储在任务2的栈空间中,但是有全局变量时,我们应该像多线程使用临界资源那样,对全局变量进行保护和防止冲突,因为全局变量的内存时多个任务公用的。这也就是为什么我们说,任务这个概念更类似与线程,而不是进程的原因。

3.2 删除task

​ 删除一个任务/释放一个任务的资源。使用vTaskDelete();可以删除使用xTaskCreate()函数创建的任务,也可以删除xTaskCreateStatic()函数创建的任务。

/*			
		@brief:删除一个任务函数
		@retval:None
    @param:TaskHandle_t xTaskToDelete:任务对应的句柄,当这个句柄的值为NULL时,代表释放自己的资源。
*/
void vTaskDelete(TaskHandle_t xTaskToDelete)

3.2.1 空闲(idle)任务(守护任务)

​ FreeRTOS的调度器决定了任何时刻处理器必须有一个任务运行,当所有的任务都处于阻塞或挂起等状态不能运行时,空闲任务就会被执行,同时,我们在创建任务后,当然需要删除(释放)该任务函数所使用的内存,这个清理的工作,就是由空闲任务来完成的。空闲任务有如下特点:

  • 对于自杀的任务,内存由空闲任务进行清理。
  • 对于被杀的任务,由调用了vTaskDelete这个函数的任务内部清理(凶手任务调用这个函数,由凶手任务清理)。
  • 空闲任务的任务优先级为0,它不能阻碍用户的任务运行。
  • 空闲任务要么处于就绪Ready态,要么处于运行态,永远不会阻塞!

ps:**也正是因为空闲任务的优先级为0(最低),所以使用vTaskDelete释放自己或其他任务时,一定要保证有机会让空闲任务执行,否则资源是得不到释放的。**举个例子,task1和task2的优先级分别为1和2,task2优先级较高所以优先执行,若task2自杀后,轮到task1执行,若task1一直处于运行态,不释放CPU的执行权限,那么task2的TCB任务块和相应的堆栈资源就一直不会被清理。空闲任务是在调度器vTaskStartScheduler()调度时自动创建的第一个任务。

3.2.2 钩子(Hook)函数

​ 相当于向空闲任务的处理函数中添加了处理程序。使能后,当调度器调度内核进入空闲任务时就会调用钩子函数

//一般情况下这个宏为0,在FreeRTOSConfig.h文件中
#define configUSE_IDLE_HOOK			0
若要使能钩子函数,需要将宏定义为1,同时编辑
  
extern void vApplicationIdleHook( void ); 函数,这里实现我们在空闲任务中想要额外实现的功能。(钩子函数并不会取消原先空闲任务中的释放内存的功能)。

在钩子函数中万万不可以使用while()或者会造成一直阻塞的函数。

3.3 设置,获取,task优先级

3.3.1 设置任务优先级

/*			
		@brief:用来设置任务的优先级
		@retval:None
    @param:TaskHandle_t xTask:对应要设置的任务的句柄
					 UBaseType_t uxNewPriority:新的优先级,取值范围是0~(configMAX_PRIORITIES-1 )
*/
void vTaskPrioritySet(TaskHandle_t xTask, UBaseType_t uxNewPriority);

3.3.2 获取任务优先级

/*			
		@brief:用来获取任务优先级
		@retval:返回优先级
    @param:const TaskHandle_t xTask:获取这个句柄的优先级,若为NULL,那么获取自己的优先级,返回值UBaseType_t是返回的优先级
*/
UBaseType_t uxTaskPriorityGet(const TaskHandle_t xTask)

3.4 任务示例

#include "main.h"
#include "usart.h"
#include "gpio.h"

#include "FreeRTOS.h"
#include "FreeRTOSConfig.h"
#include "task.h"
#include "list.h"
#include "queue.h"
#include "freeled.h"


void led_task(void *param);
void usart_task(void *param);


//。。。GPIO_INIT()等INIT初始化。。。

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
 
  //创建两个任务
	xTaskCreate(led_task, "task1", 100, NULL, 1, NULL);
	xTaskCreate(usart_task, "task2", 100, NULL, 1, NULL);
	
	vTaskStartScheduler();
}


//两个任务处理函数
void led_task(void *param)
{
	while(1)
	{
		blink_led(BLUE_LED, 500, 1);
		blink_led(RED_LED, 500, 1);
		blink_led(GREEN_LED, 500, 1);
	}
	
	vTaskDelete( NULL );
}

void usart_task(void *param)
{
	while(1)
	{
		printf("This is task 2\n");
		vTaskDelay( pdMS_TO_TICKS(1000));
	}
	vTaskDelete( NULL );
}
//实现led和printf
#define ON 1
#define OFF 0

typedef struct gpio_s{
	GPIO_TypeDef		*group;
	uint16_t				pin;
}gpio_t;

enum ENUM_LED{
	RED_LED,
	GREEN_LED,
	BLUE_LED,
	LED_MAX,
};

void turn_led(uint32_t which_led, uint32_t led_state);
int blink_led(int which_led, int interval, int num);
gpio_t leds[LED_MAX] = {
	{REDLED_GPIO_Port, REDLED_Pin},
	{GREENLED_GPIO_Port, GREENLED_Pin},
	{BLUELED_GPIO_Port, BLUELED_Pin},
};

void turn_led(uint32_t which_led, uint32_t led_state)
{
	GPIO_PinState				level;
	
	if (which_led >= LED_MAX)
	{
		return ;
	}
	
	level = (led_state == OFF) ? GPIO_PIN_SET : GPIO_PIN_RESET;
	HAL_GPIO_WritePin(leds[which_led].group, leds[which_led].pin, level);

	return ;
}

int blink_led(int which_led, int interval, int num)
{
	if (num<0 || interval<=0 || which_led>LED_MAX)
	{
		return -1;
	}
	
	while( num-- )
	{
		turn_led(which_led, ON);
		vTaskDelay(pdMS_TO_TICKS(interval));
	
		turn_led(which_led, OFF);
		vTaskDelay(pdMS_TO_TICKS(interval));//注意这里替换成freertos的延时
	};
	
	return 0;
}

需要注意的是,在使用了FreeRTOS后,我们应该摒弃使用HAL库中的Delay()函数,中断函数等,避免造成系统的崩溃。

;