FreeRTOS task notification based on STM32

Article directory

1. Introduction to Task Notification

2. Operation mechanism of task notification

3. Explanation of the function interface of task notification

1. xTaskGenericNotify()

2.xTaskNotifyGive()

3.vTaskNotifyGiveFromISR()

4.xTaskNotify()

5. xTaskNotifyFromISR()

6.xTaskNotifyAndQuery()

 7.xTaskNotifyAndQueryFromISR()

8.ulTaskNotifyTake()

9.xTaskNotifyWait()

4. Task notification experiment

1 Task notification instead of message queue

2 Task notification instead of binary semaphore

3 Task notification instead of counting semaphore

5. Experimental phenomenon


1. Introduction to Task Notification

       FreeRTOS has provided the function of task notification since version V8.2.0. Each task has a 32-bit notification value. In most cases, task notification can replace binary semaphores, counting semaphores, event groups, or A queue of length 1 (can hold a 32-bit integer or pointer value).

      Compared to the situation where queues, binary semaphores, counting semaphores, or event groups had to be created for resources that used to communicate with the FreeRTOS kernel, using task notification is obviously more flexible.

     According to the official statement of FreeRTOS, using task notification is 45% faster than unblocking tasks through ICP communication methods such as semaphores, and saves RAM memory space (using GCC compiler, -o2 optimization level), the use of task notification does not need to create queue. To use task notifications, you must set the macro definition configUSE_TASK_NOTIFICATIONS in FreeRTOSConfig.h to 1. In fact, FreeRTOS defaults to 1, so task notifications are enabled by default.

     FreeRTOS provides the following ways to send notifications to tasks:

     Send a notification to the task. If there is an unread notification, the notification value will not be overwritten.

     Send a notification to a task, directly overriding the notification value.

     Send a notification to a task, set one or more bits of the notification value, and can be used as an event group.

     Send a notification to the task and increment the notification value, which can be used as a counting semaphore. Through the reasonable use of the above task notification methods, it can replace FreeRTOS semaphores, queues, event groups, etc. in certain occasions.

      Of course, everything has pros and cons. Otherwise, why does FreeRTOS need the IPC communication mechanism of the kernel? Although the message notification process is faster and the RAM overhead is smaller, it also has the following limitations:

     Only one task can receive notification messages, because the task to receive notifications must be specified. .

     Only tasks waiting for notifications can be blocked, and tasks that send notifications will not enter the blocking state due to sending failures under any circumstances.

2. Operation mechanism of task notification

       As the name implies, task notifications are resources attached to tasks, so when tasks are created, task notifications are also initialized, and in the chapter on analyzing queues and semaphores, we know that before using queues and semaphores, we must first Create queues and semaphores for the purpose of creating queue data structures. For example, use the xQueueCreate() function to create a queue, use the xSemaphoreCreateBinary() function to create a binary semaphore, and so on. Let’s look at the task notification again. Since the data structure of the task notification is included in the task control block, as long as the task exists, the task notification data structure has been created and can be used directly, so it is very convenient to use. Task notification can send a notification to a specified task in a task, or send a notification to a specified task in an interrupt. Each task of FreeRTOS has a 32-bit notification value, and the member variable ulNotifiedValue in the task control block is the notification value. Waiting for notifications can only be done in tasks, not in interrupts. If the notification that the task is waiting for is temporarily invalid, the task will enter the blocking state according to the blocking timeout specified by the user. We can regard the task waiting for the notification as a consumer; other tasks and interrupts can send notifications to the task waiting for the notification. The task and interrupt service function of the task can be regarded as a producer. When other tasks or interrupts send task notifications to this task, after the task is notified, the task will be released from the blocking state, which is consistent with other communication mechanisms of the kernel in FreeRTOS .

3. Explanation of the function interface of task notification

1. xTaskGenericNotify()

       Let's look at the send notification API function first. There are many such functions, there are 6 of them. But careful analysis will find that they can only complete 3 kinds of operations, and each operation has two API functions, namely the version with interrupt protection and the version without interrupt protection. FreeRTOS subdivides the API into a version with interrupt protection and a version without interrupt protection in order to save the processing time of the interrupt service routine and improve performance. Through the study of the previous communication mechanism, I believe that everyone understands the style of FreeRTOS. The task notification sending function here is also expanded by using macro definitions. All functions are a macro definition, and the functions that send task notifications in tasks are all Call the xTaskGenericNotify() function to send notifications. The xTaskGenericNotify() function is a general task notification sending function. API functions that send notifications in tasks, such as xTaskNotifyGive(), xTaskNotify(), and xTaskNotifyAndQuery(), all use xTaskGenericNotify() It is a prototype, but the specified sending method is different.

2.xTaskNotifyGive()

      xTaskNotifyGive() is a macro. The macro expansion is to call the function xTaskNotify( ( xTaskToNotify ), ( 0 ), eIncrement ), that is, to send a notification to a task, and add 1 to the task notification value of the other party. This function can be used as a lightweight implementation of binary semaphore and counting semaphore, and the speed is faster. In this case, the object task should use the function ulTaskNotifyTake() instead of xTaskNotifyWait() when waiting for task notification . xTaskNotifyGive() cannot be used in interrupts, but use vTaskNotifyGiveFromISR() with interrupt protection function instead.

xTaskNotifyGive() function description 

Application example of xTaskNotifyGive() function

 static void prvTask1( void *pvParameters );
 static void prvTask2( void *pvParameters );
 
/*定义任务句柄 */
 static TaskHandle_t xTask1 = NULL, xTask2 = NULL;
 
 /* 主函数:创建两个任务,然后开始任务调度 */
 void main( void )
 {
 xTaskCreate(prvTask1, "Task1", 200, NULL, tskIDLE_PRIORITY, &xTask1);
 xTaskCreate(prvTask2, "Task2", 200, NULL, tskIDLE_PRIORITY, &xTask2);
 vTaskStartScheduler();
 }
 /*-----------------------------------------------------------*/
 
 static void prvTask1( void *pvParameters )
 {
 for ( ;; ) {
 /* 向 prvTask2()发送一个任务通知,让其退出阻塞状态 */
 xTaskNotifyGive( xTask2 );
 
 /* 阻塞在 prvTask2()的任务通知上
 如果没有收到通知,则一直等待*/
 ulTaskNotifyTake( pdTRUE, portMAX_DELAY );
 }
 }
 /*-----------------------------------------------------------*/
 
 static void prvTask2( void *pvParameters )
 {
 for ( ;; ) {
 /* 阻塞在 prvTask1()的任务通知上
 如果没有收到通知,则一直等待*/
 ulTaskNotifyTake( pdTRUE, portMAX_DELAY );
 
 /* 向 prvTask1()发送一个任务通知,让其退出阻塞状态 */
 xTaskNotifyGive( xTask1 );
 }
}

3.vTaskNotifyGiveFromISR()

      vTaskNotifyGiveFromISR() is an interrupt-protected version of vTaskNotifyGive(). It is used to send a task notification to the specified task in an interrupt, and update the task notification value of the other party (add 1 operation). In some scenarios, it can replace the semaphore operation, because the two notifications do not have a notification value.

vTaskNotifyGiveFromISR() function description 

      From the above function description, we probably know the function of the vTaskNotifyGiveFromISR() function. Each call to this function will increase the notification value of the task. Whether the task returns a value greater than zero through the receiving function determines whether the notification has been obtained. The task notification value is initialized to 0, ( If compared with the semaphore), the corresponding semaphore is invalid. When the interrupt calls the vTaskNotifyGiveFromISR() notification function to the task, the notification value of the task is increased to make it greater than zero, so that the notification value indicated by it becomes valid, and the task obtains a valid notification value and will be restored.

Application example of vTaskNotifyGiveFromISR() function

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

4.xTaskNotify()

      Each task in FreeRTOS has a 32-bit variable for task notification, which is initialized to 0 when the task is created. The 32-bit notification value is defined in the task control block TCB, see code list 22-6 for details. xTaskNotify() is used to send an event directly to another task in a task, and the task that receives the task notification may be unlocked. If you want to use task notifications to implement binary semaphores and counting semaphores, you should use the simpler function xTaskNotifyGive() instead of xTaskNotify(). The xTaskNotify() function will specify a notification value when sending task notifications , and the user can specify how the notification value is sent. Note: This function cannot be used in the interrupt, but uses the version function xTaskNotifyFromISR() of the specific interrupt protection function.

xTaskNotify() function description

 Application example of xTaskNotify() function

 /* 设置任务 xTask1Handle 的任务通知值的位 8 为 1*/
 xTaskNotify( xTask1Handle, ( 1UL << 8UL ), eSetBits );
 
 /* 向任务 xTask2Handle 发送一个任务通知
 有可能会解除该任务的阻塞状态,但是并不会更新该任务自身的任务通知值 */
 xTaskNotify( xTask2Handle, 0, eNoAction );
 
 
 /* 向任务 xTask3Handle 发送一个任务通知
 并把该任务自身的任务通知值更新为 0x50
 即使该任务的上一次的任务通知都没有读取的情况下
 即覆盖写 */
 xTaskNotify( xTask3Handle, 0x50, eSetValueWithOverwrite );
 
 /* 向任务 xTask4Handle 发送一个任务通知
 并把该任务自身的任务通知值更新为 0xfff
 但是并不会覆盖该任务之前接收到的任务通知值*/
 if(xTaskNotify(xTask4Handle,0xfff,eSetValueWithoutOverwrite)==pdPASS )
 {
 /* 任务 xTask4Handle 的任务通知值已经更新 */
 } else
 {
 /* 任务 xTask4Handle 的任务通知值没有更新
 即上一次的通知值还没有被取走*/
 }

5. xTaskNotifyFromISR()

       xTaskNotifyFromISR() is the interrupt protection version of xTaskNotify(). The function that actually works is the interrupt sending task notification general function xTaskGenericNotifyFromISR(), and xTaskNotifyFromISR() is a macro definition used to send a task notification to a specified task in an interrupt , the task notification has a notification value and the user can specify the sending method of the notification, and does not return the notification value of the previous task.

xTaskNotifyFromISR() function description

Send task notification general function xTaskGenericNotifyFromISR() in interrupt 

xTaskGenericNotifyFromISR() is a general function for sending task notifications in interrupts. Functions such as xTaskNotifyFromISR() and xTaskNotifyAndQueryFromISR() are based on it and implemented in the way of macro definition.

Example of using xTaskNotifyFromISR()

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

6.xTaskNotifyAndQuery()

xTaskNotifyAndQuery() is very similar to xTaskNotify(), both call the general task notification sending function xTaskGenericNotify() to realize the sending of notifications, the difference is that there is an additional parameter pulPreviousNotifyValue used to return the previous notification value of the receiving task, The xTaskNotifyAndQuery() function cannot be used in interrupts, but must be replaced by xTaskNotifyAndQuery()FromISR with interrupt protection.

xTaskNotifyAndQuery() function description

 7.xTaskNotifyAndQueryFromISR()

        xTaskNotifyAndQueryFromISR() is an interrupted version of xTaskNotifyAndQuery (), used to send a task notification to the specified task, and return the last notification value of the target task. This function is also a macro definition, and xTaskGenericNotifyFromISR() is what actually sends the notification.

xTaskNotifyAndQueryFromISR() function description

 Get task notification function

      Since there are so many functions for sending tasks in FreeRTOS, how do tasks get notifications? As we said, task notifications can replace semaphores, message queues, events, etc. in some scenarios. The function of getting task notification can only be used in the task, there is no version with interrupt protection, so there are only two API functions: ulTaskNotifyTake() and xTaskNotifyWait(). The former is specially designed to replace the binary semaphore and counting semaphore, and it is used in conjunction with the send notification API functions xTaskNotifyGive() and vTaskNotifyGiveFromISR(); the latter is a full-featured version of the waiting notification, which can be implemented according to different parameters. Magnitude binary semaphores, counting semaphores, event groups, and queues of length one.

      All API functions for obtaining task notifications have a specified blocking timeout parameter, which is used to specify the blocking time of the task when the task is blocked due to waiting for the notification. The mechanism is the same.

8.ulTaskNotifyTake()

      ulTaskNotifyTake() is faster as a lightweight implementation of binary semaphores and counting semaphores. If the function xSemaphoreTake() is used to obtain the semaphore in FreeRTOS, you can try to use the function ulTaskNotifyTake() instead. For this function, the task notification value is 0, and the corresponding semaphore is invalid. If the task is set to block waiting, the task is blocked and suspended. When other tasks or interrupts send the notification value so that it is not 0, the notification becomes valid, and the task waiting for the notification will get the notification, and choose to clear the notification value or execute according to the first parameter xClearCountOnExit passed by the user when exiting Subtract one operation. There are two methods for xTaskNotifyTake() to process the notification value of the task when exiting. One is to clear the notification value when the function exits. This method is suitable for implementing binary semaphores; the other is to exit the function. Decrease the notification value by 1, this method is suitable for realizing the counting semaphore.

       When a task uses its own task notification value as a binary semaphore or counting semaphore, other tasks should use the function xTaskNotifyGive() or xTaskNotify( ( xTaskToNotify ), ( 0 ), eIncrement ) to send the semaphore to it. If they are in an interrupt, their interrupt version functions should be used.

ulTaskNotifyTake() function description

 Example of ulTaskNotifyTake() function application

 /* 中断服务程序:向一个任务发送任务通知 */
 void vANInterruptHandler( void )
 {
 BaseType_t xHigherPriorityTaskWoken;
 
 /* 清除中断 */
 prvClearInterruptSource();
 
 /* xHigherPriorityTaskWoken 在使用之前必须设置为 pdFALSE
 如果调用 vTaskNotifyGiveFromISR()会解除 vHandlingTask 任务的阻塞状态,
 并且 vHandlingTask 任务的优先级高于当前处于运行状态的任务,
 则 xHigherPriorityTaskWoken 将会自动被设置为 pdTRUE */
 xHigherPriorityTaskWoken = pdFALSE;
 
 /* 发送任务通知,并解锁阻塞在该任务通知下的任务 */
 vTaskNotifyGiveFromISR( xHandlingTask, &xHigherPriorityTaskWoken );
 
 /* 如果被解锁的任务优先级比当前运行的任务的优先级高
 则在中断退出前执行一次上下文切换,在中断退出后去执行
 刚刚被唤醒的优先级更高的任务*/
 portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
 }
 /*-----------------------------------------------------------*/
 /* 任务:阻塞在一个任务通知上 */
 void vHandlingTask( void *pvParameters )
 {
 BaseType_t xEvent;
 
 for ( ;; ) {
 /* 一直阻塞(没有时间限制,所以没有必要检测函数的返回值)
 这里 RTOS 的任务通知值被用作二值信号量,所以在函数退出
 时,任务通知值要被清 0 。要注意的是真正的应用程序不应该
 无限期的阻塞*/
 ulTaskNotifyTake( pdTRUE, /* 在退出前清 0 任务通知值 */
 portMAX_DELAY ); /* 无限阻塞 */
 
 /* RTOS 任务通知被当作二值信号量使用
 当处理完所有的事情后继续等待下一个任务通知*/
 do {
 xEvent = xQueryPeripheral();
 
 if ( xEvent != NO_MORE_EVENTS ) {
 vProcessPeripheralEvent( xEvent );
 }
 
 } while ( xEvent != NO_MORE_EVENTS );
 }
 }

9.xTaskNotifyWait()

The xTaskNotifyWait() function is used to implement the full-featured version of the waiting task notification. According to the parameters specified by the user, it can be flexibly used to implement lightweight message queue queues, binary semaphores, counting semaphores and event group functions. And with a timeout wait.

  Example of using xTaskNotifyWait() function

/* 这个任务展示使用任务通知值的位来传递不同的事件
 这在某些情况下可以代替事件标志组。*/
 void vAnEventProcessingTask( void *pvParameters )
 {
  uint32_t ulNotifiedValue;
 
 for ( ;; ) {
 /* 等待任务通知,无限期阻塞(没有超时,所以没有必要检查函数返回值)
 这个任务的任务通知值的位由标志事件发生的任务或者中断来设置*/
 xTaskNotifyWait( 0x00, /* 在进入的时候不清除通知值的任何位 */
 ULONG_MAX, /* 在退出的时候复位通知值为 0 */
 &ulNotifiedValue, /* 任务通知值传递到变量
 ulNotifiedValue 中*/
 portMAX_DELAY ); /* 无限期等待 */
 
 
 /* 根据任务通知值里面的各个位的值处理事情 */
 if ( ( ulNotifiedValue & 0x01 ) != 0 ) {
 /* 位 0 被置 1 */
 prvProcessBit0Event();
 }
 
 if ( ( ulNotifiedValue & 0x02 ) != 0 ) {
 /* 位 1 被置 1 */
 prvProcessBit1Event();
 }
 
 if ( ( ulNotifiedValue & 0x04 ) != 0 ) {
 /* 位 2 被置 1 */
 prvProcessBit2Event();
 }
 
 /* ... 等等 */
  }
 }

4. Task notification experiment

1 Task notification instead of message queue

       Task notification instead of message queue creates three tasks in FreeRTOS, two of which are used to receive task notifications, and the other task sends task notifications. The three tasks run independently. The sending message task is to send a message notification by detecting the pressing of the button. The other two tasks get the message notification and wait for the message until there is no notification available in the task notification. Once the message notification is obtained, the Print the message in the serial debugging assistant.

/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
/* 开发板硬件bsp头文件 */
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.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任务句柄 */

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


/******************************* 全局变量声明 ************************************/
/*
 * 当我们在写应用程序的时候,可能需要用到一些全局变量。
 */


/******************************* 宏定义 ************************************/
/*
 * 当我们在写应用程序的时候,可能需要用到一些宏定义。
 */
#define  USE_CHAR  0  /* 测试字符串的时候配置为 1 ,测试变量配置为 0  */

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

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

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

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

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


/***********************************************************************
  * @ 函数名  : AppTaskCreate
  * @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
  * @ 参数    : 无  
  * @ 返回值  : 无
  **********************************************************************/
static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  taskENTER_CRITICAL();           //进入临界区

  /* 创建Receive1_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Receive1_Task, /* 任务入口函数 */
                        (const char*    )"Receive1_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,	/* 任务入口函数参数 */
                        (UBaseType_t    )2,	    /* 任务的优先级 */
                        (TaskHandle_t*  )&Receive1_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建Receive1_Task任务成功!\r\n");
  
  /* 创建Receive2_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Receive2_Task, /* 任务入口函数 */
                        (const char*    )"Receive2_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,	/* 任务入口函数参数 */
                        (UBaseType_t    )3,	    /* 任务的优先级 */
                        (TaskHandle_t*  )&Receive2_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建Receive2_Task任务成功!\r\n");
  
  /* 创建Send_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Send_Task,  /* 任务入口函数 */
                        (const char*    )"Send_Task",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )4, /* 任务的优先级 */
                        (TaskHandle_t*  )&Send_Task_Handle);/* 任务控制块指针 */ 
  if(pdPASS == xReturn)
    printf("创建Send_Task任务成功!\n\n");
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}



/**********************************************************************
  * @ 函数名  : Receive_Task
  * @ 功能说明: Receive_Task任务主体
  * @ 参数    :   
  * @ 返回值  : 无
  ********************************************************************/
static void Receive1_Task(void* parameter)
{	
  BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
#if USE_CHAR
  char *r_char;
#else
  uint32_t r_num;
#endif
  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
#if USE_CHAR
                            (uint32_t *)&r_char,		  //保存任务通知值
#else
                            &r_num,		  //保存任务通知值
#endif                        
                            portMAX_DELAY);	//阻塞时间
    if( pdTRUE == xReturn )
#if USE_CHAR
      printf("Receive1_Task 任务通知消息为 %s \n",r_char);                      
#else
      printf("Receive1_Task 任务通知消息为 %d \n",r_num);                      
#endif  
     
    
		LED1_TOGGLE;
  }
}

/**********************************************************************
  * @ 函数名  : Receive_Task
  * @ 功能说明: Receive_Task任务主体
  * @ 参数    :   
  * @ 返回值  : 无
  ********************************************************************/
static void Receive2_Task(void* parameter)
{	
  BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
#if USE_CHAR
  char *r_char;
#else
  uint32_t r_num;
#endif
  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
#if USE_CHAR
                            (uint32_t *)&r_char,		  //保存任务通知值
#else
                            &r_num,		  //保存任务通知值
#endif                        
                            portMAX_DELAY);	//阻塞时间
    if( pdTRUE == xReturn )
#if USE_CHAR
      printf("Receive2_Task 任务通知消息为 %s \n",r_char);                      
#else
      printf("Receive2_Task 任务通知消息为 %d \n",r_num);                      
#endif  
		LED2_TOGGLE;
  }
}

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

  
  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, /*任务句柄*/
#if USE_CHAR 
                             (uint32_t)&test_str1, /* 发送的数据,最大为4字节 */
#else
                              send1, /* 发送的数据,最大为4字节 */
#endif
                             eSetValueWithOverwrite );/*覆盖当前通知*/
      
      if( xReturn == pdPASS )
        printf("Receive1_Task_Handle 任务通知消息发送成功!\r\n");
    } 
    /* KEY2 被按下 */
    if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
    {
      xReturn = xTaskNotify( Receive2_Task_Handle, /*任务句柄*/
#if USE_CHAR 
                             (uint32_t)&test_str2, /* 发送的数据,最大为4字节 */
#else
                              send2, /* 发送的数据,最大为4字节 */
#endif
                             eSetValueWithOverwrite );/*覆盖当前通知*/
      /* 此函数只会返回pdPASS */
      if( xReturn == pdPASS )
        printf("Receive2_Task_Handle 任务通知消息发送成功!\r\n");
    }
    vTaskDelay(20);
  }
}
/***********************************************************************
  * @ 函数名  : BSP_Init
  * @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
  * @ 参数    :   
  * @ 返回值  : 无
  *********************************************************************/
static void BSP_Init(void)
{
	/*
	 * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* LED 初始化 */
	LED_GPIO_Config();

	/* 串口初始化	*/
	USART_Config();
  
  /* 按键初始化	*/
  Key_GPIO_Config();

}

/********************************END OF FILE****************************/

2 Task notification instead of binary semaphore

      Task notification instead of message queue creates three tasks in FreeRTOS, two of which are used to receive task notifications, and the other task sends task notifications. The three tasks run independently. The sending notification task is to send a notification by detecting the pressing of the button. The other two tasks get the notification and wait for the task notification until there is no notification available in the task notification. After the notification is obtained, the notification will be sent. The value is cleared to 0. This is to replace the binary semaphore. If the task synchronization is successful, it will continue to execute, and then print out the running information in the serial port debugging assistant.

 
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
/* 开发板硬件bsp头文件 */
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"
/**************************** 任务句柄 ********************************/
/* 
 * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
 * 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
 * 这个句柄可以为NULL。
 */
static TaskHandle_t AppTaskCreate_Handle = NULL;/* 创建任务句柄 */
static TaskHandle_t Receive1_Task_Handle = NULL;/* Receive1_Task任务句柄 */
static TaskHandle_t Receive2_Task_Handle = NULL;/* Receive2_Task任务句柄 */
static TaskHandle_t Send_Task_Handle = NULL;/* Send_Task任务句柄 */

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


/******************************* 全局变量声明 ************************************/
/*
 * 当我们在写应用程序的时候,可能需要用到一些全局变量。
 */


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


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

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

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

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

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


/***********************************************************************
  * @ 函数名  : AppTaskCreate
  * @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
  * @ 参数    : 无  
  * @ 返回值  : 无
  **********************************************************************/
static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  taskENTER_CRITICAL();           //进入临界区

  /* 创建Receive1_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Receive1_Task, /* 任务入口函数 */
                        (const char*    )"Receive1_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,	/* 任务入口函数参数 */
                        (UBaseType_t    )2,	    /* 任务的优先级 */
                        (TaskHandle_t*  )&Receive1_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建Receive1_Task任务成功!\r\n");
  
  /* 创建Receive2_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Receive2_Task, /* 任务入口函数 */
                        (const char*    )"Receive2_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,	/* 任务入口函数参数 */
                        (UBaseType_t    )3,	    /* 任务的优先级 */
                        (TaskHandle_t*  )&Receive2_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建Receive2_Task任务成功!\r\n");
  
  /* 创建Send_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Send_Task,  /* 任务入口函数 */
                        (const char*    )"Send_Task",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )4, /* 任务的优先级 */
                        (TaskHandle_t*  )&Send_Task_Handle);/* 任务控制块指针 */ 
  if(pdPASS == xReturn)
    printf("创建Send_Task任务成功!\n\n");
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}



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

/**********************************************************************
  * @ 函数名  : Receive_Task
  * @ 功能说明: 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 任务通知获取成功!\n\n");
    
		LED2_TOGGLE;
  }
}

/**********************************************************************
  * @ 函数名  : Send_Task
  * @ 功能说明: Send_Task任务主体
  * @ 参数    :   
  * @ 返回值  : 无
  ********************************************************************/
static void Send_Task(void* parameter)
{	 
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  while (1)
  {
    /* KEY1 被按下 */
    if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
    {
      /* 原型:BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); */
      xReturn = xTaskNotifyGive(Receive1_Task_Handle);
      /* 此函数只会返回pdPASS */
      if( xReturn == pdTRUE )
        printf("Receive1_Task_Handle 任务通知发送成功!\r\n");
    } 
    /* KEY2 被按下 */
    if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
    {
      xReturn = xTaskNotifyGive(Receive2_Task_Handle);
      /* 此函数只会返回pdPASS */
      if( xReturn == pdPASS )
        printf("Receive2_Task_Handle 任务通知发送成功!\r\n");
    }
    vTaskDelay(20);
  }
}
/***********************************************************************
  * @ 函数名  : BSP_Init
  * @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
  * @ 参数    :   
  * @ 返回值  : 无
  *********************************************************************/
static void BSP_Init(void)
{
	/*
	 * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* LED 初始化 */
	LED_GPIO_Config();

	/* 串口初始化	*/
	USART_Config();
  
  /* 按键初始化	*/
  Key_GPIO_Config();

}

/********************************END OF FILE****************************/

3 Task notification instead of counting semaphore

      The task notification instead of the counting semaphore is modified based on the counting semaphore experiment, simulating the operation of the parking lot. And two tasks are created in FreeRTOS: one is to obtain task notification, and the other is to send task notification. The two tasks run independently. The task of obtaining notification is obtained by pressing the KEY1 button to simulate the parking operation of the parking lot. The waiting time is 0; the task of sending the notification is to send the notification by detecting the pressing of the KEY2 button, simulating the operation of picking up the car in the parking lot, and output the corresponding information in the serial port debugging assistant.

 
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/* 开发板硬件bsp头文件 */
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"
/**************************** 任务句柄 ********************************/
/* 
 * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
 * 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
 * 这个句柄可以为NULL。
 */
static TaskHandle_t AppTaskCreate_Handle = NULL;/* 创建任务句柄 */
static TaskHandle_t Take_Task_Handle = NULL;/* Take_Task任务句柄 */
static TaskHandle_t Give_Task_Handle = NULL;/* Give_Task任务句柄 */

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

/******************************* 全局变量声明 ************************************/
/*
 * 当我们在写应用程序的时候,可能需要用到一些全局变量。
 */


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


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

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

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

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


/***********************************************************************
  * @ 函数名  : AppTaskCreate
  * @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
  * @ 参数    : 无  
  * @ 返回值  : 无
  **********************************************************************/
static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  taskENTER_CRITICAL();           //进入临界区

  /* 创建Take_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Take_Task, /* 任务入口函数 */
                        (const char*    )"Take_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,	/* 任务入口函数参数 */
                        (UBaseType_t    )2,	    /* 任务的优先级 */
                        (TaskHandle_t*  )&Take_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建Take_Task任务成功!\r\n");
  
  /* 创建Give_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Give_Task,  /* 任务入口函数 */
                        (const char*    )"Give_Task",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )3, /* 任务的优先级 */
                        (TaskHandle_t*  )&Give_Task_Handle);/* 任务控制块指针 */ 
  if(pdPASS == xReturn)
    printf("创建Give_Task任务成功!\n\n");
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}



/**********************************************************************
  * @ 函数名  : Take_Task
  * @ 功能说明: Take_Task任务主体
  * @ 参数    :   
  * @ 返回值  : 无
  ********************************************************************/
static void Take_Task(void* parameter)
{	
  uint32_t take_num = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
  /* 任务都是一个无限循环,不能返回 */
  while (1)
  {
    //如果KEY1被单击
		if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )       
		{
      /* uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait ); 
       * xClearCountOnExit:pdTRUE 在退出函数的时候任务任务通知值清零,类似二值信号量
       * pdFALSE 在退出函数ulTaskNotifyTakeO的时候任务通知值减一,类似计数型信号量。
       */
      //获取任务通知 ,没获取到则不等待
      take_num=ulTaskNotifyTake(pdFALSE,0);//
      if(take_num > 0)
        printf( "KEY1被按下,成功申请到停车位,当前车位为 %d \n", take_num - 1);
			else
        printf( "KEY1被按下,车位已经没有了,请按KEY2释放车位\n" );  
		}
		vTaskDelay(20);     //每20ms扫描一次		
  }
}

/**********************************************************************
  * @ 函数名  : Give_Task
  * @ 功能说明: Give_Task任务主体
  * @ 参数    :   
  * @ 返回值  : 无
  ********************************************************************/
static void Give_Task(void* parameter)
{	 
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  /* 任务都是一个无限循环,不能返回 */
  while (1)
  {
    //如果KEY2被单击
		if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )       
		{
      /* 原型:BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); */
			/* 释放一个任务通知 */
      xTaskNotifyGive(Take_Task_Handle);//发送任务通知
      /* 此函数只会返回pdPASS */
			if ( pdPASS == xReturn ) 
				printf( "KEY2被按下,释放1个停车位。\n" );
		}
		vTaskDelay(20);     //每20ms扫描一次	
  }
}
/***********************************************************************
  * @ 函数名  : BSP_Init
  * @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
  * @ 参数    :   
  * @ 返回值  : 无
  *********************************************************************/
static void BSP_Init(void)
{
	/*
	 * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* LED 初始化 */
	LED_GPIO_Config();

	/* 串口初始化	*/
	USART_Config();
  
  /* 按键初始化	*/
  Key_GPIO_Config();

}

/********************************END OF FILE****************************/

5. Experimental phenomenon

1. Task notification instead of message queue

 2 Task notification instead of binary semaphore

 3 Task notification instead of counting semaphore

Guess you like

Origin blog.csdn.net/qq_61672347/article/details/125651530