FreeRTOS-任务通知

目录

任务通知

优势

限制

任务状态和通知值

事件通知函数

xTaskNotifyGive/xTaskNotifyTake

xTaskNotify/xTaskNotifyWait

应用场景:传输计数值

应用场景:传输任意值


任务通知

使用队列、信号量、事件组等方法时,无法知道发送方身份。使用任务通知时,可以明确指定:通知哪个任务。

FreeRTOS-任务通知_第1张图片

优势

效率更高。

        使用任务通知来发送事件、数据给某个任务时,效率更高。比队列、信号量、事件组都有优势。

更节省内存。

        使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体。

限制

不能发送数据给ISR

        ISR并没有任务结构体,所以无法使用任务通知的功能给ISR发送数据。但是ISR可以使用任务通知的功能,发数据给任务。

数据只能给该任务独享

        使用队列、信号量、事件组时,数据保存在结构体中,其他任务、ISR都可以访问结构体的数据。

        使用任务通知时,数据存放入目标任务中,只有它可以访问这些数据。

        在日常工作中,这个限制影响不大。因为很多场合是从多个数据源把数据发给某个任务,而不是把一个数据源的数据发给多个任务。

无法缓存数据

        使用队列时,假设队列深度为N,则它可以保存N个数据。

        使用任务通知时,任务结构体只有一个任务通知值,只能保存一个数据。

无法广播给多个任务

        使用事件组时,可以同时给多个任务发送事件。

        使用任务通知时,只能发一个任务。

如果发送受阻,发送方无法进入阻塞状态等待

        假设队列已满,使用xQueueSendToBack()给队列发送数据时,任务可以进入阻塞状态等待发送完成。

        使用任务通知时,计数接双方无法接收数据,发送方也无法阻塞等待,只能立即返回错误。

任务状态和通知值

每个人物都有一个结构体:TCB。里面有两个成员:uint8_t ucNotifyState(表示通知状态)、uint32_t ulNotifiedValue(表示通知值)。

typedef struct tskTaskControlBlock
{
    ......

    /* configTASK_NOTIFICATION_ARRAY_ENTRIES = 1 */
    volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
    volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];

    ......
} tskTCB;

ucNotifyState有三种取值:

##define taskNOT_WAITING_NOTIFICATION ( ( uint8_t ) 0 ) /* 默认状态,任务没有在等待通知 */
##define taskWAITING_NOTIFICATION ( ( uint8_t ) 1 )     /* 任务在等待通知 */
##define taskNOTIFICATION_RECEIVED ( ( uint8_t ) 2 )    /* 任务接收到了通知 */

ulNotifiedValue可以有很多类型:

        计数值、位(类似事件组)、任意数值。

事件通知函数

使用任务通知,可以实现轻量级的队列(长度为1)、邮箱(覆盖的队列)、计数型信号量、二进制信号量、事件组。

简化版,调用简单 专业版,参数很多
发出通知

xTaskNotifyGive

xTaskNotifyGiveFromISR

xTaskNotify

xTaskNotifyFromISR

取出通知 xTaskNotifyTake

xTaskNotifyWait

xTaskNotifyGive/xTaskNotifyTake

xTaskNotifyGive / xTaskNotifyGiveFromISR都是直接给其他任务发送通知,使得通知值+1,并使得通知状态变为taskNOTIFICATION_RECEIVED,表示有数据了,待处理。

xTaskNotifyTake取出通知值。可以实现轻量级的、高效的二进制信号量、计数型信号量。

        如果通知值为0,则阻塞(可以指定超时时间)。

        如果通知值大于0,任务从阻塞态进入就绪态。

        在该函数返回之前,还可以做一些清理工作:把通知值-1,或清零通知值。

/* xTaskToNotify:任务句柄 */
/* 返回值:只能是pdPASS */
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );

/* 	xTaskToNotify:任务句柄 */
/* 	pxHigherPriorityTaskWoken:
		被通知的任务可能正处于阻塞状态。此函数发出通知后,会把它从阻塞状态切换为就绪态。
		如果被唤醒任务的优先级高于当前任务的优先级,则*pxHigherPriorityTaskWoken为pdTRUE,表示在中断返回前要进行任务切换
 */
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskHandle, BaseType_t *pxHigherPriorityTaskWoken );

/* 	xClearCountOnExit:函数返回前是否清零。
		pdTRUE:清零通知值。
		pdFALSE:如果通知值大于0,则通知值减一。
	xTicksToWait:任务进入阻塞态的超时时间,等待通知值大于0。
		0:不等待,立即返回。
		portMAX_DELAY:一直等待,直到通知值大于0。
		其他值:Tick Count。可用pdMS_TO_TICKS()。
	返回值:
		函数返回前清零/减一通知值。
		如果xTicksToWait非0,则返回值有两种情况:
			大于0:在超时前,通知值被增加了
			等于0:一直没有其他任务增加通知值,最后超时返回0
 */
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait);

xTaskNotify/xTaskNotifyWait

xTaskNotify可以使用不同参数实现各类功能,如:

        让接收任务的通知值+1:此时xTaskNotify()等同于xTaskNotifyGive()

        设置接收任务的通知值的某一位、某些位。此时为轻量级、更高效的事件组

        把一个新值写入接收任务的通知值:上一次的通知值被读走后,写入才成功。此时为轻量级、长度为1的队列

        用一个新值覆盖接收任务的通知值:无论上一次的通知值是否被读走,覆盖都成功。此时为轻量级邮箱,类似xQueueOverwrite()函数

xTaskNotifyWait可以取出任务通知:

        可以让任务等待(可以指定超时时间),等待任务状态为taskNOTIFICATION_RECEIVED

        可以在函数进入、退出时清除通知值的指定位

/*
xTaskToNotify:任务句柄
ulValue:怎么使用ulValue,由参数eAction决定
eAction:看表
返回值:
	pdPASS:成功,大部分调用都会成功
	pdFALL:当eAction为eSetValueWithoutOverwrite,并且通知状态为taskNOTIFICATION_RECEIVED
*/
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction );
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, BaseType_t *pxHigherPriorityTaskWoken );

/*
	ulBitsToClearOnEntry:在该函数的入口处要清除通知值的哪些位?通知状态非taskNOTIFICATION_RECEIVED的情况下才会清除。
		如0x01表示清除通知值的位0,0xffff ffff即ULONG_MAX表示清除所有位。
	ulBitsToClearOnExit:在该函数的出口处要清除通知值的哪些位?非超时退出时才会清除。
		如0x01表示清除通知值的位0,0xffff ffff即ULONG_MAX表示清除所有位。
	pulNotificationValue:取出通知值。在函数退出时且ulBitsToClearOnExit清除前,把通知值赋给*pulNotificationValue。不需要则设为NULL。
	xTicksToWait:任务进入阻塞态的超时时间,在等待通知状态变为taskNOTIFICATION_RECEIVED。
		0:不等待,立刻返回
		portMAX_DELAY:一直等待,直到通知状态变为taskNOTIFICATION_RECEIVED。
		其他值:Tick Count。可使用pdMS_TO_TICKS()。
	返回值:
		pdPASS:成功获得了通知。
			可能是调用函数前,通知状态为taskNOTIFICATION_RECEIVED
			可能是阻塞期间,通知状态为taskNOTIFICATION_RECEIVED
		pdFALL:没有得到通知。
*/
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait );
eNotifyAction取值 说明
eNoAction

仅仅是更新通知状态为taskNOTIFICATION_RECEIVED,未使用ulValue。

这个选项相当于轻量级的、更高效的二进制信号量。

eSetBits

通知值 = 原来的通知值 | ulValue。

这个选项相当于轻量级的、更高效的事件组。

eIncrement

通知值 = 原来的通知值 + 1,未使用ulValue。

这个选项相当于轻量级的、更高效的二进制信号量、计数型信号量。相当于xTaskNotifyGive()函数。

eSetValueWithoutOverwrite

不覆盖。

如果通知状态为taskNOTIFICATION_RECEIVED,则此次调用xTaskNotify不做任何事,返回pdFALL。

如果通知状态不为taskNOTIFICATION_RECEIVED,则通知值 = ulValue。

eSetValueWithOverwrite

覆盖。

不管通知状态是否为taskNOTIFICATION_RECEIVED,通知值 = ulValue。

应用场景:传输计数值

创建2个任务:

        发送任务:把数据写入唤醒缓冲区,使用xTaskNotifyGive()让通知值+1。

        接收任务:使用ulTaskNotifyTake()取出通知值。

int main( void )
{
	prvSetupHardware();
	
	/* 创建1个任务用于发送任务通知,优先级为2 */
	xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );

	/* 创建1个任务用于接收任务通知,优先级为1 */
	xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, &xRecvTask );
	
	/* 启动调度器 */
	vTaskStartScheduler();
	
	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;
}

static void vSenderTask(void *pvParameters)
{
	int i;
	int cnt_tx = 0;
	int cnt_ok = 0;
	int cnt_err = 0;
	char c;
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 20UL );
	
	/* 无限循环 */
	for(;;)
	{
		for (i = 0; i < 3: i++)
		{
			/*放入数据 */
			c = 'a' + cnt_tx;
			txbuf_put(c);
			cnt_tx++;
			
			/* 发出任务通知 */
			if(xTaskNotifyGive(xRecvTask) == pdPASS)
				printf("xTaskNotifyGive %d time:OK, val:%c\r\n", cnt_ok++, c);
			else
				printf("xTaskNotifyGive %d time:ERR\r\n", cnt_err++);	
		}
		
		vTaskDelay(xTicksToWait);
	}
}

static void vReceiverTask(void *pvParameters)
{
	int cnt_ok = 0;
	char c;
	int notify_val;
	
	/* 无限循环 */
	for(;;)
	{
		/* 得到了任务通知,让通知值清零 */
		notify_val = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
		
		/* 打印字符 */
		printf("ulTaskNotifyTake OK:%d, data:", cnt_ok++);	
		
		while(notify_val--)
		{
			txbuf_get(&c);	
			printf("%c", c);
		}
		
		printf("\r\n");	
	}
}

实验现象:

xTaskNotifyGive 0 time:OK, val:a

xTaskNotifyGive 1 time:OK, val:b

xTaskNotifyGive 2 time:OK, val:c

ulTaskNotifyTake OK:0, data:abc

xTaskNotifyGive 3 time:OK, val:d

xTaskNotifyGive 4 time:OK, val:e

xTaskNotifyGive 5 time:OK, val:f

ulTaskNotifyTake OK:1, data:def

...

应用场景:传输任意值

创建2个任务:

        发送任务:把数据通过xTaskNotify()发送给其他任务。

        接收任务:使用ulTaskNotifyWait()取出通知值。

int main( void )
{
	prvSetupHardware();
	
	/* 创建1个任务用于发送任务通知,优先级为2 */
	xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );

	/* 创建1个任务用于接收任务通知,优先级为1 */
	xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, &xRecvTask );
	
	/* 启动调度器 */
	vTaskStartScheduler();
	
	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;
}

static void vSenderTask(void *pvParameters)
{
	int i;
	int cnt_tx = 0;
	int cnt_ok = 0;
	int cnt_err = 0;
	char c;
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 20UL );
	
	/* 无限循环 */
	for(;;)
	{
		for (i = 0; i < 3: i++)
		{
			/*放入数据 */
			c = 'a' + cnt_tx;
			cnt_tx++;
			
			/* 发出任务通知 */
			/*
				发给谁? xRecvTask
				发什么? c
				能否覆盖? 不能,eSetValueWithoutOverwrite
			*/
			if(xTaskNotify(xRecvTask, (uint32_t)c, eSetValueWithoutOverwrite) == pdPASS)
				printf("xTaskNotifyGive %d time:OK, val:%c\r\n", cnt_ok++, c);
			else
				printf("xTaskNotifyGive %d time:ERR, val:%c\r\n", cnt_err++, c);	
		}
		
		vTaskDelay(xTicksToWait);
	}
}

static void vReceiverTask(void *pvParameters)
{
	int cnt_ok = 0;
	uint32_t ulValue;
	BaseType_t xResult;
	
	/* 无限循环 */
	for(;;)
	{
		/* 等待数据 */
		xResult = ulTaskNotifyWait(	0,	// 发送任务会存入新数据,无需接收方在进入函数时清零
									0,	// 发送任务会存入新数据,无需接收方在进入函数时清零
									&ulValue,			/* 保存读出的数据 */
									portMAX_DELAY);		/* 一直等待数据 */
		
		/* 打印字符 */
		if(xResult == pdPASS)
			printf("ulTaskNotifyTake OK:%d, data:%c\r\n", cnt_ok++, (char)ulValue);	
	}
}

实验现象:

xTaskNotifyGive 0 time:OK, val:a

xTaskNotifyGive 0 time:ERR, val:b

xTaskNotifyGive 1 time:ERR, val:c

ulTaskNotifyTake OK:0, data:a

xTaskNotifyGive 1 time:OK, val:d

xTaskNotifyGive 2 time:ERR, val:e

xTaskNotifyGive 3 time:ERR, val:f

ulTaskNotifyTake OK:1, data:d

...

本工程使用了xTaskNotify/xTaskNotifyWait实现了轻量级的队列(队列长度为1),代码更简单:无需创建队列,消耗内存更少,效率更高。

队列是个公开的资源,任何任务、ISR都可以使用它:可以存入数据、取出数据。而在该程序中,发送任务只能给指定的任务发送通知,目标明确;接收任务只能从自己的通知值中得到数据,来源明确。

注意:任务通知值只有一个,数据可能丢失,设计程序时要考虑这点。

你可能感兴趣的:(stm32)