FreeRTOS任务通知 基于STM32

文章目录

一、任务通知简介

二、任务通知的运作机制

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

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()解锁了解锁了接收该通知的任务
 而且该任务的优先级比当前运行的任务的优先级高,那么
 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();
 }
 }
 }

6.xTaskNotifyAndQuery()

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

xTaskNotifyAndQuery()函数说明

 7.xTaskNotifyAndQueryFromISR()

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

xTaskNotifyAndQueryFromISR()函数说明

 获取任务通知函数

      既然 FreeRTOS 中发送任务的函数有那么多个,那么任务怎么获取到通知呢?我们说 了,任务通知在某些场景可以替代信号量、消息队列、事件等。获取任务通知函数只能用 在任 务中,没有 带中断保 护版本,因 此只有两 个 API 函 数: ulTaskNotifyTake() 和 xTaskNotifyWait ()。前者是为代替二值信号量和计数信号量而专门设计的,它和发送通知 API 函数 xTaskNotifyGive()、vTaskNotifyGiveFromISR()配合使用;后者是全功能版的等待 通知,可以根据不同的参数实现轻量级二值信号量、计数信号量、事件组和长度为 1 的队 列。

      所有的获取任务通知 API 函数都带有指定阻塞超时时间参数,当任务因为等待通知而 进入阻塞时,用来指定任务的阻塞时间,这些超时机制与 FreeRTOS 的消息队列、信号量、 事件等的超时机制一致。

8.ulTaskNotifyTake()

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

       当一个任务使用其自身的任务通知值作为二值信号量或者计数信号量时,其他任务应 该使用函数 xTaskNotifyGive()或者 xTaskNotify( ( xTaskToNotify ), ( 0 ), eIncrement )来向其 发送信号量。如果是在中断中,则应该使用他们的中断版本函数。

ulTaskNotifyTake()函数说明

 ulTaskNotifyTake()函数应用举例

 /* 中断服务程序:向一个任务发送任务通知 */
 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 );
 }
 }

9.xTaskNotifyWait()

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();
 }
 
 /* ... 等等 */
  }
 }

四、任务通知实验

1 任务通知代替消息队列

       任务通知代替消息队列是在 FreeRTOS 中创建了三个任务,其中两个任务是用于接收 任务通知,另一个任务发送任务通知。三个任务独立运行,发送消息任务是通过检测按键 的按下情况来发送消息通知,另两个任务获取消息通知,在任务通知中没有可用的通知之 前就一直等待消息,一旦获取到消息通知就把消息打印在串口调试助手里。

/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
/* 开发板硬件bsp头文件 */
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"
#include "limits.h"
/**************************** 任务句柄 ********************************/
/* 
 * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
 * 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
 * 这个句柄可以为NULL。
 */
static TaskHandle_t AppTaskCreate_Handle = NULL;/* 创建任务句柄 */
static TaskHandle_t Receive1_Task_Handle = NULL;/* Receive1_Task任务句柄 */
static TaskHandle_t Receive2_Task_Handle = NULL;/* Receive2_Task任务句柄 */
static TaskHandle_t Send_Task_Handle = NULL;/* Send_Task任务句柄 */

/********************************** 内核对象句柄 *********************************/
/*
 * 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
 * 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
 * 们就可以通过这个句柄操作这些内核对象。
 *
 * 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
 * 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
 * 来完成的
 * 
 */


/******************************* 全局变量声明 ************************************/
/*
 * 当我们在写应用程序的时候,可能需要用到一些全局变量。
 */


/******************************* 宏定义 ************************************/
/*
 * 当我们在写应用程序的时候,可能需要用到一些宏定义。
 */
#define  USE_CHAR  0  /* 测试字符串的时候配置为 1 ,测试变量配置为 0  */

/*
*************************************************************************
*                             函数声明
*************************************************************************
*/
static void AppTaskCreate(void);/* 用于创建任务 */

static void Receive1_Task(void* pvParameters);/* Receive1_Task任务实现 */
static void Receive2_Task(void* pvParameters);/* Receive2_Task任务实现 */

static void Send_Task(void* pvParameters);/* Send_Task任务实现 */

static void BSP_Init(void);/* 用于初始化板载相关资源 */

/*****************************************************************
  * @brief  主函数
  * @param  无
  * @retval 无
  * @note   第一步:开发板硬件初始化 
            第二步:创建APP应用任务
            第三步:启动FreeRTOS,开始多任务调度
  ****************************************************************/
int main(void)
{	
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  /* 开发板硬件初始化 */
  BSP_Init();
	printf("这是一个FreeRTOS任务通知代替消息队列实验!\n");
  printf("按下KEY1或者KEY2进行任务消息通知发送 \n");
   /* 创建AppTaskCreate任务 */
  xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,  /* 任务入口函数 */
                        (const char*    )"AppTaskCreate",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )1, /* 任务的优先级 */
                        (TaskHandle_t*  )&AppTaskCreate_Handle);/* 任务控制块指针 */ 
  /* 启动任务调度 */           
  if(pdPASS == xReturn)
    vTaskStartScheduler();   /* 启动任务,开启调度 */
  else
    return -1;  
  
  while(1);   /* 正常不会执行到这里 */    
}


/***********************************************************************
  * @ 函数名  : AppTaskCreate
  * @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
  * @ 参数    : 无  
  * @ 返回值  : 无
  **********************************************************************/
static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  taskENTER_CRITICAL();           //进入临界区

  /* 创建Receive1_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Receive1_Task, /* 任务入口函数 */
                        (const char*    )"Receive1_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,	/* 任务入口函数参数 */
                        (UBaseType_t    )2,	    /* 任务的优先级 */
                        (TaskHandle_t*  )&Receive1_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建Receive1_Task任务成功!\r\n");
  
  /* 创建Receive2_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Receive2_Task, /* 任务入口函数 */
                        (const char*    )"Receive2_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,	/* 任务入口函数参数 */
                        (UBaseType_t    )3,	    /* 任务的优先级 */
                        (TaskHandle_t*  )&Receive2_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建Receive2_Task任务成功!\r\n");
  
  /* 创建Send_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Send_Task,  /* 任务入口函数 */
                        (const char*    )"Send_Task",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )4, /* 任务的优先级 */
                        (TaskHandle_t*  )&Send_Task_Handle);/* 任务控制块指针 */ 
  if(pdPASS == xReturn)
    printf("创建Send_Task任务成功!\n\n");
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}



/**********************************************************************
  * @ 函数名  : Receive_Task
  * @ 功能说明: Receive_Task任务主体
  * @ 参数    :   
  * @ 返回值  : 无
  ********************************************************************/
static void Receive1_Task(void* parameter)
{	
  BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
#if USE_CHAR
  char *r_char;
#else
  uint32_t r_num;
#endif
  while (1)
  {
    /* BaseType_t xTaskNotifyWait(uint32_t ulBitsToClearOnEntry, 
                                  uint32_t ulBitsToClearOnExit, 
                                  uint32_t *pulNotificationValue, 
                                  TickType_t xTicksToWait ); 
     * ulBitsToClearOnEntry:当没有接收到任务通知的时候将任务通知值与此参数的取
       反值进行按位与运算,当此参数为Oxfffff或者ULONG_MAX的时候就会将任务通知值清零。
     * ulBits ToClearOnExit:如果接收到了任务通知,在做完相应的处理退出函数之前将
       任务通知值与此参数的取反值进行按位与运算,当此参数为0xfffff或者ULONG MAX的时候
       就会将任务通知值清零。
     * pulNotification Value:此参数用来保存任务通知值。
     * xTick ToWait:阻塞时间。
     *
     * 返回值:pdTRUE:获取到了任务通知。pdFALSE:任务通知获取失败。
     */
    //获取任务通知 ,没获取到则一直等待
		xReturn=xTaskNotifyWait(0x0,			//进入函数的时候不清除任务bit
                            ULONG_MAX,	  //退出函数的时候清除所有的bit
#if USE_CHAR
                            (uint32_t *)&r_char,		  //保存任务通知值
#else
                            &r_num,		  //保存任务通知值
#endif                        
                            portMAX_DELAY);	//阻塞时间
    if( pdTRUE == xReturn )
#if USE_CHAR
      printf("Receive1_Task 任务通知消息为 %s \n",r_char);                      
#else
      printf("Receive1_Task 任务通知消息为 %d \n",r_num);                      
#endif  
     
    
		LED1_TOGGLE;
  }
}

/**********************************************************************
  * @ 函数名  : Receive_Task
  * @ 功能说明: Receive_Task任务主体
  * @ 参数    :   
  * @ 返回值  : 无
  ********************************************************************/
static void Receive2_Task(void* parameter)
{	
  BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
#if USE_CHAR
  char *r_char;
#else
  uint32_t r_num;
#endif
  while (1)
  {
    /* BaseType_t xTaskNotifyWait(uint32_t ulBitsToClearOnEntry, 
                                  uint32_t ulBitsToClearOnExit, 
                                  uint32_t *pulNotificationValue, 
                                  TickType_t xTicksToWait ); 
     * ulBitsToClearOnEntry:当没有接收到任务通知的时候将任务通知值与此参数的取
       反值进行按位与运算,当此参数为Oxfffff或者ULONG_MAX的时候就会将任务通知值清零。
     * ulBits ToClearOnExit:如果接收到了任务通知,在做完相应的处理退出函数之前将
       任务通知值与此参数的取反值进行按位与运算,当此参数为0xfffff或者ULONG MAX的时候
       就会将任务通知值清零。
     * pulNotification Value:此参数用来保存任务通知值。
     * xTick ToWait:阻塞时间。
     *
     * 返回值:pdTRUE:获取到了任务通知。pdFALSE:任务通知获取失败。
     */
    //获取任务通知 ,没获取到则一直等待
		xReturn=xTaskNotifyWait(0x0,			//进入函数的时候不清除任务bit
                            ULONG_MAX,	  //退出函数的时候清除所有的bit
#if USE_CHAR
                            (uint32_t *)&r_char,		  //保存任务通知值
#else
                            &r_num,		  //保存任务通知值
#endif                        
                            portMAX_DELAY);	//阻塞时间
    if( pdTRUE == xReturn )
#if USE_CHAR
      printf("Receive2_Task 任务通知消息为 %s \n",r_char);                      
#else
      printf("Receive2_Task 任务通知消息为 %d \n",r_num);                      
#endif  
		LED2_TOGGLE;
  }
}

/**********************************************************************
  * @ 函数名  : Send_Task
  * @ 功能说明: Send_Task任务主体
  * @ 参数    :   
  * @ 返回值  : 无
  ********************************************************************/
static void Send_Task(void* parameter)
{	 
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
#if USE_CHAR
  char test_str1[] = "this is a mail test 1";/* 邮箱消息test1 */
  char test_str2[] = "this is a mail test 2";/* 邮箱消息test2 */
#else
  uint32_t send1 = 1;
  uint32_t send2 = 2;
#endif
  

  
  while (1)
  {
    /* KEY1 被按下 */
    if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
    {
      /* 原型:BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, 
                                      uint32_t ulValue, 
                                      eNotifyAction eAction ); 
       * eNoAction = 0,通知任务而不更新其通知值。
       * eSetBits,     设置任务通知值中的位。
       * eIncrement,   增加任务的通知值。
       * eSetvaluewithoverwrite,覆盖当前通知
       * eSetValueWithoutoverwrite 不覆盖当前通知
       * 
       * pdFAIL:当参数eAction设置为eSetValueWithoutOverwrite的时候,
       * 如果任务通知值没有更新成功就返回pdFAIL。
       * pdPASS: eAction 设置为其他选项的时候统一返回pdPASS。
      */
      xReturn = xTaskNotify( Receive1_Task_Handle, /*任务句柄*/
#if USE_CHAR 
                             (uint32_t)&test_str1, /* 发送的数据,最大为4字节 */
#else
                              send1, /* 发送的数据,最大为4字节 */
#endif
                             eSetValueWithOverwrite );/*覆盖当前通知*/
      
      if( xReturn == pdPASS )
        printf("Receive1_Task_Handle 任务通知消息发送成功!\r\n");
    } 
    /* KEY2 被按下 */
    if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
    {
      xReturn = xTaskNotify( Receive2_Task_Handle, /*任务句柄*/
#if USE_CHAR 
                             (uint32_t)&test_str2, /* 发送的数据,最大为4字节 */
#else
                              send2, /* 发送的数据,最大为4字节 */
#endif
                             eSetValueWithOverwrite );/*覆盖当前通知*/
      /* 此函数只会返回pdPASS */
      if( xReturn == pdPASS )
        printf("Receive2_Task_Handle 任务通知消息发送成功!\r\n");
    }
    vTaskDelay(20);
  }
}
/***********************************************************************
  * @ 函数名  : BSP_Init
  * @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
  * @ 参数    :   
  * @ 返回值  : 无
  *********************************************************************/
static void BSP_Init(void)
{
	/*
	 * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* LED 初始化 */
	LED_GPIO_Config();

	/* 串口初始化	*/
	USART_Config();
  
  /* 按键初始化	*/
  Key_GPIO_Config();

}

/********************************END OF FILE****************************/

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

      任务通知代替消息队列是在 FreeRTOS 中创建了三个任务,其中两个任务是用于接收 任务通知,另一个任务发送任务通知。三个任务独立运行,发送通知任务是通过检测按键 的按下情况来发送通知,另两个任务获取通知,在任务通知中没有可用的通知之前就一直 等待任务通知,获取到通知以后就将通知值清 0,这样子是为了代替二值信号量,任务同 步成功则继续执行,然后在串口调试助手里将运行信息打印出来。

 
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
/* 开发板硬件bsp头文件 */
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"
/**************************** 任务句柄 ********************************/
/* 
 * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
 * 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
 * 这个句柄可以为NULL。
 */
static TaskHandle_t AppTaskCreate_Handle = NULL;/* 创建任务句柄 */
static TaskHandle_t Receive1_Task_Handle = NULL;/* Receive1_Task任务句柄 */
static TaskHandle_t Receive2_Task_Handle = NULL;/* Receive2_Task任务句柄 */
static TaskHandle_t Send_Task_Handle = NULL;/* Send_Task任务句柄 */

/********************************** 内核对象句柄 *********************************/
/*
 * 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
 * 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
 * 们就可以通过这个句柄操作这些内核对象。
 *
 * 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
 * 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
 * 来完成的
 * 
 */


/******************************* 全局变量声明 ************************************/
/*
 * 当我们在写应用程序的时候,可能需要用到一些全局变量。
 */


/******************************* 宏定义 ************************************/
/*
 * 当我们在写应用程序的时候,可能需要用到一些宏定义。
 */


/*
*************************************************************************
*                             函数声明
*************************************************************************
*/
static void AppTaskCreate(void);/* 用于创建任务 */

static void Receive1_Task(void* pvParameters);/* Receive1_Task任务实现 */
static void Receive2_Task(void* pvParameters);/* Receive2_Task任务实现 */

static void Send_Task(void* pvParameters);/* Send_Task任务实现 */

static void BSP_Init(void);/* 用于初始化板载相关资源 */

/*****************************************************************
  * @brief  主函数
  * @param  无
  * @retval 无
  * @note   第一步:开发板硬件初始化 
            第二步:创建APP应用任务
            第三步:启动FreeRTOS,开始多任务调度
  ****************************************************************/
int main(void)
{	
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  /* 开发板硬件初始化 */
  BSP_Init();
	printf("这是一个FreeRTOS任务通知代替二值信号量实验!\n");
  printf("按下KEY1或者KEY2进行任务与任务间的同步\n");
   /* 创建AppTaskCreate任务 */
  xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,  /* 任务入口函数 */
                        (const char*    )"AppTaskCreate",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )1, /* 任务的优先级 */
                        (TaskHandle_t*  )&AppTaskCreate_Handle);/* 任务控制块指针 */ 
  /* 启动任务调度 */           
  if(pdPASS == xReturn)
    vTaskStartScheduler();   /* 启动任务,开启调度 */
  else
    return -1;  
  
  while(1);   /* 正常不会执行到这里 */    
}


/***********************************************************************
  * @ 函数名  : AppTaskCreate
  * @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
  * @ 参数    : 无  
  * @ 返回值  : 无
  **********************************************************************/
static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  taskENTER_CRITICAL();           //进入临界区

  /* 创建Receive1_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Receive1_Task, /* 任务入口函数 */
                        (const char*    )"Receive1_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,	/* 任务入口函数参数 */
                        (UBaseType_t    )2,	    /* 任务的优先级 */
                        (TaskHandle_t*  )&Receive1_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建Receive1_Task任务成功!\r\n");
  
  /* 创建Receive2_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Receive2_Task, /* 任务入口函数 */
                        (const char*    )"Receive2_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,	/* 任务入口函数参数 */
                        (UBaseType_t    )3,	    /* 任务的优先级 */
                        (TaskHandle_t*  )&Receive2_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建Receive2_Task任务成功!\r\n");
  
  /* 创建Send_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Send_Task,  /* 任务入口函数 */
                        (const char*    )"Send_Task",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )4, /* 任务的优先级 */
                        (TaskHandle_t*  )&Send_Task_Handle);/* 任务控制块指针 */ 
  if(pdPASS == xReturn)
    printf("创建Send_Task任务成功!\n\n");
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}



/**********************************************************************
  * @ 函数名  : Receive_Task
  * @ 功能说明: Receive_Task任务主体
  * @ 参数    :   
  * @ 返回值  : 无
  ********************************************************************/
static void Receive1_Task(void* parameter)
{	
  while (1)
  {
    /* uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait ); 
     * xClearCountOnExit:pdTRUE 在退出函数的时候任务任务通知值清零,类似二值信号量
     * pdFALSE 在退出函数ulTaskNotifyTakeO的时候任务通知值减一,类似计数型信号量。
     */
    //获取任务通知 ,没获取到则一直等待
    ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
    
    printf("Receive1_Task 任务通知获取成功!\n\n");
    
		LED1_TOGGLE;
  }
}

/**********************************************************************
  * @ 函数名  : Receive_Task
  * @ 功能说明: Receive_Task任务主体
  * @ 参数    :   
  * @ 返回值  : 无
  ********************************************************************/
static void Receive2_Task(void* parameter)
{	
  while (1)
  {
    /* uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait ); 
     * xClearCountOnExit:pdTRUE 在退出函数的时候任务任务通知值清零,类似二值信号量
     * pdFALSE 在退出函数ulTaskNotifyTakeO的时候任务通知值减一,类似计数型信号量。
     */
    //获取任务通知 ,没获取到则一直等待
    ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
    
    printf("Receive2_Task 任务通知获取成功!\n\n");
    
		LED2_TOGGLE;
  }
}

/**********************************************************************
  * @ 函数名  : Send_Task
  * @ 功能说明: Send_Task任务主体
  * @ 参数    :   
  * @ 返回值  : 无
  ********************************************************************/
static void Send_Task(void* parameter)
{	 
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  while (1)
  {
    /* KEY1 被按下 */
    if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
    {
      /* 原型:BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); */
      xReturn = xTaskNotifyGive(Receive1_Task_Handle);
      /* 此函数只会返回pdPASS */
      if( xReturn == pdTRUE )
        printf("Receive1_Task_Handle 任务通知发送成功!\r\n");
    } 
    /* KEY2 被按下 */
    if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
    {
      xReturn = xTaskNotifyGive(Receive2_Task_Handle);
      /* 此函数只会返回pdPASS */
      if( xReturn == pdPASS )
        printf("Receive2_Task_Handle 任务通知发送成功!\r\n");
    }
    vTaskDelay(20);
  }
}
/***********************************************************************
  * @ 函数名  : BSP_Init
  * @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
  * @ 参数    :   
  * @ 返回值  : 无
  *********************************************************************/
static void BSP_Init(void)
{
	/*
	 * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* LED 初始化 */
	LED_GPIO_Config();

	/* 串口初始化	*/
	USART_Config();
  
  /* 按键初始化	*/
  Key_GPIO_Config();

}

/********************************END OF FILE****************************/

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

      任务通知代替计数信号量是基于计数型信号量实验修改而来,模拟停车场工作运行。 并且在 FreeRTOS 中创建了两个任务:一个是获取任务通知,一个是发送任务通知,两个 任务独立运行,获取通知的任务是通过按下 KEY1 按键获取,模拟停车场停车操作,其等 待时间是 0;发送通知的任务则是通过检测 KEY2 按键按下进行通知的发送,模拟停车场 取车操作,并且在串口调试助手输出相应信息。

 
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/* 开发板硬件bsp头文件 */
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"
/**************************** 任务句柄 ********************************/
/* 
 * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
 * 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
 * 这个句柄可以为NULL。
 */
static TaskHandle_t AppTaskCreate_Handle = NULL;/* 创建任务句柄 */
static TaskHandle_t Take_Task_Handle = NULL;/* Take_Task任务句柄 */
static TaskHandle_t Give_Task_Handle = NULL;/* Give_Task任务句柄 */

/********************************** 内核对象句柄 *********************************/
/*
 * 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
 * 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
 * 们就可以通过这个句柄操作这些内核对象。
 *
 * 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
 * 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
 * 来完成的
 * 
 */
SemaphoreHandle_t CountSem_Handle =NULL;

/******************************* 全局变量声明 ************************************/
/*
 * 当我们在写应用程序的时候,可能需要用到一些全局变量。
 */


/******************************* 宏定义 ************************************/
/*
 * 当我们在写应用程序的时候,可能需要用到一些宏定义。
 */


/*
*************************************************************************
*                             函数声明
*************************************************************************
*/
static void AppTaskCreate(void);/* 用于创建任务 */

static void Take_Task(void* pvParameters);/* Take_Task任务实现 */
static void Give_Task(void* pvParameters);/* Give_Task任务实现 */

static void BSP_Init(void);/* 用于初始化板载相关资源 */

/*****************************************************************
  * @brief  主函数
  * @param  无
  * @retval 无
  * @note   第一步:开发板硬件初始化 
            第二步:创建APP应用任务
            第三步:启动FreeRTOS,开始多任务调度
  ****************************************************************/
int main(void)
{	
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  /* 开发板硬件初始化 */
  BSP_Init();
  
  printf("这是一个任务通知代替计数信号量实验!\n");
  printf("车位默认值为0个,按下KEY1申请车位,按下KEY2释放车位!\n\n");
  
  /* 创建AppTaskCreate任务 */
  xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,  /* 任务入口函数 */
                        (const char*    )"AppTaskCreate",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )1, /* 任务的优先级 */
                        (TaskHandle_t*  )&AppTaskCreate_Handle);/* 任务控制块指针 */ 
  /* 启动任务调度 */           
  if(pdPASS == xReturn)
    vTaskStartScheduler();   /* 启动任务,开启调度 */
  else
    return -1;  
  
  while(1);   /* 正常不会执行到这里 */    
}


/***********************************************************************
  * @ 函数名  : AppTaskCreate
  * @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
  * @ 参数    : 无  
  * @ 返回值  : 无
  **********************************************************************/
static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  taskENTER_CRITICAL();           //进入临界区

  /* 创建Take_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Take_Task, /* 任务入口函数 */
                        (const char*    )"Take_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,	/* 任务入口函数参数 */
                        (UBaseType_t    )2,	    /* 任务的优先级 */
                        (TaskHandle_t*  )&Take_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建Take_Task任务成功!\r\n");
  
  /* 创建Give_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Give_Task,  /* 任务入口函数 */
                        (const char*    )"Give_Task",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )3, /* 任务的优先级 */
                        (TaskHandle_t*  )&Give_Task_Handle);/* 任务控制块指针 */ 
  if(pdPASS == xReturn)
    printf("创建Give_Task任务成功!\n\n");
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}



/**********************************************************************
  * @ 函数名  : Take_Task
  * @ 功能说明: Take_Task任务主体
  * @ 参数    :   
  * @ 返回值  : 无
  ********************************************************************/
static void Take_Task(void* parameter)
{	
  uint32_t take_num = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
  /* 任务都是一个无限循环,不能返回 */
  while (1)
  {
    //如果KEY1被单击
		if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )       
		{
      /* uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait ); 
       * xClearCountOnExit:pdTRUE 在退出函数的时候任务任务通知值清零,类似二值信号量
       * pdFALSE 在退出函数ulTaskNotifyTakeO的时候任务通知值减一,类似计数型信号量。
       */
      //获取任务通知 ,没获取到则不等待
      take_num=ulTaskNotifyTake(pdFALSE,0);//
      if(take_num > 0)
        printf( "KEY1被按下,成功申请到停车位,当前车位为 %d \n", take_num - 1);
			else
        printf( "KEY1被按下,车位已经没有了,请按KEY2释放车位\n" );  
		}
		vTaskDelay(20);     //每20ms扫描一次		
  }
}

/**********************************************************************
  * @ 函数名  : Give_Task
  * @ 功能说明: Give_Task任务主体
  * @ 参数    :   
  * @ 返回值  : 无
  ********************************************************************/
static void Give_Task(void* parameter)
{	 
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  /* 任务都是一个无限循环,不能返回 */
  while (1)
  {
    //如果KEY2被单击
		if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )       
		{
      /* 原型:BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); */
			/* 释放一个任务通知 */
      xTaskNotifyGive(Take_Task_Handle);//发送任务通知
      /* 此函数只会返回pdPASS */
			if ( pdPASS == xReturn ) 
				printf( "KEY2被按下,释放1个停车位。\n" );
		}
		vTaskDelay(20);     //每20ms扫描一次	
  }
}
/***********************************************************************
  * @ 函数名  : BSP_Init
  * @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
  * @ 参数    :   
  * @ 返回值  : 无
  *********************************************************************/
static void BSP_Init(void)
{
	/*
	 * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* LED 初始化 */
	LED_GPIO_Config();

	/* 串口初始化	*/
	USART_Config();
  
  /* 按键初始化	*/
  Key_GPIO_Config();

}

/********************************END OF FILE****************************/

五、实验现象

1. 任务通知代替消息队列

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

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

猜你喜欢

转载自blog.csdn.net/qq_61672347/article/details/125651530