文章目录
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:任务控制块地址和任务句柄并不相同,我们操控任务是通过任务句柄操作的,任务句柄最终指向任务控制块。
根据任务处理函数的原型,我们可以知道,规定任务处理函数时,需要有以下特点:
- 这个函数不能有返回值
- 他的参数void *pvParameters 是通过创建任务函数xTaskCreate的参数传递进来的。
- 可以用同一个任务处理函数创建多个任务,也就是说,多个任务执行同一个事情。
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()函数,中断函数等,避免造成系统的崩溃。