前言
本篇文章将带大家学习任务通知的概念和使用方法。
一、什么是任务通知
FreeRTOS中的任务通知(Task Notification)是一种轻量级的同步机制,允许一个任务通知另一个任务已发生的事件或条件。这对于多任务系统中的协作和同步非常有用。以下是有关FreeRTOS任务通知的详细讲解:
任务通知的作用:
任务通知的主要作用是允许一个任务通知其他任务已发生的事件,而无需使用更重的互斥锁或信号量。这可以用于线程间的通信和同步,以及处理任务之间的依赖关系。
通知值(Notification Value):
任务通知包括一个32位的通知值,用于传递信息。通知值可以是整数或位掩码,具体的含义由应用程序自行定义。任务可以等待特定的通知值或位掩码,以便在通知发生时采取相应的行动。
二、任务通知和队列,信号量的区别
任务通知、队列和信号量是FreeRTOS中用于任务间通信和同步的不同机制,它们有不同的特点和适用场景:
1.任务通知(Task Notification):
用途:任务通知主要用于任务之间的事件通知和同步,一个任务向其他任务发送通知,以表明某些事件已发生。
特点:轻量级、高效,通常用于一对一或一对多的任务通信。
通信方式:通知是无数据的,只包含一个32位的通知值,任务可以等待特定的通知值。
适用场景:适用于任务之间的事件通知、依赖关系、同步等情况,以及需要高效且快速的通信。
使用任务通知时发送方可以直接将发送信息给接收方,不需要通过中间的结构体对象(信号量,队列结构体)。
2.队列(Queue):
用途:队列用于任务之间的数据传递,允许一个任务发送数据给另一个任务。
特点:队列是有缓冲区的,可以传输多个数据元素,支持FIFO(先进先出)顺序。
通信方式:队列是带数据的通信机制,任务可以发送和接收数据。
适用场景:适用于需要任务之间传递数据的情况,如生产者-消费者问题、数据采集等。
3.信号量(Semaphore):
用途:信号量用于控制对共享资源的访问,允许任务对资源的使用进行同步和互斥。
特点:信号量通常用于资源保护和互斥访问,可以是二进制信号量(互斥锁)或计数信号量(资源计数)。
通信方式:信号量通常用于任务之间互斥,以确保只有一个任务可以访问共享资源。
适用场景:适用于共享资源的访问控制、互斥操作等情况,如保护共享内存、硬件设备等。
使用队列,信号量时都需要创建出通信对象结构体,通过这个结构体进行通信。
总的来说,任务通知适用于事件通知和轻量级的同步,队列适用于任务之间的数据传递,而信号量适用于资源访问的同步和互斥。在选择合适的通信和同步机制时,应根据具体需求和任务之间的关系来决定使用哪种机制。有时,这些机制也可以结合使用,以满足更复杂的任务间通信和同步需求。
三、任务通知的优点和缺点
1.优点
1.轻量级和高效: 任务通知是一种轻量级的通信机制,它不需要大量的内存和处理时间来维护,因此非常高效。
2.适用于一对多通信: 任务通知适用于一对多的任务通信,一个任务可以通知多个等待通知的任务,这在某些场景下非常有用。
3.实时性强: 任务通知可以提供较低的延迟,因为一旦通知被发送,接收通知的任务可以立即响应。
4.支持不同类型的通知: 任务通知可以发送不同类型的通知,任务可以等待特定的通知类型。
5.无需额外的资源: 与消息队列等机制不同,任务通知不需要为数据缓冲区分配额外的内存,因此它更节省资源。
2.缺点
1.无数据传递: 任务通知本身不支持数据传递,只能传递一个32位的通知值。如果需要传递数据,你可能需要结合其他机制来实现。
2.适用性有限: 任务通知更适用于简单的事件通知和同步需求,对于复杂的数据交换和同步需求,可能需要使用其他机制,如消息队列或信号量。
3.不适用于多生产者-多消费者问题: 任务通知通常不适合解决多生产者和多消费者问题,因为它不提供数据缓冲区来处理多个生产者和消费者之间的数据共享。
4.不适合长期阻塞: 任务通知通常用于短期同步,如果任务需要长期等待,其他机制如消息队列可能更合适。
总的来说,任务通知是一种非常高效的任务间通信机制,适用于简单的事件通知和同步需求,但对于复杂的数据传递和同步问题,可能需要结合其他FreeRTOS机制来实现。选择合适的通信机制应根据具体的应用需求来决定。
四、任务状态和通知值
每个任务都有一个结构体: TCB(Task Control Block),里面有2个成员。
一个是uint8 t类型,用来表示通知状态。
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
ucNotifyState[] 数组:
类型:volatile uint8_t
作用:通常用于存储任务通知的状态。
FreeRTOS允许任务等待多个通知。这个数组可能用于记录每个任务是否已经接收到通知,或者通知的处理状态。每个元素可能对应一个任务的通知状态。
任务通知的三种状态:
#define taskNOT_WAITING_NOTIFICATION ( ( uint8_t ) 0 )
含义:任务处于未等待通知的状态。
初始状态或者任务已经完成了对通知的等待,准备进入下一个等待通知的周期。
#define taskWAITING_NOTIFICATION ( ( uint8_t ) 1 )
含义:任务正在等待通知的状态。
当任务调用 ulTaskNotifyTake 等待通知时,它的状态将变为等待通知状态。任务会一直保持在这个状态,直到它收到通知或者等待超时。
#define taskNOTIFICATION_RECEIVED ( ( uint8_t ) 2 )
含义:任务已经收到通知的状态。
当任务成功接收到通知时,其状态将从等待通知状态切换到通知已接收状态。任务可以通过调用 ulTaskNotifyTake 函数获取通知的值,并执行相应的操作。
一个是uint32 t类型,用来表示通知值。
volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
ulNotifiedValue[] 数组:
类型:volatile uint32_t
作用:通常用于存储任务接收到的通知值。
每个任务都可以使用 ulTaskNotifyTake 函数等待通知,并在接收到通知时获得相应的值。这个数组可能被设计为记录多个任务接收到的通知值。数组的每个元素对应一个任务。
五、任务通知相关的函数
发出通知
发出通知有两个函数可以使用,分别是xTaskNotifyGive和xTaskNotify。
xTaskNotifyGive(TaskHandle_t xTaskToNotify);
功能:向指定的任务发送一个通知。
参数:xTaskToNotify 是要通知的任务的句柄(handle)。
返回值:无。
详细说明:这个函数用于向另一个任务发送通知。通知的具体内容可以是一个比特位或者一个32位的值,取决于任务通知的类型。被通知的任务可以通过 ulTaskNotifyTake 函数获取通知的值。
xTaskNotify(TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction)
功能:向指定的任务发送一个通知,可以指定通知的值和通知的行为。
参数:
xTaskToNotify:要通知的任务的句柄(handle)。
ulValue:通知的值,可以是一个比特位或者32位的值。
eAction:通知的行为,例如覆盖之前的通知值或者增加到之前的通知值。
返回值:无。
详细说明:这个函数允许发送带有值的通知,并且可以选择通知的行为。同样,被通知的任务可以通过 ulTaskNotifyTake 函数获取通知的值。
xTaskNotifyGive和xTaskNotify区别:
xTaskNotifyGive:
用途:向指定的任务发送一个通知,但不提供通知的具体值。
示例用法:xTaskNotifyGive(xTaskHandle);
适用情况:当通知的具体值不关键,只是为了触发目标任务执行某个操作时,使用此函数。
xTaskNotify:
用途:向指定的任务发送一个通知,可以指定通知的具体值和通知的行为。
示例用法:xTaskNotify(xTaskHandle, ulValue, eAction);
适用情况:当通知的具体值对于目标任务的操作非常重要时,或者需要更精细的控制通知的行为时,使用此函数。
取出通知
取出通知有两个函数可以使用,分别是ulTaskNotifyTake和xTaskNotifyWait。
ulTaskNotifyTake(BaseType_t xClearCountOnExit, TickType_t xTicksToWait);
功能:等待接收任务通知。
参数:
xClearCountOnExit:标志是否在任务等待通知时清零通知计数。
xTicksToWait:等待通知的超时时间。
返回值:接收到的通知的值。
详细说明:任务调用这个函数等待接收通知。如果在超时时间内收到通知,任务将返回通知的值;否则,返回0。通知的具体值和行为由前面的 xTaskNotify 函数设置。
xTaskNotifyWait(uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait);
功能:等待接收任务通知,并且可以设置在进入和退出时要清零的通知比特位。
参数:
ulBitsToClearOnEntry:进入等待通知时要清零的通知比特位。
ulBitsToClearOnExit:退出等待通知时要清零的通知比特位。
pulNotificationValue:指向接收到的通知值的指针。
xTicksToWait:等待通知的超时时间。
返回值:如果在超时时间内收到通知,返回 pdTRUE;否则,返回 pdFALSE。
详细说明:这个函数允许更加细粒度的控制,可以在进入和退出等待通知的时候清零通知的比特位。同样,被通知的任务可以通过 ulTaskNotifyTake 函数获取通知的值。
ulTaskNotifyTake和xTaskNotifyWait区别:
ulTaskNotifyTake:
用途:等待接收任务通知,返回接收到的通知的值。
示例用法:ulNotifiedValue = ulTaskNotifyTake(pdFALSE, xTicksToWait);
适用情况:当只需等待通知并获取其值时,使用此函数。通常用于轻量级的通知接收。
xTaskNotifyWait:
用途:等待接收任务通知,可以设置在进入和退出时要清零的通知比特位,返回是否在超时时间内收到通知。
示例用法:xResult = xTaskNotifyWait(ulBitsToClearOnEntry, ulBitsToClearOnExit, &ulNotificationValue, xTicksToWait);
适用情况:当需要更灵活的通知等待,并且需要在等待前后清零通知比特位时,使用此函数。通常用于更复杂的通知场景。
六、任务通知具体使用
1.实现轻量级信号量
xTaskNotifyGive函数可以让通知值加1,ulTaskNotifyTake可以让通知值减1,而且可以设置ulTaskNotifyTake的第一个参数来决定,是否清除通知值,设置为pdTURE则清除通知值(实现二进制信号量),设置为pdFALSE则不清除通知值(实现计数型信号量)。
二进制信号量
void Task1Function(void * param) { volatile int i = 0; while (1) { for (i = 0; i < 10000; i++) sum++; //printf("1"); for (i = 0; i < 10; i++) { // xSemaphoreGive(xSemCalc); xTaskNotifyGive(xHandleTask2); } vTaskDelete(NULL); } } void Task2Function(void * param) { int i = 0; int val; while (1) { //if (flagCalcEnd) flagCalcEnd = 0; //xSemaphoreTake(xSemCalc, portMAX_DELAY); val = ulTaskNotifyTake(pdTURE, portMAX_DELAY); flagCalcEnd = 1; printf("sum = %d, NotifyVal = %d, i = %d\r\n", sum, val, i++); } }
计数型信号量
void Task1Function(void * param) { volatile int i = 0; while (1) { for (i = 0; i < 10000; i++) sum++; for (i = 0; i < 10; i++) { xTaskNotifyGive(xHandleTask2); } vTaskDelete(NULL); } } void Task2Function(void * param) { int i = 0; int val; while (1) { val = ulTaskNotifyTake(pdFALSE, portMAX_DELAY); printf("sum = %d, NotifyVal = %d, i = %d\r\n", sum, val, i++); } }
2.实现轻量级队列
xTaskNotify和xTaskNotifyWait可以实现轻量级队列,用于传输一个uint32_t类型的数值。
void Task1Function(void * param) { volatile int i = 0; while (1) { for (i = 0; i < 10000; i++) sum++; for (i = 0; i < 10; i++) { xTaskNotify(xHandleTask2, sum, eSetValueWithOverwrite); sum++; } vTaskDelete(NULL); } } void Task2Function(void * param) { int val; int i = 0; while (1) { xTaskNotifyWait(0, 0, &val, portMAX_DELAY); printf("sum = %d, i = %d\r\n", val, i++); } }
总结
本篇文章就讲解到这里,下篇文章继续讲解FreeRTOS中的内容。