FreeRtos任务通知详解
前言
本次进行分享的是FreeRtos中任务通知相关的知识以及使用,通过此次的学习更能让我们掌握Freertos中的一些任务机制不同的实现方法,下面会用任务通知进行模拟实现前面的二值信号量、计数型信号量、信息邮箱、以及事件标志组的实现。
一、任务通知介绍
任务通知在 FreeRTOS 中是一个可选的功能,要使用任务通知的话就需要将宏 configUSE_TASK_NOTIFICATIONS 定义为 1。 FreeRTOS 的每个任务都有一个 32 位的通知值,任务控制块中的成员变量ulNotifiedValue就是这个通知值。任务通知是一个事件,假如某个任务通知的接收任务因为等待任务通知而阻塞的话,向这个接收任务发送任务通知以后就会解除这个任务的阻塞状态。也可以更新接收任务的任务通知值,任务通知可以通过如下方法更新接收任务的通知值: ●不覆盖接收任务的通知值(如果上次发送给接收任务的通知还没被处理)。 ● 覆盖接收任务的通知值。 ● 更新接收任务通知值的一个或多个 bit。 ● 增加接收任务的通知值。
二、任务通知API函数
2.1 任务通知函数
1、函数 xTaskNotify()
此函数用于发送任务通知,此函数发送任务通知的时候带有通知值,此函数是个宏,真正执行的函数 xTaskGenericNotify()
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
uint32_t ulValue, eNotifyAction eAction )
参数: xTaskToNotify: 任务句柄,指定任务通知是发送给哪个任务的。 ulValue: 任务通知值。 eAction: 任务通知更新的方法,eNotifyAction 是个枚举类型,在文件 task.h 返回值: pdFAIL: 当参数 eAction 设置为 eSetValueWithoutOverwrite 的时候,如果任务通知值没有更新成功就返回 pdFAIL。 pdPASS: eAction 设置为其他选项的时候统一返回 pdPASS。
typedef enum
{
eNoAction = 0,
eSetBits,
eIncrement,
eSetValueWithOverwrite,
eSetValueWithoutOverwrite
} eNotifyAction;
2、函数 xTaskNotifyFromISR()
此函数用于发送任务通知,是函数 xTaskNotify()的中断版本,此函数是个宏,真正执行的是函数 xTaskGenericNotifyFromISR()
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
BaseType_t * pxHigherPriorityTaskWoken );
参数: xTaskToNotify: 任务句柄,指定任务通知是发送给哪个任务的。 ulValue: 任务通知值。 eAction: 任务通知更新的方法。 pxHigherPriorityTaskWoken: 记退出此函数以后是否进行任务切换,这个变量的值函数会自动设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。 返回值: pdFAIL: 当参数 eAction 设置为 eSetValueWithoutOverwrite 的时候,如果任务通知值没有更新成功就返回 pdFAIL。 pdPASS: eAction 设置为其他选项的时候统一返回 pdPASS。
3、函数 xTaskNotifyGive()
发送任务通知,相对于函数 xTaskNotify(),此函数发送任务通知的时候不带有通知值。此函数只是将任务通知值简单的加一,此函数是个宏,真正执行的是函数 xTaskGenericNotify()
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
参数: xTaskToNotify: 任务句柄,指定任务通知是发送给哪个任务的。 返回值: pdPASS: 此函数只会返回 pdPASS。
4、函数 vTaskNotifyGiveFromISR()
此函数为 xTaskNotifyGive()的中断版本,用在中断服务函数中
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskHandle,
BaseType_t * pxHigherPriorityTaskWoken );
参数: xTaskToNotify: 任务句柄,指定任务通知是发送给哪个任务的。 pxHigherPriorityTaskWoken: 记退出此函数以后是否进行任务切换,这个变量的值函数会自动设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。 返回值: 无
5、函数 xTaskNotifyAndQuery()
此函数和 xTaskNotify()很类似,此函数比 xTaskNotify()多一个参数,此参数用来保存更新前的通知值。此函数是个宏,真正执行的是函数 xTaskGenericNotify()
BaseType_t xTaskNotifyAndQuery ( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction
uint32_t * pulPreviousNotificationValue);
参数: xTaskToNotify: 任务句柄,指定任务通知是发送给哪个任务的。 ulValue: 任务通知值。 eAction: 任务通知更新的方法。 pulPreviousNotificationValue:用来保存更新前的任务通知值。 返回值: pdFAIL: 当参数 eAction 设置为 eSetValueWithoutOverwrite 的时候,如果任务通知值没有更新成功就返回 pdFAIL。 pdPASS: eAction 设置为其他选项的时候统一返回 pdPASS。
6、函数 xTaskNotifyAndQueryFromISR()
此函数为 xTaskNorityAndQuery()的中断版本,用在中断服务函数中。此函数同样为宏,真正执行的是函数 xTaskGenericNotifyFromISR()
BaseType_t xTaskNotifyAndQueryFromISR ( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t * pulPreviousNotificationValue
BaseType_t * pxHigherPriorityTaskWoken );
参数: xTaskToNotify: 任务句柄,指定任务通知是发送给哪个任务的。 ulValue: 任务通知值。 eAction: 任务通知更新的方法。 pulPreviousNotificationValue:用来保存更新前的任务通知值。 pxHigherPriorityTaskWoken: 记退出此函数以后是否进行任务切换,这个变量的值函数会自动设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。 返回值: pdFAIL: 当参数 eAction 设置为 eSetValueWithoutOverwrite 的时候,如果任务通知值没有更新成功就返回 pdFAIL。 pdPASS: eAction 设置为其他选项的时候统一返回 pdPASS。
2.2 获取任务通知函数
1、函数 ulTaskNotifyTake()
此函数为获取任务通知函数,当任务通知用作二值信号量或者计数型信号量的时候可以使用此函数来获取信号量。
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,
TickType_t xTicksToWait );
参数: xClearCountOnExit: 参数为 pdFALSE 的话在退出函数 ulTaskNotifyTake()的时候任务通知值减一,类似计数型信号量。当此参数为 pdTRUE 的话在退出函数的时候任务任务通知值清零,类似二值信号量。 xTickToWait: 阻塞时间。 返回值: 任何值 : 任务通知值减少或者清零之前的值。
2、函数 xTaskNotifyWait()
函数也是用来获取任务通知的,不过此函数比 ulTaskNotifyTake()更为强大,不管任务通知用作二值信号量、计数型信号量、队列和事件标志组中的哪一种,都可以使用此函数来获取任务通知。但是当任务通知用作位置信号量和计数型信号量的时候推荐使用函数ulTaskNotifyTake()
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t * pulNotificationValue,
TickType_t xTicksToWait );
参数: ulBitsToClearOnEntry:当没有接收到任务通知的时候将任务通知值与此参数的取反值进行按位与运算,当此参数为 0xffffffff 或者 ULONG_MAX 的时候就会将任务通知值清零。 ulBitsToClearOnExit:如果接收到了任务通知,在做完相应的处理退出函数之前将任务通知值与此参数的取反值进行按位与运算,当此参数为 0xffffffff 或者 ULONG_MAX 的时候就会将任务通知值清零。 pulNotificationValue:此参数用来保存任务通知值。 xTickToWait: 阻塞时间。 返回值: pdTRUE: 获取到了任务通知。 pdFALSE: 任务通知获取失败。
三、任务通知模拟二值信号量
3.1 实验需求
通过串口中断接收信息后,通过任务中断函数胡向主程序的处理功能发送通知进行数据的打印
usart.c
extern TaskHandle_t DATAPROCESSTask_Handler;
BaseType_t pxHigherPriorityTaskWoken;
void USART1_IRQHandler(void)
{
u8 Res;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
Res =USART_ReceiveData(USART1);
if((USART_RX_STA&0x8000)==0)
{
if(USART_RX_STA&0x4000)
{
if(Res!=0x0a)USART_RX_STA=0;
else USART_RX_STA|=0x8000;
}
else
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;
}
}
}
}
if((USART_RX_STA&0x8000))
{
vTaskNotifyGiveFromISR(DATAPROCESSTask_Handler,&pxHigherPriorityTaskWoken);
portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);
}
}
3.2 实验结果
main.c
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "semphr.h"
#include "string.h"
#define START_TASK_PRIO 1
#define START_STK_SIZE 128
TaskHandle_t StartTask_Handler;
void start_task(void *pvParameters);
#define LED0_TASK_PRIO 4
#define LED0_STK_SIZE 50
TaskHandle_t LED0Task_Handler;
void led0_task(void *pvParameters);
#define DATAPROCESS_TASK_PRIO 2
#define DATAPROCESS_STK_SIZE 50
TaskHandle_t DATAPROCESSTask_Handler;
void dataprocess_task(void *pvParameters);
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
delay_init();
uart_init(115200);
LED_Init();
KEY_GPIO_INIT();
printf("--------------任务通知模拟二值信号量实验测试---------------\r\n");
xTaskCreate((TaskFunction_t )start_task,
(const char* )"start_task",
(uint16_t )START_STK_SIZE,
(void* )NULL,
(UBaseType_t )START_TASK_PRIO,
(TaskHandle_t* )&StartTask_Handler);
vTaskStartScheduler();
}
void start_task(void *pvParameters)
{
taskENTER_CRITICAL();
xTaskCreate((TaskFunction_t )led0_task,
(const char* )"led0_task",
(uint16_t )LED0_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED0_TASK_PRIO,
(TaskHandle_t* )&LED0Task_Handler);
xTaskCreate((TaskFunction_t )dataprocess_task,
(const char* )"dataprocess_task",
(uint16_t )DATAPROCESS_STK_SIZE,
(void* )NULL,
(UBaseType_t )DATAPROCESS_TASK_PRIO,
(TaskHandle_t* )&DATAPROCESSTask_Handler);
vTaskDelete(StartTask_Handler);
taskEXIT_CRITICAL();
}
void led0_task(void *pvParameters)
{
u8 key;
while(1)
{
key = KEY_Scan();
if(key == KEY0_Value)
{
}
LED0=~LED0;
vTaskDelay(500);
}
}
void dataprocess_task(void *pvParameters)
{
uint32_t value = pdFALSE;
while(1)
{
value = ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
if(value == 1)
{
printf("value = %d\r\n",value);
printf("dataprocess_task data = %s\r\n",USART_RX_BUF);
memset(USART_RX_BUF,0,USART_REC_LEN);
USART_RX_STA = 0;
}
}
}
四、任务通知模拟计数型信号量
不同与二值信号量,计数型信号量值可以大 1,这个最大值在创建信号量的时候可以设置。当计数型信号量有效的时候任务可以获取计数型信号量,信号量值只要大于 0 就表示计数型信号量有效。 当任务通知用作计数型信号量的时候获取信号量相当于获取任务通知值,使用函数 ulTaskNotifyTake()来替代函数 xSemaphoreTake()。函数 ulTaskNotifyTake()的参数 xClearOnExit要设置为 pdFLASE,这样每次获取任务通知成功以后任务通知值就会减一。使用任务通知发送函 数 xTaskNotifyGive() 和 vTaskNotifyGiveFromISR() 来 替 代 计 数 型 信 号 量 释 放 函 数xSemaphoreGive()和 xSemaphoreGiveFromISR()
4.1 实验需求
通过按键进行任务的通知,处理函数获取通知后将value值打印出来
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "semphr.h"
#include "string.h"
#define START_TASK_PRIO 1
#define START_STK_SIZE 128
TaskHandle_t StartTask_Handler;
void start_task(void *pvParameters);
#define LED0_TASK_PRIO 4
#define LED0_STK_SIZE 50
TaskHandle_t LED0Task_Handler;
void led0_task(void *pvParameters);
#define DATAPROCESS_TASK_PRIO 2
#define DATAPROCESS_STK_SIZE 50
TaskHandle_t DATAPROCESSTask_Handler;
void dataprocess_task(void *pvParameters);
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
delay_init();
uart_init(115200);
LED_Init();
KEY_GPIO_INIT();
printf("--------------任务通知模拟计数型信号量实验测试---------------\r\n");
xTaskCreate((TaskFunction_t )start_task,
(const char* )"start_task",
(uint16_t )START_STK_SIZE,
(void* )NULL,
(UBaseType_t )START_TASK_PRIO,
(TaskHandle_t* )&StartTask_Handler);
vTaskStartScheduler();
}
void start_task(void *pvParameters)
{
taskENTER_CRITICAL();
xTaskCreate((TaskFunction_t )led0_task,
(const char* )"led0_task",
(uint16_t )LED0_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED0_TASK_PRIO,
(TaskHandle_t* )&LED0Task_Handler);
xTaskCreate((TaskFunction_t )dataprocess_task,
(const char* )"dataprocess_task",
(uint16_t )DATAPROCESS_STK_SIZE,
(void* )NULL,
(UBaseType_t )DATAPROCESS_TASK_PRIO,
(TaskHandle_t* )&DATAPROCESSTask_Handler);
vTaskDelete(StartTask_Handler);
taskEXIT_CRITICAL();
}
void led0_task(void *pvParameters)
{
u8 key;
while(1)
{
key = KEY_Scan();
if(key == KEY0_Value)
{
if(NULL != DATAPROCESSTask_Handler)
{
xTaskNotifyGive(DATAPROCESSTask_Handler);
}
}
LED0=~LED0;
vTaskDelay(500);
}
}
void dataprocess_task(void *pvParameters)
{
uint32_t value = pdFALSE;
while(1)
{
value = ulTaskNotifyTake(pdFAIL,portMAX_DELAY);
printf("value = %d\r\n",value);
}
}
4.2 实验结果
下面使用结果全是1 因为我每次只加了一下,内部会自动进行计数减 并保留上次的值
五、任务通知模拟消息邮箱
5.1 不足说明
任务通知也可用来向任务发送数据,但是相对于用队列发送消息,任务通知向任务发送消息会受到很多限制! 1、只能发送 32 位的数据值。 2、消息被保存为任务的任务通知值,而且一次只能保存一个任务通知值,相当于队列长度为 1。 因此说任务通知可以模拟一个轻量级的消息邮箱而不是轻量级的消息队列。任务通知值就是消息邮箱的值。
5.2 实验需求
通过按键来读取键值发送给处理任务进行功能处理
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "key.h"
#include "time.h"
#include "limits.h"
#define START_TASK_PRIO 1
#define START_STK_SIZE 128
TaskHandle_t StartTask_Handler;
void start_task(void *pvParameters);
#define LED0_TASK_PRIO 2
#define LED0_STK_SIZE 50
TaskHandle_t LED0Task_Handler;
void led0_task(void *pvParameters);
#define KEY_TASK_PRIO 3
#define KEY_STK_SIZE 50
TaskHandle_t KEYTask_Handler;
void key_task(void *pvParameters);
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
delay_init();
uart_init(115200);
LED_Init();
KEY_GPIO_INIT();
TIM2_Int_Init(5000-1,7200-1);
xTaskCreate((TaskFunction_t )start_task,
(const char* )"start_task",
(uint16_t )START_STK_SIZE,
(void* )NULL,
(UBaseType_t )START_TASK_PRIO,
(TaskHandle_t* )&StartTask_Handler);
vTaskStartScheduler();
}
void start_task(void *pvParameters)
{
taskENTER_CRITICAL();
xTaskCreate((TaskFunction_t )led0_task,
(const char* )"led0_task",
(uint16_t )LED0_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED0_TASK_PRIO,
(TaskHandle_t* )&LED0Task_Handler);
xTaskCreate((TaskFunction_t )key_task,
(const char* )"key_task",
(uint16_t )KEY_STK_SIZE,
(void* )NULL,
(UBaseType_t )KEY_TASK_PRIO,
(TaskHandle_t* )&KEYTask_Handler);
vTaskDelete(StartTask_Handler);
taskEXIT_CRITICAL();
}
void led0_task(void *pvParameters)
{
u8 key;
while(1)
{
key = KEY_Scan();
if((KEYTask_Handler != NULL) && (key))
{
xTaskNotify(KEYTask_Handler,(uint32_t)key,eSetValueWithOverwrite);
}
}
}
void key_task(void *pvParameters)
{
uint32_t NotifyValue;
BaseType_t err;
while(1)
{
err = xTaskNotifyWait(0,ULONG_MAX,&NotifyValue,portMAX_DELAY);
if(err == pdPASS)
{
printf("NotifyValue = %d\r\n",NotifyValue);
switch(NotifyValue)
{
case 1:LED0 = 0;printf("LED0 OPEN\r\n");break;
case 2:LED0 = 1;printf("LED0 CLOSE\r\n");break;
}
}
}
}
5.3 实验结果
六、任务通知模拟事件标志组
事件标志组其实就是一组二进制事件标志(位),每个事件标志位的具体意义由应用程序编写者来决定。当一个任务等待事件标志组中的某几个标志(位)的时候可以进入阻塞态,当任务因为等待事件标志(位)而进入阻塞态以后这个任务就不会消耗 CPU。
6.1 实验需求
通过按键1和2 都触发后进行事件响应并且值打印出来和触发运行次数打印出来
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "semphr.h"
#include "string.h"
#include "limits.h"
#define START_TASK_PRIO 1
#define START_STK_SIZE 128
TaskHandle_t StartTask_Handler;
void start_task(void *pvParameters);
#define EVENTSETBITS_TASK_PRIO 2
#define EVENTSETBITS_STK_SIZE 50
TaskHandle_t EVENTTSETBITSask_Handler;
void eventsetbits_task(void *pvParameters);
#define EVENTGROUP_TASK_PRIO 3
#define EVENTGROUP_STK_SIZE 50
TaskHandle_t EVENTGROUPTask_Handler;
void eventgroup_task(void *pvParameters);
#define EVENTBIT0 (1 << 0)
#define EVENTBIT1 (1 << 1)
#define EVENTBIT2 (1 << 2)
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
delay_init();
uart_init(115200);
LED_Init();
KEY_GPIO_INIT();
printf("------------------任务通知模拟事件标志组实验------------------\r\n");
xTaskCreate((TaskFunction_t )start_task,
(const char* )"start_task",
(uint16_t )START_STK_SIZE,
(void* )NULL,
(UBaseType_t )START_TASK_PRIO,
(TaskHandle_t* )&StartTask_Handler);
vTaskStartScheduler();
}
void start_task(void *pvParameters)
{
taskENTER_CRITICAL();
xTaskCreate((TaskFunction_t )eventsetbits_task,
(const char* )"eventsetbits_task",
(uint16_t )EVENTSETBITS_STK_SIZE,
(void* )NULL,
(UBaseType_t )EVENTSETBITS_TASK_PRIO,
(TaskHandle_t* )&EVENTTSETBITSask_Handler);
xTaskCreate((TaskFunction_t )eventgroup_task,
(const char* )"eventgroup_task",
(uint16_t )EVENTGROUP_STK_SIZE,
(void* )NULL,
(UBaseType_t )EVENTGROUP_TASK_PRIO,
(TaskHandle_t* )&EVENTGROUPTask_Handler);
vTaskDelete(StartTask_Handler);
taskEXIT_CRITICAL();
}
void eventsetbits_task(void *pvParameters)
{
u8 keyvalue = 0;
while(1)
{
if(NULL != EVENTGROUPTask_Handler)
{
keyvalue = KEY_Scan();
switch(keyvalue)
{
case 1:
xTaskNotify(EVENTGROUPTask_Handler,EVENTBIT1,eSetBits);
break;
case 2:
xTaskNotify(EVENTGROUPTask_Handler,EVENTBIT2,eSetBits);
break;
}
}
vTaskDelay(10);
}
}
void eventgroup_task(void *pvParameters)
{
BaseType_t err;
uint32_t NotifyValue;
static u8 eventvalue1,eventvalue2,eventvalue;
u8 num = 0;
while(1)
{
err = xTaskNotifyWait(0,ULONG_MAX,&NotifyValue,portMAX_DELAY);
if(err == pdPASS)
{
if(NotifyValue & EVENTBIT1)
{
eventvalue1 = 1;
}
else if(NotifyValue & EVENTBIT2)
{
eventvalue2= 1;
}
}
eventvalue = ((eventvalue1 <<1) |(eventvalue2 <<2));
printf("eventvalue = %d\r\n",eventvalue);
if(eventvalue == 0x06)
{
num++;
eventvalue1 = 0,eventvalue2 = 0;
printf("event running = %d\r\n",num);
}
}
}
6.2 实验结果
以上就是FreeRtos关于任务通知的一些简单使用 加油
|