一、实验要求
学会 xTaskCreate( ) 和 vTaskDelete( ) 的使用:
- start_task:用来创建其他的三个任务。
- task1:实现LED1每500ms闪烁一次。
- task2:实现LED2每500ms闪烁一次。
- task3:判断按键KEY1是否按下,按下则删掉task1。
接下来的内容将会特别绕,很多概念捋不清的建议回到上一节看看。
二、重要部分
(1)参数配置初始化
这里定义了创建任务的函数需要的入口参数;
#include "freertos_demo.h"
/*necc header files*/
#include "FreeRTOS.h"
#include "task.h"
#include "gpio.h"
/*start tack config*/
#define START_TASK_STACK 128
#define STACK_TASK_PRIORITY 1
void start_task(void * pvParameters);
TaskHandle_t start_task_handle;
/* task1 config*/
#define START_TASK1_STACK 128
#define STACK_TASK1_PRIORITY 2
void task1(void * pvParameters);
TaskHandle_t start_task1_handle;
/* task2 config*/
#define START_TASK2_STACK 128
#define STACK_TASK2_PRIORITY 3
void task2(void * pvParameters);
TaskHandle_t start_task2_handle;
/* task3 config*/
#define START_TASK3_STACK 128
#define STACK_TASK3_PRIORITY 4
void task3(void * pvParameters);
TaskHandle_t start_task3_handle;
(2)入口函数
在入口函数中,我们来建立一个优先级最低的启动任务。
/*start task*/
void freertos_start(void){
/* 1.creat the start files*/
xTaskCreate((TaskFunction_t) start_task, // 任务函数指针
(char*) "start_task", //任务函数名首地址
(configSTACK_DEPTH_TYPE) START_TASK_STACK, //任务栈深度 单位:32bit = = 4 Byte
(void*) NULL, //给任务函数的传参,这里不用传,根据需要设计
(UBaseType_t) STACK_TASK_PRIORITY, //任务优先级,数字越大越高
(TaskHandle_t *) &start_task_handle); // 任务句柄的指针
/* 2.run the switcher*/
vTaskStartScheduler(); //启动任务调度器
}
在这里解释一下我们创建任务时,为什么要传任务句柄的地址,而不是任务句柄本身。大家想一个道理,任务句柄(本质是指针)是我们在上一个配置环节自定义声明的,并没有给他赋予明确的地址。
而freertos要做的一件事情,就是把我们的句柄通过系统的映射关系与TCB捆绑到一块。
如果我们直接传句柄进去,那么任务创建函数对作为全局变量的句柄是没有改动的。
这里再辨析以下HAL库外设句柄和FreeRTOS任务句柄的区别:
1. HAL库外设句柄本质是一个结构体名称,比如htim,huart等等。我们想要让函数修改实质性的修改它的内容,就必须传地址进去。如果传的是结构体名,对global的结构体本身是没有影响的。
2. FreeRTOS的任务句柄,如上图所说,本质是一个映射TCB的指针。
两者的核心点是都需要取地址来传参,进而修改全局量。
(3)启动函数
void start_task(void * pvParameters){
xTaskCreate((TaskFunction_t) task1,
(char*) "task1",
(configSTACK_DEPTH_TYPE) START_TASK1_STACK,
(void*) NULL,
(UBaseType_t) STACK_TASK1_PRIORITY,
(TaskHandle_t *) &start_task1_handle);
xTaskCreate((TaskFunction_t) task2,
(char*) "task2",
(configSTACK_DEPTH_TYPE) START_TASK2_STACK,
(void*) NULL,
(UBaseType_t) STACK_TASK2_PRIORITY,
(TaskHandle_t *) &start_task2_handle);
xTaskCreate((TaskFunction_t) task3,
(char*) "task3",
(configSTACK_DEPTH_TYPE) START_TASK3_STACK,
(void*) NULL,
(UBaseType_t) STACK_TASK3_PRIORITY,
(TaskHandle_t *) &start_task3_handle);
vTaskDelete(NULL);
}
此函数负责创建其他三个目标任务,用完立刻删除自己。
(4)其他任务函数
前两个目标任务都很简单,死循环里,死循环中要有一个FreeRTOS的delay函数模拟阻塞。注意不要用HAL库的delay,你即使delay再长,FreeRTOS也不会理解。
比较有难度的一个地方是任务三的按键消抖控制。
在FreeRTOS中,硬件方面的消抖延迟我们用HAL库的delay,软件方面的模拟阻塞延迟我们用FreeRTOS系统的delay。
在这个过程中,注意调节系统delay的大小,如果过小,会让优先级最高的task3始终处于运行态,不给其他任务一点机会。
如果过大,按键可能要死死的长按才能起效果。这个过程需要不断上手感受、调试才能ok的
void task1(void * pvParameters){
while(1){
// HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,1);
vTaskDelay(500);
}
}
void task2(void * pvParameters){
while(1){
// HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,0);
vTaskDelay(500);
}
}
void task3(void * pvParameters){
while(1){
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin)==0){
HAL_Delay(10);
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin)==0){
vTaskDelete(start_task1_handle);
start_task1_handle=NULL;
}
}
vTaskDelay(200);
}
}
(5)主函数
主函数中我们include进free_rtos自编程序的头文件,然后在死循环前调用入口函数就行。
需要注意的是,只要这一步做了,那么main函数中入口函数之后的内容永远也不会运行。
完整代码
main.c
/* USER CODE BEGIN Includes */
#include "freertos_demo.h"
/* USER CODE END Includes */
/* USER CODE BEGIN 2 */
freertos_start();
/* USER CODE END 2 */
freertos_demo.h
#ifndef __FREERTOS_DEMO_H
#define __FREERTOS_DEMO_H
#endif
#include "stm32f1xx_hal.h"
void freertos_start(void);
void start_task(void * pvParameters);
void task1(void * pvParameters);
void task2(void * pvParameters);
void task3(void * pvParameters);
freertos_demo.c
#include "freertos_demo.h"
/*necc header files*/
#include "FreeRTOS.h"
#include "task.h"
#include "gpio.h"
/*start tack config*/
#define START_TASK_STACK 128
#define STACK_TASK_PRIORITY 1
void start_task(void * pvParameters);
TaskHandle_t start_task_handle;
/* task1 config*/
#define START_TASK1_STACK 128
#define STACK_TASK1_PRIORITY 2
void task1(void * pvParameters);
TaskHandle_t start_task1_handle;
/* task2 config*/
#define START_TASK2_STACK 128
#define STACK_TASK2_PRIORITY 3
void task2(void * pvParameters);
TaskHandle_t start_task2_handle;
/* task3 config*/
#define START_TASK3_STACK 128
#define STACK_TASK3_PRIORITY 4
void task3(void * pvParameters);
TaskHandle_t start_task3_handle;
/*start task*/
void freertos_start(void){
/* 1.creat the start files*/
xTaskCreate((TaskFunction_t) start_task,
(char*) "start_task",
(configSTACK_DEPTH_TYPE) START_TASK_STACK,
(void*) NULL,
(UBaseType_t) STACK_TASK_PRIORITY,
(TaskHandle_t *) &start_task_handle);
/* 2.run the switcher*/
vTaskStartScheduler();
}
void start_task(void * pvParameters){
xTaskCreate((TaskFunction_t) task1,
(char*) "task1",
(configSTACK_DEPTH_TYPE) START_TASK1_STACK,
(void*) NULL,
(UBaseType_t) STACK_TASK1_PRIORITY,
(TaskHandle_t *) &start_task1_handle);
xTaskCreate((TaskFunction_t) task2,
(char*) "task2",
(configSTACK_DEPTH_TYPE) START_TASK2_STACK,
(void*) NULL,
(UBaseType_t) STACK_TASK2_PRIORITY,
(TaskHandle_t *) &start_task2_handle);
xTaskCreate((TaskFunction_t) task3,
(char*) "task3",
(configSTACK_DEPTH_TYPE) START_TASK3_STACK,
(void*) NULL,
(UBaseType_t) STACK_TASK3_PRIORITY,
(TaskHandle_t *) &start_task3_handle);
vTaskDelete(NULL);
}
void task1(void * pvParameters){
while(1){
// HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,1);
vTaskDelay(500);
}
}
void task2(void * pvParameters){
while(1){
// HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,0);
vTaskDelay(500);
}
}
void task3(void * pvParameters){
while(1){
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin)==0){
HAL_Delay(10);
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin)==0){
vTaskDelete(start_task1_handle);
start_task1_handle=NULL;
}
}
vTaskDelay(200);
}
}
总结
这部分开始,内容将会极其抽象复杂,对于基础内容需要不断回看。重在梳理概念之间的关系。