1. 实验目的
使用动态方法 xTaskCreate()创建任务,使用vTaskDelete()函数删除任务;创建开始任务start_task,在开始任务中创建其他三个任务,创建task1任务实现LED0每500ms闪烁一次,创建task2任务实现LED1每500ms闪烁一次,创建task3判断按键KEY0是否按下,按下则关掉task1。
2. 实验流程
- 宏定义检查;
- 创建开始任务函数;
- 创建任务函数;
- 编写任务函数;
- 编写main函数
2.1 宏定义检查
检查下面的几个宏定义,是否开启抢占式调度器,是否使能时间片调度,是否支持动态申请内存,这几个宏都定义在了FreeRTOSConfig.h中 ,一般默认都是开启的。
#define configUSE_PREEMPTION 1 /* 1: 抢占式调度器, 0: 协程式调度器, 无默认需定义 */
#define configUSE_TIME_SLICING 1 /* 1: 使能时间片调度, 默认: 1 */
#define configSUPPORT_DYNAMIC_ALLOCATION 1 /* 1: 支持动态申请内存, 默认: 1 */
2.2 创建开始任务函数
下面是动态创建任务函数:
//引入头文件
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h" //包含LED的文件
#include "./BSP/KEY/key.h" //包含按键的文件
#include "FreeRTOS.h"
#include "task.h"
//宏定义
//定义任务堆栈的大小
#define START_TASK_STACK_SIZE 128 //剩余历史最小堆栈(后面可以根据这个函数来定义这个堆栈的大小)
//定义任务优先级
#define START_TASK_PRIO 1
//定义任务句柄
TaskHandle_t start_task_handler;
//-----------------------------------------------------------
//声明task1任务函数
//定义任务堆栈的大小
#define TASK1_STACK_SIZE 128 //剩余历史最小堆栈(后面可以根据这个函数来定义这个堆栈的大小)
//定义任务优先级
#define TASK1_PRIO 2
//定义任务句柄
TaskHandle_t task1_handler;
//-----------------------------------------------------------
//声明task2任务函数
//定义任务堆栈的大小
#define TASK2_STACK_SIZE 128 //剩余历史最小堆栈(后面可以根据这个函数来定义这个堆栈的大小)
//定义任务优先级
#define TASK2_PRIO 3
//定义任务句柄
TaskHandle_t task2_handler;
//声明task3任务函数
//void task3(void * pvParameters);
//定义任务堆栈的大小
#define TASK3_STACK_SIZE 128 //剩余历史最小堆栈(后面可以根据这个函数来定义这个堆栈的大小)
//定义任务优先级
#define TASK3_PRIO 4
//定义任务句柄
TaskHandle_t task3_handler;
//声明函数,方便调用
void start_task(void * pvParameters);
void task1(void * pvParameters);
void task2(void * pvParameters);
void task3(void * pvParameters);
//创建开始任务函数,在这个开始任务中创建其他三个任务
void freertos_demo(void)
{
xTaskCreate( (TaskFunction_t ) start_task, //任务函数,创建开始函数
( char * ) "start_task", //任务名称
( configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE, //任务堆栈大小
( void * ) NULL, //传入给任务函数的参数,这里没有入口参数
( UBaseType_t ) START_TASK_PRIO, //任务优先级
( TaskHandle_t * ) &start_task_handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
//在这里开启任务调度,不加临界区,下面就会依次执行任务,先打印的就是task1,因为创建完task1就会运行task1,他的优先级就比start就高
}
2.3 创建任务函数
临界区:临界区是指那些必须完整运行的区域,在临界区中的代码必须完整运行,不能被打断。所以在退出临界区以后才会进行调度任务,这样最先开始的任务就是优先级最高的任务,也就是task3。
void start_task(void * pvParameters ){ //只需要创建一次,不用加while(1)
taskENTER_CRITICAL(); //进入临界区
//创建任务1
xTaskCreate( (TaskFunction_t ) task1, //创建开始函数
( char * ) "task1", //任务名字
( configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE, //堆栈空间
( void * ) NULL, //没有入口参数
( UBaseType_t ) TASK1_PRIO, //任务优先级
( TaskHandle_t * ) &task1_handler); //任务句柄
//创建任务2
xTaskCreate( (TaskFunction_t ) task2, //创建开始函数
( char * ) "task2", //任务名字
( configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE, //堆栈空间
( void * ) NULL, //没有入口参数
( UBaseType_t ) TASK2_PRIO, //任务优先级
( TaskHandle_t * ) &task2_handler); //任务句柄
//创建任务3
xTaskCreate( (TaskFunction_t ) task3, //创建开始函数
( char * ) "task3", //任务名字
( configSTACK_DEPTH_TYPE ) TASK3_STACK_SIZE, //堆栈空间
( void * ) NULL, //没有入口参数
( UBaseType_t ) TASK3_PRIO, //任务优先级
( TaskHandle_t * ) &task3_handler); //任务句柄
//创建完三个任务以后,需要把自己给删除
//当参数是NULL的时候就是代表删除任务自己,这里传入开始任务的任务句柄也是可以的start_task_handler
vTaskDelete(NULL);
// vTaskDelete(start_task_handler); /* 删除开始任务 */
taskEXIT_CRITICAL(); //退出临界区,所以Task3会优先执行,退出临界区才会调度
}
2.4 编写任务函数
//定义task1任务函数
//实现LED0每500ms翻转一次
void task1(void * pvParameters){
while(1){
printf("task1正在运行!!!\r\n");
LED0_TOGGLE(); //LED0翻转
vTaskDelay(500); //进入这里会阻塞!!!!!
}
}
//定义task2任务函数
//实现LED1每500ms翻转一次
void task2(void * pvParameters){
while(1){
printf("task2正在运行!!!\r\n");
LED1_TOGGLE(); //LED0翻转
vTaskDelay(500); //延时500ms
}
}
//定义task3任务函数
//判断按键按下KEY0,按下KEY0删除task1
void task3(void * pvParameters){
uint8_t key = 0;
while(1){
printf("task3正在运行!!!\r\n");
key = key_scan(0); //扫描按键
if(key == KEY0_PRES){ //KEY0按下
printf("KEY0按下了!!!\r\n");
if(task1_handler != NULL){ //判断句柄是否为0
printf("删除task1任务!!!\r\n");
vTaskDelete(task1_handler); //删除任务1
task1_handler = NULL;
}
}
vTaskDelay(50); //延时50ms
}
}
2.5 main.c函数
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
key_init(); /* 初始化按键 */
sram_init(); /* SRAM初始化 */
my_mem_init(SRAMIN); /* 初始化内部SRAM内存池 */
my_mem_init(SRAMEX); /* 初始化外部SRAM内存池 */
my_mem_init(SRAMCCM); /* 初始化内部CCM内存池 */
freertos_demo();
}
3. 实验结果
按下复位键,观察串口助手打印情况:
可以看到任务优先级高task3的先执行,随后是task2,task1。
按下KEY0按键,观察串口助手:
烧录程序到开发版,观察现象:
freertos-led
实验现象与预期一致,两个灯一起交替闪烁,按下按键,删除task1,会发现LED0不会交替闪烁(只会长亮或暗),LED1继续交替闪烁。
4. 总结
1.串口打印中文标点乱码
发现中文并没有乱码,但是有中文标点的!和汉字在一起就会乱码,检查了波特率和keil的设置没有问题后,换新版的串口助手解决问题,这里开始用的是2.6版本,后面用了2.8版本的,下图是2.8版本能正常打印。
2.句柄没有加&,导致删除task1的时候,不能正常删除,就是句柄task1_handler不指向任务task1。
//定义任务句柄
TaskHandle_t task1_handler;
//创建任务1
xTaskCreate( (TaskFunction_t ) task1, //创建开始函数
( char * ) "task1", //任务名字
( configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE, //堆栈空间
( void * ) NULL, //没有入口参数
( UBaseType_t ) TASK1_PRIO, //任务优先级
( TaskHandle_t * ) task1_handler); //任务句柄,这里没有加&
( TaskHandle_t * )是取指针,所以后面的句柄参数要加一个取地址符&。