在FreeRTOS中,任务通知、队列、信号量和事件组都是用于任务间通信和同步的机制,但它们各自具有不同的特性和适用场景。
任务通知的主要优势在于其高效性和明确性。使用任务通知发送事件或数据给某个任务时,效率更高,且可以明确指定通知哪个任务。此外,任务通知还更节省内存,因为使用它无需额外创建结构体。然而,任务通知的限制在于它不能向中断服务程序(ISR)发送数据,因为ISR并没有任务结构体。同时,任务通知发送的数据只能由指定的任务独享。
队列的特性在于它遵循先进先出(FIFO)的原则,具有有限容量,且其操作可以是阻塞的。队列常用于在任务之间传递数据,例如一个任务不断向队列中写入数据,另一个任务不断从队列中读取数据。
信号量主要用于实现任务之间的互斥和同步。二值信号量类似于互斥锁,用于确保在任何时刻只有一个任务能够访问关键资源。计数信号量则允许有更多状态,适用于多个任务对同一资源的并发访问。互斥信号量是二值信号量的一种特殊形式,用于确保在任何时刻只有一个任务能够执行临界区代码。
事件组则主要用于任务间的同步,可以简单理解为一个有多个bit位的整数,每一位都代表一个不同的事件。事件组具有“广播”作用,当某个事件发生时,会唤醒所有符合条件的任务。事件组只用于同步,不提供数据传输功能,且支持事件等待超时机制。
综上所述,任务通知、队列、信号量和事件组在FreeRTOS中各自扮演不同的角色,具有不同的特性和适用场景。选择使用哪种机制取决于具体的应用需求和目标。
1,区分之前的队列,信号量,事件组,任务通知不需要提前定义结构体。
2,任务通知有两对相关函数
使用ucNotifyState来切换任务的状态(阻塞,就绪)
使用ucNotifiedValue传递信息
3,函数原型:
1)xTaskNoitfyGive,ulTaskNotifyTake
任务A要发送任务B,任务B中有个值value
任务A使用xTaskNoitfyGive发送,他只有目标任务,没有目标值,通过让value++改变值
任务B通过ulTaskNotifyTake 去取Vlaue
2)xTaskNotify(TaskHandle xTaskToNotify,uint32_t ulvalue,eNotifyAction eAction ),ulTaskNotifyWait(uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t * pulNotificationValue.
TickType_t xTicksToWait)
xTaskNotify(TaskHandle xTaskToNotify,uint32_t ulvalue,eNotifyAction eAction ),
ulTaskNotifyWait(uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t * pulNotificationValue.
TickType_t xTicksToWait)
发送函数 | 作用 | 接收函数 | 作用 |
xTaskNotifyGive | val++ | ulTaskNotifyTake | val-- 或 val = 0 |
xTaskNotify | xTaskNotifyWait | ||
不使用val,只起通知作用 | 可以在函数进入时清除val的某些位可以在函数退出前清除val的某些位可以取得val的值 | ||
val |= (bits) | |||
val++ | |||
val = xxx 不覆盖,当ucNotifyState表示在等待才起效 | |||
val = xxx覆盖 |
eNotifyAction参数说明:
eNotifyAction取值 | 说明 |
eNoAction | 仅仅是更新通知状态为"pending",未使用ulValue。这个选项相当于轻量级的、更高效的二进制信号量。 |
eSetBits | 通知值 = 原来的通知值 | ulValue,按位或。相当于轻量级的、更高效的事件组。 |
eIncrement | 通知值 = 原来的通知值 + 1,未使用ulValue。相当于轻量级的、更高效的二进制信号量、计数型信号量。相当于 |
eSetValueWithoutOverwrite | 不覆盖。如果通知状态为"pending"(表示有数据未读),则此次调用xTaskNotify不做任何事,返回pdFAIL。如果通知状态不是"pending"(表示没有新数据),则:通知值 = ulValue。 |
eSetValueWithOverwrite | 覆盖。无论如何,不管通知状态是否为"pendng",通知值 = ulValue。 |
理论总结
- eAction=eNoAction,ulvalue不起作用,相当于轻量级,更高效二进制信号量
- eAction=eSetBits,通知值 = 原来的通知值 | ulValue,按位或。相当于轻量级的、更高效的事件组。
- eAction=eIncrement通知值 = 原来的通知值 + 1,未使用ulValue。相当于轻量级的、更高效的二进制信号量、计数型信号量。相当于
xTaskNotifyGive()
函数。
对于任务A发送数据后,如何通知任务B
ulTaskNotifyWait(uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t * pulNotificationValue.
TickType_t xTicksToWait)
ulBitsToClearOnEntry ,在入口处:val &= ~(bits)在入口处清除所有位
ulBitsToClearOnExit,,在出口处:val &= ~(bits)在出口处清除所有位
pulNotificationValue 在出口被清楚前返回*pul = val
xTicksToWait 等待时间
任务通知-轻量级信号量
使用信号量
1,使用信号量-计数值实现任务1,take sum,sum++,任务2 give 10次,并打印take give 次数
void Task1Function(void *parm)
{
int i= 0;
while(1)
{
for(i = 0;i<10;i++)
{
sum++;
xSemaphoreGive(xSemCalc) ;//让信号量+1
vTaskDelay(2);
}
vTaskDelete(NULL); //take10 次后自杀
}
}
void Task2Function(void *parm)
{
int i = 0;
while(1)
{
xSemaphoreTake(xSemCalc, portMAX_DELAY);
printf("take time = %d,sum = %d\r\n",i++,sum);
}
}
int main( void )
{
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello, world!\r\n");
xSemCalc = xSemaphoreCreateCounting(10, 0);//计数最大值10,初始值0
xTaskCreate(Task1Function,"task1",100,NULL,1,&Task1Handle);
xTaskCreate(Task2Function,"task2",100,NULL,1,&Task2Handle);
/* Start the scheduler. */
vTaskStartScheduler();
/* Will only get here if there was not enough heap space to create the
idle task. */
替换成轻量级任务通知实现相同功能
void Task1Function(void *parm)
{
int i= 0;
while(1)
{
for(i = 0;i<10;i++)
{
sum++;
//xSemaphoreGive(xSemCalc) ;//让信号量+1
xTaskNotifyGive(Task2Handle);
vTaskDelay(5);
}
vTaskDelete(NULL); //take10 次后自杀
}
}
void Task2Function(void *parm)
{
int i = 0;
int val = 0;
while(1)
{
// xSemaphoreTake(xSemCalc, portMAX_DELAY);
val = ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
printf("take time = %d,sum = %d,val = %d\r\n",i++,sum,val);
}
}
int main( void )
{
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello, world!\r\n");
// xSemCalc = xSemaphoreCreateCounting(10, 0);//计数最大值10,初始值0
xTaskCreate(Task1Function,"task1",100,NULL,1,&Task1Handle);
xTaskCreate(Task2Function,"task2",100,NULL,1,&Task2Handle);
/* Start the scheduler. */
vTaskStartScheduler();
/* Will only get here if there was not enough heap space to create the
idle task. */
return 0;
}
对比
信号量-计数 | 任务通知-轻量级信号量 | |
创建 | xSemCalc = xSemaphoreCreateCounting(10, 0);//计数最大值10,初始值0 | / |
give | xSemaphoreGive(xSemCalc) ;//让信号量+1 | xTaskNotifyGive(Task2Handle); |
take | xSemaphoreTake(xSemCalc, portMAX_DELAY); | val = ulTaskNotifyTake(pdFALSE, portMAX_DELAY); //pdFALSE,退出不清除val,pdTure,退出清除val |
总结 | 1,任务通知不需要创建结构体。参数传递通过句柄通知。(task1通知task2) |
任务通知-轻量级队列
队列 | 使用任务通知实现队列 | |
创建 | QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize ); | 无 |
发送 | BaseType_t xQueueSend( QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait ); | BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction ); |
接收 | BaseType_t xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait ); | BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait ); |
1,使用传统队列实现任务1,10次,任务2 读10次
void Task1Function(void *parm)
{
int i= 0;
while(1)
{
for(i = 0;i<10;i++)
{
sum++;
xQueueSendToBack(xQueueCalc,&sum,portMAX_DELAY);
vTaskDelay(5);
}
vTaskDelete(NULL); //take10 次后自杀
}
}
void Task2Function(void *parm)
{
int i = 0;
int val = 0;
while(1)
{
xQueueReceive(xQueueCalc,&val,portMAX_DELAY);
printf("take time = %d,sum = %d,val = %d\r\n",i++,sum,val);
}
}
int main( void )
{
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello, world!\r\n");
xQueueCalc = xQueueCreate(1, sizeof(uint32_t));
xTaskCreate(Task1Function,"task1",100,NULL,1,&Task1Handle);
xTaskCreate(Task2Function,"task2",100,NULL,1,&Task2Handle);
/* Start the scheduler. */
vTaskStartScheduler();
/* Will only get here if there was not enough heap space to create the
idle task. */
return 0;
}
2,替换成轻量级任务通知实现相同功能
void Task1Function(void *parm)
{
int i= 0;
while(1)
{
for(i = 0;i<10;i++)
{
sum++;
//xQueueSendToBack(xQueueCalc,&sum,portMAX_DELAY);
xTaskNotify(Task2Handle,sum,eSetValueWithoutOverwrite);//
vTaskDelay(5);
}
vTaskDelete(NULL); //take10 次后自杀
}
}
void Task2Function(void *parm)
{
uint32_t i = 0;
uint32_t val = 0;
while(1)
{
//xQueueReceive(xQueueCalc,&val,portMAX_DELAY);
xTaskNotifyWait(0,0,&val,portMAX_DELAY);
printf("take time = %d,sum = %d,val = %d\r\n",i++,sum,val);
}
}
int main( void )
{
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello, world!\r\n");
//xQueueCalc = xQueueCreate(1, sizeof(uint32_t));
xTaskCreate(Task1Function,"task1",100,NULL,1,&Task1Handle);
xTaskCreate(Task2Function,"task2",100,NULL,1,&Task2Handle);
/* Start the scheduler. */
vTaskStartScheduler();
/* Will only get here if there was not enough heap space to create the
idle task. */
r
总结:轻量级任务通知只能存放一个val值,要尽快take出来,否则要么被覆盖,要么不覆盖阻塞
任务通知-轻量级事件组
事件组 | 使用任务通知实现事件组 | |
创建 | EventGroupHandle_t xEventGroupCreate( void ) | 无 |
设置事件 | EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet ); | BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction ); |
等待事件 | EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait ); | BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait ); |
1,传统的事件组实现
任务1,sum++,加完设置bit0,
任务2,dec--,加完设置bit1,
任务3,判断bit0,bit1 都完成后,打印val1,val2
void Task1Function(void * param)
{
volatile int i = 0;
while (1)
{
for (i = 0; i < 100000; i++)
sum++;
xQueueSend(xQueueCalcHandle, &sum, 0);
/* 设置事件0 */
xEventGroupSetBits(xEventGroupCalc, (1<<0));
vTaskDelete(NULL);
}
}
void Task2Function(void * param)
{
volatile int i = 0;
while (1)
{
for (i = 0; i < 1000000; i++)
dec--;
xQueueSend(xQueueCalcHandle, &dec, 0);
/* 设置事件1 */
xEventGroupSetBits(xEventGroupCalc, (1<<1));
vTaskDelete(NULL);
}
}
void Task3Function(void * param)
{
int val1, val2;
while (1)
{
/*等待事件 */
xEventGroupWaitBits(xEventGroupCalc, (1<<0)|(1<<1), pdTRUE, pdTRUE, portMAX_DELAY);
xQueueReceive(xQueueCalcHandle, &val1, 0);
xQueueReceive(xQueueCalcHandle, &val2, 0);
printf("val1 = %d, val2 = %d\r\n", val1, val2);
}
}
int main( void )
{
TaskHandle_t xHandleTask1;
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello, world!\r\n");
/* 创建事件组 */
xEventGroupCalc = xEventGroupCreate();
xQueueCalcHandle = xQueueCreate(2, sizeof(int));
if (xQueueCalcHandle == NULL)
{
printf("can not create queue\r\n");
}
xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);
xTaskCreate(Task3Function, "Task3", 100, NULL, 1, &xHandleTask3);
/* Start the scheduler. */
vTaskStartScheduler();
/* Will only get here if there was not enough heap space to create the
idle task. */
return 0;
}
2,使用轻量级事件组替代
void Task1Function(void * param)
{
volatile int i = 0;
while (1)
{
for (i = 0; i < 100000; i++)
sum++;
xQueueSend(xQueueCalcHandle, &sum, 0);
/* 设置事件0 */
//xEventGroupSetBits(xEventGroupCalc, (1<<0));
xTaskNotify(xHandleTask3, (1<<0), eSetBits);
printf("Task 1 set bit 0\r\n");
vTaskDelete(NULL);
}
}
void Task2Function(void * param)
{
volatile int i = 0;
while (1)
{
for (i = 0; i < 1000000; i++)
dec--;
xQueueSend(xQueueCalcHandle, &dec, 0);
/* 设置事件1 */
//xEventGroupSetBits(xEventGroupCalc, (1<<1));
xTaskNotify(xHandleTask3, (1<<1), eSetBits);
printf("Task 2 set bit 1\r\n");
vTaskDelete(NULL);
}
}
void Task3Function(void * param)
{
int val1, val2;
int bits;
while (1)
{
/*等待事件 */
//xEventGroupWaitBits(xEventGroupCalc, (1<<0)|(1<<1), pdTRUE, pdTRUE, portMAX_DELAY);
xTaskNotifyWait(0, 0, &bits, portMAX_DELAY);
if ((bits & 0x3) == 0x3)
{
vTaskDelay(20);
xQueueReceive(xQueueCalcHandle, &val1, 0);
xQueueReceive(xQueueCalcHandle, &val2, 0);
printf("val1 = %d, val2 = %d\r\n", val1, val2);
}
else
{
vTaskDelay(20);
printf("have not get all bits, get only 0x%x\r\n", bits);
}
}
}
int main( void )
{
TaskHandle_t xHandleTask1;
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello, world!\r\n");
/* 创建事件组 */
xEventGroupCalc = xEventGroupCreate();
xQueueCalcHandle = xQueueCreate(2, sizeof(int));
if (xQueueCalcHandle == NULL)
{
printf("can not create queue\r\n");
}
xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);
xTaskCreate(Task3Function, "Task3", 100, NULL, 1, &xHandleTask3);
/* Start the scheduler. */
vTaskStartScheduler();
/* Will only get here if there was not enough heap space to create the
idle task. */
return 0;
}
总结
1,使用轻量级任务通知实现事件组只要在任务1,2中,xTaskNotify(任务3句柄) ,不管哪些位触发,都会唤醒任务3句柄中的任务。即使没有全部位触发,也会使任务3唤醒
这样进入任务3后,需要判断是否是需要满足的位达到再进入操作。
2,传统的事件组,在处理任务中的take 函数里,可以指定一个位满足,或者全部满足。
比轻量级任务通知实现事件组 多个全部满足的条件。
如果是单独指定一个位满足唤醒,进去后也可以单独在判断一次 是哪些位触发了。