在前面几篇文章我们已经对FreeRTOS任务API和任务调度原理进行了相对深入的分析这篇文章主要针对任务与任务之间的交互,信息传递相关的API组件进行分析
复制代码
说明:FreeRTOS 专栏与我的 RT-Thread 专栏不同,我的 RT-Thread 专栏是从理论学习一步一步循序渐进,从 0 起步的 完整教学,而 FreeRTOS 更偏向于 我直接拿来使用,需要用到什么,然后引出知识点,在使用中发现问题,解然后再解决问题。
本 FreeRTOS 专栏记录的开发环境:
FreeRTOS 记录(一、熟悉开发环境以及 CubeMX 下 FreeRTOS 配置)
https://xie.infoq.cn/article/e82c7e10af242105b509bee2e
FreeRTOS 记录(二、FreeRTOS 任务 API 认识和源码简析)
https://xie.infoq.cn/article/250c67b1cc68b91241347f8e6
FreeRTOS 记录(三、RTOS 任务调度原理解析 _Systick、PendSV、SVC)
https://xie.infoq.cn/article/e0c671e5ca2d472ce40d272d4
FreeRTOS 记录(四、FreeRTOS 任务堆栈溢出问题和临界区)
https://xie.infoq.cn/article/d3f7d46aa262b2118a544f250
本文主要是使用 FreeRTOS 任务通知实现一下温湿度传感器的读取,我们实现采用定时器周期采集数据和通过按钮按下采集数据。
一、任务通知基本介绍
简单用官方的话介绍一下:
FreeRTOS 的每个任务都有一个 32 位的通知值pxTCB->ulNotifiedValue,任务创建时,这个值被初始化为 0。
在大多数情况下,任务通知可以 替代二值信号量、计数信号量、事件组,也可以替代长度为 1 的队列(可以保存一个 32 位整数或指针值)
使用任务通知比通过信号量等 ICP 通信方式解除阻塞的任务要快 45%,并且更加省 RAM 内存空间(使用 GCC 编译器,-o2 优化级别),任务通知的使用无需创建队列
补充:pxTCB->ulNotifiedValue数值进行加一或减一就是计数信号量pxTCB->ulNotifiedValue数值取值 0 或 1 就是二值信号量pxTCB->ulNotifiedValue数值按位设置 bit0-bit31 就是事件标志组
局限性:
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;
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 */}
复制代码
结果如下,按照预期的结果执行:
评论