FreeRTOS_信号量

犀利的毛毛虫 发布于 2025-03-10 281 次阅读


信号量的简介(了解)

FreeRTOS中的信号量是一种用于任务间同步和资源管理的机制。信号量可以是二进制的(只能取0或1)也可以是计数型的(可以是任意正整数)。信号量的基本操作包括“获取”和“释放”。

比如动车上的卫生间,一个卫生间同时只能容纳一个人,由指示灯来表示是否有人在使用。当我们想使用卫生间的时候,有如下过程:

  • 判断卫生间是否有人使用(判断信号量是否有资源)
  • 卫生间空闲(信号量有资源),那么就可以直接进入卫生间(获取信号量成功)
  • 卫生间使用中(信号量没有资源),那么这个人可以选择不上卫生间(获取信号量失败),也可以在门口等待(任务阻塞)

信号量与队列的区别如下:

信号量队列
主要用于管理对共享资源的访问,确保在同一时刻只有一个任务可以访问共享资源用于任务之间的数据通信,通过在任务之间传递消息,实现信息的传递和同步。
可以是二进制信号量(Binary Semaphore)或计数信号量(Counting Semaphore)存储和传递消息的数据结构,任务可以发送消息到队列,也可以从队列接收消息。
适用于对资源的互斥访问,控制任务的执行顺序,或者限制同时访问某一资源的任务数量。适用于在任务之间传递数据,实现解耦和通信。

二值信号量(熟悉)

二值信号量(Binary Semaphore)是一种特殊类型的信号量,它只有两个可能的值:0和1。这种信号量主要用于实现对共享资源的互斥访问或者任务之间的同步。

  • 两个状态: 二值信号量只能处于两个状态之一,通常用0和1表示。当信号量的值为0时,表示资源不可用;当值为1时,表示资源可用。
  • 互斥访问: 常用于控制对共享资源的互斥访问,确保在同一时刻只有一个任务可以访问共享资源。任务在访问资源之前会尝试获取信号量,成功则继续执行,失败则等待。
  • 任务同步: 也可以用于任务之间的同步,例如一个任务等待另一个任务完成某个操作。

信号量 API 函数允许指定阻塞时间。 阻塞时间表示当一个任务试图“获取”信号量时, 如果信号不是立即可用,那么该任务进入阻塞状态的最大 “tick” 数。 如果 多个任务在同一个信号量上阻塞,那么具有最高优先级的任务将在下次信号量可用时最先解除阻塞

可将二进制信号量视为仅能容纳一个项目的队列。 因此,队列只能为空或满(因此称为二进制)。 使用队列的任务和中断 不在乎队列容纳的是什么——它们只想知道队列是空的还是满的。 可以 利用该机制来同步任务和中断。

二值信号量相关函数:

函数描述
xSemaphoreCreateBinary()使用动态方式创建二值信号量
xSemaphoreCreateBinaryStatic()使用静态方式创建二值信号量
xSemaphoreGive()释放信号量
xSemaphoreGiveFromISR()在中断中释放信号量
xSemaphoreTake()获取信号量
xSemaphoreTakeFromISR()在中断中获取信号量

二值信号量实验(掌握)

实验目标

学习使用 FreeRTOS 的二值信号量相关函数:

  • start_task:用来创建其他的2个任务。
  • task1:用于按键扫描,当检测到按键KEY1被按下时,释放二值信号量。
  • task2:获取二值信号量,当成功获取后打印提示信息。
#include "freertos_demo.h"
/* freertos相关的头文件,必须的 */
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h" //信号量相关的头文件
/* 需要用到的其他头文件 */
#include "LED.h"
#include "Key.h"

/* 启动任务的配置 */
#define START_TASK_STACK 128
#define START_TASK_PRIORITY 1
TaskHandle_t start_task_handle;
void start_task(void *pvParameters);

/* 任务1的配置 */
#define TASK1_STACK 128
#define TASK1_PRIORITY 2
TaskHandle_t task1_handle;
void task1(void *pvParameters);

/* 任务2的配置 */
#define TASK2_STACK 128
#define TASK2_PRIORITY 3
TaskHandle_t task2_handle;
void task2(void *pvParameters);



/**
 * @description: 启动FreeRTOS
 * @return {*}
 */
QueueHandle_t sem_handle;
void freertos_start(void)
{

    /* 创建二值信号量 */
    // xSemaphoreCreateBinary //x开头的,不会主动释放信号量
    vSemaphoreCreateBinary(sem_handle); //v开头的,创建完会主动释放一次信号量
    if(sem_handle == NULL)
    {
        printf("二值信号量创建失败\r\n");
    }


    /* 1.创建一个启动任务 */
    xTaskCreate((TaskFunction_t)start_task,               // 任务函数的地址
                (char *)"start_task",                     // 任务名字符串
                (configSTACK_DEPTH_TYPE)START_TASK_STACK, // 任务栈大小,默认最小128,单位4字节
                (void *)NULL,                             // 传递给任务的参数
                (UBaseType_t)START_TASK_PRIORITY,         // 任务的优先级
                (TaskHandle_t *)&start_task_handle);      // 任务句柄的地址

    /* 2.启动调度器:会自动创建空闲任务 */
    vTaskStartScheduler();
}

/**
 * @description: 启动任务:用来创建其他Task
 * @param {void} *pvParameters
 * @return {*}
 */
void start_task(void *pvParameters)
{
    /* 进入临界区:保护临界区里的代码不会被打断 */
    taskENTER_CRITICAL();

    /* 创建3个任务 */
    xTaskCreate((TaskFunction_t)task1,
                (char *)"task1",
                (configSTACK_DEPTH_TYPE)TASK1_STACK,
                (void *)NULL,
                (UBaseType_t)TASK1_PRIORITY,
                (TaskHandle_t *)&task1_handle);
    xTaskCreate((TaskFunction_t)task2,
                (char *)"task2",
                (configSTACK_DEPTH_TYPE)TASK2_STACK,
                (void *)NULL,
                (UBaseType_t)TASK2_PRIORITY,
                (TaskHandle_t *)&task2_handle);

    /* 启动任务只需要执行一次即可,用完就删除自己 */
    vTaskDelete(NULL);

    /* 退出临界区 */
    taskEXIT_CRITICAL();
}

/**
 * @description: 任务一:用于按键扫描,当检测到按键KEY1被按下时,释放二值信号量。
 * @param {void} *pvParameters
 * @return {*}
 */
void task1(void *pvParameters)
{

    uint8_t key = 0;
    BaseType_t res = 0;
    while (1)
    {
        key = Key_Detect();
        if(key==KEY1_PRESS)
        {
            /* 释放二值信号量 */
            res = xSemaphoreGive(sem_handle);
            if(res == pdPASS)
            {
                printf("Task1释放二值信号量成功\r\n");
            }
            else 
            {
                printf("Task1释放二值信号量失败\r\n");
            }
        }
        vTaskDelay(500);
    }
}

/**
 * @description: 任务二:获取二值信号量,当成功获取后打印提示信息
 * @param {void} *pvParameters
 * @return {*}
 */
void task2(void *pvParameters)
{
    BaseType_t res = 0;
    while (1)
    {
        /* 获取二值信号量 */
        res = xSemaphoreTake(sem_handle,portMAX_DELAY);
        if(res == pdPASS)
        {
            printf("Task2获取信号量成功\r\n");
        }
        else 
        {
            printf("Task2获取信号量失败\r\n");   
        }

    }
}


计数型信号量(熟悉)

正如二进制信号量可以被认为是长度为 1 的队列那样,计数信号量也可以被认为是长度大于 1 的队列。 信号量的用户对存储在队列中的数据不感兴趣,他们只关心队列是否为空。

计数信号量通常用于两种情况:

  • 事件计数:在此使用方案中,每次事件发生时,事件处理程序将“给出”一个信号量(信号量计数值递增) ,并且 处理程序任务每次处理事件(信号量计数值递减)时“获取”一个信号量。因此,计数值是 已发生的事件数与已处理的事件数之间的差值。在这种情况下, 创建信号量时计数值可以为零。
  • 资源管理:在此使用情景中,计数值表示可用资源的数量。要获得对资源的控制权,任务必须首先获取 一个信号量——同时递减信号量计数值。当计数值达到零时,表示没有空闲资源可用。当任务使用完资源时, “返还”一个信号量——同时递增信号量计数值。在这种情况下, 创建信号量时计数值可以等于最大计数值。

计数型信号量相关函数:

函数描述
xSemaphoreCreateCounting()使用动态方法创建计数型信号量。
xSemaphoreCreateCountingStatic()使用静态方法创建计数型信号量
uxSemaphoreGetCount()获取信号量的计数值

计数型信号量实验(掌握)实验目标

学习使用 FreeRTOS 的计数型信号量相关函数:

  • start_task:用来创建其他的2个任务。
  • task1:用于按键扫描,当检测到按键KEY1被按下时,释放计数型信号量。
  • task2:每过一秒获取一次计数型信号量,当成功获取后打印信号量计数值。

FreeRTOSConfig.h代码清单

#define configUSE_COUNTING_SEMAPHORES 1

freertos_demo.c代码清单

#include "freertos_demo.h"
/* freertos相关的头文件,必须的 */
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h" //信号量相关的头文件
/* 需要用到的其他头文件 */
#include "LED.h"
#include "Key.h"

/* 启动任务的配置 */
#define START_TASK_STACK 128
#define START_TASK_PRIORITY 1
TaskHandle_t start_task_handle;
void start_task(void *pvParameters);

/* 任务1的配置 */
#define TASK1_STACK 128
#define TASK1_PRIORITY 2
TaskHandle_t task1_handle;
void task1(void *pvParameters);

/* 任务2的配置 */
#define TASK2_STACK 128
#define TASK2_PRIORITY 3
TaskHandle_t task2_handle;
void task2(void *pvParameters);

/**
 * @description: 启动FreeRTOS
 * @return {*}
 */
QueueHandle_t sem_count_handle;
void freertos_start(void)
{
    UBaseType_t count = 0;

    /* 创建计数型信号量 */
    sem_count_handle = xSemaphoreCreateCounting(100, 0);

    if (sem_count_handle == NULL)
    {
        printf("计数型信号量创建失败\r\n");
    }
    else
    {
        /* 创建成功,获取一次计数值,打印出来 */
        count = uxSemaphoreGetCount(sem_count_handle);
        printf("创建计数型信号量成功,初始计数值=%d\r\n", count);
    }

    /* 1.创建一个启动任务 */
    xTaskCreate((TaskFunction_t)start_task,               // 任务函数的地址
                (char *)"start_task",                     // 任务名字符串
                (configSTACK_DEPTH_TYPE)START_TASK_STACK, // 任务栈大小,默认最小128,单位4字节
                (void *)NULL,                             // 传递给任务的参数
                (UBaseType_t)START_TASK_PRIORITY,         // 任务的优先级
                (TaskHandle_t *)&start_task_handle);      // 任务句柄的地址

    /* 2.启动调度器:会自动创建空闲任务 */
    vTaskStartScheduler();
}

/**
 * @description: 启动任务:用来创建其他Task
 * @param {void} *pvParameters
 * @return {*}
 */
void start_task(void *pvParameters)
{
    /* 进入临界区:保护临界区里的代码不会被打断 */
    taskENTER_CRITICAL();

    /* 创建3个任务 */
    xTaskCreate((TaskFunction_t)task1,
                (char *)"task1",
                (configSTACK_DEPTH_TYPE)TASK1_STACK,
                (void *)NULL,
                (UBaseType_t)TASK1_PRIORITY,
                (TaskHandle_t *)&task1_handle);
    xTaskCreate((TaskFunction_t)task2,
                (char *)"task2",
                (configSTACK_DEPTH_TYPE)TASK2_STACK,
                (void *)NULL,
                (UBaseType_t)TASK2_PRIORITY,
                (TaskHandle_t *)&task2_handle);

    /* 启动任务只需要执行一次即可,用完就删除自己 */
    vTaskDelete(NULL);

    /* 退出临界区 */
    taskEXIT_CRITICAL();
}

/**
 * @description: 任务一:用于按键扫描,当检测到按键KEY1被按下时,释放计数型信号量。
 * @param {void} *pvParameters
 * @return {*}
 */
void task1(void *pvParameters)
{

    uint8_t key = 0;
    BaseType_t res = 0;
    while (1)
    {
        key = Key_Detect();
        if (key == KEY1_PRESS)
        {
            /* 释放计数型信号量,计数值会+1 */
            res = xSemaphoreGive(sem_count_handle);
            if (res == pdPASS)
            {
                printf("Task1释放计数型信号量成功\r\n");
            }
            else
            {
                printf("Task1释放计数型信号量失败\r\n");
            }
        }
        vTaskDelay(500);
    }
}

/**
 * @description: 任务二:每过一秒获取一次计数型信号量,当成功获取后打印信号量计数值。
 * @param {void} *pvParameters
 * @return {*}
 */
void task2(void *pvParameters)
{
    BaseType_t res = 0;
    UBaseType_t currnet_count = 0;
    while (1)
    {
        /* 获取二值信号量 */
        res = xSemaphoreTake(sem_count_handle, portMAX_DELAY);
        if (res == pdPASS)
        {
            printf("Task2获取信号量成功\r\n");
        }
        else
        {
            printf("Task2获取信号量失败\r\n");
        }

        currnet_count = uxSemaphoreGetCount(sem_count_handle);
        printf("当前计数值=%d\r\n", currnet_count);
        
        vTaskDelay(1000);
    }
}

优先级翻转简介(熟悉)

优先级翻转是一个在实时系统中可能出现的问题,特别是在多任务环境中。该问题指的是一个较低优先级的任务阻塞了一个较高优先级任务的执行,从而导致高优先级任务无法及时完成。

典型的优先级翻转场景如下:

  • 任务A(高优先级):拥有高优先级,需要访问共享资源,比如一个关键数据结构。
  • 任务B(低优先级):拥有低优先级,目前正在访问该共享资源。
  • 任务C(中优先级):位于任务A和任务B之间,具有介于两者之间的优先级。

具体流程如下:

  • 任务A开始执行,但由于任务B正在访问共享资源,任务A被阻塞等待。
  • 任务C获得执行权,由于优先级高于任务B,它可以抢占任务B。
  • 任务C执行完成后,任务B被解除阻塞,开始执行,完成后释放了共享资源。
  • 任务A重新获取执行权,继续执行。

这个过程中,任务A因为资源被占用而被阻塞,而任务B却被中优先级的任务C抢占,导致任务B无法及时完成。这种情况称为优先级翻转,因为任务C的介入翻转了高优先级任务A的执行顺序。

优先级翻转实验(掌握)实验目标

模拟优先级翻转,观察对抢占式内核的影响:

  • start_task:用来创建其他的3个任务。
  • task1:低优先级任务,同高优先级一样的操作,不同的是低优先级任务占用信号量的时间久一点。
  • task2:中等优先级任务,简单的应用任务。
  • task3:高优先级任务,会获取二值信号量,获取成功以后打印提示信息,处理完后释放信号量。
#include "freertos_demo.h"
/* freertos相关的头文件,必须的 */
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h" //信号量相关的头文件
/* 需要用到的其他头文件 */
#include "LED.h"
#include "Key.h"

/* 启动任务的配置 */
#define START_TASK_STACK 128
#define START_TASK_PRIORITY 1
TaskHandle_t start_task_handle;
void start_task(void *pvParameters);

/* 任务1的配置 */
#define TASK1_STACK 128
#define TASK1_PRIORITY 2
TaskHandle_t task1_handle;
void task1(void *pvParameters);

/* 任务2的配置 */
#define TASK2_STACK 128
#define TASK2_PRIORITY 3
TaskHandle_t task2_handle;
void task2(void *pvParameters);

/* 任务3的配置 */
#define TASK3_STACK 128
#define TASK3_PRIORITY 4
TaskHandle_t task3_handle;
void task3(void *pvParameters);

/**
 * @description: 启动FreeRTOS
 * @return {*}
 */
QueueHandle_t sem_handle;
void freertos_start(void)
{

    /* 创建二值信号量 */
    // xSemaphoreCreateBinary //x开头的,不会主动释放信号量
    vSemaphoreCreateBinary(sem_handle); // v开头的,创建完会主动释放一次信号量
    if (sem_handle == NULL)
    {
        printf("二值信号量创建失败\r\n");
    }
    else 
    {
        printf("二值信号量创建成功\r\n");
    }


    /* 1.创建一个启动任务 */
    xTaskCreate((TaskFunction_t)start_task,               // 任务函数的地址
                (char *)"start_task",                     // 任务名字符串
                (configSTACK_DEPTH_TYPE)START_TASK_STACK, // 任务栈大小,默认最小128,单位4字节
                (void *)NULL,                             // 传递给任务的参数
                (UBaseType_t)START_TASK_PRIORITY,         // 任务的优先级
                (TaskHandle_t *)&start_task_handle);      // 任务句柄的地址

    /* 2.启动调度器:会自动创建空闲任务 */
    vTaskStartScheduler();
}

/**
 * @description: 启动任务:用来创建其他Task
 * @param {void} *pvParameters
 * @return {*}
 */
void start_task(void *pvParameters)
{
    /* 进入临界区:保护临界区里的代码不会被打断 */
    taskENTER_CRITICAL();

    /* 创建3个任务 */
    xTaskCreate((TaskFunction_t)task1,
                (char *)"task1",
                (configSTACK_DEPTH_TYPE)TASK1_STACK,
                (void *)NULL,
                (UBaseType_t)TASK1_PRIORITY,
                (TaskHandle_t *)&task1_handle);
    xTaskCreate((TaskFunction_t)task2,
                (char *)"task2",
                (configSTACK_DEPTH_TYPE)TASK2_STACK,
                (void *)NULL,
                (UBaseType_t)TASK2_PRIORITY,
                (TaskHandle_t *)&task2_handle);
    xTaskCreate((TaskFunction_t)task3,
                (char *)"task3",
                (configSTACK_DEPTH_TYPE)TASK3_STACK,
                (void *)NULL,
                (UBaseType_t)TASK3_PRIORITY,
                (TaskHandle_t *)&task3_handle);

    /* 启动任务只需要执行一次即可,用完就删除自己 */
    vTaskDelete(NULL);

    /* 退出临界区 */
    taskEXIT_CRITICAL();
}

/**
 * @description: 任务一:用于按键扫描,当检测到按键KEY1被按下时,释放二值信号量。
 * @param {void} *pvParameters
 * @return {*}
 */
void task1(void *pvParameters)
{

    BaseType_t res = 0;
    while (1)
    {

        /* 获取信号量 */
        printf("低优先级的Task1获取信号量\r\n");
        res = xSemaphoreTake(sem_handle, portMAX_DELAY);
        if (res != pdPASS)
        {
            printf("Task1获取信号量失败\r\n");
        }

        /* 执行其他逻辑 */
        printf("低优先级的Task1正在执行\r\n");
        HAL_Delay(3000);

        /* 释放信号量 */
        printf("低优先级的Task1释放信号量\r\n");
        res = xSemaphoreGive(sem_handle);
        if (res != pdPASS)
        {
            printf("Task1释放二值信号量失败\r\n");
        }

        vTaskDelay(1000);
    }
}

/**
 * @description: 任务二:
 * @param {void} *pvParameters
 * @return {*}
 */
void task2(void *pvParameters)
{
    BaseType_t res = 0;
    while (1)
    {
        printf("中优先级的Task2正在执行\r\n");
        HAL_Delay(1500);
        printf("Task2 执行完成一次\r\n");
        vTaskDelay(1000);
    }
}

/**
 * @description: 任务三:获取二值信号量,当成功获取后打印提示信息
 * @param {void} *pvParameters
 * @return {*}
 */
void task3(void *pvParameters)
{
    BaseType_t res = 0;
    while (1)
    {

        /* 获取信号量 */
        printf("高优先级的Task3获取信号量\r\n");
        res = xSemaphoreTake(sem_handle, portMAX_DELAY);
        if (res != pdPASS)
        {
            printf("Task3获取信号量失败\r\n");
        }

        /* 执行其他逻辑 */
        printf("高优先级的Task3正在执行...\r\n");
        HAL_Delay(1000);

        /* 释放信号量 */
        printf("高优先级的Task3释放信号量\r\n");
        res = xSemaphoreGive(sem_handle);
        if (res != pdPASS)
        {
            printf("Task3释放二值信号量失败\r\n");
        }

        vTaskDelay(1000);
    }
}

互斥信号量(熟悉)

互斥信号量是包含优先级继承机制的二进制信号量。二进制信号量能更好实现实现同步(任务间或任务与中断之间), 而互斥信号量有助于更好实现简单互斥(即相互排斥)。

优先级继承是一种解决实时系统中任务调度引起的优先级翻转问题的机制。在具体的任务调度中,当一个高优先级任务等待一个低优先级任务所持有的资源时,系统会提升低优先级任务的优先级,以避免高优先级任务长时间等待的情况。

优先级继承无法完全解决优先级翻转,只是在某些情况下将影响降至最低。

不能在中断中使用互斥信号量,原因如下:

  • 互斥信号量使用的优先级继承机制要求从任务中(而不是从中断中)获取和释放互斥信号量。
  • 中断无法保持阻塞来等待一个被互斥信号量保护的资源。

互斥信号量相关函数:

函数描述
xSemaphoreCreateMutex()使用动态方法创建互斥信号量。
xSemaphoreCreateMutexStatic()使用静态方法创建互斥信号量。

互斥信号量的获取和释放函数与二值信号量的相应函数相似,但有一个重要的区别:互斥信号量不支持在中断服务程序中直接调用。注意,当创建互斥信号量时,系统会自动进行一次信号量的释放操作。

互斥信号量实验(掌握)实验目标

在前面优先级翻转实验的案例中,通过互斥信号量来解决优先级翻转问题:

信号量函数改成互斥信号量,通过串口打印提示信息。

#define configUSE_MUTEXES 1
#include "freertos_demo.h"
/* freertos相关的头文件,必须的 */
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h" //信号量相关的头文件
/* 需要用到的其他头文件 */
#include "LED.h"
#include "Key.h"

/* 启动任务的配置 */
#define START_TASK_STACK 128
#define START_TASK_PRIORITY 1
TaskHandle_t start_task_handle;
void start_task(void *pvParameters);

/* 任务1的配置 */
#define TASK1_STACK 128
#define TASK1_PRIORITY 2
TaskHandle_t task1_handle;
void task1(void *pvParameters);

/* 任务2的配置 */
#define TASK2_STACK 128
#define TASK2_PRIORITY 3
TaskHandle_t task2_handle;
void task2(void *pvParameters);

/* 任务3的配置 */
#define TASK3_STACK 128
#define TASK3_PRIORITY 4
TaskHandle_t task3_handle;
void task3(void *pvParameters);

/**
 * @description: 启动FreeRTOS
 * @return {*}
 */
QueueHandle_t sem_handle;
void freertos_start(void)
{
    /* 创建互斥信号量,创建成功会自动释放一次信号量 */
    sem_handle = xSemaphoreCreateMutex();

    if (sem_handle == NULL)
    {
        printf("互斥信号量创建失败\r\n");
    }
    else 
    {
        printf("互斥信号量创建成功\r\n");
    }


    /* 1.创建一个启动任务 */
    xTaskCreate((TaskFunction_t)start_task,               // 任务函数的地址
                (char *)"start_task",                     // 任务名字符串
                (configSTACK_DEPTH_TYPE)START_TASK_STACK, // 任务栈大小,默认最小128,单位4字节
                (void *)NULL,                             // 传递给任务的参数
                (UBaseType_t)START_TASK_PRIORITY,         // 任务的优先级
                (TaskHandle_t *)&start_task_handle);      // 任务句柄的地址

    /* 2.启动调度器:会自动创建空闲任务 */
    vTaskStartScheduler();
}

/**
 * @description: 启动任务:用来创建其他Task
 * @param {void} *pvParameters
 * @return {*}
 */
void start_task(void *pvParameters)
{
    /* 进入临界区:保护临界区里的代码不会被打断 */
    taskENTER_CRITICAL();

    /* 创建3个任务 */
    xTaskCreate((TaskFunction_t)task1,
                (char *)"task1",
                (configSTACK_DEPTH_TYPE)TASK1_STACK,
                (void *)NULL,
                (UBaseType_t)TASK1_PRIORITY,
                (TaskHandle_t *)&task1_handle);
    xTaskCreate((TaskFunction_t)task2,
                (char *)"task2",
                (configSTACK_DEPTH_TYPE)TASK2_STACK,
                (void *)NULL,
                (UBaseType_t)TASK2_PRIORITY,
                (TaskHandle_t *)&task2_handle);
    xTaskCreate((TaskFunction_t)task3,
                (char *)"task3",
                (configSTACK_DEPTH_TYPE)TASK3_STACK,
                (void *)NULL,
                (UBaseType_t)TASK3_PRIORITY,
                (TaskHandle_t *)&task3_handle);

    /* 启动任务只需要执行一次即可,用完就删除自己 */
    vTaskDelete(NULL);

    /* 退出临界区 */
    taskEXIT_CRITICAL();
}

/**
 * @description: 任务一:用于按键扫描,当检测到按键KEY1被按下时,释放二值信号量。
 * @param {void} *pvParameters
 * @return {*}
 */
void task1(void *pvParameters)
{

    BaseType_t res = 0;
    while (1)
    {

        /* 获取信号量 */
        printf("低优先级的Task1获取信号量\r\n");
        res = xSemaphoreTake(sem_handle, portMAX_DELAY);
        if (res != pdPASS)
        {
            printf("Task1获取信号量失败\r\n");
        }

        /* 执行其他逻辑 */
        printf("低优先级的Task1正在执行\r\n");
        HAL_Delay(3000);

        /* 释放信号量 */
        printf("低优先级的Task1释放信号量\r\n");
        res = xSemaphoreGive(sem_handle);
        if (res != pdPASS)
        {
            printf("Task1释放二值信号量失败\r\n");
        }

        vTaskDelay(1000);
    }
}

/**
 * @description: 任务二:
 * @param {void} *pvParameters
 * @return {*}
 */
void task2(void *pvParameters)
{
    BaseType_t res = 0;
    while (1)
    {
        printf("中优先级的Task2正在执行\r\n");
        HAL_Delay(1500);
        printf("Task2 执行完成一次\r\n");
        vTaskDelay(1000);
    }
}

/**
 * @description: 任务三:获取二值信号量,当成功获取后打印提示信息
 * @param {void} *pvParameters
 * @return {*}
 */
void task3(void *pvParameters)
{
    BaseType_t res = 0;
    while (1)
    {

        /* 获取信号量 */
        printf("高优先级的Task3获取信号量\r\n");
        res = xSemaphoreTake(sem_handle, portMAX_DELAY);
        if (res != pdPASS)
        {
            printf("Task3获取信号量失败\r\n");
        }

        /* 执行其他逻辑 */
        printf("高优先级的Task3正在执行...\r\n");
        HAL_Delay(1000);

        /* 释放信号量 */
        printf("高优先级的Task3释放信号量\r\n");
        res = xSemaphoreGive(sem_handle);
        if (res != pdPASS)
        {
            printf("Task3释放二值信号量失败\r\n");
        }

        vTaskDelay(1000);
    }
}