细说STM32单片机FreeRTOS任务通知及其应用实例
目录
一、任务通知的原理和功能
1、 进程间使用任务通知直接通信的基本原理
2、任务通知的优点
3、任务通知的局限性
二、任务通知的相关函数
1、相关函数概述
2、函数详解
(1)函数xTaskNotify()
(2)函数xTaskNotifyAndQuery()
(3)函数xTaskNotifyGive()
(4)函数xTaskNotifyWait()
(5)函数ulTaskNotifyTake()
(6)函数xTaskNotifyStateClear()
三、示例:使用任务通知传递数据
1、示例功能与CubeMX项目设置
(1)RCC、SYS、Code Gennerator、USART3、TIM6
(2)ADC3_IN6
(3)TIM3
(4) RTOS
(5)NVIC
2、程序功能实现
(1)主程序
(2)FreeRTOS对象初始化
(3)任务通知的接收
(4)在ADC3的中断里发送通知
3、运行调试
任务通知(task notification)是FreeRTOS中的另外一种进程间通信技术。使用任务通知不需要创建任何中间对象,可以直接从任务向任务,或从ISR向任务发送通知,传递一个通知值(notification value)。任务通知可以模拟二值信号量、计数信号量,或长度为1的消息队列,使用任务通知,通常效率更高、消耗内存更少。
一、任务通知的原理和功能
如队列、信号量、事件组等这样的进程间通信技术都需要创建一个中间对象,进程之间通过这些中间对象进行通信或同步。
在FreeRTOS中还有一种无须创建中间对象的进程间直接通信方法,那就是任务通知方法。要使用任务通知方法,需要将参数configUSE_TASK_NOTIFICATIONS设置为1。这个参数默认值为1,且可以在CubeMX中设置。
当将参数configUSE_TASK_NOTIFICATIONS设置为1时,任务的任务控制块中会增加一个uint32_t类型的通知值变量,并且任务接收通知的状态有挂起(pending)和非挂起(not-pending)两种状态。
1、 进程间使用任务通知直接通信的基本原理
- 一个任务或ISR向另外一个指定的任务发送通知,则将发送通知的进程称为发送者(sender),将接收通知的进程称为接收者(receiver)。
- 发送者可以是任务或ISR,接收者只能是任务,不能是ISR。
- 发送者发送通知时,可以带一个通知值,或者是使接收者的通知值发生改变的计算方法,例如,使通知值加1。发送者只管发送通知,不会进入阻塞状态,是否接收和处理通知由接收者决定。
- 接收者有未处理的通知时,处于挂起状态。接收者可以进入阻塞状态等待通知,收到通知后退出阻塞状态,再做处理。
2、任务通知的优点
由任务通知的存储特点和工作原理可知,任务通知有如下优点:
- 性能更高。使用任务通知在进程间传递数据时,比使用队列或信号量等方法的速度快得多。
- 内存开销小。使用任务通知时内存开销小,因为只需在任务控制块中增加几个变量。而使用队列、信号量等,则需要先创建这些对象。
3、任务通知的局限性
当然,任务通知也有一些局限性,具体如下:
- 使用任务通知时,不能向ISR发送通知,只能是任务或ISR向任务发送通知。
- 任务通知指定了接收者,多个发送者可以向同一个接收者发送不同的通知,但是发送者不能将一个通知发送给不同的接收者,也就是不能进行消息广播。
- 任务通知一次只能发送或接收一个uint32_t类型的数据,不能像消息队列那样发送多个缓存数据,因为任务控制块的数据缓存里只有一个uint32_t类型的通知值。
使用任务通知可以代替二值信号量、计数信号量、事件组,可以代替只有一个uint32_t类型存储单元的队列。任务通知使用比较灵活,而且工作效率高。
二、任务通知的相关函数
1、相关函数概述
要使用任务通知功能,需要将参configUSE_TASK_NOTIFICATIONS设置为1,这个参数默认值为1,可在CubeMX中设置。
使用任务通知无须创建中间对象,任务操作的相关函数主要是发送者发送通知,接收者等待和接收通知。任务通知的相关函数都在文件task.h中定义。
分组 | 函数 | 功能 |
发送通知 | xTaskNotify() | 向一个任务发送通知,带有通知值,还有值的传递方式设置。适用于利用通知值直接传递数据的应用场景 |
xTaskNotifyFromISR() | xTaskNotify()的ISR版本 | |
xTaskNotifyAndQuery() | 与xTaskNotify()的功能相似,但是可以返回接收者之前的通知值 | |
xTaskNotifyAndQueryFromISR() | xTaskNotifyAndQuery()的ISR版本 | |
xTaskNotifyGive() | 向一个任务发送通知,不带通知值,只是使接收者的通知值加1。适用于将任务通知当作二值信号量或计数信号量使用的应用场景 | |
vTaskNotifyGiveFromISR() | xTaskNotifyGive()的ISR版本 | |
接收通知 | xTaskNotifyWait() | 等待并获取任务通知值的通用函数,可以设置进入和退出等待时的任务通知值,例如,进入时将通知值清零,或退出时将通知值清零 |
ulTaskNotifyTake() | 等待并获取任务通知值,可在退出等待时将通知值减1 | |
其他 | xTaskNotifyStateClear() | 清除任务的等待状态,任务的通知值不变 |
可以在任务或ISR里发送通知,所以发送通知的函数有任务和ISR两种版本,只有任务能接收通知,所以接收通知的函数没有ISR版本。发送和接收通知的函数可以分为以下两组。
- 通用版本的函数xTaskNotify()和xTaskNotifyWait(),可以发送任意的通知值,适合在进程间通过通知值直接传递数据。
- 将任务通知用作二值信号量或计数信号量的函数xTaskNotifyGive()和ulTaskNotifyTake(),发送时,使接收者的通知值加1,接收时,使通知值减1或清零。
2、函数详解
(1)函数xTaskNotify()
函数xTaskNotify()是发送通知的通用函数,它是一个宏函数,定义如下:
#define xTaskNotify( xTaskToNotify, ulValue, eAction ) xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL )
它实际上是执行了函数xTaskGenericNotify(),这个函数的原型定义如下:
BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue ) PRIVILEGED_FUNCTION;
其中,几个参数的意义如下:
- 参数xTaskToNotify是接收者任务的句柄。
- 参数ulValue是发送的通知值。
- 参数eAction是通知值的作用方式,是枚举类型eNotifyAction,其定义如下。各枚举值与参数ulValue的结合,决定了如何改变接收者的通知值。
- 参数pulPreviousNotificationValue,返回接收者的通知值被改变之前的值。
/* Actions that can be performed when vTaskNotify() is called. */
typedef enum
{eNoAction = 0, /* Notify the task without updating its notify value. */eSetBits, /* Set bits in the task's notification value. */eIncrement, /* Increment the task's notification value. */eSetValueWithOverwrite, /* Set the task's notification value to a specific value even if the previous value has not yet been read by the task. */eSetValueWithoutOverwrite /* Set the task's notification value if the previous value has been read by the task. */
} eNotifyAction;
xTaskNotify()在调用函数xTaskGenericNotify()时,没有传递最后一个参数,所以不能返回接收者更新之前的通知值。函数xTaskNotify()的返回值是更新之后接收者的通知值。
函数xTaskNotify()在发送通知时,根据参数ulValue和eAction的取值,有不同的改变接收者通知值的方式,也适用于不同的应用场景。例如,eAction等于eSetBits时,ulValue与接收者当前通知值进行按位或运算,这适用于将任务通知作为事件组使用的场景;eAction等于eIncrement时,接收者的通知值在当前基础上加1,与ulValue无关,这适用于将任务通知作为二值信号量或计数信号量使用的场景。
函数xTaskNotifyFromISR()是xTaskNotify()的ISR版本,也是一个宏函数,其原型定义如下:
BaseType_t xTaskGenericNotifyFromISR( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue, BaseType_t *pxHigherPriorityTaskWoken ) PRIVILEGED_FUNCTION;
#define xTaskNotifyFromISR( xTaskToNotify, ulValue, eAction, pxHigherPriorityTaskWoken ) xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL, ( pxHigherPriorityTaskWoken ) )
#define xTaskNotifyAndQueryFromISR( xTaskToNotify, ulValue, eAction, pulPreviousNotificationValue, pxHigherPriorityTaskWoken ) xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotificationValue ), ( pxHigherPriorityTaskWoken ) )
它实际上是调用了函数xTaskGenericNotifyFromISR(),这个函数的原型定义如下:
前4个参数与函数xTaskGenericNotify()中的参数相同,最后一个参数pxHigherPriorityTaskWoken是一个BaseType_t类型的指针,实际上是一个返回数据,表示退出中断ISR之前,是否需要申请进行上下文切换。调用函数portYIELD_FROM_ISR()进行上下文切换申请,调用该函数的示意代码如下:
BaseType_t highTaskWoken=pdFALSE;
xTaskNotifyFromISR(xTaskToNotify,ulValue,eAction,&highTaskWoken);
portYIELD_FROM_ISR(highTaskWoken);
(2)函数xTaskNotifyAndQuery()
函数xTaskNotifyAndQuery()与xTaskNotify()的功能相同,但是能返回接收者通前的值。函数xTaskNotifyAndQuery()的原型如下:
BaseType_t xTaskGenericNotifyFromISR( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue, BaseType_t *pxHigherPriorityTaskWoken ) PRIVILEGED_FUNCTION;
#define xTaskNotifyFromISR( xTaskToNotify, ulValue, eAction, pxHigherPriorityTaskWoken ) xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL, ( pxHigherPriorityTaskWoken ) )
#define xTaskNotifyAndQueryFromISR( xTaskToNotify, ulValue, eAction, pulPreviousNotificationValue, pxHigherPriorityTaskWoken ) xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotificationValue ), ( pxHigherPriorityTaskWoken ) )
(3)函数xTaskNotifyGive()
函数xTaskNotifyGive()是xTaskNotify()的一种功能简化版本,它的功能是发送通知,使接收者的通知值加1。其原型定义如下:
#define xTaskNotifyGive( xTaskToNotify ) xTaskGenericNotify( ( xTaskToNotify ), ( 0 ), eIncrement, NULL )
函数 xTaskNotifyGive()也调用了函数 xTaskGenericNotify(),但是默认传递了参数 ulValue为0,eAction为eIncrement,pulPreviousNotificationValue为NULL。所以,函数xTaskNotifyGive()的功能就是使接收者的通知值加1,这使其适用于将任务通知当作二值信号量或计数信号量使用的场合。
函数vTaskNotifyGiveFromISR()是xTaskNotifyGive()的ISR版本,其原型定义如下。其中的参数pxHigherPriorityTaskWoken与函数xTaskNotifyFromISR()中同名参数的意义和使用方法相同。
* \defgroup xTaskNotifyWait xTaskNotifyWait
* \ingroup TaskNotifications
*/
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken ) PRIVILEGED_FUNCTION;
(4)函数xTaskNotifyWait()
接收者使用函数xTaskNotifyWait()等待任务通知并获取通知值,其原型定义如下:
* \defgroup xTaskNotifyWait xTaskNotifyWait
* \ingroup TaskNotifications
*/
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;
其中,几个参数的意义如下。
- 参数u1BitsToClearOnEntry是在函数进入时需要清零的通知值的位掩码。需要清零的位在掩码中用1表示,否则,用0表示。计算方法是ulBitsToClearOnEntry按位取反后与当前的通知值进行按位与运算,用计算的结果更新通知值。例如,如果ulBitsToClearOnEntry设置为0,就是不更改当前的通知值;如果ulBitsToClearOnEntry设置为0xFFFFFFFF,就是将所有位清零,也就是使通知值清零。注意,通过ulBitsToClearOnEntry更改通知值,只在函数xTaskNotifyWait()进入且没有接收到任务通知时,才会执行,执行后进入阻塞状态,等待任务通知。如果函数在进入时已经有挂起待处理的任务通知,则不会更新当前的通知值。
- 参数ulBitsToClearOnExit是函数在退出时需要清零的通知值的位掩码。如果设置为0,就是不更改通知值;如果设置为0xFFFFFFFF,就是将通知值设置为0。注意,这个操作在函数从等待超时状态退出,也就是没有接收到任务通知时是不执行的。
- 参数pulNotificationValue是一个uint32_t类型的指针,用于返回接收的通知值。
- 参数XTicksToWait是函数在阻塞状态等待的节拍数。如果设置为常数portMAX_DELAY,就是一直等待;如果设置为0,则表示不等待。函数的返回值是pdTRUE或pdFALSE,pdTRUE表示接收了任务通知,包括函数一进入就读取已挂起的任务通知。
执行函数xTaskNotifyWait()时,如果任务是未挂起状态,也就是没有待处理的任务通知,任务就进入阻塞状态,等待接收通知;如果任务是挂起状态,也就是有未处理的任务通知,就立刻读取通知值,然后返回。在阻塞等待状态下,任务接收到新的任务通知,或等待超时就退出阻塞状态。
函数xTaskNotifyWait()是等待任务通知的通用函数。用户可以通过参数ulBitsToClearOnEntry设置通知值的初值,如设置为0。在退出时,用户可以通过参数ulBitsToClearOnExit对通知值做一些处理,如清零。
(5)函数ulTaskNotifyTake()
函数ulTaskNotifyTake()是另一个等待任务通知的函数,适用于将任务通知当作二值信号量或计数信号量使用的场合。这个函数的原型定义如下:
* \defgroup ulTaskNotifyTake ulTaskNotifyTake
* \ingroup TaskNotifications
*/
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;
- 参数xClearCountOnExit的取值为pdTRUE或pdFALSE。当取值为pdTRUE时,函数在接收通知后退出时会将通知值清零,这种情况下,任务通知被当作二值信号量使用;当取值为pdFALSE时,函数在接收通知后退出时会将通知值减1,这种情况下,任务通知被当作计数信号量使用。
- 参数xTicksToWait是在阻塞状态等待任务通知的节拍数。如果设置为常数portMAX_DELAY,则表示一直等待;如果设置为0,则表示不等待。
- 函数的返回值是减1或清零之前的通知值。
函数ulTaskNotifyTake()一般与函数xTaskNotifyGive()搭配使用,将任务通知当作二值信号量或计数信号量使用。xTaskNotifyGive()发送通知使接收者的通知值加1,uITaskNotifyTake()接收通知后,使通知值减1或复位为0。
将通知值当作二值信号量或计数信号量使用的操作如图所示,图中的变量Value表示通知值。将任务通知当作计数信号量使用时,操作特点如下。
- 接收者的通知值初始为0。
- 使用ulTaskNotifyGive()发送通知时,即使接收者没有接收和处理,通知值也会每次加1,例如,多次发送后,Value变为5。
- 执行函数ulTaskNotifyTake()时,如果通知值大于1,即使处于未挂起状态,函数也会立刻使通知值减1后返回,不会等待新的任务通知。如果当前通知值为0,接收者才会进入阻塞状态,等待新的任务通知。
(6)函数xTaskNotifyStateClear()
函数xTaskNotifyStateClear()的功能是清除接收者的任务通知等待状态,使其变为未挂起状态,但是不会将接收者的通知值清零。其原型定义如下:
BaseType_t xTaskNotifystateclear(TaskHandle_t xTask);
其中,参数xTask是需要操作的任务句柄,如果参数xTask设置为NULL,则表示清除当前任务的通知状态。
三、示例:使用任务通知传递数据
1、示例功能与CubeMX项目设置
本例设计一个示例使用中断方式进行ADC转换,然后,通过任务通知,将ADC转换结果作为通知值发送给另一个任务加以显示。
ADC3_IN6在定时器TIM3的触发下进行ADC转换,TIM3的定时周期为500ms。在ADC3的ISR里,通过函数xTaskNotifyFromISR()将ADC转换结果数据发送给任务Task_Show。
任务Task_Show总是使用函数xTaskNotifyWait()等待任务通知,读取通知值后,在串口助手上显示。
继续使用旺宝红龙开发板STM32F407ZGT6 KIT V1.0。一些设置可以参考本文作者写的其他文章:
细说STM32单片机FreeRTOS用事件组同步任务的方法及其应用实例-CSDN博客 https://wenchm.blog.csdn.net/article/details/147951964?spm=1011.2415.3001.5331
(1)RCC、SYS、Code Gennerator、USART3、TIM6
该部分的设置可以参考本文作者发布的其他文章。
(2)ADC3_IN6
(3)TIM3
(4) RTOS
在FreeRTOS中使用任务通知功能,需要将参数configUSE_TASK_NOTIFICATIONS设置为1。用户在CubeMX中可以设置这个参数,且默认为Enabled。设置任务Task_Show。
(5)NVIC
2、程序功能实现
(1)主程序
在CubeMX里重新生成代码,在CubeIDE里打开项目后,添加用户功能代码后,主程序代码如下:
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "cmsis_os.h"
#include "adc.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void MX_FREERTOS_Init(void);/*** @brief The application entry point.* @retval int*/
int main(void)
{/* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* Configure the system clock */SystemClock_Config();/* Initialize all configured peripherals */MX_GPIO_Init();MX_USART3_UART_Init();MX_ADC3_Init();MX_TIM3_Init();/* USER CODE BEGIN 2 */// Start Menuuint8_t startstr[] = "Demo8_1:Task Notification.\r\n";HAL_UART_Transmit(&huart3,startstr,sizeof(startstr),0xFFFF);uint8_t startstr1[] = "Transfer ADC value by Notification.\r\n";HAL_UART_Transmit(&huart3,startstr1,sizeof(startstr1),0xFFFF);HAL_ADC_Start_IT(&hadc3); //以中断方式启动ADCHAL_TIM_Base_Start(&htim3); //启动Timer/* USER CODE END 2 *//* Init scheduler */osKernelInitialize();/* Call init function for freertos objects (in cmsis_os2.c) */MX_FREERTOS_Init();/* Start scheduler */osKernelStart();/* We should never get here as control is now taken by the scheduler *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}//以下内容省略
在外设初始化部分,MX_ADC3_Init()用于ADC3的初始化,MX_TIM3_Init()用于定时器TIM3的初始化,它们的代码都是自动生成的。
ADC3在TIM3触发下周期性地进行ADC转换,需要执行HAL_ADC_Start_IT(&hadc3)启动ADC3的中断工作方式,执行HAL_TIM_Base_Start(&htim3)启动定时器TIM3。这样,ADC3就能每500ms进行一次ADC转换。
(2)FreeRTOS对象初始化
使用任务通知时,无须创建任何中间对象,所以函数MX_FREERTOS_Init()只需创建任务。文件freertos.c中的初始化代码如下,这些代码是自动生成的:
自动生成includes:
/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
手动添加私有includes:
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include "usart.h"
#include "adc.h"
//#include "event_groups.h" //事件组相关头文件
//#include "keyled.h"
/* USER CODE END Includes */
自动生成任务定义代码:
/* Definitions for Task_Show */
osThreadId_t Task_ShowHandle;
const osThreadAttr_t Task_Show_attributes = {.name = "Task_Show",.stack_size = 256 * 4,.priority = (osPriority_t) osPriorityNormal,
};
自动声明私有函数声明和RTOS初始化:
/* Private function prototypes -----------------------------------------------*/void AppTask_Show(void *argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) *//*** @brief FreeRTOS initialization* @param None* @retval None*/
void MX_FREERTOS_Init(void)
{/* Create the thread(s) *//* creation of Task_Show */Task_ShowHandle = osThreadNew(AppTask_Show, NULL, &Task_Show_attributes);
}
(3)任务通知的接收
ADC3中断里发送的任务通知是发送给任务Task_Show的,这个任务负责接收通知,读取通知值之后,进行处理并显示。文件freertos.c中,手动添加任务Task_Show的任务函数和相关代码如下:
/* USER CODE BEGIN Header_AppTask_Show */
/*** @brief Function implementing the Task_Show thread.* @param argument: Not used* @retval None*/
/* USER CODE END Header_AppTask_Show */
void AppTask_Show(void *argument)
{/* USER CODE BEGIN AppTask_Show *//* Infinite loop */uint32_t notifyValue=0;for(;;){uint32_t ulBitsToClearOnEntry=0x00; //进入时不清除数据uint32_t ulBitsToClearOnExit=0xFFFFFFFF; //退出时清零数据BaseType_t result=xTaskNotifyWait(ulBitsToClearOnEntry, ulBitsToClearOnExit,¬ifyValue, portMAX_DELAY); //接收任务通知if (result==pdTRUE){uint32_t tmpValue = notifyValue; //ADC原始数值printf("Notify Value = %ld\r\n",tmpValue );uint32_t Volt = 3300*tmpValue; //单位:mVVolt = Volt>>12; //除以2^12printf("Engineering Value = %ld\r\n",Volt );}}/* USER CODE END AppTask_Show */
}
上述程序通过调用函数xTaskNotifyWait()接收通知。根据程序中设置的输入参数可知:它进入时,不清除原来的通知值;退出时,清除通知值;读取的通知值保存在变量notifyValue里;进入阻塞状态后,无限等待通知。任务读取任务通知后,返回的通知值notifyValue就是ADC转换原始结果。
(4)在ADC3的中断里发送通知
ADC3采用TIM3外部触发方式进行ADC转换,在ADC3的中断里读取转换结果数据。ADC3的中断ISR框架已经在文件stm32f4xx_it.c中自动创建,只需重新实现ADC转换完成事件中断的回调函数HAL_ADC_ConvCpltCallback()。为了便于使用任务Task_Show的句柄Task_ShowHandle,直接在文件freertos.c的一个代码沙箱段内实现这个回调函数,如下所示:
/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{if (hadc->Instance != ADC3)return;uint32_t adc_value=HAL_ADC_GetValue(hadc); //Get ADC Valueif (Task_ShowHandle != NULL){BaseType_t taskWoken=pdFALSE;xTaskNotifyFromISR(Task_ShowHandle, adc_value,eSetValueWithOverwrite, &taskWoken);//Transmit ADCportYIELD_FROM_ISR(taskWoken); //Required, context switch.}
}int __io_putchar(int ch)
{HAL_UART_Transmit(&huart3,(uint8_t*)&ch,1,0xFFFF);return ch;
}
/* USER CODE END Application */
这个回调函数的代码很简单,就是读取ADC转换结果数据,然后调用函数xTaskNotifyFromISR(),将转换结果以任务通知的方式发给任务Task_Show。ADC转换结果是uint32_t型数据,正好作为任务通知的通知值。在执行函数xTaskNotifyFromISR()之后,后面的申请进行上下文切换的语句portYIELD_FROM_ISR(taskWoken)是必须执行的。
3、运行调试
构建项目后,将其下载到开发板上并运行测试,可以看到,串口助手上周期性地刷新显示ADC原始值和电压值。与使用二值信号量实现了与本示例相同的功能对照,对比两个项目的代码,可以明显看出,使用任务通知要简单一些。