发新帖本帖赏金 50.00元(功能说明)我要提问
返回列表
打印
[uCOS/RTOS]

FreeRTOS任务管理流程

[复制链接]
759|1
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 虚幻的是灵魂 于 2024-7-10 16:26 编辑

#申请原创# @21小跑堂
任务处理常见操作
操作 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: 表示的这个任务执行的函数
2. void * const pvParameters: 表示任务执行函数的参数,也就是上面函数中的参数部分。
3. const char * const pcName: 表示给这个任务起的名字。
4. const configSTACK_DEPTH_TYPE usStackDepth: 表示启动任务所需要的栈的大小,单位是byte。通常根据任务的复杂度来进行设置。
5. UBaseType_t uxPriority: 表示任务的优先级。
6.TaskHandle_t * const pxCreatedTask: 任务句柄。可以理解为任务的实例。

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

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

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

创建任务的返回值说明:

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

2. 定义任务执行函数。

void task(void *pvParameters) {
    // TODO: 任务的业务逻辑
}

3. 调用任务创建逻辑

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

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

main.c

#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) {
        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 start_task(void *pvParameters) {
    taskENTER_CRITICAL();

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

    vTaskDelete(start_handler);

    taskEXIT_CRITICAL();
}

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);
}


int main(void)
{
    systick_config();
    GPIO_config();

    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

2. 实现内存管理函数vApplicationGetIdleTaskMemory和vApplicationGetTimerTaskMemory

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;
}

3. 定义任务执行函数。

void task(void *pvParameters) {
    // TODO: 任务的业务逻辑
}

4. 调用任务创建逻辑

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 )

a. StackType_t * const puxStackBuffer:任务栈大小。得自己指定,不可更改。

b. StaticTask_t * const pxTaskBuffer:任务控制块,用来存储任务的堆栈空间,任务的状态和优先级等。

c. 返回值为任务的句柄。


以上面点灯案例为例:

main.c

#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 start_task(void *pvParameters) {
    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;
}

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);
}


int main(void)
{
    systick_config();
    GPIO_config();

                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。数值越大优先级越高。

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

main.c

#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) {
    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)
{
    systick_config();
    Usart0_init();

        printf("start\r\n");
    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。

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

任务挂起
// 挂起任务
vTaskSuspend(xTaskHandle);
任务恢复
// 恢复任务
vTaskResume(xTaskHandle);
实现案例,通过按键来操作任务的操作,点击按钮挂起任务,恢复任务。


main.c
#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) {
    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_SetPriorityGrouping(NVIC_PRIGROUP_PRE4_SUB0);
    systick_config();
    GPIO_config();
    Usart0_init();

    printf("start\r\n");
    xTaskCreate(start_task, "start_task", 128, NULL, 1, &start_handler);
    vTaskStartScheduler();

    while(1) {}
}










使用特权

评论回复

打赏榜单

21小跑堂 打赏了 50.00 元 2024-07-18
理由:恭喜通过原创审核!期待您更多的原创作品~

评论
21小跑堂 2024-7-18 14:59 回复TA
通过简单的按键和点灯,介绍RTOS的任务管理流程,任务相关知识点介绍详细,流程清晰。 

相关帖子

发新帖 本帖赏金 50.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

14

主题

115

帖子

1

粉丝