在前面几篇文章我们已经对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 */
}
结果如下,按照预期的结果执行: