FreeRTOS记录(五、FreeRTOS任务通知)

简介: 在前面几篇文章我们已经对FreeRTOS任务API和任务调度原理进行了相对深入的分析这篇文章主要针对任务与任务之间的交互,信息传递相关的API组件进行分析
在前面几篇文章我们已经对FreeRTOS任务API和任务调度原理进行了相对深入的分析
这篇文章主要针对任务与任务之间的交互,信息传递相关的API组件进行分析

说明:FreeRTOS 专栏与我的 RT-Thread 专栏不同,我的 RT-Thread 专栏是从理论学习一步一步循序渐进,从 0 起步的 完整教学,而 FreeRTOS 更偏向于 我直接拿来使用,需要用到什么,然后引出知识点,在使用中发现问题,解然后再解决问题,

本 FreeRTOS 专栏记录的开发环境:
FreeRTOS记录(一、熟悉开发环境以及CubeMX下FreeRTOS配置)
FreeRTOS记录(二、FreeRTOS任务API认识和源码简析)
FreeRTOS记录(三、RTOS任务调度原理解析_Systick、PendSV、SVC)
FreeRTOS记录(四、FreeRTOS任务堆栈溢出问题和临界区)

本文主要是使用FreeRTOS任务通知实现一下温湿度传感器的读取,我们实现采用定时器周期采集数据和通过按钮按下采集数据。

一、任务通知基本介绍

简单用官方的话介绍一下:

  • FreeRTOS 的每个任务都有一个 32 位的通知值pxTCB->ulNotifiedValue,任务创建时,这个值被初始化为0。
  • 在大多数情况下,任务通知可以 替代二值信号量、计数信号量、事件组,也可以替代长度为 1 的队列(可以保存一个 32 位整数或指针值)
  • 使用任务通知比通过信号量等 ICP 通信方式解除阻塞的任务要快 45%,并且更加省 RAM 内存空间(使用 GCC 编译器,-o2 优化级别),任务通知的使用无需创建队列

补充:
pxTCB->ulNotifiedValue数值进行加一或减一就是计数信号量
pxTCB->ulNotifiedValue数值取值0或1就是二值信号量
pxTCB->ulNotifiedValue数值按位设置bit0-bit31就是事件标志组

局限性:

  • FreeRTOS 的任务通知只能有一个接收任务,只能多对一
  • 接收任务可以因为接收任务通知而进入阻塞态,但是发送任务不会因为任务通知发送失败而阻塞

1、FreeRTOS 任务通知函数

xTaskGenericNotify函数是一个通用的任务通知发送函数,xTaskNotifyGive() xTaskNotify()xTaskNotifyAndQuery() 等函数都是以其为基础,采用宏定义的方式实现:

BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue ) PRIVILEGED_FUNCTION;

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

BaseType_t xTaskGenericNotifyFromISR( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue, BaseType_t *pxHigherPriorityTaskWoken ) PRIVILEGED_FUNCTION;

API名称 API定义 API说明
xTaskNotify #define xTaskNotify( xTaskToNotify, ulValue, eAction ) xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL ) 在发送任务通知的时候会指定一个通知值, 并且用户可以指定通知值发送的方式。 发送
xTaskNotifyFromISR #define xTaskNotifyFromISR( xTaskToNotify, ulValue, eAction, pxHigherPriorityTaskWoken ) xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL, ( pxHigherPriorityTaskWoken ) ) 在中断中向指定的任务发送一个任务通知,同上 中断中发送
xTaskNotifyAndQuery #define xTaskNotifyAndQuery( xTaskToNotify, ulValue, eAction, pulPreviousNotifyValue ) xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotifyValue ) ) 向指定的任务发送一个任务通知,并返回任务上一个通知值 发送
xTaskNotifyAndQueryFromISR #define xTaskNotifyAndQueryFromISR( xTaskToNotify, ulValue, eAction, pulPreviousNotificationValue, pxHigherPriorityTaskWoken ) xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotificationValue ), ( pxHigherPriorityTaskWoken ) ) 在中断中发送任务通知,返回通知值,同上 中断中发送
xTaskNotifyGive #define xTaskNotifyGive( xTaskToNotify ) xTaskGenericNotify( ( xTaskToNotify ), ( 0 ), eIncrement, NULL ) 在任务中向一个任务发送通知,并将对方的任务通知值加 1 发送
vTaskNotifyGiveFromISR void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken ) 在中断中向任务发送一个任务通知,将对方通知值加1,意思同上 中断中发送
ulTaskNotifyTake uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait ) pdTRUE时在函数退出时将通知值清零,这种方法适用于实现二值信号量; pdFALSE时在函数退出时将通知值减 1,这种方法适用于实现计数信号量。 接收
xTaskNotifyWait BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait ) 用于实现全功能版的等待任务通知,根据用户指定的参数的不同,可以灵活的用于实现轻量级的消息队列队列、二值信号量、计数信号量和事件组功能,并带有超时等待 接收

2、CMSIS封装后任务通知函数

在CubeMX中使用任务通知,就使用了两个函数osSignalSet发送和osSignalWait接收。

2.1 osSignalSet

根据是否在中断中使用,osSignalSet调用了xTaskGenericNotify或者xTaskGenericNotifyFromISR

/***************************  Signal Management ********************************/
/**
* @brief  Set the specified Signal Flags of an active thread.
* @param  thread_id     thread ID obtained by \ref osThreadCreate or \ref osThreadGetId.
* @param  signals       specifies the signal flags of the thread that should be set.
* @retval previous signal flags of the specified thread or 0x80000000 in case of incorrect parameters.
* @note   MUST REMAIN UNCHANGED: \b osSignalSet shall be consistent in every CMSIS-RTOS.
*/
int32_t osSignalSet (osThreadId thread_id, int32_t signal)
{
#if( configUSE_TASK_NOTIFICATIONS == 1 )    
  BaseType_t xHigherPriorityTaskWoken = pdFALSE;
  uint32_t ulPreviousNotificationValue = 0;
  
  if (inHandlerMode())
  {
    if(xTaskGenericNotifyFromISR( thread_id , (uint32_t)signal, eSetBits, &ulPreviousNotificationValue, &xHigherPriorityTaskWoken ) != pdPASS )
      return 0x80000000;
    
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
  }  
  else if(xTaskGenericNotify( thread_id , (uint32_t)signal, eSetBits, &ulPreviousNotificationValue) != pdPASS )
    return 0x80000000;
  
  return ulPreviousNotificationValue;
#else
  (void) thread_id;
  (void) signal;

  return 0x80000000; /* Task Notification not supported */     
#endif
}

2.2 osSignalWait

任务通知只能在任务中使用,不允许在中断中使用,osSignalWait调用xTaskNotifyWait实现:

/**
* @brief  Wait for one or more Signal Flags to become signaled for the current \b RUNNING thread.
* @param  signals   wait until all specified signal flags set or 0 for any single signal flag.
* @param  millisec  timeout value or 0 in case of no time-out.
* @retval  event flag information or error code.
* @note   MUST REMAIN UNCHANGED: \b osSignalWait shall be consistent in every CMSIS-RTOS.
*/
osEvent osSignalWait (int32_t signals, uint32_t millisec)
{
  osEvent ret;

#if( configUSE_TASK_NOTIFICATIONS == 1 )
    
  TickType_t ticks;

  ret.value.signals = 0;  
  ticks = 0;
  if (millisec == osWaitForever) {
    ticks = portMAX_DELAY;
  }
  else if (millisec != 0) {
    ticks = millisec / portTICK_PERIOD_MS;
    if (ticks == 0) {
      ticks = 1;
    }
  }  
  
  if (inHandlerMode())
  {
    ret.status = osErrorISR;  /*Not allowed in ISR*/
  }
  else
  {
    if(xTaskNotifyWait( 0,(uint32_t) signals, (uint32_t *)&ret.value.signals, ticks) != pdTRUE)
    {
      if(ticks == 0)  ret.status = osOK;
      else  ret.status = osEventTimeout;
    }
    else if(ret.value.signals < 0)
    {
      ret.status =  osErrorValue;     
    }
    else  ret.status =  osEventSignal;
  }
#else
  (void) signals;
  (void) millisec;
    
  ret.status =  osErrorOS;    /* Task Notification not supported */
#endif
  
  return ret;
}

二、任务通知使用

在CubeMX中,任务通知是默认使能的:
在这里插入图片描述

1、定义通知量

在程序中定义几个通知量,我们知道任务通知是32位的,所以可以任意定义,我们测试使用了2个通知:

/* USER CODE BEGIN EFP */
#define test_signal1 1
#define test_signal2 0xFFFFFFFE

2、任务中发送通知

在按键任务中,发送一个任务通知给温湿度读取任务,使用osSignalSet

if(HAL_GPIO_ReadPin(K3_GPIO_Port,K3_Pin) == 0){
      osDelay(10);
      if(HAL_GPIO_ReadPin(K3_GPIO_Port,K3_Pin) == 0){
        taskENTER_CRITICAL();
        printf("K3 pushed!!,send a tasksignal to thread...\r\n");
        taskEXIT_CRITICAL();
        osSignalSet(THreadHandle,test_signal2);
        while(HAL_GPIO_ReadPin(K3_GPIO_Port,K3_Pin) == 0){
          osDelay(10);
        }
      }
    }

3、接收通知

通过上文我们可以知道osSignalWait 返回的类型是osEvent ,所以需要定义过一个osEvent 类型的变量,然后结构体变量中有一个成员v是保存的接收的通知的值,如下:
在这里插入图片描述
我们看一下osEvent 结构体 :

/// Event structure contains detailed information about an event.
/// \note MUST REMAIN UNCHANGED: \b os_event shall be consistent in every CMSIS-RTOS.
///       However the struct may be extended at the end.
typedef struct  {
  osStatus                 status;     ///< status code: event or error information
  union  {
    uint32_t                    v;     ///< message as 32-bit value
    void                       *p;     ///< message or mail as void pointer
    int32_t               signals;     ///< signal flags
  } value;                             ///< event value
  union  {
    osMailQId             mail_id;     ///< mail id obtained by \ref osMailCreate
    osMessageQId       message_id;     ///< message id obtained by \ref osMessageCreate
  } def;                               ///< event definition
} osEvent;

所以最终 温湿度读取任务函数改为:

/* USER CODE END Header_StartTHread */
void StartTHread(void const * argument)
{
  /* USER CODE BEGIN StartTHread */
  float T=0,H=0;
  osEvent th_readevent;
  /*128会溢出字的内存空间不够SHT21 协议读取*/
  /* Infinite loop */
  for(;;)
  {
    th_readevent = osSignalWait(test_signal2,osWaitForever);
    if(th_readevent.value.v == test_signal2){
      SHT2X_THMeasure();
      T=(getTemperature()/100.0);
      H=(getHumidity()/100.0); 
      taskENTER_CRITICAL();
      printf("0x%x",th_readevent.value.v);
      printf("\r\n%4.2f C\r\n%4.2f%%\r\n",T,H);
      taskEXIT_CRITICAL();
    }
    osDelay(1);
  }
  /* USER CODE END StartTHread */
}

结果如下,按照预期的结果执行:
在这里插入图片描述

4、中断中发送通知

我们在上次开启的定时器中断中,增加任务通知发送任务:

#include "cmsis_os.h"
...
/* USER CODE BEGIN EV */
extern osThreadId THreadHandle;
/* USER CODE END EV */
...
/**
  * @brief This function handles TIM3 global interrupt.
  */
void TIM3_IRQHandler(void)
{
  /* USER CODE BEGIN TIM3_IRQn 0 */
  time3_count++;
  if(time3_count >= 10){
    osSignalSet(THreadHandle,test_signal1);
    time3_count = 0;
  }
  /* USER CODE END TIM3_IRQn 0 */
  HAL_TIM_IRQHandler(&htim3);
  /* USER CODE BEGIN TIM3_IRQn 1 */

  /* USER CODE END TIM3_IRQn 1 */
}

把THread任务再次修改一下:

void StartTHread(void const * argument)
{
  /* USER CODE BEGIN StartTHread */
  float T=0,H=0;
  osEvent th_readevent;
  /*128会溢出字的内存空间不够SHT21 协议读取*/
  /* Infinite loop */
  for(;;)
  {
    th_readevent = osSignalWait(test_signal2|test_signal1,osWaitForever);
    if(th_readevent.value.v == test_signal2){
      SHT2X_THMeasure();
      T=(getTemperature()/100.0);
      H=(getHumidity()/100.0); 
      taskENTER_CRITICAL();
      printf("get signal from key! signal value is 0x%x\r\n",th_readevent.value.v);
      printf("\r\n%4.2f C\r\n%4.2f%%\r\n",T,H);
      taskEXIT_CRITICAL();
    }
    else if(th_readevent.value.v == test_signal1){
      SHT2X_THMeasure();
      T=(getTemperature()/100.0);
      H=(getHumidity()/100.0); 
      taskENTER_CRITICAL();
      printf("get signal from ISR! signal value is %d\r\n",th_readevent.value.v);
      printf("\r\n%4.2f C\r\n%4.2f%%\r\n",T,H);
      taskEXIT_CRITICAL();
    }
    osDelay(1);
  }
  /* USER CODE END StartTHread */
}

结果如下,按照预期的结果执行:
在这里插入图片描述

相关文章
|
6月前
【FreeRTOS】中断管理(三)
【FreeRTOS】中断管理
|
6月前
【FreeRTOS】中断管理(二)
【FreeRTOS】中断管理
124 0
|
6月前
|
API 调度
【FreeRTOS】软件定时器的使用
【FreeRTOS】软件定时器的使用
169 0
|
IDE 调度 开发工具
如何在S32DS中使用SystemView分析FreeRTOS
如何在S32DS中使用SystemView分析FreeRTOS
如何在S32DS中使用SystemView分析FreeRTOS
|
6月前
【FreeRTOS】任务通知的使用
【FreeRTOS】任务通知的使用
|
6月前
|
API C语言
【FreeRTOS】中断管理(一)
【FreeRTOS】中断管理
121 0
|
6月前
|
API
FreeRTOS软件定时器的原理以及使用实例
FreeRTOS软件定时器的原理以及使用实例
144 0
|
6月前
|
API 调度
FreeRTOS深入教程(中断管理)
FreeRTOS深入教程(中断管理)
303 0
|
API
FreeRTOS学习笔记—任务挂起和恢复
本文学习了FreeRTOS任务挂起和解挂的内容。编程实现了任务挂起和解挂。对于编程实现中的问题进行了分析总结。
182 0
FreeRTOS学习笔记—任务挂起和恢复
|
安全 测试技术 API
5- FreeRTOS任务通知
5- FreeRTOS任务通知