一、任务创建与删除
1、动态任务创建
xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint16_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask );
①:使用此函数前需将宏 configSUPPORT_DYNAMIC_ALLOCATION 配置为置1
#define configSUPPORT_DYNAMIC_ALLOCATION 1 //支持动态内存申请
②: 定义函数入口参数
首先进行强制类型转换,避免警告(就是给定义的参数类型带上括号)
任务函数:就指向任务函数的指针,我此处的任务函数是 void start_task( void * pvParameters ),所以任务函数就是 start_task;
任务名称:一般与任务函数一致;
任务堆栈大小:通过 #define TASK1_STACK_SIZE 128 宏定义,将堆栈设置为128字,即128*4字节;
传递给任务函数的参数:无,即NULL;
任务优先级:通过 #define START_TASK_PRIO 1 宏定义,将优先级设为1;
任务句柄:定义 TaskHandle_t 类型的任务句柄,将地址传入;
/*START_TASK任务配置*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128 //128字=128*4字节
TaskHandle_t start_task_handler;
void start_task( void * pvParameters );
/*入口函数*/
void freertos_demo(void)
{
xTaskCreate( (TaskFunction_t ) start_task, //任务函数
(char * ) "start_task", //任务名称
(uint16_t ) START_TASK_STACK_SIZE, //任务堆栈大小
(void * ) NULL, //传递给任务函数的参数
(UBaseType_t ) START_TASK_PRIO, //任务优先级
(TaskHandle_t * ) &start_task_handler ); //任务句柄
vTaskStartScheduler(); //开启任务调度器
}
void start_task( void * pvParameters )
{
for(::)
{
//任务主体
}
}
③:编写任务函数
任务将会在一个无限循环中循环执行,可以使用for循环,也可以使用while循环,后者比较简单,笔者喜欢后者。此任务执行的是LED0不断闪烁并在串口打印 “task1正在运行”。
void start_task( void * pvParameters )
{
while(1)
{
printf("task1正在运行\r\n");
LED0=!LED0;
vTaskDelay(500);
}
}
2、静态任务创建
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 );
①:使用此函数前需将宏 configSUPPORT_STATIC_ALLOCATION 配置为1
#define configSUPPORT_STATIC_ALLOCATION 1 //只是静态内存
②: 实现空闲任务与软件定时器接口函数
定义三个参数,任务控制块、任务堆栈、堆栈大小。空闲任务使用的堆栈大小在FreeRTOSConfig.h 中有定义:
#define configMINIMAL_STACK_SIZE ((unsigned short)130) //空闲任务使用的堆栈大小
软件定时器任务堆栈大小在FreeRTOSConfig.h 中也有定义:
#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE*2) //软件定时器任务堆栈大小
在函数内部分别将定义好的参数赋值;
/*空闲任务内存分配*/
StaticTask_t idle_task_tcb;
StackType_t idle_task_stack[configMINIMAL_STACK_SIZE];
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, //任务控制块
StackType_t **ppxIdleTaskStackBuffer, //任务堆栈
uint32_t *pulIdleTaskStackSize ) //堆栈大小
{
*ppxIdleTaskTCBBuffer=&idle_task_tcb;
*ppxIdleTaskStackBuffer=idle_task_stack;
*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;
}
/*软件内存分配*/
StaticTask_t timer_task_tcb;
StackType_t timer_task_stack[configTIMER_TASK_STACK_DEPTH];
void vApplicationGetTimerTaskMemory( StaticTask_t **ppxTimerTaskTCBBuffer, //任务控制块
StackType_t **ppxTimerTaskStackBuffer, //任务堆栈
uint32_t *pulTimerTaskStackSize ) //堆栈大小
{
*ppxTimerTaskTCBBuffer=&timer_task_tcb;
*ppxTimerTaskStackBuffer=timer_task_stack;
*pulTimerTaskStackSize=configTIMER_TASK_STACK_DEPTH;
}
③:定义函数入口参数
与动态任务创建类似,不同的是需要再多定义一个用户分配任务堆栈和用户分配任务控制块指针,而动态创建是计算机自己分配的。静态创建好处就是可以自己规定存储位置,坏处就是比较繁琐。
用户分配任务堆栈一般定义为一个数组,数组的大小即任务堆栈的大小:START_TASK_STACK_SIZE
xTaskCreateStatic()参数中没有句柄,那如果要找到这个任务要怎么办呢?其实它的句柄正是这个函数的返回值!!!所以还要用刚定义的句柄:task1_handler 来接住返回值。
/*START_TASK任务配置*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128 //128字=128*4字节
StackType_t start_task_stack[START_TASK_STACK_SIZE];
TaskHandle_t start_task_handler;
StaticTask_t start_task_tcb;
void start_task( void * pvParameters );
/*入口函数*/
void freertos_demo(void)
{
//创建开始任务
start_task_handler=xTaskCreateStatic( (TaskFunction_t )start_task, //指向任务函数的指针
(const char* )"start_task", //任务名称
(uint32_t )START_TASK_STACK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(StackType_t* )start_task_stack, //用户分配任务堆栈,一般为数组
(StaticTask_t* )&start_task_tcb); //用户分配任务控制块指针
vTaskStartScheduler(); //开启任务调度器
}
void start_task( void * pvParameters )
{
while(1)
{
//任务主体
}
}
④: 编写任务函数
void start_task( void * pvParameters )
{
while(1)
{
printf("task1正在运行\r\n");
LED0=!LED0;
vTaskDelay(500);
}
}
此代码与动态任务创建很类似就不做展示了。
3、动态静态的区别
动态创建任务:任务的控制块以及任务的栈空间所需的内存均由 FreeRTOS 从 FreeRTOS 管理的堆中分配;
静态创建任务:任务的任务控制块以及任务的栈空间所需的内存,需要用户分配提供。
4、任务删除
①:使用此函数前需将宏 INCLUDE_vTaskDelete 配置为1
#define INCLUDE_vTaskDelete 1
②: 调用函数 vTaskDelete();
vTaskDelete(NULL); //vTaskDelete(start_task_handler);
当参数为对应任务的句柄时,将删除对应任务;
若参数为 NULL ,则删除正在执行的任务;
空闲任务会负责释放被删除任务中由系统分配的内存(动态创建),但由用户在任务删除前申请的内存(静态创建),则需要用户在任务被删除前提前释放,否则将导致内存泄露。
5、完整代码
此代码使用动态任务创建的方式,先创建了start_task任务,然后在start_task任务中创建了task1、task2、task3任务。task1 和 task2 用来让 LED0 和 LED1 闪烁,task3 用来判断 KEY0 是否被按下,如果按下则删除 task1。
1、vTaskStartScheduler(); //开启任务调度器
此函数用来开启任务调度器,在任务调度器开启后,任务调度器就会开始去就绪列表调用任务
2、 taskENTER_CRITICAL(); //进入临界区
taskEXIT_CRITICAL(); //退出临界区
此函数用来关闭freertos所管理的中断以保护那些不想被打断的程序段,中断关闭后,任务将不能被调度,必须等退出临界区任务才能被调度。
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "timer.h"
#include "lcd.h"
#include "FreeRTOS.h"
#include "task.h"
#include "freertos_demo.h"
/*START_TASK任务配置*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128 //128字=128*4字节
TaskHandle_t start_task_handler;
void start_task( void * pvParameters );
/*TASK1任务配置*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128 //128字=128*4字节
TaskHandle_t task1_handler;
void task1( void * pvParameters );
/*TASK2任务配置*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128 //128字=128*4字节
TaskHandle_t task2_handler;
void task2( void * pvParameters );
/*TASK3任务配置*/
#define TASK3_PRIO 4
#define TASK3_STACK_SIZE 128 //128字=128*4字节
TaskHandle_t task3_handler;
void task3( void * pvParameters );
/*入口函数*/
void freertos_demo(void)
{
xTaskCreate( (TaskFunction_t ) start_task, //任务函数
(char * ) "start_task", //任务名称
(uint16_t ) START_TASK_STACK_SIZE, //任务堆栈大小
(void * ) NULL, //传递给任务函数的参数
(UBaseType_t ) START_TASK_PRIO, //任务优先级
(TaskHandle_t * ) &start_task_handler ); //任务句柄
vTaskStartScheduler(); //开启任务调度器
}
void start_task( void * pvParameters )
{
taskENTER_CRITICAL(); //进入临界区
printf("start_task正在运行\r\n");
xTaskCreate( (TaskFunction_t ) task1, //任务函数
(char * ) "task1", //任务名称
(uint16_t ) TASK1_STACK_SIZE, //任务堆栈大小
(void * ) NULL, //传递给任务函数的参数
(UBaseType_t ) TASK1_PRIO, //任务优先级
(TaskHandle_t * ) &task1_handler ); //任务句柄
xTaskCreate( (TaskFunction_t ) task2, //任务函数
(char * ) "task2", //任务名称
(uint16_t ) TASK2_STACK_SIZE, //任务堆栈大小
(void * ) NULL, //传递给任务函数的参数
(UBaseType_t ) TASK2_PRIO, //任务优先级
(TaskHandle_t * ) &task2_handler ); //任务句柄
xTaskCreate( (TaskFunction_t ) task3, //任务函数
(char * ) "task3", //任务名称
(uint16_t ) TASK3_STACK_SIZE, //任务堆栈大小
(void * ) NULL, //传递给任务函数的参数
(UBaseType_t ) TASK3_PRIO, //任务优先级
(TaskHandle_t * ) &task3_handler ); //任务句柄
vTaskDelete(NULL); //vTaskDelete(start_task_handler);
taskEXIT_CRITICAL(); //退出临界区
}
void task1( void * pvParameters )
{
while(1)
{
printf("task1正在运行\r\n");
LED0=!LED0;
vTaskDelay(500);
}
}
void task2( void * pvParameters )
{
while(1)
{
printf("task2正在运行\r\n");
LED1=!LED1;
vTaskDelay(500);
}
}
void task3( void * pvParameters )
{
uint8_t key=0;
while(1)
{
printf("task3正在运行,key=%u\r\n",&key);
key=KEY_Scan(0);
if(key==KEY0_PRES)
{
if(task1_handler!=NULL)
{
printf("task1被删除\r\n");
vTaskDelete(task1_handler);
task1_handler=NULL;
}
}
vTaskDelay(10);
}
}
二、任务挂起与恢复
1、任务挂起函数
xTaskSuspend()调用后任务将被挂起,不能再执行,直到任务被恢复。
使用前需将此宏 INCLUDE_vTaskSuspend 配置为1;
当参数为对应任务的句柄时,将挂起对应任务;
若参数为 NULL ,则挂起正在执行的任务;
2、任务恢复函数(任务中恢复)
vTaskResume()调用后,任务被恢复,并进入就绪态。
使用前需将此宏 INCLUDE_vTaskSuspend 配置为1;
void task3( void * pvParameters )
{
uint8_t key=0;
while(1)
{
key=KEY_Scan(0);
if(key==KEY0_PRES)
{
printf("任务挂起");
vTaskSuspend(task1_handler);
}else if(key==KEY1_PRES)
{
printf("任务解挂");
vTaskResume(task1_handler);
}
vTaskDelay(10);
}
}
3、任务恢复函数(中断中恢复)
xTaskResumeFromISR()调用后,任务被恢复,并进入就绪态(专用于中断服务函数)。
使用前需将宏 INCLUDE_vTaskSuspend 和 INCLUDE_xTaskResumeFromISR 都配置为1;
需判断返回值(返回值类型为:BaseType_t):
pdTRUE:说明任务优先级大于正在执行的任务,任务恢复后需要进行任务切换;
pdFALSE:任务恢复后不需要进行任务切换;
中断服务程序中只要调用了FreeRTOS的API函数则中断优先级不能高于FreeRTOS所管理的最高优先级;FreeRTOS所管理的中断优先级范围是:5~15;
为了方便处理,中断分组需设置到第4组,,即抢占优先级0~15,子优先级0。
extern TaskHandle_t task1_handler;
void EXTI0_IRQHandler(void) //中断服务函数
{
delay_xms(10); //消抖
BaseType_t xYieldRequired; //定义变量来接收返回值
if(WK_UP==1)
{
printf("中断解挂");
xYieldRequired=xTaskResumeFromISR(task1_handler);
}
if(xYieldRequired==pdTRUE)
{
portYIELD_FROM_ISR(xYieldRequired);//任务切换
}
EXTI_ClearITPendingBit(EXTI_Line0); //清除EXTI0线路挂起位
}
由于在中断服务程序中,在 a.c 中定义的变量要在 b.c 中使用,就需要将 task1_handler 定义为全局变量:
extern TaskHandle_t task1_handler;
任务切换函数:
portYIELD_FROM_ISR(xYieldRequired);//任务切换
三、总结
大家一定要多动手。不管再简单的知识,再简单的任务,自己动手敲一遍总能遇到一些意想不到的问题。而解决这些问题就是我们不断进步的阶梯!!!