学习目标

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

    学习内容

    任务处理常见操作

    | 操作 | API | | —- | —- | | 动态任务创建 | xTaskCreate | | 任务删除 | vTaskDelete | | 静态任务创建 | vTaskCreateStatic | | 挂起任务 | vTaskSuspend | | 恢复任务 | vTaskResume |

任务组成

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

  1. BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
  2. const char * const pcName,
  3. const configSTACK_DEPTH_TYPE usStackDepth,
  4. void * const pvParameters,
  5. UBaseType_t uxPriority,
  6. TaskHandle_t * const pxCreatedTask );

创建任务的参数说明:

  1. TaskFunction_t pxTaskCode: 表示的这个任务执行的函数,函数格式为:

    1. // typedef void (* TaskFunction_t)( void * );
    2. void task(void *pvParameters);
  2. void * const pvParameters: 表示任务执行函数的参数,也就是上面函数中的参数部分。

  3. const char * const pcName: 表示给这个任务起的名字。
  4. const configSTACK_DEPTH_TYPE usStackDepth: 表示启动任务所需要的栈的大小,单位是byte。通常根据任务的复杂度来进行设置。
  5. UBaseType_t uxPriority: 表示任务的优先级。

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

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

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

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

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

动态任务创建流程

  1. 此步骤可以省略。因为默认值为1。但是需要了解这个配置。配置FreeRTOS.h中的configSUPPORT_DYNAMIC_ALLOCATION为1.

    1. #ifndef configSUPPORT_DYNAMIC_ALLOCATION
    2. /* Defaults to 1 for backward compatibility. */
    3. #define configSUPPORT_DYNAMIC_ALLOCATION 1
    4. #endif
  2. 定义任务执行函数。

    1. void task(void *pvParameters) {
    2. // TODO: 任务的业务逻辑
    3. }
  3. 调用任务创建逻辑

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

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

    include “gd32f4xx.h”

    include “systick.h”

    include

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

  1. xTaskCreate(task1, "task1", 64, NULL, 2, &task1_handler);
  2. xTaskCreate(task2, "task2", 64, NULL, 2, &task2_handler);
  3. vTaskDelete(start_handler);
  4. 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. // 1. 时钟初始化
  2. rcu_periph_clock_enable(RCU_GPIOD);
  3. // 2. 配置GPIO 输入输出模式
  4. gpio_mode_set(GPIOD, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_7);
  5. // 3. 配置GPIO 模式的操作方式
  6. gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_7);

}

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

  1. xTaskCreate(start_task, "start_task", 128, NULL, 1, &start_handler);
  2. vTaskStartScheduler();
  3. while(1) {}

}

  1. <a name="MMIeV"></a>
  2. ### 静态任务创建流程
  3. 1. 配置`FreeRTOS.h`中的`configSUPPORT_STATIC_ALLOCATION`为1.
  4. ```c
  5. #i#ifndef configSUPPORT_STATIC_ALLOCATION
  6. /* Defaults to 0 for backward compatibility. */
  7. #define configSUPPORT_STATIC_ALLOCATION 1
  8. #endif
  1. 实现内存管理函数vApplicationGetIdleTaskMemoryvApplicationGetTimerTaskMemory ```c 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 ) {

  1. * ppxIdleTaskTCBBuffer = &idle_task_tcb;
  2. * ppxIdleTaskStackBuffer = idle_task_stack;
  3. * pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;

}

void vApplicationGetTimerTaskMemory( StaticTask_t ppxTimerTaskTCBBuffer, StackType_t ppxTimerTaskStackBuffer, uint32_t * pulTimerTaskStackSize ) {

  1. * ppxTimerTaskTCBBuffer = &timer_task_tcb;
  2. * ppxTimerTaskStackBuffer = timer_task_stack;
  3. * pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;

}

  1. 3. 定义任务执行函数。
  2. ```c
  3. void task(void *pvParameters) {
  4. // TODO: 任务的业务逻辑
  5. }
  1. 调用任务创建逻辑

    1. TaskHandle_t xTaskCreateStatic(TaskFunction_t pxTaskCode,
    2. const char * const pcName,
    3. const uint32_t ulStackDepth,
    4. void * const pvParameters,
    5. UBaseType_t uxPriority,
    6. StackType_t * const puxStackBuffer,
    7. StaticTask_t * const pxTaskBuffer )
    1. StackType_t * const puxStackBuffer:任务栈大小。得自己指定,不可更改。
    2. StaticTask_t * const pxTaskBuffer:任务控制块,用来存储任务的堆栈空间,任务的状态和优先级等。
    3. 返回值为任务的句柄。

以上面点灯案例为例:

  1. #include "gd32f4xx.h"
  2. #include "systick.h"
  3. #include <stdio.h>
  4. #include "main.h"
  5. #include "FreeRTOS.h"
  6. #include "task.h"
  7. StaticTask_t idle_task_tcb;
  8. StackType_t idle_task_stack[configMINIMAL_STACK_SIZE];
  9. StaticTask_t timer_task_tcb;
  10. StackType_t timer_task_stack[configTIMER_TASK_STACK_DEPTH];
  11. TaskHandle_t start_handler;
  12. TaskHandle_t task1_handler;
  13. TaskHandle_t task2_handler;
  14. #define TASK_STACK_SIZE 128
  15. StackType_t task_stack[TASK_STACK_SIZE];
  16. StaticTask_t task_tcb;
  17. #define TASK1_STACK_SIZE 64
  18. StackType_t task1_stack[TASK1_STACK_SIZE];
  19. StaticTask_t task1_tcb;
  20. #define TASK2_STACK_SIZE 64
  21. StackType_t task2_stack[TASK2_STACK_SIZE];
  22. StaticTask_t task2_tcb;
  23. void task1(void *pvParameters) {
  24. while(1) {
  25. vTaskDelay(300);
  26. gpio_bit_set(GPIOE, GPIO_PIN_3);
  27. vTaskDelay(300);
  28. gpio_bit_reset(GPIOE, GPIO_PIN_3);
  29. }
  30. }
  31. void task2(void *pvParameters) {
  32. while(1) {
  33. vTaskDelay(1000);
  34. gpio_bit_set(GPIOD, GPIO_PIN_7);
  35. vTaskDelay(1000);
  36. gpio_bit_reset(GPIOD, GPIO_PIN_7);
  37. }
  38. }
  39. void start_task(void *pvParameters) {
  40. taskENTER_CRITICAL();
  41. task1_handler = xTaskCreateStatic(task1, "task1", TASK1_STACK_SIZE, NULL, 2, task1_stack, &task1_tcb);
  42. task2_handler = xTaskCreateStatic(task2, "task2", TASK2_STACK_SIZE, NULL, 2, task2_stack, &task2_tcb);
  43. vTaskDelete(start_handler);
  44. taskEXIT_CRITICAL();
  45. }
  46. void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
  47. StackType_t ** ppxIdleTaskStackBuffer,
  48. uint32_t * pulIdleTaskStackSize )
  49. {
  50. * ppxIdleTaskTCBBuffer = &idle_task_tcb;
  51. * ppxIdleTaskStackBuffer = idle_task_stack;
  52. * pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
  53. }
  54. void vApplicationGetTimerTaskMemory( StaticTask_t ** ppxTimerTaskTCBBuffer,
  55. StackType_t ** ppxTimerTaskStackBuffer,
  56. uint32_t * pulTimerTaskStackSize )
  57. {
  58. * ppxTimerTaskTCBBuffer = &timer_task_tcb;
  59. * ppxTimerTaskStackBuffer = timer_task_stack;
  60. * pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
  61. }
  62. void GPIO_config() {
  63. // 1. 时钟初始化
  64. rcu_periph_clock_enable(RCU_GPIOE);
  65. // 2. 配置GPIO 输入输出模式
  66. gpio_mode_set(GPIOE, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_3);
  67. // 3. 配置GPIO 模式的操作方式
  68. gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_3);
  69. // 1. 时钟初始化
  70. rcu_periph_clock_enable(RCU_GPIOD);
  71. // 2. 配置GPIO 输入输出模式
  72. gpio_mode_set(GPIOD, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_7);
  73. // 3. 配置GPIO 模式的操作方式
  74. gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_7);
  75. }
  76. int main(void)
  77. {
  78. systick_config();
  79. GPIO_config();
  80. start_handler = xTaskCreateStatic(start_task, "start_task", TASK_STACK_SIZE, NULL, 2, task_stack, &task_tcb);
  81. vTaskStartScheduler();
  82. while(1) {}
  83. }

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

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

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

动态任务:

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

区别:

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

选择静态任务还是动态任务取决于具体的应用需求和系统约束。
静态任务在一些资源有限的系统中更常用,可以避免动态内存分配的开销和内存碎片问题。
而动态任务可以在运行时根据需要动态分配内存,灵活性更高。
通常采用动态任务创建更多。

任务优先级

任务的优先级等级是在FreeRTOSConfig.h中定义的,configMAX_PRIORITIES定义了最大任务优先级值,默认值为5。那么优先级取值为0到4。数值越大优先级越高。
我们采用日志打印的方式进行验证,开启两个任务,分别打印日志,开启任务时设置不同优先级进行测试,以下是示例代码。

  1. #include "gd32f4xx.h"
  2. #include "systick.h"
  3. #include <stdio.h>
  4. #include "main.h"
  5. #include "FreeRTOS.h"
  6. #include "task.h"
  7. #include "usart0.h"
  8. TaskHandle_t start_handler;
  9. TaskHandle_t task1_handler;
  10. TaskHandle_t task2_handler;
  11. void task1(void *pvParameters) {
  12. while(1) {
  13. printf("task1\r\n");
  14. vTaskDelay(1000);
  15. }
  16. }
  17. void task2(void *pvParameters) {
  18. while(1) {
  19. printf("task2\r\n");
  20. vTaskDelay(1000);
  21. }
  22. }
  23. void Usart0_recv(uint8_t *data, uint32_t len) {
  24. printf("recv: %s\r\n", data);
  25. }
  26. void start_task(void *pvParameters) {
  27. taskENTER_CRITICAL();
  28. xTaskCreate(task1, "task1", 64, NULL, 2, &task1_handler);
  29. xTaskCreate(task2, "task2", 64, NULL, 3, &task2_handler);
  30. vTaskDelete(start_handler);
  31. taskEXIT_CRITICAL();
  32. }
  33. int main(void)
  34. {
  35. systick_config();
  36. Usart0_init();
  37. printf("start\r\n");
  38. xTaskCreate(start_task, "start_task", 128, NULL, 1, &start_handler);
  39. vTaskStartScheduler();
  40. while(1) {}
  41. }

任务操作

任务删除

函数格式定义如下

  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。

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

任务挂起

  1. // 挂起任务
  2. vTaskSuspend(xTaskHandle);

任务恢复

  1. // 恢复任务
  2. vTaskResume(xTaskHandle);

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

  1. #include "gd32f4xx.h"
  2. #include "systick.h"
  3. #include <stdio.h>
  4. #include "main.h"
  5. #include "FreeRTOS.h"
  6. #include "task.h"
  7. #include "usart0.h"
  8. TaskHandle_t start_handler;
  9. TaskHandle_t task_key_handler;
  10. TaskHandle_t task1_handler;
  11. TaskHandle_t task2_handler;
  12. void task1(void *pvParameters) {
  13. while(1) {
  14. printf("task1\r\n");
  15. vTaskDelay(1000);
  16. }
  17. }
  18. void task2(void *pvParameters) {
  19. while(1) {
  20. printf("task2\r\n");
  21. vTaskDelay(1000);
  22. }
  23. }
  24. void task_key(void *pvParameters) {
  25. uint32_t flag = 0;
  26. FlagStatus pre_state = RESET;
  27. BaseType_t result;
  28. while(1) {
  29. FlagStatus state = gpio_input_bit_get(GPIOA, GPIO_PIN_0);
  30. if(SET == state && pre_state == RESET) {
  31. // 当前高电平, 上一次为低电平,按下
  32. pre_state = state;
  33. if(flag == 0) {
  34. // 挂起
  35. vTaskSuspend(task1_handler);
  36. } else if(flag == 1) {
  37. // 恢复
  38. vTaskResume(task1_handler);
  39. }
  40. flag++;
  41. if(flag > 1) flag = 0;
  42. } else if(RESET == state && pre_state == SET) {
  43. // 当前高电平, 上一次为低电平,抬起
  44. pre_state = state;
  45. }
  46. vTaskDelay(20);
  47. }
  48. }
  49. void Usart0_recv(uint8_t *data, uint32_t len) {
  50. printf("recv: %s\r\n", data);
  51. }
  52. void start_task(void *pvParameters) {
  53. taskENTER_CRITICAL();
  54. xTaskCreate(task_key, "task_key", 64, NULL, 2, &task_key_handler);
  55. xTaskCreate(task1, "task1", 64, NULL, 2, &task1_handler);
  56. xTaskCreate(task2, "task2", 64, NULL, 3, &task2_handler);
  57. vTaskDelete(start_handler);
  58. taskEXIT_CRITICAL();
  59. }
  60. static void GPIO_config() {
  61. // 时钟初始化
  62. rcu_periph_clock_enable(RCU_GPIOA);
  63. // 配置GPIO模式
  64. gpio_mode_set(GPIOA, GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, GPIO_PIN_0);
  65. }
  66. int main(void)
  67. {
  68. //NVIC_SetPriorityGrouping(NVIC_PRIGROUP_PRE4_SUB0);
  69. systick_config();
  70. GPIO_config();
  71. Usart0_init();
  72. printf("start\r\n");
  73. xTaskCreate(start_task, "start_task", 128, NULL, 1, &start_handler);
  74. vTaskStartScheduler();
  75. while(1) {}
  76. }

练习题

  1. 实现多任务打印
  2. 实现任务挂起和唤醒