1.简介
在FreeRTOS的配置参数中的configUSE_TASK_NOTIFICATIONS宏打开,一般RTOS会默认打开,如图1所示。
图1 notify宏开关
RTOS在创建任务时,会创建一个32位的通知值ulNotifiedValue,并初始化0,RTOS进行任务通知时,相当于直接向另一个任务的句柄发送notify通知,不用借助于其他内核对象,如信号量等,接收任务收到notify通知后执行对应的操作,从而解除任务阻塞。如图2所示。
图2 任务之间的通知
可以配置/更新接收任务的notification值的方式可以是如下的方式:
- 直接写一个32位的notify值,类似于一个邮箱,可以发送同步内容也可发送指针
- 让接收任务的notification自加1,可用于信号量(二值信号量、计数信号量)
- 置位notification的某些位(可用于同步事件组)
- 不改变接收任务的notification(当接收任务没有处理完时,不会更新发送的通知值
凡是都有利弊,不然的话 FreeRTOS 还要内核的 IPC 通信机制干嘛,消息通知虽然处理更快,RAM 开销更小,但也有以下限制 :
- 只能有一个任务接收通知消息,因为必须指定接收通知的任务。
- 只有等待通知的任务可以被阻塞,发送通知的任务,在任何情况下都不会因为发送失败而进入阻塞态。
2.应用
2.1 轻量级二值信号量
相当于一个二值信号量,eAction模式为eIncrement
发送任务的notify: xTaskNotifyGive( TaskHandle_t xTaskToNotify )
其中,xTaskToNotify为通知任务的句柄
接收任务的notify:rtval = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
其中,pdTRUE的话就是每获取take一次notify,value值复位到0,相当于二值 信号量,第二个量为等待时间..
rtval是接收到的返回值,他是递减前或复位前的值。
demo分析
#include "APPTaskDef.h"
#include "led.h"
#include "delay.h"
#include "key.h"
#define START_TASK_PRIO 1
#define START_STACK_SIZE 128
TaskHandle_t startTask_handler; //开始任务句柄
static void start_task(void *param);
#define TASK1_PRIO 3
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler; //控制task1任务句柄
static void task1(void *param);
#define TASK2_PRIO 2
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler; //控制task2任务句柄
static void task2(void *param);
void APP_task(void)
{
xTaskCreate((TaskFunction_t)start_task,
(const char *)"start_task",
(uint16_t)START_STACK_SIZE,
NULL,
(UBaseType_t)START_TASK_PRIO,
(TaskHandle_t *)&startTask_handler);
vTaskStartScheduler(); //开启任务调度
}
static void start_task(void *param)
{
taskENTER_CRITICAL(); //进入临界区
//创建获取event的任务
xTaskCreate((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&task1_handler);
xTaskCreate((TaskFunction_t )task2,
(const char* )"task2",
(uint16_t )TASK2_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&task2_handler);
vTaskDelete(startTask_handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
/*
timer control task
*/
static void task1(void *param)
{
u32 NotifyValue = 0;
int i = 0;
while(1)
{
xTaskNotifyGive(task2_handler);
printf("send notify!\r\n");
LED0 = !LED0;
vTaskDelay(1000);
}
}
static void task2(void *param)
{
u32 NotifyValue = 0;
while(1)
{
NotifyValue = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
//其中,为pdTRUE的话就是每获取take一次notify,value值复位到0----------------------------------------------------相当于二值信号量
if(NotifyValue - 1 > 0)
{
LED1 = !LED1;
printf("receive notify!\r\n");
printf("cur notify value is %d\r\n", NotifyValue - 1);
printf("--------------------------------------------\r\n");
}else{
vTaskDelay(1000);
}
}
}
2.2 轻量级计数信号量
notify模拟计数信号量与二值信号量的区别是接受的信号值是以递减的方式,即pdFALSE, eAction模式为eIncrement
发送任务的notify: xTaskNotifyGive( TaskHandle_t xTaskToNotify )
其中,xTaskToNotify为通知任务的句柄
接收任务的notify:rtval = ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
其中,pdFALSE的话就是每获取take一次notify,notifyvalue值减1,相当于 计数信号量,第二个量为等待时间..
rtval是接收到的返回值,他是递减前或复位前的值。
demo分析
#include "APPTaskDef.h"
#include "led.h"
#include "delay.h"
#include "key.h"
#define START_TASK_PRIO 1
#define START_STACK_SIZE 128
TaskHandle_t startTask_handler; //开始任务句柄
static void start_task(void *param);
#define TASK1_PRIO 3
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler; //控制task1任务句柄
static void task1(void *param);
#define TASK2_PRIO 2
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler; //控制task2任务句柄
static void task2(void *param);
void APP_task(void)
{
xTaskCreate((TaskFunction_t)start_task,
(const char *)"start_task",
(uint16_t)START_STACK_SIZE,
NULL,
(UBaseType_t)START_TASK_PRIO,
(TaskHandle_t *)&startTask_handler);
vTaskStartScheduler(); //开启任务调度
}
static void start_task(void *param)
{
taskENTER_CRITICAL(); //进入临界区
//创建获取event的任务
xTaskCreate((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&task1_handler);
xTaskCreate((TaskFunction_t )task2,
(const char* )"task2",
(uint16_t )TASK2_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&task2_handler);
vTaskDelete(startTask_handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
/*
timer control task
*/
static void task1(void *param)
{
u32 NotifyValue = 0;
int i = 0;
while(1)
{
for(i = 0; i < 3; i++)
{
xTaskNotifyGive(task2_handler);
}
printf("send notify!\r\n");
LED0 = !LED0;
vTaskDelay(1000);
}
}
static void task2(void *param)
{
u32 NotifyValue = 0;
while(1)
{
NotifyValue = ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
//ulTaskNotifyTake(pdFALSE, portMAX_DELAY);中为pdFALSE的话就是每获取take一次notify,value值减1-----------相当于计数信号量
//为pdTRUE的话就是每获取take一次notify,value值复位到0----------------------------------------------------相当于二值信号量
if(NotifyValue - 1 > 0)
{
LED1 = !LED1;
printf("receive notify!\r\n");
printf("cur notify value is %d\r\n", NotifyValue - 1);
printf("--------------------------------------------\r\n");
}else{
vTaskDelay(1000);
}
}
}
2.3 轻量级邮箱
任务通知也可用来向任务发送数据,但是相对于用队列发送消息,任务通知向任务发送消
息会受到很多限制!
1)只能发送 32 位的数据值。
2)消息被保存为任务的任务通知值,而且一次只能保存一个任务通知值,相当于队列长度
为 1。
因此说任务通知可以模拟一个轻量级的消息邮箱而不是轻量级的消息队列。任务通知值就
是消息邮箱的值。
邮箱发送:xTaskNotify( xTaskToNotify, ulValue, eAction )
xTaskToNotify:通知任务句柄,
ulValue:通知的值(邮箱的内容)
eAction:4种模式:
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;
邮箱接收: BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, 进入函数需要清空位
uint32_t ulBitsToClearOnExit, 退出函数前需要清空的位
uint32_t *pulNotificationValue, 获得的notify值
TickType_t xTicksToWait ) 等待的时间
demo分析
#include "APPTaskDef.h"
#include "led.h"
#include "delay.h"
#include "key.h"
#include "event_groups.h"
#define START_TASK_PRIO 1
#define START_STACK_SIZE 128
TaskHandle_t startTask_handler; //开始任务句柄
static void start_task(void *param);
#define NOTIFY_MAIL_SEND_TASK_PRIO 3
#define NOTIFY_MAIL_SEND_TASK_STACK_SIZE 128
TaskHandle_t notify_mail_send_task_handler; //任务1句柄
static void notify_mail_send_task(void *pvPara);
#define NOTIFY_MAIL_RCV_TASK_PRIO 2
#define NOTIFY_MAIL_RCV_TASK_STACK_SIZE 128
TaskHandle_t notify_mail_rcv_task_handler; //任务1句柄
static void notify_mail_rcv_task(void *pvPara);
//EventGroupHandle_t key_event; //key event
#define EVENTBIT_0 (1<<0) //事件位
#define EVENTBIT_1 (1<<1)
#define EVENTBIT_2 (1<<2)
#define EVENTBIT_ALL (EVENTBIT_0|EVENTBIT_1|EVENTBIT_2)
void APP_task(void)
{
xTaskCreate((TaskFunction_t)start_task,
(const char *)"start_task",
(uint16_t)START_STACK_SIZE,
NULL,
(UBaseType_t)START_TASK_PRIO,
(TaskHandle_t *)&startTask_handler);
vTaskStartScheduler(); //开启任务调度
}
static void start_task(void *param)
{
EventGroupHandle_t key_event = xEventGroupCreate(); //创建一个事件event
taskENTER_CRITICAL(); //进入临界区
//创建获取event的任务
xTaskCreate((TaskFunction_t )notify_mail_send_task,
(const char* )"notifyMailSend",
(uint16_t )NOTIFY_MAIL_SEND_TASK_STACK_SIZE,
(void* )key_event,
(UBaseType_t )NOTIFY_MAIL_SEND_TASK_PRIO,
(TaskHandle_t* )¬ify_mail_send_task_handler);
//创建设置event的任务
xTaskCreate((TaskFunction_t )notify_mail_rcv_task,
(const char* )"set_event",
(uint16_t )NOTIFY_MAIL_RCV_TASK_STACK_SIZE,
(void* )key_event,
(UBaseType_t )NOTIFY_MAIL_RCV_TASK_PRIO,
(TaskHandle_t* )¬ify_mail_rcv_task_handler);
vTaskDelete(startTask_handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
/*
创建task0,不调用vTaskDelay();即不会引起任务调度,
为大致限定任务的执行时间,用delay_xms(10);这个函数不会引起任务调度,只是CPU在空等
*/
static void notify_mail_send_task(void *param)
{
u8 key_value = 0;
BaseType_t error = 0;
while(1){
key_value = KEY_Scan(0);
if(notify_mail_rcv_task_handler && key_value)
{
error = xTaskNotify(notify_mail_rcv_task_handler,
key_value,
eSetValueWithOverwrite);
}
if(error == pdFAIL)
{
printf("send notify failure!\r\n");
}else{
printf("send notify succeed\r\n");
}
vTaskDelay(100);
}
}
/*
创建task0,不调用vTaskDelay();即不会引起任务调度,
为大致限定任务的执行时间,用delay_xms(10);这个函数不会引起任务调度,只是CPU在空等
*/
static void notify_mail_rcv_task(void *param)
{
u32 notify_val;
while(1)
{
xTaskNotifyWait(0, //进入函数前不清空
0xffffffff, //退出函数后清空所有位
¬ify_val, //得到notify
portMAX_DELAY); //永远等待
switch(notify_val)
{
case KEY0_PRESS:
printf("KEY0 press\r\n");
break;
case KEY1_PRESS:
printf("KEY1 press\r\n");
break;
case KEY_UP_PRESS:
printf("KEY_UP press\r\n");
break;
default:
break;
}
vTaskDelay(100);
}
}
2.4 模拟事件组
事件组与邮箱很类似,只是模式不同,只是eAction为eSetBits,其他处理类似邮箱,只要通过事件与,事件或得到接收事件值即可,这里不作详细赘述。