ARM32开发--FreeRTOS-任务管理

知不足而奋进 望远山而前行


目录

知不足而奋进 望远山而前行​编辑

文章目录

前言

学习目标

学习内容

任务处理常见操作

任务组成

动态任务创建流程

静态任务创建流程

动态任务和静态任务的区别

任务优先级

任务操作

任务删除

任务挂起

任务恢复

总结


前言

在嵌入式系统开发中,任务管理是保证系统高效运行的关键之一。FreeRTOS作为一款流行的实时操作系统,提供了丰富的任务管理功能,包括动态和静态任务创建、任务删除、任务挂起与恢复等操作。本文将深入探讨这些任务管理机制及其应用,帮助开发者理解和掌握如何通过FreeRTOS实现灵活的任务管理和调度,以满足不同嵌入式应用的需求。


学习目标

  1. 理解任务管理机制
  2. 掌握动态任务创建
  3. 掌握任务删除
  4. 掌握任务挂起和恢复
  5. 了解静态任务创建
  6. 了解任务调度机制
  7. 了解临界区的概念

学习内容

任务处理常见操作

操作

API

动态任务创建

xTaskCreate

任务删除

vTaskDelete

静态任务创建

vTaskCreateStatic

挂起任务

vTaskSuspend

恢复任务

vTaskResume

任务组成

此处以创建任务的代码为例,搞清楚几个关键词。

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
                            const char * const pcName,
                            const configSTACK_DEPTH_TYPE usStackDepth,
                            void * const pvParameters,
                            UBaseType_t uxPriority,
                            TaskHandle_t * const pxCreatedTask );

创建任务的参数说明:

  1. TaskFunction_t pxTaskCode: 表示的这个任务执行的函数,函数格式为:
// typedef void (* TaskFunction_t)( void * );
void task(void *pvParameters);
  1. void * const pvParameters: 表示任务执行函数的参数,也就是上面函数中的参数部分。
  2. const char * const pcName: 表示给这个任务起的名字。
  3. const configSTACK_DEPTH_TYPE usStackDepth: 表示启动任务所需要的栈的大小,单位是byte。通常根据任务的复杂度来进行设置。
  4. UBaseType_t uxPriority: 表示任务的优先级。

以下是关于FreeRTOS任务优先级的几个要点:

  1. 数值越大,优先级越高:在FreeRTOS中,任务的优先级数值越大,优先级越高。例如,优先级为1的任务比优先级为0的任务具有更高的优先级。
  2. 优先级为0的任务是最低优先级:通常称为IDLE任务或空闲任务。该任务在没有其他任务需要运行时执行,确保系统在空闲时也有任务可以运行。
  3. 相同优先级的任务采用时间片轮转调度:当有多个任务具有相同优先级时,FreeRTOS会使用时间片轮转调度算法来平均分配CPU时间。每个任务在一轮时间片内执行一段时间,然后切换到下一个任务。
  4. 高优先级任务可以抢占低优先级任务:如果一个高优先级任务就绪并准备好运行,它可以抢占当前正在运行的低优先级任务,从而提供更好的实时性。
  5. 优先级反映任务调度顺序:任务的优先级决定了任务调度的顺序。当有多个任务就绪并等待运行时,任务调度器会选择具有最高优先级的就绪任务来执行。

需要注意的是,任务优先级的设置应根据应用的实时需求和任务间的相对重要性进行合理的规划。过多或过少的优先级级别可能导致调度问题或资源竞争。在任务优先级设置时,需要综合考虑系统的响应性、任务的相互影响和资源的使用情况等因素。

  1. TaskHandle_t * const pxCreatedTask: 任务句柄。可以理解为任务的实例。

创建任务的返回值说明:

BaseType_t类型为创建任务的返回值,结果为pdPASS或者pdFAIL(成功或者失败)。

动态任务创建流程

  1. 此步骤可以省略。因为默认值为1。但是需要了解这个配置。配置FreeRTOS.h中的configSUPPORT_DYNAMIC_ALLOCATION为1.
#ifndef configSUPPORT_DYNAMIC_ALLOCATION
    /* Defaults to 1 for backward compatibility. */
    #define configSUPPORT_DYNAMIC_ALLOCATION    1
#endif
  1. 定义任务执行函数。
void task(void *pvParameters) {
    // TODO: 任务的业务逻辑
}
  1. 调用任务创建逻辑
xTaskCreate(task1, "task1", 64, NULL, 2, &task1_handler);

我们编写一个HelloWorld示例,点亮PD8和PD9的灯,通过两个不同的任务,进行灯的闪烁控制,观察效果。

#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "FreeRTOS.h"
#include "task.h"

TaskHandle_t            start_handler;
TaskHandle_t            task1_handler;
TaskHandle_t            task2_handler;

void task1(void *pvParameters) {
  while(1) {
    gpio_bit_toggle(GPIOD, GPIO_PIN_8);
    vTaskDelay(500);
  }
}

void task2(void *pvParameters) {
  while(1) {
    gpio_bit_toggle(GPIOD, GPIO_PIN_9);
    vTaskDelay(1000);
  }
}

static void GPIO_config(rcu_periph_enum rcu, uint32_t port, uint32_t pin) {
  // 1. 时钟初始化
  rcu_periph_clock_enable(rcu);
  // 2. 配置GPIO 输入输出模式
  gpio_mode_set(port, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, pin);
  // 3. 配置GPIO 输出选项
  gpio_output_options_set(port, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, pin);
  // 4. 默认输出电平
  gpio_bit_write(port, pin, RESET);
}

void GPIO_init() {

  GPIO_config(RCU_GPIOC, GPIOC, GPIO_PIN_6);
  GPIO_config(RCU_GPIOD, GPIOD, GPIO_PIN_8);
  GPIO_config(RCU_GPIOD, GPIOD, GPIO_PIN_9);
}

void start_task(void *pvParameters) {
    GPIO_init();
    
    taskENTER_CRITICAL();

    xTaskCreate(task1, "task1", 64, NULL, 2, &task1_handler);
    xTaskCreate(task2, "task2", 64, NULL, 2, &task2_handler);

    vTaskDelete(start_handler);

    taskEXIT_CRITICAL();
}

int main(void)
{
    nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0);
    xTaskCreate(start_task, "start_task", 128, NULL, 1, &start_handler);
    vTaskStartScheduler();

    while(1) {}
}

静态任务创建流程

  1. 配置FreeRTOS.h中的configSUPPORT_STATIC_ALLOCATION为1.
#i#ifndef configSUPPORT_STATIC_ALLOCATION
    /* Defaults to 0 for backward compatibility. */
    #define configSUPPORT_STATIC_ALLOCATION    1
#endif
  1. 实现内存管理函数vApplicationGetIdleTaskMemoryvApplicationGetTimerTaskMemory
StaticTask_t idle_task_tcb;
StackType_t  idle_task_stack[configMINIMAL_STACK_SIZE];

StaticTask_t timer_task_tcb;
StackType_t  timer_task_stack[configTIMER_TASK_STACK_DEPTH];

void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
                                    StackType_t ** ppxIdleTaskStackBuffer,
                                    uint32_t * pulIdleTaskStackSize )
{
    * ppxIdleTaskTCBBuffer = &idle_task_tcb;
    * ppxIdleTaskStackBuffer = idle_task_stack;
    * pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}


void vApplicationGetTimerTaskMemory( StaticTask_t ** ppxTimerTaskTCBBuffer,
                                     StackType_t ** ppxTimerTaskStackBuffer,
                                     uint32_t * pulTimerTaskStackSize )
{
    * ppxTimerTaskTCBBuffer = &timer_task_tcb;
    * ppxTimerTaskStackBuffer = timer_task_stack;
    * pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}
  1. 定义任务执行函数。
void task(void *pvParameters) {
    // TODO: 任务的业务逻辑
}
  1. 调用任务创建逻辑
TaskHandle_t xTaskCreateStatic(TaskFunction_t pxTaskCode,
                               const char * const pcName,
                               const uint32_t ulStackDepth,
                               void * const pvParameters,
                               UBaseType_t uxPriority,
                               StackType_t * const puxStackBuffer,
                               StaticTask_t * const pxTaskBuffer )
    1. StackType_t * const puxStackBuffer:任务栈大小。得自己指定,不可更改。
    2. StaticTask_t * const pxTaskBuffer:任务控制块,用来存储任务的堆栈空间,任务的状态和优先级等。
    3. 返回值为任务的句柄。

以上面点灯案例为例:

#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "FreeRTOS.h"
#include "task.h"


StaticTask_t idle_task_tcb;
StackType_t  idle_task_stack[configMINIMAL_STACK_SIZE];

StaticTask_t timer_task_tcb;
StackType_t  timer_task_stack[configTIMER_TASK_STACK_DEPTH];

TaskHandle_t            start_handler;
TaskHandle_t            task1_handler;
TaskHandle_t            task2_handler;

#define TASK_STACK_SIZE   128
StackType_t     task_stack[TASK_STACK_SIZE];
StaticTask_t    task_tcb;

#define TASK1_STACK_SIZE   64
StackType_t     task1_stack[TASK1_STACK_SIZE];
StaticTask_t    task1_tcb;

#define TASK2_STACK_SIZE   64
StackType_t     task2_stack[TASK2_STACK_SIZE];
StaticTask_t    task2_tcb;

void task1(void *pvParameters) {
    while(1) {
        vTaskDelay(300);
        gpio_bit_set(GPIOE, GPIO_PIN_3);
        vTaskDelay(300);
        gpio_bit_reset(GPIOE, GPIO_PIN_3);
    }
}

void task2(void *pvParameters) {
    while(1) {
        vTaskDelay(1000);
        gpio_bit_set(GPIOD, GPIO_PIN_7);
        vTaskDelay(1000);
        gpio_bit_reset(GPIOD, GPIO_PIN_7);
    }
}

void GPIO_config() {
    // 1. 时钟初始化
    rcu_periph_clock_enable(RCU_GPIOE);
    // 2. 配置GPIO 输入输出模式
    gpio_mode_set(GPIOE, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_3);
    // 3. 配置GPIO 模式的操作方式
    gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_3);

    // 1. 时钟初始化
    rcu_periph_clock_enable(RCU_GPIOD);
    // 2. 配置GPIO 输入输出模式
    gpio_mode_set(GPIOD, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_7);
    // 3. 配置GPIO 模式的操作方式
    gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_7);
}

void start_task(void *pvParameters) {
    GPIO_config();
    taskENTER_CRITICAL();

    task1_handler = xTaskCreateStatic(task1, "task1", TASK1_STACK_SIZE, NULL, 2, task1_stack, &task1_tcb);
    task2_handler = xTaskCreateStatic(task2, "task2", TASK2_STACK_SIZE, NULL, 2, task2_stack, &task2_tcb);

    vTaskDelete(start_handler);

    taskEXIT_CRITICAL();
}

void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
                                   StackType_t ** ppxIdleTaskStackBuffer,
                                   uint32_t * pulIdleTaskStackSize )
{
    * ppxIdleTaskTCBBuffer = &idle_task_tcb;
    * ppxIdleTaskStackBuffer = idle_task_stack;
    * pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}


void vApplicationGetTimerTaskMemory( StaticTask_t ** ppxTimerTaskTCBBuffer,
                                    StackType_t ** ppxTimerTaskStackBuffer,
                                    uint32_t * pulTimerTaskStackSize )
{
    * ppxTimerTaskTCBBuffer = &timer_task_tcb;
    * ppxTimerTaskStackBuffer = timer_task_stack;
    * pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}


int main(void)
{

    start_handler = xTaskCreateStatic(start_task, "start_task", TASK_STACK_SIZE, NULL, 2, task_stack, &task_tcb);
    vTaskStartScheduler();

    while(1) {}
}

动态任务和静态任务的区别

在FreeRTOS中,任务可以使用静态分配方式或动态分配方式创建。这两种方式在任务创建和内存管理方面存在一些区别。

静态任务:

  1. 静态任务是在编译时分配内存的任务。
  2. 在创建静态任务时,需要提前为任务分配足够的内存空间。
  3. 静态任务的内存分配是固定的,任务的内存大小在编译时确定,并在运行时保持不变。
  4. 静态任务使用 xTaskCreateStatic() 函数创建。

动态任务:

  1. 动态任务是在运行时分配内存的任务。
  2. 在创建动态任务时,不需要提前为任务分配内存空间,而是在运行时使用动态内存分配函数进行分配。
  3. 动态任务的内存分配是动态的,任务的内存大小可以根据需要进行调整。
  4. 动态任务使用 xTaskCreate() 函数创建。

区别:

  1. 静态任务的内存分配是在编译时完成,而动态任务的内存分配是在运行时完成。
  2. 静态任务需要手动为任务分配内存空间,而动态任务会自动进行内存分配和释放。
  3. 静态任务的内存大小在编译时确定,不能在运行时改变;而动态任务的内存大小可以在运行时进行动态调整。
  4. 静态任务对内存的使用是固定的,不会有内存碎片的问题;而动态任务的内存使用可能存在碎片化的风险。

选择静态任务还是动态任务取决于具体的应用需求和系统约束。

静态任务在一些资源有限的系统中更常用,可以避免动态内存分配的开销和内存碎片问题。

而动态任务可以在运行时根据需要动态分配内存,灵活性更高。

通常采用动态任务创建更多。

任务优先级

任务的优先级等级是在FreeRTOSConfig.h中定义的,configMAX_PRIORITIES定义了最大任务优先级值,默认值为5。那么优先级取值为0到4。数值越大优先级越高。

我们采用日志打印的方式进行验证,开启两个任务,分别打印日志,开启任务时设置不同优先级进行测试,以下是示例代码。

#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "FreeRTOS.h"
#include "task.h"
#include "usart0.h"

TaskHandle_t            start_handler;
TaskHandle_t            task1_handler;
TaskHandle_t            task2_handler;

void task1(void *pvParameters) {
    while(1) {
        printf("task1\r\n");
        vTaskDelay(1000);
    }
}

void task2(void *pvParameters) {
    while(1) {
        printf("task2\r\n");
        vTaskDelay(1000);
    }
}

void Usart0_recv(uint8_t *data, uint32_t len) {
    printf("recv: %s\r\n", data);
}

void start_task(void *pvParameters) {
    Usart0_init();

    printf("start\r\n");
    taskENTER_CRITICAL();

    xTaskCreate(task1, "task1", 64, NULL, 2, &task1_handler);
    xTaskCreate(task2, "task2", 64, NULL, 3, &task2_handler);

    vTaskDelete(start_handler);

    taskEXIT_CRITICAL();
}

int main(void)
{
    nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0);
    xTaskCreate(start_task, "start_task", 128, NULL, 1, &start_handler);
    vTaskStartScheduler();

    while(1) {}
}

任务操作

任务删除

函数格式定义如下

BaseType_t xTaskDelete(TaskHandle_t xTaskToDelete);

其中,xTaskToDelete 是要删除的任务的句柄(TaskHandle_t 类型)。可以将任务的句柄传递给 xTaskDelete() 函数,以删除指定的任务。

任务删除的几个要点如下:

  1. 当前任务的删除:如果在任务的执行过程中调用 xTaskDelete(NULL),表示删除当前任务。当前任务将被立即删除,并且不会继续执行后续代码
  2. 删除其他任务:如果要删除除当前任务之外的任务,需要传递相应任务的句柄给 xTaskDelete() 函数。这样,指定的任务将被删除。
  3. 任务删除的影响:任务删除后,其占用的资源(如堆栈、任务控制块等)会被释放,其他任务可以继续执行。删除任务时需要注意任务间的同步和资源释放,以避免产生悬空指针或资源泄漏等问题。
  4. 返回值:xTaskDelete() 函数的返回值是 BaseType_t 类型,表示任务删除成功与否。如果任务删除成功,返回值为 pdPASS。如果任务删除失败,返回值为 errTASK_NOT_DELETED。
  5. 需要确保配置了如下宏:#define INCLUDE_vTaskDelete 1

需要注意的是,在任务删除之前,需要确保不再需要该任务的执行,并且合理处理任务间的同步和资源释放。不正确地删除任务可能会导致未定义行为和系统不稳定性。

任务挂起
// 挂起任务
vTaskSuspend(xTaskHandle);
任务恢复
// 恢复任务
vTaskResume(xTaskHandle);

实现案例,通过按键来操作任务的操作,点击按钮挂起任务,恢复任务。

#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "FreeRTOS.h"
#include "task.h"
#include "usart0.h"

TaskHandle_t            start_handler;
TaskHandle_t            task_key_handler;
TaskHandle_t            task1_handler;
TaskHandle_t            task2_handler;

void task1(void *pvParameters) {
    while(1) {
        printf("task1\r\n");
        vTaskDelay(1000);
    }
}

void task2(void *pvParameters) {
    while(1) {
        printf("task2\r\n");
        vTaskDelay(1000);
    }
}

void task_key(void *pvParameters) {
    uint32_t flag = 0;
    FlagStatus pre_state = RESET;
    BaseType_t result;
    while(1) {
        FlagStatus state = gpio_input_bit_get(GPIOA, GPIO_PIN_0);
        if(SET == state && pre_state == RESET) {
            // 当前高电平, 上一次为低电平,按下
            pre_state = state;

            if(flag == 0) {
                // 挂起
                vTaskSuspend(task1_handler);
            } else if(flag == 1) {
                // 恢复
                vTaskResume(task1_handler);
            }
            flag++;
            if(flag > 1) flag = 0;
        } else if(RESET == state && pre_state == SET) {
            // 当前高电平, 上一次为低电平,抬起
            pre_state = state;
        }
        vTaskDelay(20);
    }
}

void USART0_recv(uint8_t *data, uint32_t len) {
    printf("recv: %s\r\n", data);
}

void start_task(void *pvParameters) {
    GPIO_config();
    USART0_init();

    printf("start\r\n");
    
    taskENTER_CRITICAL();

    xTaskCreate(task_key, "task_key", 64, NULL, 2, &task_key_handler);
    xTaskCreate(task1, "task1", 64, NULL, 2, &task1_handler);
    xTaskCreate(task2, "task2", 64, NULL, 3, &task2_handler);

    vTaskDelete(start_handler);

    taskEXIT_CRITICAL();
}

static void GPIO_config() {
    // 时钟初始化
    rcu_periph_clock_enable(RCU_GPIOA);
    // 配置GPIO模式
    gpio_mode_set(GPIOA, GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, GPIO_PIN_0);
}



int main(void)
{
    nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0);

    xTaskCreate(start_task, "start_task", 128, NULL, 1, &start_handler);
    vTaskStartScheduler();

    while(1) {}
}

总结

任务管理是实时操作系统中的核心概念之一,合理的任务管理可以有效提升系统的响应速度和资源利用率。本文通过介绍FreeRTOS中的任务管理机制,涵盖了动态和静态任务创建、任务删除、任务挂起与恢复等重要操作,详细解析了任务优先级设置的注意事项以及任务间的同步与资源释放策略。通过本文的学习,读者可以更好地理解和应用FreeRTOS的任务管理功能,为嵌入式系统开发提供强有力的支持。

  • 27
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

薛慕昭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值