任务通知功能是干什么用的呢?举个例子,假设一种场景,当按下按键时,LED灯会亮。比较简单的实现代码如下:
void func(void)
{
if(pressed)
led on;
}
在基于时间片任务调度下,基于模块化的考虑,或者基于可读性的考虑,也可以按照如下代码实现
unsigned char button_state;
void led(void)
{
if (button_state == PRESSED)
led on;
}
void button(void)
{
while (button_gpio == PRESSED)
button_state = PRESSED;
}
void func(void)
{
while(1)
{
button();
led();
}
}
但是在有操作系统的情况下,每个任务都是一个死循环,那么这两个函数之间该如何通讯呢?这就是任务通知干的活。当然队列、信号量等都可以使用,这儿我们只讨论用任务通知如何实现该功能.
用事件通知解除阻塞任务会比二值信号量快45%并且使用更少的RAM空间。
在实现相同功能的情况下,任务通知具有速度和RAM空间的双重优势。但是这种优势也有一定的限制:
- 只有唯一的任务可以接受通知的情况下,才可以使用。这个条件已经可以满足大部分的应用了。
- 只有以下情况,任务通知才可以替代队列:当有一个处在阻塞状态下的任务等待通知时,那个发送通知的任务如果不能马上完成发送,必须不能进入阻塞状态等待发送完成。
说了这么多,到底是干嘛用的,估计还是不清楚,简短的例子是最好的老师,举个例子。看下面一段代码
我们先用裸机代码实现如下功能,功能很简单:
static void RecFunc(void);
static void SendFunc(void);
unsigned int send_val;
unsigned int rec_val;
static void SendFunc(void)
{
for(;;)
{
delay_5ms();
if (send_val == 5)
{
send_val = 0;
}
else
{
send_val++;
}
}
}
static void RecFunc(void)
{
for(;;)
{
rec_val = 0;
/*wait here until send_val == 5*/
while(send_val != 5);
rec_val = 1;
delay_5ms();
}
}
void main(void)
{
SendFunc();
RecFunc();
}
再用带操作系统的代码实现以上功能,乍看一下,代码很长,去掉注释,仔细分析一下,核心代码就两三行,耐心看完:
/**
******************************************************************************
* @file test_task_notification.c
* @author cyf
* @version V1.0
* @date 2016-7-29 10:19:12
* @brief 学习FreeRTOS的任务通知功能使用方法
*/
/*
*这个例子没有实际的使用意义,仅仅是为了演示任务通知功能的使用
*
*在函数SendFunc()中,send_val 的值会每间隔5ms自增1,当send_val=5时,会通知RecFunc(),条件已满足,
*你可以执行操作了,通知完后,会清零send_val.
*
*函数Recfunc()里的rec_val值会一直为0,只有在接收到任务通知时,会使rec_val=1,设置为1后,马上就会变为0,等待下一次任务通知
*
*/
#include "FreeRTOS.h"
#include "task.h"
static void RecFunc(void *pvParameters);
static void SendFunc(void *pvParameters);
/*TN is short for Task Notification*/
#define TN_STACK_SIZE configMINIMAL_STACK_SIZE
#define TN_PRIORITY ( tskIDLE_PRIORITY + 2 )
unsigned int send_val;
unsigned int rec_val;
static TaskHandle_t xRecTask = NULL;
/*
* 任务通知发送函数
*/
static void SendFunc(void *pvParameters)
{
TickType_t xDelay = 5/portTICK_PERIOD_MS;
for(;;)
{
vTaskDelay(xDelay);
if (send_val == 5)
{
/*Send a notificaton to RecFunc(),bringing it out of the Blocked state*/
xTaskNotifyGive(xRecTask);
send_val = 0;
}
else
{
send_val++;
}
}
}
/*
* 任务通知接收函数
*/
static void RecFunc(void *pvParameters)
{
TickType_t xDelay = 5/portTICK_PERIOD_MS;
for(;;)
{
rec_val = 0;
/*Block to wait for SendFunc() to notify this task*/
ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
rec_val = 1;
vTaskDelay(xDelay);
}
}
/*
* 生产两个任务实例,本函数在main函数中被调用
* 函数SendFunc设置条件
* 函数RecFunc接收条件,当满足条件后,执行操作
*
*/
void vCreateTaskNotification(void)
{
xTaskCreate(SendFunc,"Send",TN_STACK_SIZE,NULL,TN_PRIORITY,NULL);
xTaskCreate(RecFunc,"Send",TN_STACK_SIZE,NULL,TN_PRIORITY,&xRecTask);
}
/***************************************** END OF FILE ********************************************************/
编译后,我们进入keil的调试功能:
1. 调出“Logic Analyzer”窗口
2. 调出“Symbol Window”的串口
3. 把变量send_val、rec_val加入到Logic Analyzer”窗口中
如下图
4. 单击”Run”按钮运行
得到如下图
仔细观察图片中蓝色虚线所表示的位置,当send_val的值为0的时候,rec_val的值为1,结合程序源码,应该不难理解任务通知的功能。
前后对比,感受一下任务通知的功能及使用方法。
任务通知功能主要使用了两个函数来实现,一个是xTaskNotifyGive(),一个是ulTaskNotifyTake(),
* | * |
---|---|
xTaskNotifyGive() | ulTaskNotifyTake() |
给 | 拿 |
我给你,你才能拿,拿到了,才能去干活, 拿不到就等着。
当然,这里只是举例两个简单的函数,还有一下其他的函数及参数,具体的可以去看一下官网的介绍,解释的很详细。