嵌入式

FreeRTOS学习笔记(8)——任务通知

2020-12-25  本文已影响0人  Leung_ManWah

一、头文件

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

二、任务通知

2.1 基本概念

FreeRTOS 从 V8.2.0 版本开始提供任务通知这个功能,每个任务都有 一个 32 位 的通知值,在大多数情况下,任务通知可以 替代二值信号量、计数信号量、事件组,也可以替代长度为 1 的队列(可以保存一个 32 位整数或指针值)

相对于以前使用 FreeRTOS 内核通信的资源,必须创建队列、二进制信号量、计数信号量或事件组的情况,使用任务通知显然更灵活。按照 FreeRTOS 官方的说法,使用任务通知比通过信号量等 ICP 通信方式解除阻塞的任务要快 45%,并且更加省 RAM 内存空间(使用 GCC 编译器,-o2 优化级别),任务通知的使用无需创建队列。 想要使用任务通知,必须将 FreeRTOSConfig.h 中的宏定义 configUSE_TASK_NOTIFICATIONS 设置为 1,其实FreeRTOS 默认是为 1 的,所以任务通知是默认使能的。

FreeRTOS 提供以下几种方式发送通知给任务 :

  • 发送通知给任务, 如果有通知未读,不覆盖通知值。
  • 发送通知给任务,直接覆盖通知值。
  • 发送通知给任务,设置通知值的一个或者多个位 ,可以当做事件组来使用。
  • 发送通知给任务,递增通知值,可以当做计数信号量使用。
    通过对以上任务通知方式的合理使用,可以在一定场合下替代 FreeRTOS 的信号量,队列、事件组等。

当然,凡是都有利弊,不然的话 FreeRTOS 还要内核的 IPC 通信机制干嘛,消息通知虽然处理更快,RAM 开销更小,但也有以下限制 :

  • 只能有一个任务接收通知消息,因为必须指定接收通知的任务。
  • 只有等待通知的任务可以被阻塞,发送通知的任务,在任何情况下都不会因为发送失败而进入阻塞态。

2.2 运作机制

由于任务通知的数据结构包含在任务控制块中,只要任务存在,任务通知数据结构就已经创建完毕,可以直接使用,所以使用的时候很是方便。

任务通知可以在任务中向指定任务发送通知,也可以在中断中向指定任务发送通知,FreeRTOS 的每个任务都有一个 32 位的通知值,任务控制块中的成员变量 ulNotifiedValue 就是这个通知值。只有在任务中可以等待通知,而不允许在中断中等待通知。如果任务在等待的通知暂时无效,任务会根据用户指定的阻塞超时时间进入阻塞状态,我们可以将等待通知的任务看作是消费者;其它任务和中断可以向等待通知的任务发送通知,发送通知的任务和中断服务函数可以看作是生产者,当其他任务或者中断向这个任务发送任务通知,任务获得通知以后,该任务就会从阻塞态中解除,这与 FreeRTOS 中内核的其他通信机制一致。

三、相关API说明

3.1 xTaskNotifyGive

用于在任务中向指定任务发送任务通知,并更新对方的任务通知值(加 1 操作)。
该函数可以作为二值信号量和计数信号量的一种轻量型的实现,速度更快,在这种情况下对象任务在等待任务通知的时候应该是使用函数 ulTaskNotifyTake() 而不是 xTaskNotifyWait()。xTaskNotifyGive() 不能在中断里面使用,而是使用具有中断保护功能的 vTaskNotifyGiveFromISR() 来代替。

函数 #define xTaskNotifyGive( xTaskToNotify ) xTaskGenericNotify( ( xTaskToNotify ), ( 0 ), eIncrement, NULL )
参数 xTaskToNotify: 接收通知的任务句柄,并让其自身的任务通知值加 1
返回值 总是返回 pdPASS

3.2 vTaskNotifyGiveFromISR

vTaskNotifyGiveFromISR() 是 vTaskNotifyGive() 的中断保护版本。用于在中断中向指定任务发送任务通知,并更新对方的任务通知值(加 1 操作),在某些场景中可以替代信号量操作,因为这两个通知都是不带有通知值的。

函数 void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken )
参数 xTaskToNotify: 接收通知的任务句柄,并让其自身的任务通知值加 1
pxHigherPriorityTaskWoken: 在使用之前必须先初始化为 pdFALSE。当调用该函数发送一个任务通知时,目标任务接收到通知后将从阻塞态变为就绪态,并且如果其优先级比当前运行的任务的优先级高,那么 *pxHigherPriorityTaskWoken 会被设置为 pdTRUE,然后在中断退出前执行一次上下文切换,去执行刚刚被唤醒的中断优先级较高的任务。pxHigherPriorityTaskWoken 是一个可选的参数可以设置为 NULL
返回值

3.3 xTaskNotify

向指定的任务发送一个任务通知,带有通知值并且用户可以指定通知值的发送方式。
如果你想使用任务通知来实现二值信号量和计数信号量,那么应该使用更加简单的函数 xTaskNotifyGive() ,而不是使用 xTaskNotify()。注意:该函数不能在中断里面使用,而是使用具体中断保护功能的版本函数 xTaskNotifyFromISR()。

函数 BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction )
参数 xTaskToNotify: 接收通知的任务句
ulValue: 用于更新接收任务通知的任务通知值,具体如何更新由形参 eAction
eAction: 任务通知值更新方式,具体见下面表格
返回值 参数 eAction 为 eSetValueWithoutOverwrite 时,如果被通知任务还没取走上一个通知,又接收到了一个通知,则这次通知值未能更新并返回 pdFALSE,而其他情况均返回 pdPASS
eAction 取值 含义
eNoAction 对象任务接收任务通知,但是任务自身的任务通知值不更新,即形参 ulValue 没有用。
eSetBits 对象任务接收任务通知,同时任务自身的任务通知值与 ulValue 按位或。如果 ulValue 设置为 0x01,那么任务的通知值的位 0 将被置为 1。同样的如果 ulValue 设置为 0x04,那么任务的通知值的位 2 将被置为 1。 在这种方式下,任务通知可以看成是事件标志的一种轻量型的实现,速度更快。
eIncrement 对象任务接收任务通知,任务自身的任务通知值加 1,即形参ulValue 没有用。这个时候调用 xTaskNotify() 等同于调用 xTaskNotifyGive()。
eSetValueWithOverwrite 对象任务接收任务通知,且任务自身的任务通知值会无条件的被设置为 ulValue。 在这种方式下,任务通知可以看成是函数xQueueOverwrite() 的一种轻量型的实现,速度更快。
eSetValueWithoutOverwrite 对象任务接收任务通知,且对象任务没有通知值,那么通知值就会被设置为 ulValue。对象任务接收任务通知,但是上一次接收到的通知值并没有取走,那么本次的通知值将不会更新,同时函数返回 pdFALSE。 在这种方式下,任务通知可以看成是函数 xQueueSend() 应用在队列深度为 1 的队列上的一种轻量型实现,速度更快。

3.4 xTaskNotifyFromISR

xTaskNotifyFromISR()是 xTaskNotify()的中断保护版本,用于在中断中向指定的任务发送一个任务通知,该任务通知是带有通知值并且用户可以指定通知的发送方式,不返回上一个任务在的通知值。

函数 BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, BaseType_t *pxHigherPriorityTaskWoken )
参数 xTaskToNotify: 接收通知的任务句
ulValue: 用于更新接收任务通知的任务通知值,具体如何更新由形参 eAction
eAction: 任务通知值更新方式,具体见上面表格
pxHigherPriorityTaskWoken: 在使用之前必须先初始化为 pdFALSE。当调用该函数发送一个任务通知时,目标任务接收到通知后将从阻塞态变为就绪态,并且如果其优先级比当前运行的任务的优先级高,那么 *pxHigherPriorityTaskWoken 会被设置为 pdTRUE,然后在中断退出前执行一次上下文切换,去执行刚刚被唤醒的中断优先级较高的任务。pxHigherPriorityTaskWoken 是一个可选的参数可以设置为 NULL
返回值 参数 eAction 为 eSetValueWithoutOverwrite 时,如果被通知任务还没取走上一个通知,又接收到了一个通知,则这次通知值未能更新并返回 pdFALSE,而其他情况均返回 pdPASS

3.5 xTaskNotifyAndQuery

xTaskNotifyAndQuery() 与 xTaskNotify() 很像,都是调用通用的任务通知发送函数xTaskGenericNotify() 来实现通知的发送,不同的是多了一个附加的参数 pulPreviousNotifyValue 用于回传接收任务的上一个通知值。
xTaskNotifyAndQuery()函数不能用在中断中,而是必须使用带中断保护功能的xTaskNotifyAndQuery()FromISR 来代替。

函数 BaseType_t xTaskNotifyAndQuery( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotifyValue )
参数 xTaskToNotify: 接收通知的任务句
ulValue: 用于更新接收任务通知的任务通知值,具体如何更新由形参 eAction
eAction: 任务通知值更新方式,具体见上面表格
pulPreviousNotifyValue: 对象任务的上一个任务通知值,如果为 NULL,则不需要回传,这个时候就等价于函数 xTaskNotify()
返回值 参数 eAction 为 eSetValueWithoutOverwrite 时,如果被通知任务还没取走上一个通知,又接收到了一个通知,则这次通知值未能更新并返回 pdFALSE,而其他情况均返回 pdPASS

3.6 xTaskNotifyAndQueryFromISR

xTaskNotifyAndQueryFromISR()是 xTaskNotifyAndQuery ()的中断版本,用于向指定的任务发送一个任务通知,并返回对象任务的上一个通知值。

函数 BaseType_t xTaskNotifyAndQueryFromISR(TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotifyValue, BaseType_t *pxHigherPriorityTaskWoken )
参数 xTaskToNotify: 接收通知的任务句
ulValue: 用于更新接收任务通知的任务通知值,具体如何更新由形参 eAction
eAction: 任务通知值更新方式,具体见上面表格
pulPreviousNotifyValue: 对象任务的上一个任务通知值,如果为 NULL,则不需要回传,这个时候就等价于函数 xTaskNotify()
pxHigherPriorityTaskWoken: 在使用之前必须先初始化为 pdFALSE。当调用该函数发送一个任务通知时,目标任务接收到通知后将从阻塞态变为就绪态,并且如果其优先级比当前运行的任务的优先级高,那么 *pxHigherPriorityTaskWoken 会被设置为 pdTRUE,然后在中断退出前执行一次上下文切换,去执行刚刚被唤醒的中断优先级较高的任务。pxHigherPriorityTaskWoken 是一个可选的参数可以设置为 NULL
返回值 参数 eAction 为 eSetValueWithoutOverwrite 时,如果被通知任务还没取走上一个通知,又接收到了一个通知,则这次通知值未能更新并返回 pdFALSE,而其他情况均返回 pdPASS

3.7 ulTaskNotifyTake

用于获取一个任务通知,获取二值信号量、计数信号量类型的任务通知。
ulTaskNotifyTake()作为二值信号量和计数信号量的一种轻量级实现,速度更快。如果 FreeRTOS 中使用函数 xSemaphoreTake() 来获取信号量,这个时候则可以试试使用函数 ulTaskNotifyTake()来代替。

函数 uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait )
参数 xClearCountOnExit: 设置为 pdFALSE 时,函数 xTaskNotifyTake()退出前,将任务的通知值减 1,可以用来实现计数信号量;设置为 pdTRUE 时,函数xTaskNotifyTake() 退出前,将任务通知值清零,可以用来实现二值信号量
xTicksToWait: 超时时间,单位为系统节拍周期。宏pdMS_TO_TICKS 用于将毫 秒转化为系统节拍数
返回值 返回任务的当前通知值,在其减 1 或者清 0 之前

3.8 xTaskNotifyWait

xTaskNotifyWait()函数用于实现全功能版的等待任务通知,根据用户指定的参数的不同,可以灵活的用于实现轻量级的消息队列队列、二值信号量、计数信号量和事件组功能,并带有超时等待。

函数 BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait )
参数 ulBitsToClearOnEntry: ulBitsToClearOnEntry 表示在使用通知之前,将任务通知值的哪些位清0,实现过程就是将任务的通知值与参数 ulBitsToClearOnEntry 的按位取反值按位与操作。如果 ulBitsToClearOnEntry 设置为 0x01,那么在函数进入前,任务通知值的位1会被清0,其他位保持不变。如果ulBitsToClearOnEntry 设置为 0xFFFFFFFF (ULONG_MAX),那么在进入函数前任务通知值的所有位都会被清 0,表示清零任务通知值
ulBitsToClearOnExit: ulBitsToClearOnExit 表示在函数 xTaskNotifyWait()退出前,决定任务接收到的通知值的哪些位会被清 0,实现过程就是将任务的通知值与参数 ulBitsToClearOnExit 的按位取反值按位与操作。在清 0 前,接收到的任务通知值会先被保存到形参 *pulNotificationValue 中。如果 ulBitsToClearOnExit 设置为 0x03,那么在函数退出前,接收到的任务通知值的位 0 和位 1 会被清 0,其他位保持不变。如果ulBitsToClearOnExi 设 置 为 0xFFFFFFFF(ULONG_MAX),那么在退出函数前接收到的任务通知值的所有位都会被清 0,表示退出时清零任务通知值
pulNotificationValue: 用于保存接收到的任务通知值。如果接收到的任务通知不需要使用,则设置为 NULL 即可。这个通知值在参数 ulBitsToClearOnExit 起作用前将通知值拷贝到 *pulNotificationValue 中
xTicksToWait: 等待超时时间,单位为系统节拍周期。宏 pdMS_TO_TICKS 用于将单位毫秒转化为系统节拍数
返回值 如果获取任务通知成功则返回 pdTRUE,失败则返回 pdFALSE

四、示例

4.1 任务通知代替消息队列

两个任务是用于接收任务通知,另一个任务发送任务通知。三个任务独立运行,发送消息任务是通过检测按键的按下情况来发送消息通知,另两个任务获取消息通知,在任务通知中没有可用的通知之前就一直等待消息,一旦获取到消息通知就把消息打印在串口调试助手里。

主要用到函数 xTaskNotify()xTaskNotifyWait()

/* FreeRTOS 头文件 */
#include "FreeRTOS.h"
#include "task.h"
/* 开发板硬件 bsp 头文件 */
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"

/**************************** 任务句柄 ********************************/
/*
* 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
* 这个句柄可以为 NULL。
*/
static TaskHandle_t AppTaskCreate_Handle = NULL;/*创建任务句柄 */
static TaskHandle_t Receive1_Task_Handle = NULL;/*Receive1_Task 任务句柄 */
static TaskHandle_t Receive2_Task_Handle = NULL;/*Receive2_Task 任务句柄 */
static TaskHandle_t Send_Task_Handle = NULL;/* Send_Task 任务句柄 */

/*
*************************************************************************
* 函数声明
 *************************************************************************
*/
static void AppTaskCreate(void);/* 用于创建任务 */

static void Receive1_Task(void* pvParameters);/* Receive1_Task 任务实现 */
static void Receive2_Task(void* pvParameters);/* Receive2_Task 任务实现 */

static void Send_Task(void* pvParameters);/* Send_Task 任务实现 */

static void BSP_Init(void);/* 用于初始化板载相关资源 */

/*****************************************************************
* @brief 主函数
* @param 无
* @retval 无
* @note 第一步:开发板硬件初始化
        第二步:创建 APP 应用任务
        第三步:启动 FreeRTOS,开始多任务调度
****************************************************************/
int main(void)
{
    BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
  
    /* 开发板硬件初始化 */
    BSP_Init();
    printf("按下 KEY1 或者 KEY2 向任务发送消息通知\n");
    /* 创建 AppTaskCreate 任务 */
    xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /*任务入口函数 */
                          (const char* )"AppTaskCreate",/* 任务名字 */
                          (uint16_t )512, /* 任务栈大小 */
                          (void* )NULL,/* 任务入口函数参数 */
                          (UBaseType_t )1, /* 任务的优先级 */
                          (TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针 */
    /* 启动任务调度 */
    if (pdPASS == xReturn)
    {
        vTaskStartScheduler(); /* 启动任务,开启调度 */
    }
    else
    {
        return -1;
    }

    while (1); /* 正常不会执行到这里 */
}

/***********************************************************************
* @ 函数名 : AppTaskCreate
* @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
* @ 参数 : 无
* @ 返回值 : 无
****************************************************************/
static void AppTaskCreate(void)
{
    BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
   
    taskENTER_CRITICAL(); //进入临界区
  
    /* 创建 Receive1_Task 任务 */
    xReturn = xTaskCreate((TaskFunction_t )Receive1_Task,/*任务入口函数 */
                          (const char* )"Receive1_Task",/* 任务名字 */
                          (uint16_t )512, /* 任务栈大小 */
                          (void* )NULL, /* 任务入口函数参数 */
                          (UBaseType_t )2, /* 任务的优先级 */
                          (TaskHandle_t*)&Receive1_Task_Handle);/*任务控制块指针 */
    if (pdPASS == xReturn)
    {
        printf("创建 Receive1_Task 任务成功!\r\n");
    }
    /* 创建 Receive2_Task 任务 */
    xReturn = xTaskCreate((TaskFunction_t )Receive2_Task, /* 任务入口函数
*/
                          (const char* )"Receive2_Task",/* 任务名字 */
                          (uint16_t )512, /* 任务栈大小 */
                          (void* )NULL, /* 任务入口函数参数 */
                          (UBaseType_t )3, /* 任务的优先级 */
                          (TaskHandle_t*)&Receive2_Task_Handle);/*任务控制块指针 */
    if (pdPASS == xReturn)
    {
        printf("创建 Receive2_Task 任务成功!\r\n");
    }
  
    /* 创建 Send_Task 任务 */
    xReturn = xTaskCreate((TaskFunction_t )Send_Task, /* 任务入口函数 */
                          (const char* )"Send_Task",/* 任务名字 */
                          (uint16_t )512, /* 任务栈大小 */
                          (void* )NULL,/* 任务入口函数参数 */
                          (UBaseType_t )4, /* 任务的优先级 */
                          (TaskHandle_t* )&Send_Task_Handle);/* 任务控制块指针 */
    if (pdPASS == xReturn)
    {
        printf("创建 Send_Task 任务成功!\n\n");
    }
  
    vTaskDelete(AppTaskCreate_Handle); //删除 AppTaskCreate 任务
  
    taskEXIT_CRITICAL(); //退出临界区
}

/**********************************************************************
* @ 函数名 : Receive_Task
* @ 功能说明: Receive_Task 任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
static void Receive1_Task(void* parameter) 
{ 
    BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为 pdPASS */ 

    uint32_t r_num; 
    while (1) 
    { 
        //获取任务通知 ,没获取到则一直等待 
        xReturn=xTaskNotifyWait(0x0, //进入函数的时候不清除任务 bit 
                                ULONG_MAX, //退出函数的时候清除所有的 bit  
                                &r_num, //保存任务通知值 
                                portMAX_DELAY); //阻塞时间 
        if ( pdTRUE == xReturn ) 
        {
            printf("Receive1_Task 任务通知为 %d \n",r_num); 
        }

        LED1_TOGGLE; 
    } 
}

static void Receive2_Task(void* parameter) 
{ 
    BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为 pdPASS */ 

    char *r_char;
    while (1) 
    { 
        //获取任务通知 ,没获取到则一直等待 
        xReturn=xTaskNotifyWait(0x0, //进入函数的时候不清除任务 bit 
                                ULONG_MAX, //退出函数的时候清除所有的 bit  
                                (uint32_t *)&r_char, //保存任务通知值
                                portMAX_DELAY); //阻塞时间 
        if ( pdTRUE == xReturn ) 
        {
            printf("Receive2_Task 任务通知为 %s \n",r_char);
        }

        LED2_TOGGLE; 
    } 
}

/**********************************************************************
* @ 函数名 : Send_Task
* @ 功能说明: Send_Task 任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
static void Send_Task(void* parameter) 
{ 
    BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */ 
    uint32_t send1 = 1;
    char test_str2[] = "this is a mail test 2";/* 消息 test2 */

    while (1) 
    { 
        /* KEY1 被按下 */ 
        if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) 
        { 
            xReturn = xTaskNotify( Receive1_Task_Handle, /*任务句柄*/ 
            send1, /* 发送的数据,最大为 4 字节 */ 
            eSetValueWithOverwrite );/*覆盖当前通知*/ 

            if ( xReturn == pdPASS ) 
            {
                printf("Receive1_Task_Handle 任务通知释放成功!\r\n"); 
            }
        } 
        /* KEY2 被按下 */ 
        if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) 
        { 
            xReturn = xTaskNotify( Receive2_Task_Handle, /*任务句柄*/ 
                                  (uint32_t)&test_str2,/* 发送的数据,最大为 4 字节 */ 
                                  eSetValueWithOverwrite );/*覆盖当前通知*/ 
            /* 此函数只会返回 pdPASS */ 
            if ( xReturn == pdPASS ) 
            {
                printf("Receive2_Task_Handle 任务通知释放成功!\r\n"); 
            }
        } 
        vTaskDelay(20); 
    } 
}

/***********************************************************************
* @ 函数名 : BSP_Init
* @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
* @ 参数 :
* @ 返回值 : 无
*********************************************************************/
static void BSP_Init(void)
{
    /*
    * STM32 中断优先级分组为 4,即 4bit 都用来表示抢占优先级,范围为:0~15
    * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
    * 都统一用这个优先级分组,千万不要再分组,切忌。
    */
    NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
  
    /* LED 初始化 */
    LED_GPIO_Config();
   
    /* 串口初始化 */
    USART_Config();
  
    /* 按键初始化 */
    Key_GPIO_Config();
}

4.2 任务通知代替二值信号量

两个任务是用于接收任务通知,另一个任务发送任务通知。三个任务独立运行,发送通知任务是通过检测按键的按下情况来发送通知,另两个任务获取通知,在任务通知中没有可用的通知之前就一直等待任务通知,获取到通知以后就将通知值清 0,这样子是为了代替二值信号量,任务同步成功则继续执行,然后在串口调试助手里将运行信息打印出来。

主要用到函数 xTaskNotifyGive()ulTaskNotifyTake()

/* FreeRTOS 头文件 */
#include "FreeRTOS.h"
#include "task.h"
/* 开发板硬件 bsp 头文件 */
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"

/**************************** 任务句柄 ********************************/
/*
* 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
* 这个句柄可以为 NULL。
*/
static TaskHandle_t AppTaskCreate_Handle = NULL;/*创建任务句柄 */
static TaskHandle_t Receive1_Task_Handle = NULL;/*Receive1_Task 任务句柄 */
static TaskHandle_t Receive2_Task_Handle = NULL;/*Receive2_Task 任务句柄 */
static TaskHandle_t Send_Task_Handle = NULL;/* Send_Task 任务句柄 */

/*
*************************************************************************
* 函数声明
 *************************************************************************
*/
static void AppTaskCreate(void);/* 用于创建任务 */

static void Receive1_Task(void* pvParameters);/* Receive1_Task 任务实现 */
static void Receive2_Task(void* pvParameters);/* Receive2_Task 任务实现 */

static void Send_Task(void* pvParameters);/* Send_Task 任务实现 */

static void BSP_Init(void);/* 用于初始化板载相关资源 */

/*****************************************************************
* @brief 主函数
* @param 无
* @retval 无
* @note 第一步:开发板硬件初始化
        第二步:创建 APP 应用任务
        第三步:启动 FreeRTOS,开始多任务调度
****************************************************************/
int main(void)
{
    BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
  
    /* 开发板硬件初始化 */
    BSP_Init();
    printf("按下 KEY1 或者 KEY2 进行任务与任务间的同步\n");
    /* 创建 AppTaskCreate 任务 */
    xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /*任务入口函数 */
                          (const char* )"AppTaskCreate",/* 任务名字 */
                          (uint16_t )512, /* 任务栈大小 */
                          (void* )NULL,/* 任务入口函数参数 */
                          (UBaseType_t )1, /* 任务的优先级 */
                          (TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针 */
    /* 启动任务调度 */
    if (pdPASS == xReturn)
    {
        vTaskStartScheduler(); /* 启动任务,开启调度 */
    }
    else
    {
        return -1;
    }

    while (1); /* 正常不会执行到这里 */
}

/***********************************************************************
* @ 函数名 : AppTaskCreate
* @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
* @ 参数 : 无
* @ 返回值 : 无
****************************************************************/
static void AppTaskCreate(void)
{
    BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
   
    taskENTER_CRITICAL(); //进入临界区
  
    /* 创建 Receive1_Task 任务 */
    xReturn = xTaskCreate((TaskFunction_t )Receive1_Task,/*任务入口函数 */
                          (const char* )"Receive1_Task",/* 任务名字 */
                          (uint16_t )512, /* 任务栈大小 */
                          (void* )NULL, /* 任务入口函数参数 */
                          (UBaseType_t )2, /* 任务的优先级 */
                          (TaskHandle_t*)&Receive1_Task_Handle);/*任务控制块指针 */
    if (pdPASS == xReturn)
    {
        printf("创建 Receive1_Task 任务成功!\r\n");
    }
    /* 创建 Receive2_Task 任务 */
    xReturn = xTaskCreate((TaskFunction_t )Receive2_Task, /* 任务入口函数
*/
                          (const char* )"Receive2_Task",/* 任务名字 */
                          (uint16_t )512, /* 任务栈大小 */
                          (void* )NULL, /* 任务入口函数参数 */
                          (UBaseType_t )3, /* 任务的优先级 */
                          (TaskHandle_t*)&Receive2_Task_Handle);/*任务控制块指针 */
    if (pdPASS == xReturn)
    {
        printf("创建 Receive2_Task 任务成功!\r\n");
    }
  
    /* 创建 Send_Task 任务 */
    xReturn = xTaskCreate((TaskFunction_t )Send_Task, /* 任务入口函数 */
                          (const char* )"Send_Task",/* 任务名字 */
                          (uint16_t )512, /* 任务栈大小 */
                          (void* )NULL,/* 任务入口函数参数 */
                          (UBaseType_t )4, /* 任务的优先级 */
                          (TaskHandle_t* )&Send_Task_Handle);/* 任务控制块指针 */
    if (pdPASS == xReturn)
    {
        printf("创建 Send_Task 任务成功!\n\n");
    }
  
    vTaskDelete(AppTaskCreate_Handle); //删除 AppTaskCreate 任务
  
    taskEXIT_CRITICAL(); //退出临界区
}

/**********************************************************************
* @ 函数名 : Receive_Task
* @ 功能说明: Receive_Task 任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
static void Receive1_Task(void* parameter) 
{ 
    while (1) 
    { 
        /* uint32_t ulTaskNotifyTake(BaseType_t xClearCountOnExit,TickType_t TicksToWait ); 
        * xClearCountOnExit:pdTRUE 在退出函数的时候任务任务通知值清零,类似二值信号量 
        * pdFALSE 在退出函数 ulTaskNotifyTakeO 的时候任务通知值减一,类似计数型信号量。 
        */ 
        //获取任务通知 ,没获取到则一直等待
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY); 
   
        printf("Receive1_Task 任务通知获取成功!\n\n"); 
  
        LED1_TOGGLE;
    } 
}

static void Receive2_Task(void* parameter) 
{ 
        /* uint32_t ulTaskNotifyTake(BaseType_t xClearCountOnExit,TickType_t TicksToWait ); 
        * xClearCountOnExit:pdTRUE 在退出函数的时候任务任务通知值清零,类似二值信号量 
        * pdFALSE 在退出函数 ulTaskNotifyTakeO 的时候任务通知值减一,类似计数型信号量。 
        */ 
        //获取任务通知 ,没获取到则一直等待
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY); 
   
        printf("Receive2_Task 任务通知获取成功!\n\n"); 
  
        LED2_TOGGLE;
    } 
}

/**********************************************************************
* @ 函数名 : Send_Task
* @ 功能说明: Send_Task 任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
static void Send_Task(void* parameter) 
{ 
    BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */ 
    while (1) 
    { 
        /* KEY1 被按下 */ 
        if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) 
        { 
            xReturn = xTaskNotifyGive(Receive1_Task_Handle); 
            /* 此函数只会返回 pdPASS */ 
            if ( xReturn == pdTRUE ) 
            {
                printf("Receive1_Task_Handle 任务通知释放成功!\r\n"); 
            } 
        }
        /* KEY2 被按下 */ 
        if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) 
        { 
            xReturn = xTaskNotifyGive(Receive2_Task_Handle); 
            /* 此函数只会返回 pdPASS */ 
            if ( xReturn == pdPASS ) 
            {
                printf("Receive2_Task_Handle 任务通知释放成功!\r\n"); 
            } 
        }
        vTaskDelay(20); 
    }
}

/***********************************************************************
* @ 函数名 : BSP_Init
* @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
* @ 参数 :
* @ 返回值 : 无
*********************************************************************/
static void BSP_Init(void)
{
    /*
    * STM32 中断优先级分组为 4,即 4bit 都用来表示抢占优先级,范围为:0~15
    * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
    * 都统一用这个优先级分组,千万不要再分组,切忌。
    */
    NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
  
    /* LED 初始化 */
    LED_GPIO_Config();
   
    /* 串口初始化 */
    USART_Config();
  
    /* 按键初始化 */
    Key_GPIO_Config();
}

4.3 任务通知代替计数信号量

一个是获取任务通知,一个是发送任务通知,两个任务独立运行,获取通知的任务是通过按下 KEY1 按键获取,模拟停车场停车操作,其等待时间是 0;发送通知的任务则是通过检测 KEY2 按键按下进行通知的发送,模拟停车场取车操作,并且在串口调试助手输出相应信息。

主要用到函数 xTaskNotifyGive()ulTaskNotifyTake()

/* FreeRTOS 头文件 */
#include "FreeRTOS.h"
#include "task.h"
/* 开发板硬件 bsp 头文件 */
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"

/**************************** 任务句柄 ********************************/
/*
* 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
* 这个句柄可以为 NULL。
*/
static TaskHandle_t AppTaskCreate_Handle = NULL;/*创建任务句柄 */
static TaskHandle_t Take_Task_Handle = NULL;/* Take_Task 任务句柄 */
static TaskHandle_t Give_Task_Handle = NULL;/* Give_Task 任务句柄 */

/*
*************************************************************************
* 函数声明
 *************************************************************************
*/
static void AppTaskCreate(void);/* 用于创建任务 */

static void Take_Task(void* pvParameters);/* Take_Task 任务实现 */
static void Give_Task(void* pvParameters);/* Give_Task 任务实现 */

static void BSP_Init(void);/* 用于初始化板载相关资源 */

/*****************************************************************
* @brief 主函数
* @param 无
* @retval 无
* @note 第一步:开发板硬件初始化
        第二步:创建 APP 应用任务
        第三步:启动 FreeRTOS,开始多任务调度
****************************************************************/
int main(void)
{
    BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
  
    /* 开发板硬件初始化 */
    BSP_Init();
    printf("车位默认值为 0 个,按下 KEY1 申请车位,按下 KEY2 释放车位!\n\n");
    /* 创建 AppTaskCreate 任务 */
    xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /*任务入口函数 */
                          (const char* )"AppTaskCreate",/* 任务名字 */
                          (uint16_t )512, /* 任务栈大小 */
                          (void* )NULL,/* 任务入口函数参数 */
                          (UBaseType_t )1, /* 任务的优先级 */
                          (TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针 */
    /* 启动任务调度 */
    if (pdPASS == xReturn)
    {
        vTaskStartScheduler(); /* 启动任务,开启调度 */
    }
    else
    {
        return -1;
    }

    while (1); /* 正常不会执行到这里 */
}

/***********************************************************************
* @ 函数名 : AppTaskCreate
* @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
* @ 参数 : 无
* @ 返回值 : 无
****************************************************************/
static void AppTaskCreate(void)
{
    BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
   
    taskENTER_CRITICAL(); //进入临界区
  
    /* 创建 Take_Task 任务 */
    xReturn = xTaskCreate((TaskFunction_t )Take_Task,/*任务入口函数 */
                          (const char* )"Take_Task",/* 任务名字 */
                          (uint16_t )512, /* 任务栈大小 */
                          (void* )NULL, /* 任务入口函数参数 */
                          (UBaseType_t )2, /* 任务的优先级 */
                          (TaskHandle_t*)&Take_Task_Handle);/*任务控制块指针 */
    if (pdPASS == xReturn)
    {
        printf("创建 Take_Task 任务成功!\r\n");
    }
    /* 创建 Give_Task 任务 */
    xReturn = xTaskCreate((TaskFunction_t )Give_Task, /* 任务入口函数
*/
                          (const char* )"Give_Task",/* 任务名字 */
                          (uint16_t )512, /* 任务栈大小 */
                          (void* )NULL, /* 任务入口函数参数 */
                          (UBaseType_t )3, /* 任务的优先级 */
                          (TaskHandle_t*)&Give_Task_Handle);/*任务控制块指针 */
    if (pdPASS == xReturn)
    {
        printf("创建 Give_Task 任务成功!\r\n");
    }
  
    vTaskDelete(AppTaskCreate_Handle); //删除 AppTaskCreate 任务
  
    taskEXIT_CRITICAL(); //退出临界区
}

/**********************************************************************
* @ 函数名 : Take_Task
* @ 功能说明: Take_Task 任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
static void Take_Task(void* parameter) 
{ 
    uint32_t take_num = pdTRUE;/* 定义一个创建信息返回值,默认为 pdPASS */

    while (1) 
    { 
        //如果 KEY1 被按下 
        if ( Key_Scan(KEY1_GPIO_PORT, KEY1_GPIO_PIN) == KEY_ON ) 
        { 
            /*uint32_t ulTaskNotifyTake(BaseType_t xClearCountOnExit,TickType_t xTicksToWait ); 
            * xClearCountOnExit:pdTRUE 在退出函数的时候任务任务通知值清零,类似二值信号量 
            * pdFALSE 在退出函数 ulTaskNotifyTakeO 的时候任务通知值减一,类似计数型信号量。 
            */ 
            //获取任务通知 ,没获取到则不等待 
            take_num=ulTaskNotifyTake(pdFALSE,0);
            if (take_num > 0) 
            {
                printf( "KEY1 被按下,成功申请到停车位。当前车位为 %d \n", take_num - 1); 
            }
            else 
            {
                printf( "KEY1 被按下,车位已经没有了。按 KEY2 释放车位\n" ); 
            }
        } 
        vTaskDelay(20); //每 20ms 扫描一次
    } 
}

/**********************************************************************
* @ 函数名 : Give_Task
* @ 功能说明: Give_Task 任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
static void Give_Task(void* parameter) 
{ 
    BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */ 
    while (1) 
    { 
        /* KEY2 被按下 */ 
        if ( Key_Scan(KEY2_GPIO_PORT, KEY2_GPIO_PIN) == KEY_ON ) 
        { 
            xReturn = xTaskNotifyGive(Receive1_Task_Handle); 
            /* 此函数只会返回 pdPASS */ 
            if ( xReturn == pdTRUE ) 
            {
                printf("Receive1_Task_Handle 任务通知释放成功!\r\n"); 
            } 
        }
        /* KEY2 被按下 */ 
        if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) 
        { 
            xReturn = xTaskNotifyGive(Receive2_Task_Handle); 
            /* 此函数只会返回 pdPASS */ 
            if ( xReturn == pdPASS ) 
            {
                printf( "KEY2 被按下,释放 1 个停车位。\n" );
            } 
        }
        vTaskDelay(20); 
    }
}

/***********************************************************************
* @ 函数名 : BSP_Init
* @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
* @ 参数 :
* @ 返回值 : 无
*********************************************************************/
static void BSP_Init(void)
{
    /*
    * STM32 中断优先级分组为 4,即 4bit 都用来表示抢占优先级,范围为:0~15
    * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
    * 都统一用这个优先级分组,千万不要再分组,切忌。
    */
    NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
  
    /* LED 初始化 */
    LED_GPIO_Config();
   
    /* 串口初始化 */
    USART_Config();
  
    /* 按键初始化 */
    Key_GPIO_Config();
}

4.4 任务通知代替事件组

一个是发送事件通知任务,一个是等待事件通知任务,两个任务独立运行,发送事件通知任务通过检测按键的按下情况设置不同的通知值位,等待事件通知任务则获取这任务通知值,并且根据通知值判断两个事件是否都发生,如果是则输出相应信息,LED 进行翻转。等待事件通知任务的等待时间是 portMAX_DELAY,一直在等待事件通知的发生,等待获取到事件之后清除对应的任务通知值的位。

主要用到函数 xTaskNotify()xTaskNotifyWait()

/* FreeRTOS 头文件 */
#include "FreeRTOS.h"
#include "task.h"
/* 开发板硬件 bsp 头文件 */
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"

/**************************** 任务句柄 ********************************/
/*
* 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
* 这个句柄可以为 NULL。
*/
static TaskHandle_t AppTaskCreate_Handle = NULL;/*创建任务句柄 */
static TaskHandle_t LED_Task_Handle = NULL;/* LED_Task 任务句柄 */
static TaskHandle_t KEY_Task_Handle = NULL;/* KEY_Task 任务句柄 */

/************************** 宏定义 ************************************/
#define KEY1_EVENT (0x01 << 0)//设置事件掩码的位 0 
#define KEY2_EVENT (0x01 << 1)//设置事件掩码的位 1

/*
*************************************************************************
* 函数声明
 *************************************************************************
*/
static void AppTaskCreate(void);/* 用于创建任务 */

static void LED_Task(void* pvParameters);/* LED_Task 任务实现 */
static void KEY_Task(void* pvParameters);/* KEY_Task 任务实现 */

static void BSP_Init(void);/* 用于初始化板载相关资源 */

/*****************************************************************
* @brief 主函数
* @param 无
* @retval 无
* @note 第一步:开发板硬件初始化
        第二步:创建 APP 应用任务
        第三步:启动 FreeRTOS,开始多任务调度
****************************************************************/
int main(void)
{
    BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
  
    /* 开发板硬件初始化 */
    BSP_Init();
    printf("按下 KEY1|KEY2 发送任务事件通知!\n");
    /* 创建 AppTaskCreate 任务 */
    xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /*任务入口函数 */
                          (const char* )"AppTaskCreate",/* 任务名字 */
                          (uint16_t )512, /* 任务栈大小 */
                          (void* )NULL,/* 任务入口函数参数 */
                          (UBaseType_t )1, /* 任务的优先级 */
                          (TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针 */
    /* 启动任务调度 */
    if (pdPASS == xReturn)
    {
        vTaskStartScheduler(); /* 启动任务,开启调度 */
    }
    else
    {
        return -1;
    }

    while (1); /* 正常不会执行到这里 */
}

/***********************************************************************
* @ 函数名 : AppTaskCreate
* @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
* @ 参数 : 无
* @ 返回值 : 无
****************************************************************/
static void AppTaskCreate(void)
{
    BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
   
    taskENTER_CRITICAL(); //进入临界区
  
    /* 创建 LED_Task 任务 */
    xReturn = xTaskCreate((TaskFunction_t )LED_Task,/*任务入口函数 */
                          (const char* )"LED_Task",/* 任务名字 */
                          (uint16_t )512, /* 任务栈大小 */
                          (void* )NULL, /* 任务入口函数参数 */
                          (UBaseType_t )2, /* 任务的优先级 */
                          (TaskHandle_t*)&LED_Task_Handle);/*任务控制块指针 */
    if (pdPASS == xReturn)
    {
        printf("创建 LED_Task 任务成功!\r\n");
    }
    /* 创建 KEY_Task 任务 */
    xReturn = xTaskCreate((TaskFunction_t )KEY_Task, /* 任务入口函数
*/
                          (const char* )"KEY_Task",/* 任务名字 */
                          (uint16_t )512, /* 任务栈大小 */
                          (void* )NULL, /* 任务入口函数参数 */
                          (UBaseType_t )3, /* 任务的优先级 */
                          (TaskHandle_t*)&KEY_Task_Handle);/*任务控制块指针 */
    if (pdPASS == xReturn)
    {
        printf("创建 KEY_Task 任务成功!\r\n");
    }
  
    vTaskDelete(AppTaskCreate_Handle); //删除 AppTaskCreate 任务
  
    taskEXIT_CRITICAL(); //退出临界区
}

/**********************************************************************
* @ 函数名 : LED_Task
* @ 功能说明: TLED_Task 任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
static void LED_Task(void* parameter) 
{ 
    uint32_t r_event = 0; /* 定义一个事件接收变量 */ 
    uint32_t last_event = 0;/* 定义一个保存事件的变量 */ 
    BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为 pdPASS */ 
    /* 任务都是一个无限循环,不能返回 */ 
    while (1) 
    { 
        /* BaseType_t xTaskNotifyWait(uint32_t ulBitsToClearOnEntry, 
        *                             uint32_t ulBitsToClearOnExit, 
        *                             uint32_t *pulNotificationValue, 
        *                             TickType_t xTicksToWait ); 
        * ulBitsToClearOnEntry:当没有接收到任务通知的时候将任务通知值与此参数的取 
        * 反值进行按位与运算,当此参数为 Oxfffff 或者 ULONG_MAX 的时候就会将任务通知值清零。 
        * ulBits ToClearOnExit:如果接收到了任务通知,在做完相应的处理退出函数之前将 
        * 任务通知值与此参数的取反值进行按位与运算,当此参数为 0xfffff 或者 ULONG MAX 的时候 
        * 就会将任务通知值清零。 
        * pulNotification Value:此参数用来保存任务通知值。 
        * xTick ToWait:阻塞时间。 
        * 返回值:pdTRUE:获取到了任务通知。pdFALSE:任务通知获取失败。 
        */ 
        //获取任务通知 ,没获取到则一直等待 
        xReturn = xTaskNotifyWait(0x0, //进入函数的时候不清除任务 bit 
                                  ULONG_MAX, //退出函数的时候清除所有的 bitR 
                                  &r_event, //保存任务通知值 
                                  portMAX_DELAY); //阻塞时间 
        if ( pdTRUE == xReturn ) 
        { 
            last_event |= r_event; 
            /* 如果接收完成并且正确 */ 
            if (last_event == (KEY1_EVENT|KEY2_EVENT)) 
            { 
                last_event = 0; /* 上一次的事件清零 */ 
                printf ( "Key1 与 Key2 都按下\n"); 
                LED1_TOGGLE; //LED1 反转 
            } 
            else /* 否则就更新事件 */ 
            {
                last_event = r_event; /* 更新上一次触发的事件 */ 
            }
        }  
    }
}

/**********************************************************************
* @ 函数名 : KEY_Task
* @ 功能说明: KEY_Task 任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
static void KEY_Task(void* parameter) 
{ 
    /* 任务都是一个无限循环,不能返回 */ 
    while (1) 
    { 
        if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) 
        { 
            printf ( "KEY1 被按下\n" ); 
            /* 触发一个事件 1 */ 
            xTaskNotify((TaskHandle_t)LED_Task_Handle,//接收任务通知的任务句柄 
                        (uint32_t)KEY1_EVENT, //要触发的事件 
                        (eNotifyAction)eSetBits); //设置任务通知值中的位  
        } 
  
        if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) 
        { 
            printf ( "KEY2 被按下\n" ); 
            /* 触发一个事件 2 */ 
            xTaskNotify((TaskHandle_t )LED_Task_Handle,//接收任务通知的任务句柄 
                        (uint32_t )KEY2_EVENT, //要触发的事件 
                        (eNotifyAction)eSetBits); //设置任务通知值中的位 
        } 
        vTaskDelay(20); //每 20ms 扫描一次 
    }
}

/***********************************************************************
* @ 函数名 : BSP_Init
* @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
* @ 参数 :
* @ 返回值 : 无
*********************************************************************/
static void BSP_Init(void)
{
    /*
    * STM32 中断优先级分组为 4,即 4bit 都用来表示抢占优先级,范围为:0~15
    * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
    * 都统一用这个优先级分组,千万不要再分组,切忌。
    */
    NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
  
    /* LED 初始化 */
    LED_GPIO_Config();
   
    /* 串口初始化 */
    USART_Config();
  
    /* 按键初始化 */
    Key_GPIO_Config();
}

4.5 xTaskNotifyAndQuery()应用

uint32_t ulPreviousValue;
 
/* 设置对象任务 xTask1Handle 的任务通知值的位 8 为 1
在更新位 8 的值之前把任务通知值回传存储在变量 ulPreviousValue 中*/
xTaskNotifyAndQuery( xTask1Handle, ( 1UL << 8UL ), eSetBits, 
&ulPreviousValue );
  
/* 向对象任务 xTask2Handle 发送一个任务通知,有可能解除对象任务的阻塞状态
但是不更新对象任务的通知值,并将对象任务的通知值存储在变量 ulPreviousValue 中 */
xTaskNotifyAndQuery( xTask2Handle, 0, eNoAction, &ulPreviousValue );
 
/* 覆盖式设置对象任务的任务通知值为 0x50
且对象任务的任务通知值不用回传,则最后一个形参设置为 NULL */
xTaskNotifyAndQuery( xTask3Handle, 0x50, eSetValueWithOverwrite, NULL );

/* 设置对象任务的任务通知值为 0xfff,但是并不会覆盖对象任务通过
xTaskNotifyWait()和 ulTaskNotifyTake()这两个函数获取到的已经存在
的任务通知值。对象任务的前一个任务通知值存储在变量 ulPreviousValue 中*/
if ( xTaskNotifyAndQuery( xTask4Handle,
                          0xfff,
                          eSetValueWithoutOverwrite,
                          &ulPreviousValue ) == pdPASS )
{
    /* 任务通知值已经更新 */
}
else
{
    /* 任务通知值没有更新 */
}

4.6 任务通知中断版

4.6.1 vTaskNotifyGiveFromISR()应用

static TaskHandle_t xTaskToNotify = NULL;

/* 外设驱动的数据传输函数 */
void StartTransmission( uint8_t *pcData, size_t xDataLength )
{
    /* 在这个时候,xTaskToNotify 应为 NULL,因为发送并没有进行。
    如果有必要,对外设的访问可以用互斥量来保护*/
    configASSERT( xTaskToNotify == NULL );
   
    /* 获取调用函数 StartTransmission()的任务的句柄 */
    xTaskToNotify = xTaskGetCurrentTaskHandle();
  
    /* 开始传输,当数据传输完成时产生一个中断 */
    vStartTransmit( pcData, xDatalength );
}
/*-----------------------------------------------------------*/
/* 数据传输完成中断 */
void vTransmitEndISR( void )
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 
    /* 这个时候不应该为 NULL,因为数据传输已经开始 */
    configASSERT( xTaskToNotify != NULL );
  
    /* 通知任务传输已经完成 */
    vTaskNotifyGiveFromISR( xTaskToNotify, &xHigherPriorityTaskWoken );
   
    /* 传输已经完成,所以没有任务需要通知 */
    xTaskToNotify = NULL;
    
    /* 如果为 pdTRUE,则进行一次上下文切换 */
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/*-----------------------------------------------------------*/
/* 任务:启动数据传输,然后进入阻塞态,直到数据传输完成 */
void vAFunctionCalledFromATask( uint8_t ucDataToTransmit, size_t xDataLength )
{
    uint32_t ulNotificationValue;
    const TickType_t xMaxBlockTime = pdMS_TO_TICKS( 200 );
  
    /* 调用上面的函数 StartTransmission()启动传输 */
    StartTransmission( ucDataToTransmit, xDataLength );
   
    /* 等待传输完成 */
    ulNotificationValue = ulTaskNotifyTake( pdFALSE, xMaxBlockTime );
   
  /* 当传输完成时,会产生一个中断
    在中断服务函数中调用 vTaskNotifyGiveFromISR()向启动数据
    传输的任务发送一个任务通知,并将对象任务的任务通知值加 1
    任务通知值在任务创建的时候是初始化为 0 的,当接收到任务后就变成 1 */
    if ( ulNotificationValue == 1 ) 
    {
        /* 传输按预期完成 */
    } 
    else 
    {
        /* 调用函数 ulTaskNotifyTake()超时 */
    }
}

4.6.2 xTaskNotifyFromISR()应用

/* 中断:向一个任务发送任务通知,并根据不同的中断将目标任务的任务通知值的相应位置 1 */
void vANInterruptHandler( void )
{
    BaseType_t xHigherPriorityTaskWoken;
    uint32_t ulStatusRegister;
   
    /* 读取中断状态寄存器,判断到来的是哪个中断 
    这里假设了 Rx、Tx 和 buffer overrun 三个中断 */ 
    ulStatusRegister = ulReadPeripheralInterruptStatus(); 
  
    /* 清除中断标志位 */
    vClearPeripheralInterruptStatus( ulStatusRegister );
     
    /* xHigherPriorityTaskWoken 在使用之前必须初始化为 pdFALSE
    如果调用函数 xTaskNotifyFromISR()解锁了解锁了接收该通知的任务
    而且该任务的优先级比当前运行的任务的优先级高,那么
    xHigherPriorityTaskWoken 就会自动的被设置为 pdTRUE*/
    xHigherPriorityTaskWoken = pdFALSE;
   
    /* 向任务 xHandlingTask 发送任务通知,并将其任务通知值
    与 ulStatusRegister 的值相或,这样可以不改变任务通知其它位的值*/
    xTaskNotifyFromISR( xHandlingTask, 
    ulStatusRegister, 
    eSetBits, 
    &xHigherPriorityTaskWoken ); 
   
    /* 如果 xHigherPriorityTaskWoken 的值为 pdRTUE
    则执行一次上下文切换*/
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/* ----------------------------------------------------------- */
/* 任务:等待任务通知,然后处理相关的事情 */
void vHandlingTask( void *pvParameters )
{
    uint32_t ulInterruptStatus;
     
    for ( ;; ) 
    {
        /* 等待任务通知,无限期阻塞(没有超时,所以没有必要检查函数返回值)*/
        xTaskNotifyWait( 0x00, /* 在进入的时候不清除通知值的任何位 */
                         ULONG_MAX, /* 在退出的时候复位通知值为 0 */
                         &ulNotifiedValue, /* 任务通知值传递到变量ulNotifiedValue 中*/
                         portMAX_DELAY ); /* 无限期等待 */
  
        /* 根据任务通知值里面的各个位的值处理事情 */
        if ( ( ulInterruptStatus & 0x01 ) != 0x00 )
        {
            /* Rx 中断 */
            prvProcessRxInterrupt();
        }
  
        if ( ( ulInterruptStatus & 0x02 ) != 0x00 ) 
        {
            /* Tx 中断 */
            prvProcessTxInterrupt();
        }
  
        if ( ( ulInterruptStatus & 0x04 ) != 0x00 ) 
        {
            /* 缓冲区溢出中断 */
            prvClearBufferOverrun();
        }
    }
}

4.6.3 xTaskNotifyAndQueryFromISR()应用

void vAnISR( void )
{
    /* xHigherPriorityTaskWoken 在使用之前必须设置为 pdFALSE */
    BaseType_t xHigherPriorityTaskWoken = pdFALSE.
    uint32_t ulPreviousValue;
   
    /* 设置目标任务 xTask1Handle 的任务通知值的位 8 为 1
    在任务通知值的位 8 被更新之前把上一次的值存储在变量     
    ulPreviousValue 中*/
    xTaskNotifyAndQueryFromISR( xTask1Handle, 
                                ( 1UL << 8UL ), 
                                eSetBits, 
                                &ulPreviousValue, 
                                &xHigherPriorityTaskWoken ); 
   
    /* 如果任务 xTask1Handle 阻塞在任务通知上,那么现在已经被解锁进入就绪态
    如果其优先级比当前正在运行的任务的优先级高,则 xHigherPriorityTaskWoken
    会被设置为 pdRTUE,然后在中断退出前执行一次上下文切换,在中断退出后则去
    执行这个被唤醒的高优先级的任务 */
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

• 由 Leung 写于 2020 年 12 月 25 日

• 参考:野火FreeRTOS视频与PDF教程

上一篇下一篇

猜你喜欢

热点阅读