FreeRTOS(2):基于FreeRTOS的任务创建与删除(动态)

📅 2026/7/29 14:12:35
FreeRTOS(2):基于FreeRTOS的任务创建与删除(动态)
实验大体思路及目标现象基于 FreeRTOS 抢占式多任务创建 3 个业务任务task1优先级 3PA1 引脚 LED 周期性 500ms 闪烁task2优先级 2PA2 引脚 LED 周期性 500ms 闪烁task3优先级 1按键扫描按下 PB1 按键则永久销毁 LED1 任务 task1且仅能删除一次防止重复删除引发系统异常采用启动任务统一创建所有业务任务创建完成后自销毁简化 main 函数逻辑实验现象两个LED灯闪烁按下按键后PA1引脚的LED灯停止闪烁PA2的LED灯继续闪烁注本文是笔者在学习B站博主正点原子的FreeRTOS视频后根据自己的stm32f103c8t6最小系统板所复刻的实验,若有不了解的地方可去观看该播主的视频。创建工程复制上一篇文章中完成的工程文件FreeRTOS移植粘贴并重命名为FreeRTOS_任务创建与删除动态进入该文件夹后添加一个新的文件夹文件夹名为Hardware将别的工程中的key.c和key.hLED.c和LED.h复制粘贴到Hardware文件夹中笔者是先跟着江协科技的stm32教程走了一遍若不是根据江协科技的走的可以根据自己的代码调节本文最后笔者也会给出以上四个文件的代码随后点击打开keil按照下图添加Hardware的文件夹随后按照下图添加路径最后点击两次OK保存代码实现由于使用了FreeRTOS所以裸机使用的Delay函数就不能使用了如果使用了裸机的Delay函数可能会导致函数运行到那里直接停止了不会在继续运行下去了这也是和FreeRTOS的有关所以我们要修改一下Key.c中的代码使的Key.c中的代码只具有识别是哪个按键的额功能按键消抖我们在调用的时候实现下面就是修改后的Key.c的代码#include stm32f10x.h // Device header void Key_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); 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); } uint8_t Key_GetNum(void) { if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)0) { return 1; } if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)0) { return 2; } return 0; }我们使用了stm32f103c8t6最小系统板的PB1和PB11两个接口但是本次实验只会用到PB1这个接口。随后在user文件夹下创建FreeRTOS.c和FreeRTOS.h两个文件其中FreeRTOS.c用来存放本次实验的代码主体FreeRTOS.h来向外部声明。在FreeRTOS.c中我们先引用如下头函数随后先写一个FreeRTOS_Task函数生成生成task1task2task3的函数同时可以在这个函数中将按键和LED灯初始化随后就可以使用xTaskCreate函数创建start_task函数来生成三个task函数在写好这个函数后可以右键该函数然后点击下图红框所框出的地方跳转到这个函数的定义跳转后应该显示的是下图所示函数复制该函数然后到FreeRTOS_Task函数中粘贴整理然后到FreeRTOS_Task函数中粘贴整理得到下图所示函数现在我们还没有定义所以报错是正常的接下来我们就要来定义各个变量pxTaskCode就是将要创建的任务的名称由于我们还没有创建所以我们可以在下方创建一下这个函数函数名为Start_task接下来的configSTACK_DEPTH_TYPE usStackDepth则是这个任务的栈的大小我们可以在上方定义一个宏定义来定义这一变量pvParameters是传递给任务函数的入口参数通常我们直接写NULL即可uxPriority是创建的任务的优先级我们也可以在开头定义一个宏变量即可优先级是数字越大优先级越高pxCreatedTask用来接收创建成功后返回的任务句柄而任务句柄我们也还没有创建所以我们需要在开头创建一个任务句柄任务句柄的类型就是pxCreatedTask前的类型即为TaskHandle_t类型下图为创建好后的函数vTaskStartScheduler函数就会启动 FreeRTOS 任务调度器开启多任务并发运行随后我们就该写start_task中的内容start_task函数就是创建三个task函数实现LED灯闪烁和按键基本方法和上文类似笔者就不再赘述了注意这个函数在创建好了三个函数后要销毁所以在最后要加上vTaskDelete(NULL);函数来销毁自己其中taskENTER_CRITICAL()函数是进入临界区taskEXIT_CRITICAL()是退出临界区这两个函数的作用就是在start_task函数执行完创建三个函数的代码后FreeRTOS才开始按照优先级执行那三个函数而不是start_task创建一个函数FreeRTOS就执行一个函数这是笔者的理解若有错误请联系笔者改正。以下就是这段内容的代码#include stm32f10x.h // Device header #include freertos.h #include task.h #include led.h #include key.h void Start_task( void * pvParameters ); void task_1( void * pvParameters ); void task_2( void * pvParameters ); void task_3( void * pvParameters ); #define START_TASK_SIZE 128 #define START_TASK_PRIO 1 #define TASK1_SIZE 128 #define TASK1_PRIO 3 #define TASK2_SIZE 128 #define TASK2_PRIO 2 #define TASK3_SIZE 128 #define TASK3_PRIO 1 TaskHandle_t start_task_handle; TaskHandle_t task1_handle; TaskHandle_t task2_handle; TaskHandle_t task3_handle; void FreeRTOS_Task(void) { Key_Init(); LED_Init(); xTaskCreate( (TaskFunction_t ) Start_task, (char * ) Start_task, (configSTACK_DEPTH_TYPE) START_TASK_SIZE, (void * ) NULL, (UBaseType_t ) START_TASK_PRIO, (TaskHandle_t * ) start_task_handle ); vTaskStartScheduler(); } void Start_task( void * pvParameters ) { taskENTER_CRITICAL(); //进入临界区 xTaskCreate( (TaskFunction_t ) task_1, (char * ) task_1, (configSTACK_DEPTH_TYPE) TASK1_SIZE, (void * ) NULL, (UBaseType_t ) TASK1_PRIO, (TaskHandle_t * ) task1_handle ); xTaskCreate( (TaskFunction_t ) task_2, (char * ) task_2, (configSTACK_DEPTH_TYPE) TASK2_SIZE, (void * ) NULL, (UBaseType_t ) TASK2_PRIO, (TaskHandle_t * ) task2_handle ); xTaskCreate( (TaskFunction_t ) task_3, (char * ) task_3, (configSTACK_DEPTH_TYPE) TASK3_SIZE, (void * ) NULL, (UBaseType_t ) TASK3_PRIO, (TaskHandle_t * ) task3_handle ); vTaskDelete(NULL); //删除自己这个任务 taskEXIT_CRITICAL(); //退出临界区 } /*实现LED0 500ms闪烁一次*/ void task_1( void * pvParameters ) { } /*实现LED1 500ms闪烁一次*/ void task_2( void * pvParameters ) { } uint8_t task1_is_delete 0; /*实现按键删除task1*/ void task_3( void * pvParameters ) { }接下来我们就要来实现三个task函数中的内容第一个是LED0的闪烁while(1) { LED1_ON(); //PA1 vTaskDelay(500); LED1_OFF(); vTaskDelay(500); }这就是Task_1函数中的内容注意延时函数要使用FreeRTOS自带的延时函数否则会导致程序卡死Task_2中的内容与Task_1中的内容差不多下方为Task_2中的代码while(1) { LED2_ON(); //PA2 vTaskDelay(500); LED2_OFF(); vTaskDelay(500); }接下来是Task_3中的代码由于在Key.c的板块中没有进行消抖所以在Task_3中要进行消抖下方是Task_3中的代码uint8_t KeyVal 0; while(1) { KeyVal Key_GetNum(); //按键接在PB1上时Key_GetNum输出为1接在PB11上时输出为2 if(KeyVal ! 0) { vTaskDelay(pdMS_TO_TICKS(20)); // RTOS延时让出CPULED正常闪烁 if(Key_GetNum() KeyVal) // 二次确认消抖 { // 等待按键松开 while(Key_GetNum() ! 0) { vTaskDelay(pdMS_TO_TICKS(10)); } vTaskDelay(pdMS_TO_TICKS(20)); // 按键触发逻辑 if(KeyVal 1 task1_is_delete 0) { vTaskDelete(task1_handle); task1_is_delete 1; } } } vTaskDelay(pdMS_TO_TICKS(10)); }其中要在Task_3函数外定义一个变量task1_is_delete 这个变量是用来保证按键删除一次后就不会在触发vTaskDelete函数重复删除task1的任务。接下来就就要在FreeRTOS.h文件中声明这些函数了下方就是在此函数中的代码#ifndef __FREERTOS_TASK_H #define __FREERTOS_TASK_H void FreeRTOS_Task(void); #endif最后就可以在主函数中调用FreeRTOS_Task函数就行了下方是主函数的代码#include stm32f10x.h #include FreeRTOS.h #include task.h #include freertos_task.h int main(void) { FreeRTOS_Task(); while(1); }最后在对应的引脚接上LED和按键就可以观察到现象按下按键后接到PA1的LED停止闪烁PA2的LED继续闪烁不受影响。最后是Key.cKey.hLED.cLED.h的代码Key.c的代码#include stm32f10x.h // Device header void Key_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); 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); } uint8_t Key_GetNum(void) { if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)0) { return 1; } if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)0) { return 2; } return 0; }Key.h的代码#ifndef __KEY_H #define __KEY_H uint8_t Key_GetNum(void); void Key_Init(void); #endifLED.c的代码#include stm32f10x.h // Device header void LED_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); GPIO_InitTypeDef GPIO_Initstructure; GPIO_Initstructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_Initstructure.GPIO_Pin GPIO_Pin_1 | GPIO_Pin_2; GPIO_Initstructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA,GPIO_Initstructure); GPIO_SetBits(GPIOA,GPIO_Pin_1 | GPIO_Pin_2); } void LED1_Turn(void) //实现按按键后可以实现小灯的开或关 { if(GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_1) 0) { GPIO_SetBits(GPIOA,GPIO_Pin_1); } else { GPIO_ResetBits(GPIOA,GPIO_Pin_1); } } void LED1_ON(void) { GPIO_ResetBits(GPIOA,GPIO_Pin_1); } void LED1_OFF(void) { GPIO_SetBits(GPIOA,GPIO_Pin_1); } void LED2_Turn(void) { if(GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_2) 0) { GPIO_SetBits(GPIOA,GPIO_Pin_2); } else { GPIO_ResetBits(GPIOA,GPIO_Pin_2); } } void LED2_ON(void) { GPIO_ResetBits(GPIOA,GPIO_Pin_2); } void LED2_OFF(void) { GPIO_SetBits(GPIOA,GPIO_Pin_2); } void LED_ON(int a) { if(a1) { GPIO_ResetBits(GPIOA,GPIO_Pin_1); } else { GPIO_ResetBits(GPIOA,GPIO_Pin_2); } } void LED_OFF(int a) { if(a1) { GPIO_SetBits(GPIOA,GPIO_Pin_1); } else { GPIO_SetBits(GPIOA,GPIO_Pin_2); } }LED.h的代码#ifndef __LED_H #define __LED_H void LED_Init(void); void LED_ON(int a); void LED_OFF(int a); void LED1_ON(void); void LED2_ON(void); void LED1_OFF(void); void LED2_OFF(void); void LED1_Turn(void); void LED2_Turn(void); #endif笔者水平有限若有错误还请海涵。