Bootstrap

STM32 FreeRTOS 任务创建和删除实验(动态方法)

目录

实验目标

 CubeMX环境准备

SysMode配置

RCC配置 

​编辑LED1引脚配置 

LED2引脚配置

KEY1引脚配置

串口USART1配置

NVIC配置

 项目管理

代码生成配置

生成代码

Keil配置

打开项目:

配置使用微库

配置每次烧录后“复位并运行”

FreeRTOS移植

移植配置完成后的包含目录如下

工程对象窗口如下

编译

创建外设及测试程序文件

在项目根目录下创建Int目录

在Int目录下创建Key.c、Key.h、Led.c、Led.h四个文件

在项目根目录下的Core/Inc目录下创建FreeRTOS_demo.h

在Core/Src目录下创建FreeRTOS_demo.c

在Keil中将Int添加为头文件包含目录

在项目对象中添加Int组 ,并在该组下添加Key.c和Led.c

在Application/User/Core组下添加FreeRTOS_demo.c

项目导入Keil Assistant

通过Code打开工作目录

选择VSCode配置文件

确保该配置下包含Keil Assistant插件。

 切换后可以看到KEIL UVISION PROJECT,如下

导入项目

自动导入失败时手动导入

单击导入项目按钮

在弹出的窗口中选择打开项目文件即可

此时会提示是否切换工作区,选择OK即可

展开项目对象列表

在项目中显示头文件

在FreeRTOS_demo.c中写入以下内容

Key.c写入内容

Led.c写入内容

重新编译

展开源文件可以看到引用的头文件列表

处理“without a newline”警告

重定向printf

usart.h代码清单

usart.c代码清单

外设文件代码清单

Key.h

key.c

Led.h

Led.c

FreeRTOSConfig.h代码清单

FreeRTOS_demo.h代码清单

FreeRTOS_demo.c代码清单

引入头文件

任务设置

入口函数

启动任务函数

task1函数

task2函数

task3函数

main.c代码清单

引入头文件

调用入口函数

测试

编译并烧录后,可以看到LED1和LED2同时闪烁

打开串口工具可以看到以下内容

运行日志

按下KEY1,可以看到LED1不再闪烁

此外,task1不再输出日志

代码逻辑


动态创建,堆栈是在FreeRTOS管理的堆内存里,注意任务不要重复创建。

xxxxx_STACK_SIZE 128

uxTaskGetStackHighWaterMark()获取指定任务的任务栈的历史剩余最小值,根据这个结果适当调整启动任务的大小。

实验目标

学会 xTaskCreate( ) 和 vTaskDelete( ) 的使用:

  • start_task:用来创建其他的三个任务。
  • task1:实现LED1每500ms闪烁一次。
  • task2:实现LED2每500ms闪烁一次。
  • task3:判断按键KEY1是否按下,按下则删掉task1。

 CubeMX环境准备

SysMode配置

HAL和FreeRTOS都依赖SysTick,保险起见,所有项目都将HAL库的时钟源替换为TIM7。 

RCC配置 

LED1引脚配置 

LED2引脚配置

KEY1引脚配置

串口USART1配置

NVIC配置

将TIM7调整为HAL时钟源后,其中断默认开启,且不能关闭,为了避免使用HAL库时钟源时被FreeRTOS调度中断导致卡死,TIM7的中断优先级配置为1,下文同理。

 项目管理

代码生成配置

生成代码

Keil配置

打开项目:

生成代码后,在弹出的串口选择“Open Project”,用Keil打开项目

或者点击“Open Folder”打开目录

 

也可以直接在文件资源管理器中找到CubeMX中指定的项目文件路径。

双击MDK-ARM目录下后缀为.uvprojx的文件即可在Keil中打开生成的项目

配置使用微库

我们要重定向printf(),需要调用微库实现。

配置每次烧录后“复位并运行”

打开Target选项窗口

打开ST-Link Debugger的配置

配置每次烧录后“复位并运行”

确定

 OK

FreeRTOS移植

按照STM32 FreeRTOS移植-CSDN博客

配置即可,需要注意的是,最后一步建议将时钟源由SysTick替换为其它定时器,此处可以不做。

移植配置完成后的包含目录如下

工程对象窗口如下

编译

编译后出现如下结果则移植成功

创建外设及测试程序文件

在项目根目录下创建Int目录

在Int目录下创建Key.c、Key.h、Led.c、Led.h四个文件

在项目根目录下的Core/Inc目录下创建FreeRTOS_demo.h

在Core/Src目录下创建FreeRTOS_demo.c

在Keil中将Int添加为头文件包含目录

确认即可

在项目对象中添加Int组 ,并在该组下添加Key.c和Led.c

确认即可

在Application/User/Core组下添加FreeRTOS_demo.c

项目导入Keil Assistant

通过Code打开工作目录

进入项目根目录下的MDK-ARM目录,右键空白处

选择VSCode配置文件

确保该配置下包含Keil Assistant插件。

 切换后可以看到KEIL UVISION PROJECT,如下

导入项目

切换配置文件后稍待片刻点击KEIL UVISION PROJECT标签即可看到以下内容

 如果项目没有导入,可以多次点击KEIL UVISION PROJECT标签,若此法不奏效,可以手动导入,步骤如下。

自动导入失败时手动导入
单击导入项目按钮

在弹出的窗口中选择打开项目文件即可

此时会提示是否切换工作区,选择OK即可

展开项目对象列表

有时某些项目对象未被加载,如Int/,此时可以在Keil中重新编译项目即可加载。

在项目中显示头文件

在FreeRTOS_demo.c中写入以下内容
#include "FreeRTOS_demo.h"
Key.c写入内容
#include "Key.h"
Led.c写入内容
#include "LED.h"
重新编译

展开源文件可以看到引用的头文件列表

Led.h和Key.h同理。

处理“without a newline”警告

上述警告是因为编译器要求每行以\n结尾,因此,文件末尾要有空行

添加空行并重新编译,即可消除警告。

重定向printf

usart.h代码清单
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */

要注意,用户自定义的头文件应该在USER CODE BEGIN Includes注释标签对之间,这样在CubeMX重新生成代码时,这部分内容才不会被清除。

usart.c代码清单
/* USER CODE BEGIN 0 */
int fputc(int ch, FILE *f) {
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);
  return 0;
}
/* USER CODE END 0 */

同理,重写的fputc()函数代码应置于USER CODE BEGIN标签之间。

外设文件代码清单

Key.h
#ifndef __KEY_H
#define __KEY_H

#include "main.h"

#define KEY1        HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin)   
/* 读取KEY1引脚状态(上拉输入) */

#define KEY1_PRESS    1              

uint8_t Key_Detect(void);

#endif
key.c
#include "Key.h"

/**
 * @description: 检测按键
 * @return {*} 按下的按键值
 */
uint8_t Key_Detect(void)
{
    uint8_t res = 0;
    if (KEY1 == GPIO_PIN_RESET)  
    {
        HAL_Delay(10);           /* 去抖动 */
        /* 按这个顺序,如果多个按键同时按,优先级:KEY4>KEY3>KEY2>KEY1 */
        if (KEY1 == GPIO_PIN_RESET)  res = KEY1_PRESS;
    }
    return res;
}
Led.h
#ifndef __LED_H
#define __LED_H

#include "gpio.h"

#define LED uint16_t
void LED_Turn_On(LED led);
void LED_Turn_Off(LED led);
void LED_Toggle(LED led);

void LED_Turn_Off_All(LED led[], uint8_t len);

#endif
Led.c
#include "Led.h"

/**
 * @description: 点亮LED
 * @param {LED} led
 */
void LED_Turn_On(LED led)
{
    HAL_GPIO_WritePin(GPIOA, led, GPIO_PIN_RESET);
}

/**
 * @description: 熄灭LED
 * @param {LED} led
 */
void LED_Turn_Off(LED led)
{
    HAL_GPIO_WritePin(GPIOA, led, GPIO_PIN_SET);
}

/**
 * @description: 翻转LED的状态
 * @param {LED} led
 */
void LED_Toggle(LED led)
{
    HAL_GPIO_TogglePin(GPIOA, led);
}

/**
 * @description: 关闭所有LED
 * @param {LED} led
 * @param {uint8_t} len
 * @return {*}
 */
void LED_Turn_Off_All(LED led[], uint8_t len)
{
    uint8_t i;
    for (i = 0; i < len; i++)
    {
        LED_Turn_Off(led[i]);
    }
}

FreeRTOSConfig.h代码清单

#define configSUPPORT_DYNAMIC_ALLOCATION                1

实际上,配置项configSUPPORT_DYNAMIC_ALLOCATION的默认值为1,上述代码可以省略。在FreeRTOS.h的959-962行有如下代码

#ifndef configSUPPORT_DYNAMIC_ALLOCATION
    /* Defaults to 1 for backward compatibility. */
    #define configSUPPORT_DYNAMIC_ALLOCATION    1
#endif

由此可知,configSUPPORT_DYNAMIC_ALLOCATION默认值为1。

FreeRTOS_demo.h代码清单

#ifndef __FREERTOS_DEMO_H__
#define __FREERTOS_DEMO_H__

void FreeRTOS_Start(void);

#endif

FreeRTOS_demo.c代码清单

引入头文件
#include "FreeRTOS_demo.h"
#include "FreeRTOS.h"
#include "task.h"
#include "Key.h"
#include "Led.h"
#include <stdio.h>
任务设置
/* 启动任务函数 */
#define START_TASK_PRIORITY 1
#define START_TASK_STACK_DEPTH 128
TaskHandle_t start_task_handler;
void Start_Task(void *pvParameters);

/* Task1 任务 配置 */
#define TASK1_PRIORITY 2
#define TASK1_STACK_DEPTH 128
TaskHandle_t task1_handler;
void Task1(void *pvParameters);

/* Task2 任务 配置 */
#define TASK2_PRIORITY 3
#define TASK2_STACK_DEPTH 128
TaskHandle_t task2_handler;
void Task2(void *pvParameters);

/* Task3 任务 配置 */
#define TASK3_PRIORITY 4
#define TASK3_STACK_DEPTH 128
TaskHandle_t task3_handler;
void Task3(void *pvParameters);
入口函数
/**
 * @description: FreeRTOS入口函数:创建任务函数并开始调度
 * @return {*}
 */
void FreeRTOS_Start(void)
{
    xTaskCreate((TaskFunction_t)Start_Task,
                (char *)"Start_Task",
                (configSTACK_DEPTH_TYPE)START_TASK_STACK_DEPTH,
                (void *)NULL,
                (UBaseType_t)START_TASK_PRIORITY,
                (TaskHandle_t *)&start_task_handler);
    vTaskStartScheduler();
}
启动任务函数
void Start_Task( void * pvParameters )
{
    taskENTER_CRITICAL();               /* 进入临界区 */
    xTaskCreate((TaskFunction_t         )   Task1,
                (char *                 )   "Task1",
                (configSTACK_DEPTH_TYPE )   TASK1_STACK_DEPTH,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK1_PRIORITY,
                (TaskHandle_t *         )   &task1_handler );
                
    xTaskCreate((TaskFunction_t         )   Task2,
                (char *                 )   "Task2",
                (configSTACK_DEPTH_TYPE )   TASK2_STACK_DEPTH,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK2_PRIORITY,
                (TaskHandle_t *         )   &task2_handler );
                
    xTaskCreate((TaskFunction_t         )   Task3,
                (char *                 )   "Task2",
                (configSTACK_DEPTH_TYPE )   TASK3_STACK_DEPTH,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK3_PRIORITY,
                (TaskHandle_t *         )   &task3_handler );
    vTaskDelete(NULL);                  
    taskEXIT_CRITICAL();                /* 退出临界区 */
}
task1函数
/**
 * @description: LED1每500ms翻转一次
 * @param {void *} pvParameters
 * @return {*}
 */
void Task1(void * pvParameters)
{
    while(1)
    {
        printf("task1运行....\r\n");
        LED_Toggle(LED1_Pin);
        vTaskDelay(500);
    }
}
task2函数
/**
 * @description: LED2每500ms翻转一次
 * @param {void *} pvParameters
 * @return {*}
 */
void Task2(void * pvParameters)
{
    while(1)
    {
        printf("task2运行....\r\n");
        LED_Toggle(LED2_Pin);
        vTaskDelay(500);
    }
}
task3函数
/**
 * @description: 按下KEY1删除task1
 * @param {void *} pvParameters
 * @return {*}
 */
void Task3(void * pvParameters)
{
    uint8_t key = 0;
    while(1)
    {
        printf("task3正在运行...\r\n");
        key = Key_Detect();
        if(key == KEY1_PRESS)
        {
            if(task1_handler != NULL)
            {
                printf("删除task1任务...\r\n");
                vTaskDelete(task1_handler);
                task1_handler = NULL;
            }
        }
        vTaskDelay(10);
    }

}

main.c代码清单

引入头文件
/* USER CODE BEGIN Includes */
#include "FreeRTOS_demo.h"
/* USER CODE END Includes */
调用入口函数
  /* USER CODE BEGIN 2 */
  FreeRTOS_Start();
  /* USER CODE END 2 */

测试

编译并烧录后,可以看到LED1和LED2同时闪烁
打开串口工具可以看到以下内容

运行日志

task1和task2约半秒执行一次,task3约10ms执行一次,每隔半秒可以看到task2和task1运行日志,如下。

按下KEY1,可以看到LED1不再闪烁
此外,task1不再输出日志

代码逻辑

主体代码逻辑,首先启动任务task---创建任务开始调度---创建task1会抢占task,执行task1---在task1里进行阻塞,在阻塞过程会让出cpu---task继续执行---创建task2---task2抢占task---task2也有延迟---task继续执行---创建task3---task3同样有延迟---task继续执行---删除task---task3因为延迟低优先级高会先刷屏---然后等task1和task2阻塞结束后,根据优先级task2比task1高先执行task2---再执行task1---按下按键---删除task1---只剩下task2和task3运行。

;