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运行。

    作者:雁过留声花欲落

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 FreeRTOS 任务创建和删除实验(动态方法)

    发表回复