当前位置: 代码迷 >> 综合 >> 从0到1学习FreeRTOS:FreeRTOS 内核应用开发:(二十四)任务通知 NO.2 函数接口讲解
  详细解决方案

从0到1学习FreeRTOS:FreeRTOS 内核应用开发:(二十四)任务通知 NO.2 函数接口讲解

热度:83   发布时间:2023-12-16 14:08:41.0

从0到1学习FreeRTOS:FreeRTOS 内核应用开发:(二十四)任务通知 NO.2 函数接口讲解

一、发送任务通知函数

1、xTaskGenericNotify()

        xTaskGenericNotify()函数是一个通用的任务通知发送函数,在任务中发送通知的 API函数 , 如 xTaskNotifyGive() 、 xTaskNotify() 、 xTaskNotifyAndQuery() , 都是以xTaskGenericNotify()为原型的,只不过指定的发生方式不同而已。

2、 xTaskNotifyGive()

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

/* 函数声明 */
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()函数作用, 每次调用该函数都会增加任务的通知值, 任务通过接收函数返回值是否大于零,判断是否获取到了通知,任务通知值初始化为 0, (如果与信号量做对比)则对应为信号量无效。当中断调用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 里面定义。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()是一个宏定义,用于在中断中向指定的任务发送一个任务通知, 该任务通知是带有通知值并且用户可以指定通知的发送方式, 不返回上一个任务在的通知值。

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

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

/* 中断:向一个任务发送任务通知,并根据不同的中断将目标任务的
任务通知值的相应位置 1 */
void vANInterruptHandler( void )
{
BaseType_t xHigherPriorityTaskWoken;
uint32_t ulStatusRegister;
/* 读取中断状态寄存器,判断到来的是哪个中断
这里假设了 Rx、 Tx 和 buffer overrun 三个中断 */
ulStatusRegister = ulReadPeripheralInterruptStatus();
/* 清除中断标志位 */
vClearPeripheralInterruptStatus( ulStatusRegister );
/* xHigherPriorityTaskWoken 在使用之前必须初始化为 pdFALSE
如果调用函数 xTaskNotifyFromISR()解锁了解锁了接收该通知的任务
而且该任务的优先级比当前运行的任务的优先级高,那么
xHigherPriorityTaskWoken 就会自动的被设置为 pdTRUE*/
xHigherPriorityTaskWoken = pdFALSE;
/* 向任务 xHandlingTask 发送任务通知,并将其任务通知值
与 ulStatusRegister 的值相或,这样可以不改变任务通知其它位的值*/
xTaskNotifyFromISR( xHandlingTask,
ulStatusRegister,
eSetBits,
&xHigherPriorityTaskWoken );
/* 如果 xHigherPriorityTaskWoken 的值为 pdRTUE
则执行一次上下文切换*/
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/* ----------------------------------------------------------- */
/* 任务:等待任务通知,然后处理相关的事情 */
void vHandlingTask( void *pvParameters )
{
uint32_t ulInterruptStatus;
for ( ;; ) {
/* 等待任务通知,无限期阻塞(没有超时,所以没有必要检查函数返回值) */
xTaskNotifyWait( 0x00, /* 在进入的时候不清除通知值的任何位 */
ULONG_MAX, /* 在退出的时候复位通知值为 0 */
&ulNotifiedValue, /* 任务通知值传递到变量
ulNotifiedValue 中*/
portMAX_DELAY ); /* 无限期等待 */
/* 根据任务通知值里面的各个位的值处理事情 */
if ( ( ulInterruptStatus & 0x01 ) != 0x00 ) {
/* Rx 中断 */
prvProcessRxInterrupt();
}
if ( ( ulInterruptStatus & 0x02 ) != 0x00 ) {
/* Tx 中断 */
prvProcessTxInterrupt();
}
if ( ( ulInterruptStatus & 0x04 ) != 0x00 ) {
/* 缓冲区溢出中断 */
prvClearBufferOverrun();
}
}
}

7、xTaskNotifyAndQuery()

       xTaskNotifyAndQuery()与 xTaskNotify()很像,都是调用通用的任务通知发送函数xTaskGenericNotify()来实现通知的发送,不同的是多了一个附加的参数pulPreviousNotifyValue 用于回传接收任务的上一个通知值。 xTaskNotifyAndQuery()函数不能用在中断中,而是必须使用带中断保护功的xTaskNotifyAndQuery()FromISR 来代替。 

uint32_t ulPreviousValue;
/* 设置对象任务 xTask1Handle 的任务通知值的位 8 为 1
在更新位 8 的值之前把任务通知值回传存储在变量 ulPreviousValue 中*/
xTaskNotifyAndQuery( xTask1Handle, ( 1UL << 8UL ), eSetBits,&ulPreviousValue );
/* 向对象任务 xTask2Handle 发送一个任务通知,有可能解除对象任务的阻塞状态
但是不更新对象任务的通知值,并将对象任务的通知值存储在变量 ulPreviousValue 中 */
xTaskNotifyAndQuery( xTask2Handle, 0, eNoAction, &ulPreviousValue );
/* 覆盖式设置对象任务的任务通知值为 0x50
且对象任务的任务通知值不用回传,则最后一个形参设置为 NULL */
xTaskNotifyAndQuery( xTask3Handle, 0x50, eSetValueWithOverwrite, NULL );
/* 设置对象任务的任务通知值为 0xfff,但是并不会覆盖对象任务通过
xTaskNotifyWait()和 ulTaskNotifyTake()这两个函数获取到的已经存在
的任务通知值。对象任务的前一个任务通知值存储在变量 ulPreviousValue 中*/
if ( xTaskNotifyAndQuery( xTask4Handle,
0xfff,
eSetValueWithoutOverwrite,
&ulPreviousValue ) == pdPASS )
{
/* 任务通知值已经更新 */
} else
{
/* 任务通知值没有更新 */
}

8、xTaskNotifyAndQueryFromISR()

        xTaskNotifyAndQueryFromISR()是 xTaskNotifyAndQuery ()的中断版本,用于向指定的任务发送一个任务通知, 并返回对象任务的上一个通知值, 该函数也是一个宏定义, 真正实现发送通知的是 xTaskGenericNotifyFromISR()。

void vAnISR( void )
{
/* xHigherPriorityTaskWoken 在使用之前必须设置为 pdFALSE */
BaseType_t xHigherPriorityTaskWoken = pdFALSE.
uint32_t ulPreviousValue;
/* 设置目标任务 xTask1Handle 的任务通知值的位 8 为 1
在任务通知值的位 8 被更新之前把上一次的值存储在变量 ulPreviousValue 中*/
xTaskNotifyAndQueryFromISR( xTask1Handle,
( 1UL << 8UL ),
eSetBits,
&ulPreviousValue,
&xHigherPriorityTaskWoken );
/* 如果任务 xTask1Handle 阻塞在任务通知上,那么现在已经被解锁进入就绪态
如果其优先级比当前正在运行的任务的优先级高,则 xHigherPriorityTaskWoken
会被设置为 pdRTUE,然后在中断退出前执行一次上下文切换,在中断退出后则去
执行这个被唤醒的高优先级的任务 */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

二、获取任务通知函数:

       只有两个 API 函数: ulTaskNotifyTake()和xTaskNotifyWait ()。前者是为代替二值信号量和计数信号量而专门设计的,它和发送通知API 函数 xTaskNotifyGive()、 vTaskNotifyGiveFromISR()配合使用后者是全功能版的等待通知,可以根据不同的参数实现轻量级二值信号量、计数信号量、事件组和长度为 1 的队列。
       所有的获取任务通知 API 函数都带有指定阻塞超时时间参数,当任务因为等待通知而进入阻塞时,用来指定任务的阻塞时间,这些超时机制与 FreeRTOS 的消息队列、信号量、事件等的超时机制一致。

1、ulTaskNotifyTake()

       ulTaskNotifyTake()作为二值信号量计数信号量的一种轻量级实现,速度更快。 
       对于这个函数,任务通知值为 0,对应信号量无效,如果任务设置了阻塞等待,任务被阻塞挂起。当其他任务或中断发送了通知值使其不为 0 后,通知变为有效,等待通知的任务将获取到通知, 并且在退出时候根据用户传递的第一个参数 xClearCountOnExit 选择清零通知值或者执行减一操作。
       ulTaskNotifyTake()在退出的时候处理任务的通知值的时候有两种方法,一种是在函数退出时将通知值清零,这种方法适用于实现二值信号量;另外一种是在函数退出时将通知值减 1,这种方法适用于实现计数信号量
       当一个任务使用其自身的任务通知值作为二值信号量或者计数信号量时, 其他任务应该使用函数 xTaskNotifyGive()或者 xTaskNotify( ( xTaskToNotify ), ( 0 ), eIncrement )来向其发送信号量。 如果是在中断中,则应该使用他们的中断版本函数。 

/* 中断服务程序:向一个任务发送任务通知 */
void vANInterruptHandler( void )
{
BaseType_t xHigherPriorityTaskWoken;
/* 清除中断 */
prvClearInterruptSource();
/* xHigherPriorityTaskWoken 在使用之前必须设置为 pdFALSE
如果调用 vTaskNotifyGiveFromISR()会解除 vHandlingTask 任务的阻塞状态,
并且 vHandlingTask 任务的优先级高于当前处于运行状态的任务,
则 xHigherPriorityTaskWoken 将会自动被设置为 pdTRUE */
xHigherPriorityTaskWoken = pdFALSE;
/* 发送任务通知,并解锁阻塞在该任务通知下的任务 */
vTaskNotifyGiveFromISR( xHandlingTask, &xHigherPriorityTaskWoken );
/* 如果被解锁的任务优先级比当前运行的任务的优先级高
则在中断退出前执行一次上下文切换,在中断退出后去执行
刚刚被唤醒的优先级更高的任务*/
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/*-----------------------------------------------------------*/
/* 任务:阻塞在一个任务通知上 */
void vHandlingTask( void *pvParameters )
{
BaseType_t xEvent;
for ( ;; ) {
/* 一直阻塞(没有时间限制,所以没有必要检测函数的返回值)
这里 RTOS 的任务通知值被用作二值信号量,所以在函数退出
时,任务通知值要被清 0 。要注意的是真正的应用程序不应该
无限期的阻塞*/
ulTaskNotifyTake( pdTRUE, /* 在退出前清 0 任务通知值 */
portMAX_DELAY ); /* 无限阻塞 */
/* RTOS 任务通知被当作二值信号量使用
当处理完所有的事情后继续等待下一个任务通知*/
do {
xEvent = xQueryPeripheral();
if ( xEvent != NO_MORE_EVENTS ) {
vProcessPeripheralEvent( xEvent );
}
} while ( xEvent != NO_MORE_EVENTS );
}
}

2、 xTaskNotifyWait()

        xTaskNotifyWait()函数用于实现全功能版的等待任务通知,根据用户指定的参数的不同,可以灵活的用于实现轻量级的消息队列队列、二值信号量、计数信号量和事件组功能,并带有超时等待。

/* 这个任务展示使用任务通知值的位来传递不同的事件
这在某些情况下可以代替事件标志组。 */
void vAnEventProcessingTask( void *pvParameters )
{
uint32_t ulNotifiedValue;
for ( ;; ) {
/* 等待任务通知,无限期阻塞(没有超时,所以没有必要检查函数返回值)
这个任务的任务通知值的位由标志事件发生的任务或者中断来设置*/
xTaskNotifyWait( 0x00, /* 在进入的时候不清除通知值的任何位 */
ULONG_MAX, /* 在退出的时候复位通知值为 0 */
&ulNotifiedValue, /* 任务通知值传递到变量
ulNotifiedValue 中*/
portMAX_DELAY ); /* 无限期等待 */
/* 根据任务通知值里面的各个位的值处理事情 */
if ( ( ulNotifiedValue & 0x01 ) != 0 ) {
/* 位 0 被置 1 */
prvProcessBit0Event();
}
if ( ( ulNotifiedValue & 0x02 ) != 0 ) {
/* 位 1 被置 1 */
prvProcessBit1Event();
}
if ( ( ulNotifiedValue & 0x04 ) != 0 ) {
/* 位 2 被置 1 */
prvProcessBit2Event();
}
/* ... 等等 */
}
}

 

 

 

 

 

 

  相关解决方案