FreeRTOS任务通知 基于STM32

本文围绕FreeRTOS的任务通知功能展开,介绍了其自V8.2.0版本开始提供,可替代二值信号量、计数信号量等,阐述了运作机制,详细讲解多个函数接口,还通过实验展示其代替消息队列、二值信号量和计数信号量的应用及现象。

文章目录

一、任务通知简介

二、任务通知的运作机制

三、任务通知的函数接口讲解

1. xTaskGenericNotify()

2.xTaskNotifyGive()

3.vTaskNotifyGiveFromISR()

4.xTaskNotify()

5. xTaskNotifyFromISR()

6.xTaskNotifyAndQuery()

 7.xTaskNotifyAndQueryFromISR()

8.ulTaskNotifyTake()

9.xTaskNotifyWait()

四、任务通知实验

1 任务通知代替消息队列

2 任务通知代替二值信号量

3 任务通知代替计数信号量

五、实验现象


一、任务通知简介

       FreeRTOS 从 V8.2.0 版本开始提供任务通知这个功能,每个任务都有一个 32 位的通知 值,在大多数情况下,任务通知可以替代二值信号量、计数信号量、事件组,也可以替代 长度为 1 的队列(可以保存一个 32位整数或指针值)。

      相对于以前使用 FreeRTOS 内核通信的资源,必须创建队列、二进制信号量、计数信 号量或事件组的情况,使用任务通知显然更灵活。

     按照 FreeRTOS 官方的说法,使用任务 通知比通过信号量等 ICP 通信方式解除阻塞的任务要快 45%,并且更加省 RAM 内存空间 (使用 GCC 编译器,-o2 优化级别),任务通知的使用无需创建队列。想要使用任务通知, 必须将 FreeRTOSConfig.h 中的宏定义 configUSE_TASK_NOTIFICATIONS 设置为 1,其实 FreeRTOS 默认是为 1 的,所以任务通知是默认使能的。

     FreeRTOS 提供以下几种方式发送通知给任务 :

     发送通知给任务, 如果有通知未读,不覆盖通知值。

     发送通知给任务,直接覆盖通知值。

     发送通知给任务,设置通知值的一个或者多个位,可以当做事件组来使用。

     发送通知给任务,递增通知值,可以当做计数信号量使用。 通过对以上任务通知方式的合理使用,可以在一定场合下替代 FreeRTOS 的信号量, 队列、事件组等。

      当然,凡是都有利弊,不然的话 FreeRTOS 还要内核的 IPC 通信机制干嘛,消息通知 虽然处理更快,RAM 开销更小,但也有以下限制 :

     只能有一个任务接收通知消息,因为必须指定接收通知的任务。。

     只有等待通知的任务可以被阻塞,发送通知的任务,在任何情况下都不会因为发 送失败而进入阻塞态。

二、任务通知的运作机制

       顾名思义,任务通知是属于任务中附带的资源,所以在任务被创建的时候,任务通知 也被初始化的,而在分析队列和信号量的章节中,我们知道在使用队列、信号量前,必须 先创建队列和信号量,目的是为了创建队列数据结构。比如使用 xQueueCreate()函数创建 队列,用 xSemaphoreCreateBinary()函数创建二值信号量等等。再来看任务通知,由于任务 通知的数据结构包含在任务控制块中,只要任务存在,任务通知数据结构就已经创建完毕, 可以直接使用,所以使用的时候很是方便。 任务通知可以在任务中向指定任务发送通知,也可以在中断中向指定任务发送通知, FreeRTOS 的每个任务都有一个 32 位的通知值,任务控制块中的成员变量 ulNotifiedValue 就是这个通知值。只有在任务中可以等待通知,而不允许在中断中等待通知。如果任务在等待的通知暂时无效,任务会根据用户指定的阻塞超时时间进入阻塞状态,我们可以将等 待通知的任务看作是消费者;其它任务和中断可以向等待通知的任务发送通知,发送通知 的任务和中断服务函数可以看作是生产者,当其他任务或者中断向这个任务发送任务通知, 任务获得通知以后,该任务就会从阻塞态中解除,这与 FreeRTOS 中内核的其他通信机制 一致。

三、任务通知的函数接口讲解

1. xTaskGenericNotify()

       我们先看一下发送通知 API 函数。这类函数比较多,有 6 个。但仔细分析会发现它们 只能完成 3 种操作,每种操作有两个 API 函数,分别为带中断保护版本和不带中断保护版 本。FreeRTOS 将 API 细分为带中断保护版本和不带中断保护版本是为了节省中断服务程 序处理时间,提升性能。通过前面通信机制的学习,相信大家都了解了 FreeRTOS 的风格, 这里的任务通知发送函数也是利用宏定义来进行扩展的,所有的函数都是一个宏定义,在 任务中发送任务通知的函数均是调用 xTaskGenericNotify()函数进行发送通知,xTaskGenericNotify()函数是一个通用的任务通知发送函数,在任务中发送通知的 API 函 数 , 如 xTaskNotifyGive() 、 xTaskNotify() ,xTaskNotifyAndQuery() , 都 是 以 xTaskGenericNotify()为原型的,只不过指定的发送方式不同而已。

2.xTaskNotifyGive()

      xTaskNotifyGive()是一个宏,宏展开是调用函数 xTaskNotify( ( xTaskToNotify ), ( 0 ), eIncrement ),即向一个任务发送通知,并将对方的任务通知值加 1。该函数可以作为二值 信号量和计数信号量的一种轻量型的实现,速度更快,在这种情况下对象任务在等待任务 通 知 的 时 候 应 该 是 使 用 函 数 ulTaskNotifyTake() 而不是 xTaskNotifyWait() 。xTaskNotifyGive() 不 能 在 中 断 里 面 使 用 , 而 是 使 用 具 有 中 断 保 护 功 能 的 vTaskNotifyGiveFromISR()来代替。

xTaskNotifyGive()函数说明 

xTaskNotifyGive()函数应用举例

 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()是 vTaskNotifyGive()的中断保护版本。用于在中断中向指定 任务发送任务通知,并更新对方的任务通知值(加 1 操作),在某些场景中可以替代信号 量操作,因为这两个通知都是不带有通知值的。

vTaskNotifyGiveFromISR()函数说明 

      从上面的函数说明我们大概知道 vTaskNotifyGiveFromISR()函数作用,每次调用该函 数都会增加任务的通知值,任务通过接收函数返回值是否大于零,判断是否获取到了通知, 任务通知值初始化为 0,(如果与信号量做对比)则对应为信号量无效。当中断调用 vTaskNotifyGiveFromISR()通知函数给任务的时候,任务的通知值增加,使其大于零,使其 表示的通知值变为有效,任务获取有效的通知值将会被恢复。

vTaskNotifyGiveFromISR()函数应用举例

 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()

      FreeRTOS 每个任务都有一个 32 位的变量用于实现任务通知,在任务创建的时候初始 化为 0。这个 32 位的通知值在任务控制块 TCB 里面定义,具体见代码清单 22-6。 xTaskNotify()用于在任务中直接向另外一个任务发送一个事件,接收到该任务通知的任务 有可能解锁。如果你想使用任务通知来实现二值信号量和计数信号量,那么应该使用更加 简单的函数 xTaskNotifyGive() ,而不是使用 xTaskNotify(),xTaskNotify()函数在发送任务 通知的时候会指定一个通知值,并且用户可以指定通知值发送的方式。 注意:该函数不能在中断里面使用,而是使用具体中断保护功能的版本函数 xTaskNotifyFromISR()。

xTaskNotify()函数说明

 xTaskNotify()函数应用举例

 /* 设置任务 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()是 xTaskNotify()的中断保护版本,真正起作用的函数是中断发送 任务通知通用函数 xTaskGenericNotifyFromISR(),而 xTaskNotifyFromISR()是一个宏定义,用于在中断中向指定的任务发送一个任务通知,该任务通知是带有 通知值并且用户可以指定通知的发送方式,不返回上一个任务在的通知值。

xTaskNotifyFromISR()函数说明

中断中发送任务通知通用函数 xTaskGenericNotifyFromISR() 

xTaskGenericNotifyFromISR() 是一个在中断中发送任务通知的通用函数, xTaskNotifyFromISR()、xTaskNotifyAndQueryFromISR()等函数都是以其为基础,采用宏定 义的方式实现。

xTaskNotifyFromISR()使用实例

 /* 中断:向一个任务发送任务通知,并根据不同的中断将目标任务的
 任务通知值的相应位置 1 */
 void vANInterruptHandler( void )
 {
 BaseType_t xHigherPriorityTaskWoken;
 uint32_t ulStatusRegister;
 
 /* 读取中断状态寄存器,判断到来的是哪个中断 
 这里假设了 Rx、Tx 和 buffer overrun 三个中断 */ 
 ulStatusRegister = ulReadPeripheralInterruptStatus(); 
 
 /* 清除中断标志位 */
 vClearPeripheralInterruptStatus( ulStatusRegister );
 
 /* xHigherPriorityTaskWoken 在使用之前必须初始化为 pdFALSE
 如果调用函数 xTaskNotifyFromISR()解锁了解锁了接收该通知的任务
 而且该任务的优先级比当前运行的任务
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值