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

举报
矜辰所致 发表于 2022/09/29 16:28:26 2022/09/29
【摘要】 在前面几篇文章我们已经对FreeRTOS任务API和任务调度原理进行了相对深入的分析 这篇文章主要针对任务与任务之间的交互,信息传递相关的API组件进行分析
在前面几篇文章我们已经对FreeRTOS任务API和任务调度原理进行了相对深入的分析
这篇文章主要针对任务与任务之间的交互,信息传递相关的API组件进行分析

前言

本文主要是使用FreeRTOS任务通知实现一下温湿度传感器的读取,我们实现采用定时器周期采集数据和通过按钮按下采集数据。
说明:FreeRTOS 专栏与我的 RT-Thread 专栏不同,我的 RT-Thread 专栏是从理论学习一步一步循序渐进,从 0 起步的 完整教学,而 FreeRTOS 更偏向于 我直接拿来使用,需要用到什么,然后引出知识点,在使用中发现问题,解然后再解决问题。

FreeRTOS记录(一、熟悉开发环境以及CubeMX下FreeRTOS配置)
FreeRTOS 记录(二、FreeRTOS 任务 API 认识和源码简析)
FreeRTOS 记录(三、RTOS 任务调度原理解析 _Systick、PendSV、SVC)(上)
FreeRTOS 记录(三、RTOS 任务调度原理解析 _Systick、PendSV、SVC)(下)
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 */
}

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

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。