文章目录
一、任务通知是什么?
我们使用队列、信号量、事件组等等方法时,并不知道对方是谁。使用任务通知时,可以明确指定:通知哪个任务。
使用队列、信号量、事件组时,我们都要事先创建对应的结构体,双方通过中间的结构体通信。
使用任务通知时,任务结构体TCB中就包含了内部对象,可以直接接收别人发过来的"通知"。
1.1任务通知的优势
效率高: 使用任务通知来发送事件、数据给某个任务时,效率更高。比队列、信号量、事件组都
有大的优势。
省内存: 使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体。启用任务通知功能的开销固定为每个任务8个字节的RAM
1.2任务通知的限制
- 不能发送数据给ISR: ISR并没有任务结构体,所以无法使用任务通知的功能给ISR发送数据。但是ISR可以使用任务通知的功能,发数据给任务。
- 数据只能给该任务独享: 使用队列、信号量、事件组时,数据保存在这些结构体中,其他任务、ISR都可以访问这些数据。使用任务通知时,数据存放入目标任务中,只有它可以访问这些数据。在日常工作中,这个限制影响不大。因为很多场合是从多个数据源把数据发给某个任务,而不是把一个数据源的数据发给多个任务。
- 无法缓冲数据: 使用队列时,假设队列深度为N,那么它可以保持N个数据。使用任务通知时,任务结构体中只有一个任务通知值,只能保持一个数据。
- 无法广播给多个任务: 事件组可以同时给多个任务发送事件。使用任务通知,只能发个一个任务。
- 发送方无法进入阻塞状态: 接收方无法接收数据,发送方也无法阻塞等待,只能即刻返回错误。
1.3通知状态和通知值
每个任务都有一个结构体TCB(Task Control Block),里面有2个成员:
一个是uint8_t类型,用来表示通知状态
一个是uint32_t类型,用来表示通知值
二、任务通知的使用
使用任务通知,可以实现轻量级的队列(长度为1)、邮箱(覆盖的队列)、(计数型/二进制)信号量、事件组。
2.1任务通知使用_轻量级信号量
在任务中使用xTaskNotifyGive
函数,在ISR中使用vTaskNotifyGiveFromISR
函数,都是直接给其他任务发送通知:
- 使得通知值加一
- 使得通知状态变为"pending",也就是
taskNOTIFICATION_RECEIVED
,表示有数据了、待处理可以使用ulTaskNotifyTake
函数来取出通知值: - 如果通知值等于0,则阻塞(可以指定超时时间)
- 当通知值大于0时,任务从阻塞态进入就绪态
- 在ulTaskNotifyTake返回之前,还可以做些清理工作:把通知值减一,或者把通知值清零
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(pdFALSE, portMAX_DELAY);
flagCalcEnd = 1;
printf("sum = %d, NotifyVal = %d, i = %d\r\n", sum, val, i++);
}
}
2.2任务通知使用_轻量级队列
因为TCB结构体value
只能存一个数据,所以任务通知实现队列可以只能容纳一个32位数据,且写队列不可阻塞,数据可覆盖也可不覆盖。正常队列可以容纳多个数据,且数据大小可指定,写队列可以阻塞。
void Task1Function(void * param)
{
volatile int i = 0;
while (1)
{
for (i = 0; i < 10000; i++)
sum++;
//printf("1");
//flagCalcEnd = 1;
//vTaskDelete(NULL);
for (i = 0; i < 10; i++)
{
//xQueueSend(xQueueCalcHandle, &sum, portMAX_DELAY);
//xTaskNotify(xHandleTask2, sum, eSetValueWithoutOverwrite);
xTaskNotify(xHandleTask2, sum, eSetValueWithOverwrite);
sum++;
}
vTaskDelete(NULL);
//sum = 1;
}
}
void Task2Function(void * param)
{
int val;
int i = 0;
while (1)
{
//if (flagCalcEnd)
flagCalcEnd = 0;
//xQueueReceive(xQueueCalcHandle, &val, portMAX_DELAY);
xTaskNotifyWait(0, 0, &val, portMAX_DELAY);
flagCalcEnd = 1;
printf("sum = %d, i = %d\r\n", val, i++);
}
}
2.3任务通知使用_轻量级事件组
使用任务通知实现轻量级的事件组,我们将eAction
配置为 eSetBits
,通知值 = 原来的通知值 | ulValue。并且只要调用xTaskNotify就会唤醒目标任务,这与事件组 只有设置某一位或某几位满足条件才会唤醒任务 不符合。所以在 目标任务需要做一些判断才能模拟事件组。
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);
}
}
}