02_FreeRTOS任务调度策略与挂起

0. 前言

硬件平台:

  • STM32F103

1. 任务调度策略

什么是任务调度?

  • 任务调度是FreeRTOS实时操作系统中确保系统实时性的一种机制。
  • FreeRTOS通过任务调度器对任务进行调度与管理,从而确保任务在指定的时间内执行。

FreeRTOS中的任务调度机制:

  1. 抢占式调度策略:在FreeRTOS中,任务分为不同的优先级,高优先级的任务可以打断低优先级的任务,从而执行高优先级的任务。
  2. 时间片轮询策略:在FreeRTOS中,如果相同优先级的任务,采用时间片轮询的机制进行调度管理,时间片即FreeRTOS系统分配给任务的执行时间,当执行时间消耗完后,切换下一个任务进行执行。

1.1 抢占式调度示例

示例说明:

  1. 在起始任务中创建两个任务
    1. Task1
    2. Task2
  2. 任务1的优先级设置为3,每500ms打印一次Task1,这里让高优先级的任务1s后再执行
  3. 任务2的优先级设置为2,每500ms打印一次Task2

FreeRTOSConfig配置文件:
![[Pasted image 20250104120456.png]]

起始任务相关配置:

/* 起始任务相关配置 */
void startFunc(void *args);                         /* 起始任务的执行函数 */
#define START_NAME "Start"                          /* 起始任务的名称 */
#define START_STACK_DEPTH 128                       /* 起始任务执行函数的参数 */
#define START_PRIORITY 5                            /* 起始任务优先级 */
TaskHandle_t startHandle;                           /* 起始任务代码控制块结构体:存放起始任务的信息 */

Task1相关配置:

/* Task1相关配置 */
void task1Func(void *args);                         /* Task1任务的执行函数 */
#define TASK1_NAME "Start"                          /* Task1任务的名称 */
#define TASK1_STACK_DEPTH 128                       /* Task1任务执行函数的参数 */
#define TASK1_PRIORITY 3                            /* Task1任务优先级 */
TaskHandle_t task1Handle;                           /* Task1任务代码控制块结构体:存放起始任务的信息 */

Task2相关配置:

/* Task2相关配置 */
void task2Func(void *args);                         /* Task2任务的执行函数 */
#define TASK2_NAME "Start"                          /* Task2任务的名称 */
#define TASK2_STACK_DEPTH 128                       /* Task2任务执行函数的参数 */
#define TASK2_PRIORITY 2                            /* Task2任务优先级 */
TaskHandle_t task2Handle;                           /* Task2任务代码控制块结构体:存放起始任务的信息 */

系统启动函数:

  1. 创建起始任务
  2. 启动任务调度器
/* 启动操作系统 */
void FreeRTOS_Start(void)
{
    /* 1. 创建起始任务 */
    xTaskCreate(
        startFunc,                              
        START_NAME,                             
        START_STACK_DEPTH,                      
        NULL,                                   
        START_PRIORITY,                         
        &startHandle                            
    );

    /* 2. 启动任务调度器 */
    vTaskStartScheduler();
}

起始任务执行函数:

  1. 让代码进入临界区,进入临界区确保代码执行的时候不被中断打断
  2. 创建任务1和任务2
  3. 代码退出临界区
  4. 回收起始任务,参数为NULL表示回收任务本身,也可以输入任务的控制块结构体
void startFunc(void *args)                          /* 起始任务的执行函数 */
{
    /* 1. 让代码进入临界区,防止中断被打断 */
    vPortEnterCritical();
    debug_printfln("起始任务开始执行...");

    /* 2. 创建Task1和Task2 */
    xTaskCreate(                                    /* Task1 */
        task1Func,                              
        TASK1_NAME,                             
        TASK1_STACK_DEPTH,                      
        NULL,                                   
        TASK1_PRIORITY,                         
        &task1Handle                            
    );
    xTaskCreate(                                    /* Task2 */
        task2Func,                              
        TASK2_NAME,                             
        TASK2_STACK_DEPTH,                      
        NULL,                                   
        TASK2_PRIORITY,                         
        &task2Handle                            
    );

    /* 3. 代码退出临界区 */
    debug_printfln("起始任务执行完毕...");
    portEXIT_CRITICAL();

    /* 4. 回收起始任务 */
    vTaskDelete(NULL);
}

任务1与任务2执行函数:

  • Delay_ms与vTaskDelay的区别:
    1. Delay_ms是自己写的延时函数,本质是让cpu计算计数,达到指定时间,占用cpu资源
    2. vTaskDelay是FreeRTOS提供的延时函数,让任务进入阻塞状态,暂停程序的运行
void task1Func(void *args)                          /* Task1任务的执行函数 */
{
    vTaskDelay(1000);                               /* 延时1s后再执行 */
    while (1)
    {
        debug_printfln("Task1...");
        Delay_ms(500);
    }
    
}

void task2Func(void *args)                          /* Task2任务的执行函数 */
{
    while (1)
    {
        debug_printfln("Task2...");
        Delay_ms(500);
    }
    
}

在main函数中调用系统启动函数:
![[Pasted image 20250104120550.png]]

执行状况:

  • 1s之前执行低优先级任务2
  • 1s之后执行高优先级任务1
    ![[Pasted image 20250104115800.png]]

1.2 时间片轮询调度示例

在抢占式调度示例中的代码保持不变,只将对应的任务优先级都改为2

  • 同时删除任务1的延时1s
    ![[Pasted image 20250104120725.png]]

执行状况:

  • 每个任务根据时间片进行轮询执行
    这里可以看到,代码在答应的时候,时间片轮询时间到了,暂停输出打印另外的任务。
    ![[Pasted image 20250104121630.png]]

解决方法:

  • 将打印函数放入临界区内执行
    ![[Pasted image 20250104121811.png]]

![[Pasted image 20250104121839.png]]

FreeRTOS_Demo.h

#ifndef __FREERTOS_DEMO_H
#define __FREERTOS_DEMO_H

#include "FreeRTOS.h"
#include "task.h"

/* 调式 */
#include "Common_Debug.h"
#include "Common_Delay.h"

/* 1. 启动操作系统 */
void FreeRTOS_Start(void);

#endif


FreeRTOSDemo.c

#include "FreeRTOS_Demo.h"

/* 起始任务相关配置 */
void startFunc(void *args);                         /* 起始任务的执行函数 */
#define START_NAME "Start"                          /* 起始任务的名称 */
#define START_STACK_DEPTH 128                       /* 起始任务执行函数的参数 */
#define START_PRIORITY 5                            /* 起始任务优先级 */
TaskHandle_t startHandle;                           /* 起始任务代码控制块结构体:存放起始任务的信息 */

/* Task1相关配置 */
void task1Func(void *args);                         /* Task1任务的执行函数 */
#define TASK1_NAME "Start"                          /* Task1任务的名称 */
#define TASK1_STACK_DEPTH 128                       /* Task1任务执行函数的参数 */
#define TASK1_PRIORITY 2                            /* Task1任务优先级 */
TaskHandle_t task1Handle;                           /* Task1任务代码控制块结构体:存放起始任务的信息 */

/* Task2相关配置 */
void task2Func(void *args);                         /* Task2任务的执行函数 */
#define TASK2_NAME "Start"                          /* Task2任务的名称 */
#define TASK2_STACK_DEPTH 128                       /* Task2任务执行函数的参数 */
#define TASK2_PRIORITY 2                            /* Task2任务优先级 */
TaskHandle_t task2Handle;                           /* Task2任务代码控制块结构体:存放起始任务的信息 */

/* 启动操作系统 */
void FreeRTOS_Start(void)
{
    /* 1. 创建起始任务 */
    xTaskCreate(
        startFunc,                              
        START_NAME,                             
        START_STACK_DEPTH,                      
        NULL,                                   
        START_PRIORITY,                         
        &startHandle                            
    );

    /* 2. 启动任务调度器 */
    vTaskStartScheduler();
}

void startFunc(void *args)                          /* 起始任务的执行函数 */
{
    /* 1. 让代码进入临界区,防止中断被打断 */
    vPortEnterCritical();
    debug_printfln("起始任务开始执行...");

    /* 2. 创建Task1和Task2 */
    xTaskCreate(                                    /* Task1 */
        task1Func,                              
        TASK1_NAME,                             
        TASK1_STACK_DEPTH,                      
        NULL,                                   
        TASK1_PRIORITY,                         
        &task1Handle                            
    );
    xTaskCreate(                                    /* Task2 */
        task2Func,                              
        TASK2_NAME,                             
        TASK2_STACK_DEPTH,                      
        NULL,                                   
        TASK2_PRIORITY,                         
        &task2Handle                            
    );

    /* 3. 代码退出临界区 */
    debug_printfln("起始任务执行完毕...");
    portEXIT_CRITICAL();

    /* 4. 回收起始任务 */
    vTaskDelete(NULL);
}

void task1Func(void *args)                          /* Task1任务的执行函数 */
{
    while (1)
    {
        portENTER_CRITICAL();
        debug_printfln("Task1...");
        portEXIT_CRITICAL();
        Delay_ms(500);
    }
    
}

void task2Func(void *args)                          /* Task2任务的执行函数 */
{
    while (1)
    {
        portENTER_CRITICAL();
        debug_printfln("Task2...");
        portEXIT_CRITICAL();
        Delay_ms(500);
    }
    
}

extern void xPortSysTickHandler(void);
void  SysTick_Handler(void)
{
    if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
    {
        xPortSysTickHandler();
    }
}

2. 任务挂起与恢复

什么是任务挂起?

  • 任务挂起是FreeRTOS提供的一中让任务进入暂停状态的机制,方便开发者在指定情况下对任务进行挂起以等待其他时间的发生。

2.1 任务挂起与恢复相关API

任务挂起与恢复常用API:

  • vTaskSuspend:挂起任务API
  • vTaskResume:恢复任务API
  • xTaskResumeFromISR:在中断中恢复任务API

2.1.1 挂起任务API - vTaskSuspend

函数说明:

  • 注意实现: 宏定义INCLUDE_vTaskSuspend设置为1
  • 参数:挂起任务的控制块结构体
void vTaskSuspend( TaskHandle_t xTaskToSuspend );

2.1.2 恢复任务API - vTaskResume

函数说明:

  • 注意实现: 宏定义INCLUDE_vTaskSuspend设置为1
  • 参数:恢复任务的控制块结构体
void vTaskResume( TaskHandle_t xTaskToResume );

2.1.3 在中断中恢复任务API - xTaskResumeFromISR

函数说明:

  • 注意实现: 宏定义INCLUDE_vTaskSuspend与INCLUDE_xTaskResumeFromISR均设置为1
  • 参数:恢复任务的控制块结构体
  • return:
    • pdTRUE:恢复任务同时切换上下文
    • pdFALSE:ISR 使用此信息来确定 ISR 之后是否需要上下文切换。
BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume );

2.2 挂起与恢复示例

示例说明:

  • 使用时间片轮询调度策略的代码进行修改
  • 添加任务3:
    • 对按键进行扫描,KEY1挂起任务1,KEY2恢复任务1

Task3相关配置:

/* Task3相关配置 */
void task3Func(void *args);                         /* Task3任务的执行函数 */
#define TASK3_NAME "Start"                          /* Task3任务的名称 */
#define TASK3_STACK_DEPTH 128                       /* Task3任务执行函数的参数 */
#define TASK3_PRIORITY 3                            /* Task3任务优先级 */
TaskHandle_t task3Handle;                           /* Task3任务代码控制块结构体:存放起始任务的信息 */

起始任务执行函数:

  • 创建任务3
void startFunc(void *args)                          /* 起始任务的执行函数 */
{
    /* 1. 让代码进入临界区,防止中断被打断 */
    vPortEnterCritical();
    debug_printfln("起始任务开始执行...");

    /* 2. 创建Task1和Task2 */
    xTaskCreate(                                    /* Task1 */
        task1Func,                              
        TASK1_NAME,                             
        TASK1_STACK_DEPTH,                      
        NULL,                                   
        TASK1_PRIORITY,                         
        &task1Handle                            
    );
    xTaskCreate(                                    /* Task2 */
        task2Func,                              
        TASK2_NAME,                             
        TASK2_STACK_DEPTH,                      
        NULL,                                   
        TASK2_PRIORITY,                         
        &task2Handle                            
    );
    xTaskCreate(                                    /* Task3 */
        task3Func,                              
        TASK3_NAME,                             
        TASK3_STACK_DEPTH,                      
        NULL,                                   
        TASK3_PRIORITY,                         
        &task3Handle                            
    );

    /* 3. 代码退出临界区 */
    debug_printfln("起始任务执行完毕...");
    portEXIT_CRITICAL();

    /* 4. 回收起始任务 */
    vTaskDelete(NULL);
}

任务3执行代码;

  • 带按键进行扫描
  • 每50ms进行扫描一次
void task3Func(void *args)                         /* Task3任务的执行函数 */
{
    uint8_t key;
    while (1)
    {
        key = Inf_Key_Detect();
        switch (key)
        {
        case KEY1_PRESS:                            /* 按键1被按下 */
            debug_printfln("Task1挂起...");
            vTaskSuspend(task1Handle);              /* 挂起任务 */
            break;
        case KEY2_PRESS:                            /* 按键2被按下 */
            debug_printfln("Task1恢复...");
            vTaskResume(task1Handle);               /* 恢复任务 */
            break;
        }
        vTaskDelay(50);
    }
    
}

执行状况:

  • 按下key1,任务1挂起,剩下任务2在打印信息
  • 按下key2,任务1恢复,恢复任务1执行,继续打印信息
    ![[Pasted image 20250104123130.png]]

3. 结语

FreeRTOS通过任务调度器对任务进行调度,从而确保系统的实时性,FreeRTOS的调度策略分为抢占式调度和时间片轮询调度,抢占式调度是高优先级打断低优先级任务,执行高优先级任务,时间片轮询为相同任务优先级,每个任务执行相同的时间片。

文章声明:

  • 本文章为作者学习FreeRTOS实时操作系统的笔记与巩固输出,欢迎同行交流与指教,文章借鉴来源小破站的某谷RTOS教程。

FreeRTOSDemo.h文件代码

#ifndef __FREERTOS_DEMO_H
#define __FREERTOS_DEMO_H

#include "FreeRTOS.h"
#include "task.h"

/* 调式 */
#include "Common_Debug.h"
#include "Common_Delay.h"

/* 按键驱动 */
#include "Inf_Key.h"

/* 1. 启动操作系统 */
void FreeRTOS_Start(void);

#endif


FreeRTOSDemo.c文件代码

#include "FreeRTOS_Demo.h"

/* 起始任务相关配置 */
void startFunc(void *args);                         /* 起始任务的执行函数 */
#define START_NAME "Start"                          /* 起始任务的名称 */
#define START_STACK_DEPTH 128                       /* 起始任务执行函数的参数 */
#define START_PRIORITY 5                            /* 起始任务优先级 */
TaskHandle_t startHandle;                           /* 起始任务代码控制块结构体:存放起始任务的信息 */

/* Task1相关配置 */
void task1Func(void *args);                         /* Task1任务的执行函数 */
#define TASK1_NAME "Start"                          /* Task1任务的名称 */
#define TASK1_STACK_DEPTH 128                       /* Task1任务执行函数的参数 */
#define TASK1_PRIORITY 2                            /* Task1任务优先级 */
TaskHandle_t task1Handle;                           /* Task1任务代码控制块结构体:存放起始任务的信息 */

/* Task2相关配置 */
void task2Func(void *args);                         /* Task2任务的执行函数 */
#define TASK2_NAME "Start"                          /* Task2任务的名称 */
#define TASK2_STACK_DEPTH 128                       /* Task2任务执行函数的参数 */
#define TASK2_PRIORITY 2                            /* Task2任务优先级 */
TaskHandle_t task2Handle;                           /* Task2任务代码控制块结构体:存放起始任务的信息 */

/* Task3相关配置 */
void task3Func(void *args);                         /* Task3任务的执行函数 */
#define TASK3_NAME "Start"                          /* Task3任务的名称 */
#define TASK3_STACK_DEPTH 128                       /* Task3任务执行函数的参数 */
#define TASK3_PRIORITY 3                            /* Task3任务优先级 */
TaskHandle_t task3Handle;                           /* Task3任务代码控制块结构体:存放起始任务的信息 */

/* 启动操作系统 */
void FreeRTOS_Start(void)
{
    /* 1. 创建起始任务 */
    xTaskCreate(
        startFunc,                              
        START_NAME,                             
        START_STACK_DEPTH,                      
        NULL,                                   
        START_PRIORITY,                         
        &startHandle                            
    );

    /* 2. 启动任务调度器 */
    vTaskStartScheduler();
}

void startFunc(void *args)                          /* 起始任务的执行函数 */
{
    /* 1. 让代码进入临界区,防止中断被打断 */
    vPortEnterCritical();
    debug_printfln("起始任务开始执行...");

    /* 2. 创建Task1和Task2 */
    xTaskCreate(                                    /* Task1 */
        task1Func,                              
        TASK1_NAME,                             
        TASK1_STACK_DEPTH,                      
        NULL,                                   
        TASK1_PRIORITY,                         
        &task1Handle                            
    );
    xTaskCreate(                                    /* Task2 */
        task2Func,                              
        TASK2_NAME,                             
        TASK2_STACK_DEPTH,                      
        NULL,                                   
        TASK2_PRIORITY,                         
        &task2Handle                            
    );
    xTaskCreate(                                    /* Task3 */
        task3Func,                              
        TASK3_NAME,                             
        TASK3_STACK_DEPTH,                      
        NULL,                                   
        TASK3_PRIORITY,                         
        &task3Handle                            
    );

    /* 3. 代码退出临界区 */
    debug_printfln("起始任务执行完毕...");
    portEXIT_CRITICAL();

    /* 4. 回收起始任务 */
    vTaskDelete(NULL);
}

void task1Func(void *args)                          /* Task1任务的执行函数 */
{
    while (1)
    {
        portENTER_CRITICAL();
        debug_printfln("Task1...");
        portEXIT_CRITICAL();
        Delay_ms(500);
    }
    
}

void task2Func(void *args)                          /* Task2任务的执行函数 */
{
    while (1)
    {
        portENTER_CRITICAL();
        debug_printfln("Task2...");
        portEXIT_CRITICAL();
        Delay_ms(500);
    }
    
}

void task3Func(void *args)                         /* Task3任务的执行函数 */
{
    uint8_t key;
    while (1)
    {
        key = Inf_Key_Detect();
        switch (key)
        {
        case KEY1_PRESS:                            /* 按键1被按下 */
            debug_printfln("Task1挂起...");
            vTaskSuspend(task1Handle);              /* 挂起任务 */
            break;
        case KEY2_PRESS:                            /* 按键2被按下 */
            debug_printfln("Task1恢复...");
            vTaskResume(task1Handle);               /* 恢复任务 */
            break;
        }
        vTaskDelay(50);
    }
    
}

extern void xPortSysTickHandler(void);
void  SysTick_Handler(void)
{
    if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
    {
        xPortSysTickHandler();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值