FreeRTOS菜鸟入门(十六)·任务通知·八万字通过详细举例解析任务通知

目录

1.  基本概念

2.  运作机制

3.  任务通知的数据结构

4.  任务通知接口函数

4.1  发送任务通知函数

4.1.1  xTaskNotifyGive()

4.1.1.1  二值信号量

4.1.1.2  计数信号量

4.1.2  vTaskNotifyGiveFromISR()

4.1.3  xTaskNotify()

4.1.4  xTaskNotifyFromISR()

4.1.5  中断中发送任务通知通用函数 xTaskGenericNotifyFromISR()

4.1.6  xTaskNotifyAndQuery()

4.1.7  xTaskNotifyAndQueryFromISR()

4.2  获取任务通知函数

4.2.1  ulTaskNotifyTake()

4.2.1.1  二值信号量

4.2.1.2  计数信号量

4.2.2  xTaskNotifyWait()

5.  实例演示

5.1  xTaskNotify()实例演示

5.1.1  二值信号量——eNoAction作为参数

5.1.2  事件——eSetBits作为参数

5.1.3  计数信号量——eIncrement

5.1.4  消息队列——eSetValueWithOverwrite

5.1.5  消息队列——eSetValueWithoutOverwrite

5.2  xTaskNotifyAndQuery()实例演示


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 还要内核的 IPC 通信机制干嘛,消息通知虽然处理更快,RAM 开销更小,但也有以下限制 :

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

2.  运作机制

        任务通知是属于任务中附带的资源,所以在任务被创建的时候,任务通知也被初始化的,而在分析队列和信号量的章节中,我们知道在使用队列、信号量前,必须先创建队列和信号量,目的是为了创建队列数据结构。再来看任务通知,由于任务通知的数据结构包含在任务控制块中,只要任务存在,任务通知数据结构就已经创建完毕,可以直接使用,所以使用的时候很是方便。

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

3.  任务通知的数据结构

typedef struct tskTaskControlBlock {
    volatile StackType_t *pxTopOfStack;

#if ( portUSING_MPU_WRAPPERS == 1 )
    xMPU_SETTINGS xMPUSettings;
#endif

    ListItem_t xStateListItem;
    ListItem_t xEventListItem;
    UBaseType_t uxPriority;
    StackType_t *pxStack;
    char pcTaskName[ configMAX_TASK_NAME_LEN ];

#if ( portSTACK_GROWTH > 0 )
    StackType_t *pxEndOfStack;
#endif

#if ( portCRITICAL_NESTING_IN_TCB == 1 )
    UBaseType_t uxCriticalNesting;
#endif

#if ( configUSE_TRACE_FACILITY == 1 )
    UBaseType_t uxTCBNumber;
    UBaseType_t uxTaskNumber;
#endif

#if ( configUSE_MUTEXES == 1 )
    UBaseType_t uxBasePriority;
    UBaseType_t uxMutexesHeld;
#endif

#if ( configUSE_APPLICATION_TASK_TAG == 1 )
    TaskHookFunction_t pxTaskTag;
#endif

#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
    void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
#endif

#if( configGENERATE_RUN_TIME_STATS == 1 )
    uint32_t ulRunTimeCounter;
#endif

#if ( configUSE_NEWLIB_REENTRANT == 1 )
    struct _reent xNewLib_reent;
#endif

#if( configUSE_TASK_NOTIFICATIONS == 1 ) 
    volatile uint32_t ulNotifiedValue;//任务通知的值,可以保存一个 32 位整数或指针值。
    volatile uint8_t ucNotifyState;//任务通知状态,用于标识任务是否在等待通知。
#endif 

#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
    uint8_t ucStaticallyAllocated;
#endif

#if( INCLUDE_xTaskAbortDelay == 1 )
    uint8_t ucDelayAborted;
#endif

} tskTCB;

typedef tskTCB TCB_t;

4.  任务通知接口函数

4.1  发送任务通知函数

        我们先看一下发送通知 API 函数。这类函数比较多,有 6 个。但仔细分析会发现它们只能完成 3 种操作,每种操作有两个 API 函数,分别为带中断保护版本和不带中断保护版本。FreeRTOS 将 API 细分为带中断保护版本和不带中断保护版本是为了节省中断服务程序处理时间,提升性能。

        通过前面通信机制的学习,相信大家都了解了 FreeRTOS 的风格,这里的任务通知发送函数也是利用宏定义来进行扩展的,所有的函数都是一个宏定义,在任务中发送任务通知的函数均是调用 xTaskGenericNotify()函数进行发送通知,下面来看看xTaskGenericNotify()的源码:

#if( configUSE_TASK_NOTIFICATIONS == 1 )

BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify,//被通知的任务句柄,指定通知的任务。
                             uint32_t ulValue,//发送的通知值。
                             eNotifyAction eAction,//枚举类型,指明更新通知值的方式
                             uint32_t *pulPreviousNotificationValue )//任务原本的通知值返回。
{
    TCB_t * pxTCB;
    BaseType_t xReturn = pdPASS;
    uint8_t ucOriginalNotifyState;

    configASSERT( xTaskToNotify );
    pxTCB = ( TCB_t * ) xTaskToNotify;

    taskENTER_CRITICAL();
    {
        if( pulPreviousNotificationValue != NULL ) {
            /* 回传未被更新的任务通知值,保存在 pulPreviousNotificationValue 中 */
            *pulPreviousNotificationValue = pxTCB->ulNotifiedValue;
        }

        /* 获取任务通知的状态,看看任务是否在等待通知,方便在发送通知后恢复任务 */
        ucOriginalNotifyState = pxTCB->ucNotifyState;

        /* 不管状态是怎么样的,反正现在发送通知,任务就收到任务通知 */
        pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED;

        /* 指定更新任务通知的方式 */
        switch( eAction ) {
            /* 通知值按位或上 ulValue。
               使用这种方法可以某些场景下代替事件组,但执行速度更快。 */
            case eSetBits:
                pxTCB->ulNotifiedValue |= ulValue;
                break;

            /* 被通知任务的通知值增加 1,这种发送通知方式,参数 ulValue 未使用 */
            case eIncrement:
                ( pxTCB->ulNotifiedValue )++;
                break;

            /* 将被通知任务的通知值设置为 ulValue。无论任务是否还有通知,
               都覆盖当前任务通知值。使用这种方法,
               可以在某些场景下代替 xQueueOverwrite()函数,但执行速度更快。 */
            case eSetValueWithOverwrite:
                pxTCB->ulNotifiedValue = ulValue;
                break;

            /* 如果被通知任务当前没有通知,则被通知任务的通知值设置为 ulValue;
               在某些场景下替代长度为 1 的 xQueueSend(),但速度更快。 */
            case eSetValueWithoutOverwrite:
                if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED ) {
                    pxTCB->ulNotifiedValue = ulValue;
                } else {
                    /* 如果被通知任务还没取走上一个通知,本次发送通知,
                       任务又接收到了一个通知,则这次通知值丢弃,
                       在这种情况下,函数调用失败并返回 pdFALSE。 */
                    xReturn = pdFAIL;
                }
                break;

            /* 发送通知但不更新通知值,这意味着参数 ulValue 未使用。 */
            case eNoAction:
                break;
        }

        traceTASK_NOTIFY();

        /* 如果被通知任务由于等待任务通知而挂起 */
        if( ucOriginalNotifyState == taskWAITING_NOTIFICATION ) {
            /* 唤醒任务,将任务从阻塞列表中移除,添加到就绪列表中 */
            ( void ) uxListRemove( &( pxTCB->xStateListItem ) );
            prvAddTaskToReadyList( pxTCB );

            /* 刚刚唤醒的任务优先级比当前任务高 */
            if( pxTCB->uxPriority > pxCurrentTCB->uxPriority ) {
                /* 任务切换 */
                taskYIELD_IF_USING_PREEMPTION();
            } else {
                mtCOVERAGE_TEST_MARKER();
            }
        } else {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    taskEXIT_CRITICAL();

    return xReturn;
}

#endif

        xTaskGenericNotify()函数是一个通用的任务通知发送函数,在任务中发送通知的 API 函数,如xTaskNotifyGive() 、 xTaskNotify() 、 xTaskNotifyAndQuery() , 都是以 xTaskGenericNotify() 为原型的,只不过指定的发送方式不同而已。

4.1.1  xTaskNotifyGive()

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

函数原型#define xTaskNotifyGive( xTaskToNotify ) 
             xTaskGenericNotify( ( xTaskToNotify ), ( 0 ), eIncrement, NULL )
功能用于在任务中向指定任务发送任务通知,并更新对方的任务通知值(加 1 操作)。
参数xTaskToNotify接收通知的任务句柄,并让其自身的任务通知值加 1。
返回值总是返回 pdPASS。

        上面也说了此函数可以适用于二值信号量和计数信号量,那么我们逐一进行举例。

4.1.1.1  二值信号量

        开始前我们想了解一下二者的区别:

特性任务通知(Task Notification)二值信号量(Binary Semaphore)
本质每个任务自带的通知标志和32位值独立的同步对象(由内核管理)
是否需要创建对象❌ 不需要(内置于TCB)✅ 需要 xSemaphoreCreateBinary()
是否占用内存❌ 无额外开销✅ 需要分配信号量结构体
适用场景任务间轻量级通信任务间、任务-ISR间同步
是否支持多任务等待❌ 只能一对一(一个任务只能等待自己的通知)✅ 可以多个任务等待同一个信号量

二值信号量的调用相关可以查看:

FreeRTOS菜鸟入门(十二)·信号量·二值信号量与计数信号量_#include "semphr.h-CSDN博客

        这里我们还是使用之前创建好的空白工程:

基于STM32F1系列移植FreeRTOS模版资源-CSDN文库

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

        开始前可以在FreeRTOSConfig.h看一下,任务通知相关宏定义是否开启,默认是开启的:

        回到主函数,既然要创建三个任务,那么我们就先创建三个任务句柄,用于方便后续对任务的操作:

//接收相关
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任务句柄 */

        对于接收任务,我们这里想不填充,等下面讲获取的时候再进行填充,这里主要是对发送函数的调用讲解:

//Receive_Task任务主体
static void Receive1_Task(void* parameter)
{	
  while (1)
  {

  }
}

//Receive_Task任务主体
static void Receive2_Task(void* parameter)
{	
  while (1)
  {

  }
}

        对于发送任务,主要通过按键按下进行发送函数API的调用:

//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 )
    {
      /* 原型:BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); */
      xReturn = xTaskNotifyGive(Receive1_Task_Handle);
      /* 此函数只会返回pdPASS */
      if( xReturn == pdTRUE )
        printf("\r\nReceive1_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("\r\nReceive2_Task_Handle 任务通知发送成功!\r\n");
    }
    vTaskDelay(20);
  }
}

        然后是对三个任务优先级,栈空间等的创建,这里把接收的也一并创建了:

//任务创建函数
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任务成功!\r\n"); 
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}

        完整main函数代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

#include "LED.h"
#include "Usart.h"
#include "Key.h"  

/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.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 All_Function_Init(void);/* 用于初始化板载相关资源 */

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

	All_Function_Init();//硬件初始化
	
	while (1)
	{
		 /* 创建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;  
	}
}

//任务创建函数
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任务成功!\r\n"); 
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}

//Receive_Task任务主体
static void Receive1_Task(void* parameter)
{	
  while (1)
  {

  }
}

//Receive_Task任务主体
static void Receive2_Task(void* parameter)
{	
  while (1)
  {

  }
}

//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 )
    {
      /* 原型:BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); */
      xReturn = xTaskNotifyGive(Receive1_Task_Handle);
      /* 此函数只会返回pdPASS */
      if( xReturn == pdTRUE )
        printf("\r\nReceive1_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("\r\nReceive2_Task_Handle 任务通知发送成功!\r\n");
    }
    vTaskDelay(20);
  }
}


//初始化声明
static void All_Function_Init(void)
{
	/*
	 * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* LED 初始化 */
	LED_GPIO_Config();

	/* 串口初始化	*/
	USART_Config();

	//按键初始化
	Key_GPIO_Config();
  
}



        运行结果:

完整工程:

FreeRTOS任务通知-替代二值信号量-发送函数.zip资源-CSDN文库

4.1.1.2  计数信号量
对比项任务通知计数信号量
速度⚡ 更快🏃 较慢
内存占用❌ 无额外开销✅ 需要额外对象
多任务共享❌ 不支持✅ 支持
适用场景单任务私有计数多任务共享资源管理

计数信号量的调用相关可以查看:

FreeRTOS菜鸟入门(十二)·信号量·二值信号量与计数信号量_#include "semphr.h-CSDN博客

        任务通知代替计数信号量是基于计数型信号量实验修改而来,模拟停车场工作运行。并且在 FreeRTOS 中创建了两个任务:一个是获取任务通知,一个是发送任务通知,两个任务独立运行,获取通知的任务是通过按下 KEY1 按键获取,模拟停车场停车操作,其等待时间是 0;发送通知的任务则是通过检测 KEY2 按键按下进行通知的发送,模拟停车场取车操作,并且在串口调试助手输出相应信息。

         这里我们还是使用之前创建好的空白工程:

基于STM32F1系列移植FreeRTOS模版资源-CSDN文库

        同样的这里有两个任务,一个发送,一个接收,我们为其创建两个任务句柄:

static TaskHandle_t Take_Task_Handle = NULL;/* Take_Task任务句柄 */
static TaskHandle_t Give_Task_Handle = NULL;/* Give_Task任务句柄 */

        同样接收相关的下面在进行讲解,先留空白:

//Take_Task 任务主体
static void Take_Task(void* parameter)
{	
  uint32_t take_num = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
  /* 任务都是一个无限循环,不能返回 */
  while (1)
  {
  }
}

        发送的通过按键来进行实现:

//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 )       
		{
      /* 原型:BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); */
			/* 释放一个任务通知 */
      xTaskNotifyGive(Take_Task_Handle);//发送任务通知
      /* 此函数只会返回pdPASS */
			if ( pdPASS == xReturn ) 
				printf( "KEY2被按下,释放1个停车位!\r\n" );
				
		}
		vTaskDelay(20);     //每20ms扫描一次	
  }
}

        这样运行结果:

        这里我是想像之前计数信号量那边直接调用API查看当前计数值,但是在 FreeRTOS 中,任务通知(Task Notification)本身并不是一个标准的计数信号量,它是通过ulTaskNotifyTake() 和 xTaskNotifyGive() 来模拟类似计数信号量的行为,因此,任务通知没有直接提供查看当前计数值的 API,这里声明一个全局变量进行累加的操作:

static volatile uint32_t s_notify_count = 0; // 全局计数器

//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 )       
		{
      /* 原型:BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); */
			/* 释放一个任务通知 */
      xTaskNotifyGive(Take_Task_Handle);//发送任务通知
      /* 此函数只会返回pdPASS */
			if ( pdPASS == xReturn ) 
			{
				s_notify_count++;
				printf("\r\nKEY2被按下,释放1个停车位!\r\n" );
				printf("当前计数值: %lu\r\n", s_notify_count);
			}
				
		}
		vTaskDelay(20);     //每20ms扫描一次	
  }
}

        运行结果:

        main函数完整代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

#include "LED.h"
#include "Usart.h"
#include "Key.h"  

/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
//#include "semphr.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 volatile uint32_t s_notify_count = 0; // 全局计数器

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

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

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

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

	All_Function_Init();//硬件初始化
	
	while (1)
	{
		 /* 创建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;  
	}
}

//任务创建函数
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 任务主体
static void Take_Task(void* parameter)
{	
  uint32_t take_num = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
  /* 任务都是一个无限循环,不能返回 */
  while (1)
  {
  }
}

//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 )       
		{
      /* 原型:BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); */
			/* 释放一个任务通知 */
      xTaskNotifyGive(Take_Task_Handle);//发送任务通知
      /* 此函数只会返回pdPASS */
			if ( pdPASS == xReturn ) 
			{
				s_notify_count++;
				printf("\r\nKEY2被按下,释放1个停车位!\r\n" );
				printf("当前计数值: %lu\r\n", s_notify_count);
			}
				
		}
		vTaskDelay(20);     //每20ms扫描一次	
  }
}

//初始化声明
static void All_Function_Init(void)
{
	/*
	 * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* LED 初始化 */
	LED_GPIO_Config();

	/* 串口初始化	*/
	USART_Config();

	//按键初始化
	Key_GPIO_Config();
  
}



完整工程:

FreeRTOS任务通知-替代计数信号量-发送函数.zip资源-CSDN文库

4.1.2  vTaskNotifyGiveFromISR()

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

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

        从上面的函数说明我们大概知道 vTaskNotifyGiveFromISR()函数作用,每次调用该函数都会增加任务的通知值,任务通过接收函数返回值是否大于零,判断是否获取到了通知,任务通知值初始化为 0,(如果与信号量做对比)则对应为信号量无效。当中断调用vTaskNotifyGiveFromISR()通知函数给任务的时候,任务的通知值增加,使其大于零,使其表示的通知值变为有效,任务获取有效的通知值将会被恢复。那么该函数是怎么实现的呢?下面一起来看看 vTaskNotifyGiveFromISR()函数的源码:

#if( configUSE_TASK_NOTIFICATIONS == 1 )

void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify,
                            BaseType_t *pxHigherPriorityTaskWoken )
{
    TCB_t * pxTCB;
    uint8_t ucOriginalNotifyState;
    UBaseType_t uxSavedInterruptStatus;

    configASSERT( xTaskToNotify );
    portASSERT_IF_INTERRUPT_PRIORITY_INVALID();

    pxTCB = ( TCB_t * ) xTaskToNotify;

    /* 进入临界区 */
    uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
    {
        /* 保存任务通知的原始状态,
           看看任务是否在等待通知,方便在发送通知后恢复任务 */
        ucOriginalNotifyState = pxTCB->ucNotifyState;

        /* 不管状态是怎么样的,反正现在发送通知,任务就收到任务通知 */
        pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED;

        /* 通知值自加,类似于信号量的释放 */
        ( pxTCB->ulNotifiedValue )++;

        traceTASK_NOTIFY_GIVE_FROM_ISR();

        /* 如果任务在阻塞等待通知 */
        if( ucOriginalNotifyState == taskWAITING_NOTIFICATION ) {
            /* 如果任务调度器运行中 */
            if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE ) {
                /* 唤醒任务,将任务从阻塞列表中移除,添加到就绪列表中 */
                ( void ) uxListRemove( &( pxTCB->xStateListItem ) );
                prvAddTaskToReadyList( pxTCB );
            } else {
                /* 调度器处于挂起状态,中断依然正常发生,但是不能直接操作就绪列表
                   将任务加入到就绪挂起列表,任务调度恢复后会移动到就绪列表 */
                vListInsertEnd( &( xPendingReadyList ),
                              &( pxTCB->xEventListItem ) );
            }

            /* 如果刚刚唤醒的任务优先级比当前任务高,
               则设置上下文切换标识,等退出函数后手动切换上下文,
               或者在系统节拍中断服务程序中自动切换上下文 */
            if( pxTCB->uxPriority > pxCurrentTCB->uxPriority ) {
                /* 设置返回参数,表示需要任务切换,在退出中断前进行任务切换 */
                if( pxHigherPriorityTaskWoken != NULL ) {
                    *pxHigherPriorityTaskWoken = pdTRUE;
                } else {
                    /* 设置自动切换标志 */
                    xYieldPending = pdTRUE;
                }
            } else {
                mtCOVERAGE_TEST_MARKER();
            }
        }
    }
    /* 退出临界区 */
    portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
}

#endif

4.1.3  xTaskNotify()

        xTaskNotify()用于在任务中直接向另外一个任务发送一个事件,接收到该任务通知的任务有可能解锁。相比于 xTaskNotifyGive()函数 ,xTaskNotify()函数在发送任务通知的时候会指定一个通知值,并且用户可以指定通知值发送的方式。

任务通知在任务控制块中的定义:

#if( configUSE_TASK_NOTIFICATIONS == 1 )
    volatile uint32_t ulNotifiedValue;
    volatile uint8_t ucNotifyState;
#endif

xTaskNotify()函数原型:

#define xTaskNotify( xTaskToNotify, ulValue, eAction ) 
        xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL )

xTaskGenericNotify()函数源码:

#if( configUSE_TASK_NOTIFICATIONS == 1 )

	BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue )
	{
	TCB_t * pxTCB;
	BaseType_t xReturn = pdPASS;
	uint8_t ucOriginalNotifyState;

		configASSERT( xTaskToNotify );
		pxTCB = ( TCB_t * ) xTaskToNotify;

		taskENTER_CRITICAL();
		{
			if( pulPreviousNotificationValue != NULL )
			{
				*pulPreviousNotificationValue = pxTCB->ulNotifiedValue;
			}

			ucOriginalNotifyState = pxTCB->ucNotifyState;

			pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED;

			switch( eAction )
			{
				case eSetBits	:
					pxTCB->ulNotifiedValue |= ulValue;
					break;

				case eIncrement	:
					( pxTCB->ulNotifiedValue )++;
					break;

				case eSetValueWithOverwrite	:
					pxTCB->ulNotifiedValue = ulValue;
					break;

				case eSetValueWithoutOverwrite :
					if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED )
					{
						pxTCB->ulNotifiedValue = ulValue;
					}
					else
					{
						/* The value could not be written to the task. */
						xReturn = pdFAIL;
					}
					break;

				case eNoAction:
					/* The task is being notified without its notify value being
					updated. */
					break;
			}

			traceTASK_NOTIFY();

			/* If the task is in the blocked state specifically to wait for a
			notification then unblock it now. */
			if( ucOriginalNotifyState == taskWAITING_NOTIFICATION )
			{
				( void ) uxListRemove( &( pxTCB->xStateListItem ) );
				prvAddTaskToReadyList( pxTCB );

				/* The task should not have been on an event list. */
				configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL );

				#if( configUSE_TICKLESS_IDLE != 0 )
				{
					/* If a task is blocked waiting for a notification then
					xNextTaskUnblockTime might be set to the blocked task's time
					out time.  If the task is unblocked for a reason other than
					a timeout xNextTaskUnblockTime is normally left unchanged,
					because it will automatically get reset to a new value when
					the tick count equals xNextTaskUnblockTime.  However if
					tickless idling is used it might be more important to enter
					sleep mode at the earliest possible time - so reset
					xNextTaskUnblockTime here to ensure it is updated at the
					earliest possible time. */
					prvResetNextTaskUnblockTime();
				}
				#endif

				if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
				{
					/* The notified task has a priority above the currently
					executing task so a yield is required. */
					taskYIELD_IF_USING_PREEMPTION();
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		taskEXIT_CRITICAL();

		return xReturn;
	}

#endif /* configUSE_TASK_NOTIFICATIONS */

xTaskNotify()函数说明:

函数原型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 的队列上的一种轻量型实现,速度更快。

这里的调用比较多,我们下面在最后进行统一举例说明,下面几个函数同样。

4.1.4  xTaskNotifyFromISR()

        xTaskNotifyFromISR()是 xTaskNotify()的中断保护版本,真正起作用的函数是中断发送任务通知通用函数 xTaskGenericNotifyFromISR(),而 xTaskNotifyFromISR()是一个宏定义:

/**
 * @brief 从中断服务程序(ISR)发送任务通知的宏
 * 
 * @param xTaskToNotify 要通知的任务句柄
 * @param ulValue 要传递的通知值(具体含义取决于eAction)
 * @param eAction 通知动作类型,决定如何处理ulValue
 *        - eNoAction: 仅通知,不更新通知值
 *        - eSetBits: 按位或操作通知值
 *        - eIncrement: 通知值加1
 *        - eSetValueWithOverwrite: 覆盖通知值
 *        - eSetValueWithoutOverwrite: 仅在无未读通知时设置
 * @param pxHigherPriorityTaskWoken 用于返回是否需要上下文切换
 * 
 * @note 此宏实际调用xTaskGenericNotifyFromISR()函数,并将pulPreviousNotificationValue参数设为NULL
 */
#define xTaskNotifyFromISR( xTaskToNotify, \
                           ulValue, \
                           eAction, \
                           pxHigherPriorityTaskWoken ) \
    xTaskGenericNotifyFromISR( ( xTaskToNotify ), \
                              ( ulValue ), \
                              ( eAction ), \
                              NULL, \
                              ( pxHigherPriorityTaskWoken ) )

xTaskNotifyFromISR()函数说明:

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

4.1.5  中断中发送任务通知通用函数 xTaskGenericNotifyFromISR()

        xTaskGenericNotifyFromISR() 是一个在中断中发送任务通知的通用函数,xTaskNotifyFromISR()、xTaskNotifyAndQueryFromISR()等函数都是以其为基础,采用宏定义的方式实现。xTaskGenericNotifyFromISR()的源码:

#if (configUSE_TASK_NOTIFICATIONS == 1)

/**
 * @brief 从中断服务程序(ISR)发送通用任务通知
 * @param xTaskToNotify 要通知的任务句柄
 * @param ulValue 要传递的通知值
 * @param eAction 通知动作类型
 * @param pulPreviousNotificationValue 用于返回之前的通知值(可选)
 * @param pxHigherPriorityTaskWoken 用于返回是否需要上下文切换
 * @return pdPASS 成功, pdFAIL 仅当eSetValueWithoutOverwrite且已有未读通知时
 */
BaseType_t xTaskGenericNotifyFromISR(TaskHandle_t xTaskToNotify,
                                   uint32_t ulValue,
                                   eNotifyAction eAction,
                                   uint32_t *pulPreviousNotificationValue,
                                   BaseType_t *pxHigherPriorityTaskWoken)
{
    TCB_t *pxTCB;
    uint8_t ucOriginalNotifyState;
    BaseType_t xReturn = pdPASS;
    UBaseType_t uxSavedInterruptStatus;

    configASSERT(xTaskToNotify);
    portASSERT_IF_INTERRUPT_PRIORITY_INVALID();

    pxTCB = (TCB_t *)xTaskToNotify;

    /* 进入中断临界区 */
    uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
    {
        /* 返回之前的通知值(如果请求) */
        if (pulPreviousNotificationValue != NULL) {
            *pulPreviousNotificationValue = pxTCB->ulNotifiedValue;
        }

        /* 保存原始通知状态 */
        ucOriginalNotifyState = pxTCB->ucNotifyState;
        
        /* 标记任务已收到通知 */
        pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED;

        /* 根据动作类型处理通知值 */
        switch (eAction) {
            /* 按位或操作通知值 */
            case eSetBits:
                pxTCB->ulNotifiedValue |= ulValue;
                break;

            /* 递增通知值 */
            case eIncrement:
                (pxTCB->ulNotifiedValue)++;
                break;

            /* 覆盖写入通知值 */
            case eSetValueWithOverwrite:
                pxTCB->ulNotifiedValue = ulValue;
                break;

            /* 仅在无未读通知时写入 */
            case eSetValueWithoutOverwrite:
                if (ucOriginalNotifyState != taskNOTIFICATION_RECEIVED) {
                    pxTCB->ulNotifiedValue = ulValue;
                } else {
                    xReturn = pdFAIL;  /* 已有未读通知,写入失败 */
                }
                break;

            /* 仅通知不修改值 */
            case eNoAction:
                break;
        }

        traceTASK_NOTIFY_FROM_ISR();

        /* 如果任务正在等待通知 */
        if (ucOriginalNotifyState == taskWAITING_NOTIFICATION) {
            if (uxSchedulerSuspended == (UBaseType_t)pdFALSE) {
                /* 将任务移出阻塞列表,加入就绪列表 */
                (void)uxListRemove(&(pxTCB->xStateListItem));
                prvAddTaskToReadyList(pxTCB);
            } else {
                /* 调度器挂起时加入待处理就绪列表 */
                vListInsertEnd(&(xPendingReadyList),
                             &(pxTCB->xEventListItem));
            }

            /* 检查是否需要任务切换 */
            if (pxTCB->uxPriority > pxCurrentTCB->uxPriority) {
                if (pxHigherPriorityTaskWoken != NULL) {
                    *pxHigherPriorityTaskWoken = pdTRUE;
                } else {
                    xYieldPending = pdTRUE;
                }
            } else {
                mtCOVERAGE_TEST_MARKER();
            }
        }
    }
    /* 退出中断临界区 */
    portCLEAR_INTERRUPT_MASK_FROM_ISR(uxSavedInterruptStatus);

    return xReturn;
}

#endif /* configUSE_TASK_NOTIFICATIONS */

4.1.6  xTaskNotifyAndQuery()

        xTaskNotifyAndQuery()与 xTaskNotify()很像,都是调用通用的任务通知发送函数xTaskGenericNotify() 来实现通知的发送,不同的是多了一个附加的参数 pulPreviousNotifyValue 用于回传接收任务的上一个通知值,函数原型:

/**
 * @brief 发送任务通知并查询先前通知值的宏
 * 
 * @param xTaskToNotify 要通知的任务句柄
 * @param ulValue 要传递的通知值
 * @param eAction 通知动作类型:
 *        - eNoAction: 仅通知不修改值
 *        - eSetBits: 按位或操作
 *        - eIncrement: 值递增
 *        - eSetValueWithOverwrite: 覆盖写入
 *        - eSetValueWithoutOverwrite: 无覆盖写入
 * @param pulPreviousNotifyValue 用于返回先前的通知值
 * 
 * @note 此宏封装了xTaskGenericNotify(),提供更简洁的查询接口
 * @note 必须先确保configUSE_TASK_NOTIFICATIONS == 1
 */
#define xTaskNotifyAndQuery( xTaskToNotify, \
                            ulValue, \
                            eAction, \
                            pulPreviousNotifyValue ) \
    xTaskGenericNotify( ( xTaskToNotify ), \
                      ( ulValue ), \
                      ( eAction ), \
                      ( pulPreviousNotifyValue ) )
函数原型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。
 

4.1.7  xTaskNotifyAndQueryFromISR()

        xTaskNotifyAndQueryFromISR()是 xTaskNotifyAndQuery ()的中断版本,用于向指定的任务发送一个任务通知,并返回对象任务的上一个通知值,该函数也是一个宏定义,真正实现发送通知的是 xTaskGenericNotifyFromISR()。xTaskNotifyAndQueryFromISR()函数说明:

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

4.2  获取任务通知函数

        获取任务通知函数只能用在任务中,没有带中断保护版本,因此只有两个API 函数: ulTaskNotifyTake() 和 xTaskNotifyWait ()。前者是为代替二值信号量和计数信号量而专门设计的,它和发送通知 API 函数 xTaskNotifyGive()、vTaskNotifyGiveFromISR()配合使用;后者是全功能版的等待通知,可以根据不同的参数实现轻量级二值信号量、计数信号量、事件组和长度为 1 的队列。

        所有的获取任务通知 API 函数都带有指定阻塞超时时间参数,当任务因为等待通知而进入阻塞时,用来指定任务的阻塞时间,这些超时机制与 FreeRTOS 的消息队列、信号量、事件等的超时机制一致。

4.2.1  ulTaskNotifyTake()

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

        对于这个函数,任务通知值为 0,对应信号量无效,如果任务设置了阻塞等待,任务被阻塞挂起。当其他任务或中断发送了通知值使其不为 0 后,通知变为有效,等待通知的任务将获取到通知,并且在退出时候根据用户传递的第一个参数 xClearCountOnExit 选择清零通知值或者执行减一操作。

        xTaskNotifyTake()在退出的时候处理任务的通知值的时候有两种方法:

  • 一种是在函数退出时将通知值清零,这种方法适用于实现二值信号量;
  • 另外一种是在函数退出时将通知值减 1,这种方法适用于实现计数信号量。
函数原型uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,
                                           TickType_t xTicksToWait );
功能用于获取一个任务通知,获取二值信号量、计数信号量类型的任务通知。
参数xClearCountOnExit

设置为 pdFALSE 时,函数 xTaskNotifyTake()退出前,将任务的通知值减 1,可以用来实现计数信号量;

设置为 pdTRUE 时,函数 xTaskNotifyTake()退出前,将任务通知值清零,可以用来实现二值信号量。

xTicksToWait超时时间,单位为系统节拍周期。宏 pdMS_TO_TICKS 用于将毫秒转化为系统节拍数。
返回值返回任务的当前通知值,在其减 1 或者清 0 之前。

ulTaskNotifyTake()源码:

#if (configUSE_TASK_NOTIFICATIONS == 1)

/**
 * @brief 等待并获取任务通知值
 * @param xClearCountOnExit 退出时是否清零通知值
 *        - pdTRUE: 清零通知值
 *        - pdFALSE: 通知值减1(类似计数信号量)
 * @param xTicksToWait 最大等待时间(ticks)
 * @return 获取到的通知值(可能为0表示超时)
 */
uint32_t ulTaskNotifyTake(BaseType_t xClearCountOnExit,
                         TickType_t xTicksToWait)
{
    uint32_t ulReturn;

    /* 进入临界区 */
    taskENTER_CRITICAL();
    {
        /* 检查当前通知值,进入临界区,先看看任务通知值是否有效,有效才能获取,无效则根据指定超时时间等待,标记一下任务状态,表示任务在等待通知。任务通知在任务初始化的时候是默认为无效的 */
        // 如果通知值为 0 ,阻塞任务
        // 默认初始化通知值为 0, 说明没有未读通知
        if (pxCurrentTCB->ulNotifiedValue == 0UL) {
            /* 设置任务为等待通知状态 */
            pxCurrentTCB->ucNotifyState = taskWAITING_NOTIFICATION;

            /* 如果需要阻塞等待 */
            if (xTicksToWait > (TickType_t)0) {
                /* 将任务添加到延迟列表 */
                prvAddCurrentTaskToDelayedList(xTicksToWait, pdTRUE);
                traceTASK_NOTIFY_TAKE_BLOCK();
                
                /* 执行任务切换 */
                portYIELD_WITHIN_API();
            } else {
                mtCOVERAGE_TEST_MARKER();
            }
        } else {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    taskEXIT_CRITICAL();

    /* 再次进入临界区处理通知值 */
    taskENTER_CRITICAL();
    {
        traceTASK_NOTIFY_TAKE();
        ulReturn = pxCurrentTCB->ulNotifiedValue;

        /* 处理有效通知值 */
        if (ulReturn != 0UL) {
            if (xClearCountOnExit != pdFALSE) {
                /* 清零通知值 */
                pxCurrentTCB->ulNotifiedValue = 0UL;
            } else {
                /* 通知值减1(模拟计数信号量) */
                pxCurrentTCB->ulNotifiedValue = ulReturn - 1;
            }
        } else {
            mtCOVERAGE_TEST_MARKER();
        }

        /* 重置任务通知状态 */
        pxCurrentTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
    }
    taskEXIT_CRITICAL();

    return ulReturn;
}

#endif /* configUSE_TASK_NOTIFICATIONS */

        我们知道ulTaskNotifyTake() 是为代替二值信号量和计数信号量而专门设计的,我们在4.1.1举了两个例子,当时没有填充接收的东西,下面我们来填充一下。

4.2.1.1  二值信号量

        这里其他部分代码不需要更改,主要是对接收部分的更改:

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

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

        其中Toggle_LED_R()是一个电平翻转函数:

void Toggle_LED_R(void)
{
	BitAction LED_R = (BitAction)(1 - GPIO_ReadOutputDataBit(LED1_GPIO_PORT, LED1_GPIO_PIN));
	GPIO_WriteBit(LED1_GPIO_PORT, LED1_GPIO_PIN, LED_R);
}

        完整main函数代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

#include "LED.h"
#include "Usart.h"
#include "Key.h"  

/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.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 All_Function_Init(void);/* 用于初始化板载相关资源 */

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

	All_Function_Init();//硬件初始化
	
	while (1)
	{
		 /* 创建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;  
	}
}

//任务创建函数
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任务成功!\r\n"); 
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}

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

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


//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 )
    {
      /* 原型:BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); */
      xReturn = xTaskNotifyGive(Receive1_Task_Handle);
      /* 此函数只会返回pdPASS */
      if( xReturn == pdTRUE )
        printf("\r\nReceive1_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("\r\nReceive2_Task_Handle 任务通知发送成功!\r\n");
    }
    vTaskDelay(20);
  }
}


//初始化声明
static void All_Function_Init(void)
{
	/*
	 * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* LED 初始化 */
	LED_GPIO_Config();

	/* 串口初始化	*/
	USART_Config();

	//按键初始化
	Key_GPIO_Config();
  
}



        运行结果:

完整工程:

FreeRTOS任务通知-替代二值信号量-接收函数.zip资源-CSDN文库

4.2.1.2  计数信号量

        同样别的不需要改,仅改动接收相关的代码:

//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( "\r\nKEY1被按下,成功申请到停车位,当前剩余车位为:%d \r\n", take_num - 1);
			else
        printf( "\r\nKEY1被按下,车位已经没有了,请按KEY2释放车位!\r\n" );  
		}
		vTaskDelay(20);     //每20ms扫描一次		
  }
}

        完整main函数代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

#include "LED.h"
#include "Usart.h"
#include "Key.h"  

/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
//#include "semphr.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 volatile uint32_t s_notify_count = 0; // 全局计数器

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

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

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

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

	All_Function_Init();//硬件初始化
	
	while (1)
	{
		 /* 创建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;  
	}
}

//任务创建函数
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 任务主体
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( "\r\nKEY1被按下,成功申请到停车位,当前剩余车位为:%d \r\n", take_num - 1);
			else
        printf( "\r\nKEY1被按下,车位已经没有了,请按KEY2释放车位!\r\n" );  
		}
		vTaskDelay(20);     //每20ms扫描一次		
  }
}

//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 )       
		{
      /* 原型:BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); */
			/* 释放一个任务通知 */
      xTaskNotifyGive(Take_Task_Handle);//发送任务通知
      /* 此函数只会返回pdPASS */
			if ( pdPASS == xReturn ) 
			{
				s_notify_count++;
				printf("\r\nKEY2被按下,释放1个停车位,当前剩余车位为: %lu\r\n" ,s_notify_count);
			}
				
		}
		vTaskDelay(20);     //每20ms扫描一次	
  }
}

//初始化声明
static void All_Function_Init(void)
{
	/*
	 * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* LED 初始化 */
	LED_GPIO_Config();

	/* 串口初始化	*/
	USART_Config();

	//按键初始化
	Key_GPIO_Config();
  
}



        运行结果:

完整工程:

FreeRTOS任务通知-替代计数信号量-接收函数.zip资源-CSDN文库

4.2.2  xTaskNotifyWait()

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

函数原型BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
                                                uint32_t ulBitsToClearOnExit,
                                                uint32_t *pulNotificationValue,
                                                TickType_t xTicksToWait );
功能用于等待一个任务通知,并带有超时等待。
参数ulBitsToClearOnEntryulBitsToClearOnEntry 表示在使用通知之前,将任务通知值的哪些位清0,实现过程就是将任务的通知值与参数 ulBitsToClearOnEntry 的按位取反值按位与操作。
如果 ulBitsToClearOnEntry 设置为 0x01,那么在函数进入前,任务通知值的位 1 会被清 0,其他位保持不变 。如 果ulBitsToClearOnEntry 设置为 0xFFFFFFFF (ULONG_MAX),那么在进入函数前任务通知值的所有位都会被清 0,表示清零任务通知值。
ulBitsToClearOnExitulBitsToClearOnExit 表示在函数 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。

xTaskNotifyWait()源码:

#if (configUSE_TASK_NOTIFICATIONS == 1)

/**
 * @brief 等待任务通知并可选地清除通知值的指定位
 * @param ulBitsToClearOnEntry 进入时清除通知值的位掩码
 * @param ulBitsToClearOnExit 退出时清除通知值的位掩码
 * @param pulNotificationValue 用于返回通知值的指针(可选)
 * @param xTicksToWait 最大等待时间(ticks)
 * @return pdTRUE 收到通知, pdFALSE 超时
 */
BaseType_t xTaskNotifyWait(uint32_t ulBitsToClearOnEntry,
                         uint32_t ulBitsToClearOnExit,
                         uint32_t *pulNotificationValue,
                         TickType_t xTicksToWait)
{
    BaseType_t xReturn;

    /* 进入临界区 */
    taskENTER_CRITICAL();
    {
        /* 检查当前是否有未处理的通知,只有任务当前没有收到任务通知,才会将任务阻塞 */
        if (pxCurrentTCB->ucNotifyState != taskNOTIFICATION_RECEIVED) {
            /* 清除指定的通知值位 */
            pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnEntry;

            /* 设置任务为等待通知状态 */
            pxCurrentTCB->ucNotifyState = taskWAITING_NOTIFICATION;

            /* 如果需要阻塞等待 */
            if (xTicksToWait > (TickType_t)0) {
                /* 将任务添加到延迟列表 */
                prvAddCurrentTaskToDelayedList(xTicksToWait, pdTRUE);
                traceTASK_NOTIFY_WAIT_BLOCK();
                
                /* 执行任务切换 */
                portYIELD_WITHIN_API();
            } else {
                mtCOVERAGE_TEST_MARKER();
            }
        } else {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    taskEXIT_CRITICAL();

    /* 再次进入临界区处理结果程序能执行到这里说明其它任务或中断向这个任务发送了通知或者任务阻塞超时,任务从阻塞态变成运行态,现在继续处理。 */
    taskENTER_CRITICAL();
    {
        traceTASK_NOTIFY_WAIT();

        /* 返回当前通知值(如果请求) */
        if (pulNotificationValue != NULL) {
            *pulNotificationValue = pxCurrentTCB->ulNotifiedValue;
        }

        /* 检查是否成功收到通知 */
        if (pxCurrentTCB->ucNotifyState == taskWAITING_NOTIFICATION) {
            /* 超时返回 */
            xReturn = pdFALSE;
        } else {
            /* 清除指定的退出位 */
            pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnExit;
            xReturn = pdTRUE;
        }

        /* 重置任务通知状态 */
        pxCurrentTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
    }
    taskEXIT_CRITICAL();

    return xReturn;
}

#endif /* configUSE_TASK_NOTIFICATIONS */

5.  实例演示

        对于 xTaskNotifyGive() 和 ulTaskNotifyTake() 这两个函数是为代替二值信号量和计数信号量而专门设计的,需要配套使用,使用方法也很简单,我们上面介绍API函数的时候一起举例说明了,这里就不在做过多的描述了。

        对于 xTaskNotifyWait () 是全功能版的等待通知,可以根据不同的参数实现轻量级二值信号量、计数信号量、事件组和长度为 1 的队列,下面我们配合发送函数举几个例子调用一下,我们在回顾一下其中任务通知的几种状态。

任务通知值的状态:

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 的队列上的一种轻量型实现,速度更快。

以下所有例子全是使用之前创建好的空白工程:

基于STM32F1系列移植FreeRTOS模版资源-CSDN文库

        注意为了方便观察现象,我在其中又加入了LED电平翻转函数:

void Toggle_LED_R(void)
{
	BitAction LED_R = (BitAction)(1 - GPIO_ReadOutputDataBit(LED1_GPIO_PORT, LED1_GPIO_PIN));
	GPIO_WriteBit(LED1_GPIO_PORT, LED1_GPIO_PIN, LED_R);
}

5.1  xTaskNotify()实例演示

5.1.1  二值信号量——eNoAction作为参数

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

        创建三个任务的任务句柄,方便后续对任务的操作:

//接收相关
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任务句柄 */

        然后开始对三个任务进行任务主体编写,首先对于发送任务,我们通过按键来实现,创还能两个按键一个不进行通知值传递,一个进行:

//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 )
    {
			/**
			 * BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
                                         uint32_t ulValue,
                                         eNotifyAction eAction );

			 * @param xTaskToNotify 要通知的任务句柄
			 * @param ulValue 要传递的通知值(具体含义取决于eAction)
			 * @param eAction 通知动作类型,决定如何处理ulValue
			 *        - eNoAction: 仅通知,不更新通知值
			 *        - eSetBits: 按位或操作通知值
			 *        - eIncrement: 通知值加1
			 *        - eSetValueWithOverwrite: 覆盖通知值
			 *        - eSetValueWithoutOverwrite: 仅在无未读通知时设置
			**/
      xReturn = xTaskNotify(Receive1_Task_Handle, 
                            0,                  // ulValue被忽略
                            eNoAction );         // 仅唤醒任务
												
      /* 此函数只会返回pdPASS */
      if( xReturn == pdTRUE )
        printf("\r\nReceive1_Task_Handle 任务通知发送成功!\r\n");
    } 

    /* KEY2 被按下 */
    if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
    {
      xReturn = xTaskNotify(Receive2_Task_Handle, 
                            0,                  // ulValue被忽略
                            eNoAction );         // 仅唤醒任务
//                            eSetBits );        
      /* 此函数只会返回pdPASS */
      if( xReturn == pdPASS )
        printf("\r\nReceive2_Task_Handle 任务通知发送成功!\r\n");
    }
    vTaskDelay(20);
  }
}

        对接收任务主体进行编写,不接收值传递:

//Receive_Task任务主体
static void Receive1_Task(void* parameter)
{	
	BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */

  while (1)
  {
		xReturn = xTaskNotifyWait(0,              // 进入时不清除任何位
										          0,              // 退出时不清除任何位
										          NULL,           // 不获取通知值
										          portMAX_DELAY);   // 无限期等待

		if(xReturn == pdTRUE)
		{
			Toggle_LED_R();
			printf("Receive1_Task_Handle 任务通知接收成功!\r\n");
		}
  }
}

        接收值传递:

//Receive_Task任务主体
static void Receive2_Task(void* parameter)
{	
	BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
	uint32_t ulNotifiedValue = 0;  // 用于存储通知值

  while (1)
  {
		xTaskNotifyWait(0,                  // 进入时不清除任何位
										0,                  // 退出时不清除任何位
										&ulNotifiedValue,   // 获取通知值,NULL 不获取通知值
										portMAX_DELAY);     // 无限期等待

		if(xReturn == pdTRUE)
		{
			Toggle_LED_R();
			printf("Receive2_Task_Handle 任务通知接收成功!\r\n");
			printf("Receive2_Task_Handle 收到通知! 通知值: 0x%08X\r\n", ulNotifiedValue);
		}
		else
		{
			printf("通知等待异常!\r\n");  // 实际不会执行,因portMAX_DELAY是无限等待
		}
  }
}

        然后创建三个任务:

//任务创建函数
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任务成功!\r\n"); 
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}

        完整main函数:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

#include "LED.h"
#include "Usart.h"
#include "Key.h"  

/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.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 All_Function_Init(void);/* 用于初始化板载相关资源 */

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

	All_Function_Init();//硬件初始化
	
	while (1)
	{
		 /* 创建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;  
	}
}

//任务创建函数
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任务成功!\r\n"); 
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}

//Receive_Task任务主体
static void Receive1_Task(void* parameter)
{	
	BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */

  while (1)
  {
		xReturn = xTaskNotifyWait(0,              // 进入时不清除任何位
										          0,              // 退出时不清除任何位
										          NULL,           // 不获取通知值
										          portMAX_DELAY);   // 无限期等待

		if(xReturn == pdTRUE)
		{
			Toggle_LED_R();
			printf("Receive1_Task_Handle 任务通知接收成功!\r\n");
		}
  }
}

//Receive_Task任务主体
static void Receive2_Task(void* parameter)
{	
	BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
	uint32_t ulNotifiedValue = 0;  // 用于存储通知值

  while (1)
  {
		xTaskNotifyWait(0,                  // 进入时不清除任何位
										0,                  // 退出时不清除任何位
										&ulNotifiedValue,   // 获取通知值,NULL 不获取通知值
										portMAX_DELAY);     // 无限期等待

		if(xReturn == pdTRUE)
		{
			Toggle_LED_R();
			printf("Receive2_Task_Handle 任务通知接收成功!\r\n");
			printf("Receive2_Task_Handle 收到通知! 通知值: 0x%08X\r\n", ulNotifiedValue);
		}
		else
		{
			printf("通知等待异常!\r\n");  // 实际不会执行,因portMAX_DELAY是无限等待
		}
  }
}

//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 )
    {
			/**
			 * BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
                                         uint32_t ulValue,
                                         eNotifyAction eAction );

			 * @param xTaskToNotify 要通知的任务句柄
			 * @param ulValue 要传递的通知值(具体含义取决于eAction)
			 * @param eAction 通知动作类型,决定如何处理ulValue
			 *        - eNoAction: 仅通知,不更新通知值
			 *        - eSetBits: 按位或操作通知值
			 *        - eIncrement: 通知值加1
			 *        - eSetValueWithOverwrite: 覆盖通知值
			 *        - eSetValueWithoutOverwrite: 仅在无未读通知时设置
			**/
      xReturn = xTaskNotify(Receive1_Task_Handle, 
                            0,                  // ulValue被忽略
                            eNoAction );         // 仅唤醒任务
												
      /* 此函数只会返回pdPASS */
      if( xReturn == pdTRUE )
        printf("\r\nReceive1_Task_Handle 任务通知发送成功!\r\n");
    } 

    /* KEY2 被按下 */
    if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
    {
      xReturn = xTaskNotify(Receive2_Task_Handle, 
                            0x20,                  // ulValue被忽略
                            eNoAction );         // 仅唤醒任务
//                            eSetBits );        
      /* 此函数只会返回pdPASS */
      if( xReturn == pdPASS )
        printf("\r\nReceive2_Task_Handle 任务通知发送成功!\r\n");
    }
    vTaskDelay(20);
  }
}


//初始化声明
static void All_Function_Init(void)
{
	/*
	 * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* LED 初始化 */
	LED_GPIO_Config();

	/* 串口初始化	*/
	USART_Config();

	//按键初始化
	Key_GPIO_Config();
  
}



        运行结果,可以看出由于调用的是 eNoAction 其会忽视通知值,因此显示0:

        若是我们将按键发送的调用使用eSetBits,可以看出通知值能进行传递(代码中已经写了,把注释改掉即可):

完整工程:

FreeRTOS任务通知-替代二值信号量-eNoAction.zip资源-CSDN文库

5.1.2  事件——eSetBits作为参数

        上面我们也理解到eSetBits作为xTaskNotify()函数的参数时,xTaskNotify()函数将按位或传递参数,而这也刚好符合事件的特性,事件相关介绍可以参考:

FreeRTOS菜鸟入门(十四)·事件-CSDN博客

        那么我们就举一个实际的例子看一下,该实验是在 FreeRTOS 中创建了两个任务,一个是发送事件通知任务,一个是等待事件通知任务,两个任务独立运行,发送事件通知任务通过检测按键的按下情况设置不同的通知值位,等待事件通知任务则获取这任务通知值,并且根据通知值判断两个事件是否都发生,如果是则输出相应信息,LED 进行翻转。等待事件通知任务的等待时间是 portMAX_DELAY,一直在等待事件通知的发生,等待获取到事件之后清除对应的任务通知值的位,同样找到空白工程(该小节上面有链接)。

        既然我们要创建两个任务,那么首先任务句柄是需要的:

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

        创建发送任务主体:

//KEY_Task任务主体
static void KEY_Task(void* parameter)
{	 
    /* 任务都是一个无限循环,不能返回 */
  while (1)
  {
    if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )       
		{
      printf ( "KEY1被按下!\r\n" );
      /* 原型:BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, 
                                      uint32_t ulValue, 
                                      eNotifyAction eAction ); 
       * eNoAction = 0,通知任务而不更新其通知值。
       * eSetBits,     设置任务通知值中的位。
       * eIncrement,   增加任务的通知值。
       * eSetvaluewithoverwrite,覆盖当前通知
       * eSetValueWithoutoverwrite 不覆盖当前通知
       * 
       * pdFAIL:当参数eAction设置为eSetValueWithoutOverwrite的时候,
       * 如果任务通知值没有更新成功就返回pdFAIL。
       * pdPASS: eAction 设置为其他选项的时候统一返回pdPASS。
      */
			/* 触发一个事件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被按下!\r\n" );	
			/* 触发一个事件2 */
			xTaskNotify((TaskHandle_t	)LED_Task_Handle,//接收任务通知的任务句柄
                  (uint32_t		)KEY2_EVENT,			//要触发的事件
                  (eNotifyAction)eSetBits);			//设置任务通知值中的位				
		}
		vTaskDelay(20);     //每20ms扫描一次		
  }
}

        接收任务主体:

//LED_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
                              0xffffffffUL,	  //退出函数的时候清除所有的bitR,写成0xffffffffUL也可,就是它的宏定义,记得包含头文件#include "limits.h"
                              &r_event,		  //保存任务通知值                    
                              portMAX_DELAY);	//阻塞时间
    if( pdTRUE == xReturn )
    { 
      last_event |= r_event;
      /* 如果接收完成并且正确 */      
      if(last_event == (KEY1_EVENT|KEY2_EVENT)) //如果发送改成具体值,这里需要改为发送写的具体的值
      {
        last_event = 0;     /* 上一次的事件清零 */
        printf ( "\r\nKey1与Key2都按下!\r\n");		
        Toggle_LED_R();       //LED1	反转 
      }
      else  /* 否则就更新事件 */
        last_event = r_event;   /* 更新上一次触发的事件 */
    }
    
  }
}

        然后就是对任务创建函数,创建任务:

//任务创建函数
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();            //退出临界区
}

        完整main函数:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

#include "LED.h"
#include "Usart.h"
#include "Key.h"  

/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "limits.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 All_Function_Init(void);/* 用于初始化板载相关资源 */

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

	All_Function_Init();//硬件初始化
	
	while (1)
	{
		 /* 创建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;  
	}
}

//任务创建函数
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任务主体
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
                              0xffffffffUL,	  //退出函数的时候清除所有的bitR,写成0xffffffffUL也可,就是它的宏定义,记得包含头文件#include "limits.h"
                              &r_event,		  //保存任务通知值                    
                              portMAX_DELAY);	//阻塞时间
    if( pdTRUE == xReturn )
    { 
      last_event |= r_event;
      /* 如果接收完成并且正确 */      
      if(last_event == (KEY1_EVENT|KEY2_EVENT)) //如果发送改成具体值,这里需要改为发送写的具体的值
      {
        last_event = 0;     /* 上一次的事件清零 */
        printf ( "\r\nKey1与Key2都按下!\r\n");		
        Toggle_LED_R();       //LED1	反转 
      }
      else  /* 否则就更新事件 */
        last_event = r_event;   /* 更新上一次触发的事件 */
    }
    
  }
}

//KEY_Task任务主体
static void KEY_Task(void* parameter)
{	 
    /* 任务都是一个无限循环,不能返回 */
  while (1)
  {
    if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )       
		{
      printf ( "KEY1被按下!\r\n" );
      /* 原型:BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, 
                                      uint32_t ulValue, 
                                      eNotifyAction eAction ); 
       * eNoAction = 0,通知任务而不更新其通知值。
       * eSetBits,     设置任务通知值中的位。
       * eIncrement,   增加任务的通知值。
       * eSetvaluewithoverwrite,覆盖当前通知
       * eSetValueWithoutoverwrite 不覆盖当前通知
       * 
       * pdFAIL:当参数eAction设置为eSetValueWithoutOverwrite的时候,
       * 如果任务通知值没有更新成功就返回pdFAIL。
       * pdPASS: eAction 设置为其他选项的时候统一返回pdPASS。
      */
			/* 触发一个事件1 */
			xTaskNotify((TaskHandle_t	)LED_Task_Handle,//接收任务通知的任务句柄
                  (uint32_t		)KEY1_EVENT,			//要触发的事件,也可以直接写具体值如0x01,不过要记得将接收的值也换掉
                  (eNotifyAction)eSetBits);			//设置任务通知值中的位
									
		}
    
		if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )       
		{
      printf ( "KEY2被按下!\r\n" );	
			/* 触发一个事件2 */
			xTaskNotify((TaskHandle_t	)LED_Task_Handle,//接收任务通知的任务句柄
                  (uint32_t		)KEY2_EVENT,			//要触发的事件,也可以直接写具体值如0x02,不过要记得将接收的值也换掉
                  (eNotifyAction)eSetBits);			//设置任务通知值中的位				
		}
		vTaskDelay(20);     //每20ms扫描一次		
  }
}

//初始化声明
static void All_Function_Init(void)
{
	/*
	 * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* LED 初始化 */
	LED_GPIO_Config();

	/* 串口初始化	*/
	USART_Config();

	//按键初始化
	Key_GPIO_Config();
  
}



        运行结果:

完整工程:

FreeRTOS任务通知-替代事件-eSetBits.zip资源-CSDN文库

5.1.3  计数信号量——eIncrement

        任务通知代替计数信号量是基于计数型信号量实验修改而来,模拟停车场工作运行。并且在 FreeRTOS 中创建了两个任务:一个是获取任务通知,一个是发送任务通知,两个任务独立运行,获取通知的任务是通过按下 KEY1 按键获取,模拟停车场停车操作,其等待时间是 0;发送通知的任务则是通过检测 KEY2 按键按下进行通知的发送,模拟停车场取车操作,并且在串口调试助手输出相应信息。

        对于这个的模拟有点绕,我们逐步讲解一下,首先两个任务句柄:

static TaskHandle_t Take_Task_Handle = NULL;/* Take_Task任务句柄 */
static TaskHandle_t Give_Task_Handle = NULL;/* 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 )       
		{
			xTaskNotify(Take_Task_Handle, 
                  0,          // ulValue无效
                  eIncrement); // 通知值+1

			printf("\r\nKEY2被按下,释放1个停车位!\r\n");			
		}
		vTaskDelay(20);     //每20ms扫描一次	
  }
}

        我们可以跳转到xTaskNotify()函数内部看一下,实际上就是++的操作:

        对于接收函数,最开始我是想判断xReturn是否等于pdTRUE,不过这有一个问题,如果没有通知传递过来,并且我们设置非阻塞,他会直接返回pdPASS,表示函数成功执行,但通知没有发生:

//Take_Task 任务主体
static void Take_Task(void* parameter)
{	
	uint32_t ulNotifiedValue;
	BaseType_t xReturn;
	
	while (1)
	{
		if(Key_Scan(KEY1_GPIO_PORT, KEY1_GPIO_PIN) == KEY_ON)       
		{
			/* 模拟信号量Take:退出时清零通知值 */
			xReturn = xTaskNotifyWait(0,                  // 进入时不清除位
																0,                  // 退出时不清除位
																&ulNotifiedValue,   // 获取当前通知值
																0       // 不阻塞,立即返回
														   );

			if(xReturn == pdTRUE)
			{
					Toggle_LED_R();
					printf("\r\nKEY1按下:当前剩余车位:%lu\r\n", ulNotifiedValue);
			} 
			else 
			{
					printf("\r\nKEY1按下:无可用车位,请等待!\r\n");
			}
		}
		vTaskDelay(20); // 防抖
	}
}

        这就回出现一个问题,如果我们有车位但是已经处理过一次任务通知,再次按按键KEY1,这时不会显示当前剩余车位:

        然后更改通过判断ulNotifiedValue > 0来进行判断:

static void Take_Task(void* parameter)
{	
	uint32_t ulNotifiedValue;
	BaseType_t xReturn;
	
	while (1)
	{
		if(Key_Scan(KEY1_GPIO_PORT, KEY1_GPIO_PIN) == KEY_ON)       
		{
			/* 模拟信号量Take:退出时清零通知值 */
			xReturn = xTaskNotifyWait(0,                  // 进入时不清除位
																0,                  // 退出时不清除位
																&ulNotifiedValue,   // 获取当前通知值
																0       // 不阻塞,立即返回
														   );

			if(ulNotifiedValue > 0) 
			{
					Toggle_LED_R();
					printf("\r\nKEY1按下:当前剩余车位:%lu\r\n", ulNotifiedValue);
			} 
			else 
			{
					printf("\r\nKEY1按下:无可用车位,请等待!\r\n");
			}
		}
		vTaskDelay(20); // 防抖
	}
}

        运行结果并不能体现出递减的操作:

        后面又试了几种方法发送方累加没问题,接收方累减有点出入,还是用xTaskNotifyGive() 和 ulTaskNotifyTake() 这两个函数比较好使。

把源码放这里了,欢迎讨论:

FreeRTOS任务通知-替代计数信号量-eIncrement.zip资源-CSDN文库

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( "\r\nKEY1被按下,成功申请到停车位,当前剩余车位为:%d \r\n", take_num - 1);
			else
        printf( "\r\nKEY1被按下,车位已经没有了,请按KEY2释放车位!\r\n" );  
		}
		vTaskDelay(20);     //每20ms扫描一次		
  }
}

5.1.4  消息队列——eSetValueWithOverwrite

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

        老规矩三个任务,还是先创建三个句柄:

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任务句柄 */

        创建完任务的句柄,下面就开始创建任务的主体部分,对于发送函数任务主体部分,我们创建两个数组进行发送数组里面的数据:

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

  char test_str1[] = "this is a mail test 1";/* 邮箱消息test1 */
  char test_str2[] = "this is a mail test 2";/* 邮箱消息test2 */

  while (1)
  {
    /* KEY1 被按下 */
    if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
    {
      /* 原型:BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, 
                                      uint32_t ulValue, 
                                      eNotifyAction eAction ); 
       * eNoAction = 0,通知任务而不更新其通知值。
       * eSetBits,     设置任务通知值中的位。
       * eIncrement,   增加任务的通知值。
       * eSetvaluewithoverwrite,覆盖当前通知
       * eSetValueWithoutoverwrite 不覆盖当前通知
       * 
       * pdFAIL:当参数eAction设置为eSetValueWithoutOverwrite的时候,
       * 如果任务通知值没有更新成功就返回pdFAIL。
       * pdPASS: eAction 设置为其他选项的时候统一返回pdPASS。
      */
      xReturn = xTaskNotify( Receive1_Task_Handle, /*任务句柄*/

                             (uint32_t)&test_str1, /* 发送的数据,最大为4字节 */

                             eSetValueWithOverwrite );/*覆盖当前通知*/
      
      if( xReturn == pdPASS )
        printf("\r\nReceive1_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("\r\nReceive2_Task_Handle 任务通知消息发送成功!\r\n");
    }
    vTaskDelay(20);
  }
}

        对于接收部分,没少好说的调用xTaskNotifyWait()函数,进行接收:

//Receive_Task任务主体
static void Receive1_Task(void* parameter)
{	
  BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
  char *r_char;

  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,	  //退出函数的时候清除所有的bit
                            (uint32_t *)&r_char,		  //保存任务通知值                    
                            portMAX_DELAY);	//阻塞时间

    if( pdTRUE == xReturn )
      printf("Receive1_Task 任务通知消息为 %s !\r\n",r_char);                      
   
		Toggle_LED_R();
  }
}

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

  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,	  //退出函数的时候清除所有的bit
                            (uint32_t *)&r_char,		  //保存任务通知值                    
                            portMAX_DELAY);	//阻塞时间

    if( pdTRUE == xReturn )
      printf("Receive2_Task 任务通知消息为 %s !\r\n",r_char);                      
   
		Toggle_LED_R();
  }
}

        然后就是对任务函数的创建:

//任务创建函数
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任务成功!\r\n");  
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}

        完整主函数代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

#include "LED.h"
#include "Usart.h"
#include "Key.h"  

/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "limits.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 All_Function_Init(void);/* 用于初始化板载相关资源 */

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

	All_Function_Init();//硬件初始化
	
	while (1)
	{
		 /* 创建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;  
	}
}

//任务创建函数
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任务成功!\r\n");  
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}

//Receive_Task任务主体
static void Receive1_Task(void* parameter)
{	
  BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
  char *r_char;

  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,	  //退出函数的时候清除所有的bit
                            (uint32_t *)&r_char,		  //保存任务通知值                    
                            portMAX_DELAY);	//阻塞时间

    if( pdTRUE == xReturn )
      printf("Receive1_Task 任务通知消息为 %s !\r\n",r_char);                      
   
		Toggle_LED_R();
  }
}

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

  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,	  //退出函数的时候清除所有的bit
                            (uint32_t *)&r_char,		  //保存任务通知值                    
                            portMAX_DELAY);	//阻塞时间

    if( pdTRUE == xReturn )
      printf("Receive2_Task 任务通知消息为 %s !\r\n",r_char);                      
   
		Toggle_LED_R();
  }
}

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

  char test_str1[] = "this is a mail test 1";/* 邮箱消息test1 */
  char test_str2[] = "this is a mail test 2";/* 邮箱消息test2 */

  while (1)
  {
    /* KEY1 被按下 */
    if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
    {
      /* 原型:BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, 
                                      uint32_t ulValue, 
                                      eNotifyAction eAction ); 
       * eNoAction = 0,通知任务而不更新其通知值。
       * eSetBits,     设置任务通知值中的位。
       * eIncrement,   增加任务的通知值。
       * eSetvaluewithoverwrite,覆盖当前通知
       * eSetValueWithoutoverwrite 不覆盖当前通知
       * 
       * pdFAIL:当参数eAction设置为eSetValueWithoutOverwrite的时候,
       * 如果任务通知值没有更新成功就返回pdFAIL。
       * pdPASS: eAction 设置为其他选项的时候统一返回pdPASS。
      */
      xReturn = xTaskNotify( Receive1_Task_Handle, /*任务句柄*/

                             (uint32_t)&test_str1, /* 发送的数据,最大为4字节 */

                             eSetValueWithOverwrite );/*覆盖当前通知*/
      
      if( xReturn == pdPASS )
        printf("\r\nReceive1_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("\r\nReceive2_Task_Handle 任务通知消息发送成功!\r\n");
    }
    vTaskDelay(20);
  }
}


//初始化声明
static void All_Function_Init(void)
{
	/*
	 * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* LED 初始化 */
	LED_GPIO_Config();

	/* 串口初始化	*/
	USART_Config();

	//按键初始化
	Key_GPIO_Config();
  
}



        运行结果:

完整工程:

FreeRTOS任务通知-替代消息队列-eSetValueWithOverwrite.zip资源-CSDN文库

        这里可能有人说看不出通知被覆盖,那么我们更改一下逻辑,创建两个任务按键KEY1每次按下计数加1发送,按键KEY2接收,继续找一个空白工程,两个任务句柄:

static TaskHandle_t Receive1_Task_Handle = NULL;/* Receive1_Task任务句柄 */
static TaskHandle_t Send_Task_Handle = NULL;/* Send_Task任务句柄 */

        发送函数,累加操作:

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

  uint32_t send1 = 0;

  while (1)
  {
    /* KEY1 被按下 */
    if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
    {
      /* 原型:BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, 
                                      uint32_t ulValue, 
                                      eNotifyAction eAction ); 
       * eNoAction = 0,通知任务而不更新其通知值。
       * eSetBits,     设置任务通知值中的位。
       * eIncrement,   增加任务的通知值。
       * eSetvaluewithoverwrite,覆盖当前通知
       * eSetValueWithoutoverwrite 不覆盖当前通知
       * 
       * pdFAIL:当参数eAction设置为eSetValueWithoutOverwrite的时候,
       * 如果任务通知值没有更新成功就返回pdFAIL。
       * pdPASS: eAction 设置为其他选项的时候统一返回pdPASS。
      */

			send1++;

      xReturn = xTaskNotify( Receive1_Task_Handle, /*任务句柄*/
                             send1, /* 发送的数据,最大为4字节 */
                             eSetValueWithOverwrite );/*覆盖当前通知*/
      
      if( xReturn == pdPASS )
        printf("\r\nReceive1_Task_Handle 任务通知消息发送成功!\r\n");
    } 

    vTaskDelay(20);
  }
}

        接收函数:

//Receive_Task任务主体
static void Receive1_Task(void* parameter)
{	
  BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
	uint32_t r_num;

  while (1)
  {
		if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
    {		
			/* 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,	  //退出函数的时候清除所有的bit
															(uint32_t *)&r_num,		  //保存任务通知值                    
															0);	//阻塞时间

			if( pdTRUE == xReturn )
				printf("\r\nReceive1_Task 任务通知消息为 %d !\r\n",r_num);                      
		 
			Toggle_LED_R();
		}
  }
}

        任务创建函数,创建两个任务:

//任务创建函数
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");
    
  /* 创建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任务成功!\r\n");  
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}

        主函数代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

#include "LED.h"
#include "Usart.h"
#include "Key.h"  

/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "limits.h"

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

static TaskHandle_t Receive1_Task_Handle = NULL;/* Receive1_Task任务句柄 */
static TaskHandle_t Send_Task_Handle = NULL;/* Send_Task任务句柄 */

/********************************** 内核对象句柄 *********************************/
/*
 * 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
 * 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
 * 们就可以通过这个句柄操作这些内核对象。
 *
 * 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
 * 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
 * 来完成的
 * 
 */


/******************************* 宏定义 ************************************/
/*
 * 当我们在写应用程序的时候,可能需要用到一些宏定义。
 */


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

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

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

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

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

	All_Function_Init();//硬件初始化
	
	while (1)
	{
		 /* 创建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;  
	}
}

//任务创建函数
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");
    
  /* 创建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任务成功!\r\n");  
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}

//Receive_Task任务主体
static void Receive1_Task(void* parameter)
{	
  BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
	uint32_t r_num;

  while (1)
  {
		if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
    {		
			/* 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,	  //退出函数的时候清除所有的bit
															(uint32_t *)&r_num,		  //保存任务通知值                    
															0);	//阻塞时间

			if( pdTRUE == xReturn )
				printf("\r\nReceive1_Task 任务通知消息为 %d !\r\n",r_num);                      
		 
			Toggle_LED_R();
		}
  }
}

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

  uint32_t send1 = 0;

  while (1)
  {
    /* KEY1 被按下 */
    if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
    {
      /* 原型:BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, 
                                      uint32_t ulValue, 
                                      eNotifyAction eAction ); 
       * eNoAction = 0,通知任务而不更新其通知值。
       * eSetBits,     设置任务通知值中的位。
       * eIncrement,   增加任务的通知值。
       * eSetvaluewithoverwrite,覆盖当前通知
       * eSetValueWithoutoverwrite 不覆盖当前通知
       * 
       * pdFAIL:当参数eAction设置为eSetValueWithoutOverwrite的时候,
       * 如果任务通知值没有更新成功就返回pdFAIL。
       * pdPASS: eAction 设置为其他选项的时候统一返回pdPASS。
      */

			send1++;

      xReturn = xTaskNotify( Receive1_Task_Handle, /*任务句柄*/
                             send1, /* 发送的数据,最大为4字节 */
                             eSetValueWithOverwrite );/*覆盖当前通知*/
      
      if( xReturn == pdPASS )
        printf("\r\nReceive1_Task_Handle 任务通知消息发送成功!\r\n");
    } 

    vTaskDelay(20);
  }
}


//初始化声明
static void All_Function_Init(void)
{
	/*
	 * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* LED 初始化 */
	LED_GPIO_Config();

	/* 串口初始化	*/
	USART_Config();

	//按键初始化
	Key_GPIO_Config();
  
}



        运行结果:

完整工程:

FreeRTOS任务通知-替代消息队列-eSetValueWithOverwrite-模拟覆盖.zip资源-CSDN文库

5.1.5  消息队列——eSetValueWithoutOverwrite

        对象任务接收任务通知,但是上一次接收到的通知值并没有取走,那么本次的通知值将不会更新,同时函数返 回 pdFALSE。

        在这种方式下,任务通知可以看成是函数 xQueueSend() 应用在队列深度为 1 的队列上的一种轻量型实现,速度更快。

        这里代码我们直接沿用上一小节的代码稍微更改一下,主要是发送端代码更改:

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

  uint32_t send1 = 0;

  while (1)
  {
    /* KEY1 被按下 */
    if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
    {
      /* 原型:BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, 
                                      uint32_t ulValue, 
                                      eNotifyAction eAction ); 
       * eNoAction = 0,通知任务而不更新其通知值。
       * eSetBits,     设置任务通知值中的位。
       * eIncrement,   增加任务的通知值。
       * eSetvaluewithoverwrite,覆盖当前通知
       * eSetValueWithoutoverwrite 不覆盖当前通知
       * 
       * pdFAIL:当参数eAction设置为eSetValueWithoutOverwrite的时候,
       * 如果任务通知值没有更新成功就返回pdFAIL。
       * pdPASS: eAction 设置为其他选项的时候统一返回pdPASS。
      */

			send1++;

      printf("\r\n按键KEY1按下,当前send1的计数为:%d \r\n",send1);

      xReturn = xTaskNotify( Receive1_Task_Handle, /*任务句柄*/
                             send1, /* 发送的数据,最大为4字节 */
                             eSetValueWithoutOverwrite );/*不覆盖当前通知*/
      
      if( xReturn == pdPASS )
        printf("Receive1_Task_Handle 任务通知消息发送成功!\r\n");
			else if(xReturn == pdFALSE)
			{
	      printf("任务通知上次通知还未被取走,请取走后再发送!\r\n");
			}
    } 

    vTaskDelay(20);
  }
}

        运行结果:

        这里我们可以看出,当我们点击一次发送,紧接着再点击一次接收,此时任务通知一发一收正常现象,但是当我们在③点击发送,然后多点击几次会发现,提示需要先取走之前的通知才能正常发送,并且我们④取走的也是③发送的通知。

完整工程:

FreeRTOS任务通知-替代消息队列-eSetValueWithoutOverwrite.zip资源-CSDN文库

5.2  xTaskNotifyAndQuery()实例演示

        xTaskNotifyAndQuery()与 xTaskNotify()很像,都是调用通用的任务通知发送函数xTaskGenericNotify() 来实现通知的发送,不同的是多了一个附加的参数 pulPreviousNotifyValue 用于回传接收任务的上一个通知值。

        这里我们就以5.1.4的函数为例进行更改,主要是对发送函数的更改:

//Send_Task任务主体
static void Send_Task(void* parameter)
{	 
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  uint32_t send1 = 0;
	uint32_t previous_value;


  while (1)
  {
    /* KEY1 被按下 */
    if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
    {
      /* 原型:BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, 
                                      uint32_t ulValue, 
                                      eNotifyAction eAction ); 
       * eNoAction = 0,通知任务而不更新其通知值。
       * eSetBits,     设置任务通知值中的位。
       * eIncrement,   增加任务的通知值。
       * eSetvaluewithoverwrite,覆盖当前通知
       * eSetValueWithoutoverwrite 不覆盖当前通知
       * 
       * pdFAIL:当参数eAction设置为eSetValueWithoutOverwrite的时候,
       * 如果任务通知值没有更新成功就返回pdFAIL。
       * pdPASS: eAction 设置为其他选项的时候统一返回pdPASS。
      */

			send1++;

      xReturn = xTaskNotifyAndQuery( Receive1_Task_Handle, /*任务句柄*/
                                     send1, /* 发送的数据,最大为4字节 */
                                     eSetValueWithOverwrite,/*覆盖当前通知*/
									 &previous_value);/*存储旧值*/
      
      if( xReturn == pdPASS )
			{
        printf("\r\nReceive1_Task_Handle 任务通知消息发送成功!\r\n");
        printf("发送的新值:%d,存储的旧值:%d\r\n",send1,previous_value);
			}
    } 

    vTaskDelay(20);
  }
}

        对于发送更改为退出时不清除通知值:

			xReturn=xTaskNotifyWait(0,			//进入函数的时候不清除任务bit
									0,	  //退出函数的时候不清除所有的bit
									(uint32_t *)&r_num,		  //保存任务通知值                    
									0);	//阻塞时间
//Receive_Task任务主体
static void Receive1_Task(void* parameter)
{	
  BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
	uint32_t r_num;

  while (1)
  {
		if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
    {		
			/* 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(0,			//进入函数的时候不清除任务bit
															0,	  //退出函数的时候不清除所有的bit
															(uint32_t *)&r_num,		  //保存任务通知值                    
															0);	//阻塞时间

			if( pdTRUE == xReturn )
				printf("\r\nReceive1_Task 任务通知消息为 %d !\r\n",r_num);                      
		 
			Toggle_LED_R();
		}
  }
}

        运行结果:

完整工程:

FreeRTOS任务通知-替代消息队列-读取上次通知值.zip资源-CSDN文库

        至此任务通知结束,其他函数也可以自己测试一下,这里因为篇幅问题不在一一举例了。

FreeRTOS菜鸟入门系列_时光の尘的博客-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

时光の尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值