一、任务通知简介
FreeRTOS 从 V8.2.0 版本开始提供任务通知这个功能,每个任务都有一个 32 位的通知值,在大多数情况下任务通知可以代替二值信号量,计数信号量 ,事件组,以及长度为1的队列。对于之前学习的二值信号量,计数信号量和事件组都需要我们创建队列,使用任务通知则不需要创建队列,使用起来更加的方便,使用任务 通知比通过信号量等 ICP 通信方式解除阻塞的任务要快 45%,并且更加省 RAM 内存空间 ,使用之前记得将FreeRTOSConfig.h 中的宏定义 configUSE_TASK_NOTIFICATIONS 设置为 1。
FreeRTOS 提供以下几种方式发送通知给任务 :
1、发送通知给任务,如果有通知没有读取,不覆盖通知值。
2、发送通知给任务,不管读与未读都直接覆盖通知值。
3、 发送通知给任务,设置通知值的一个或者多个位,可以当做事件组来使用。
4、发送通知给任务,递增通知值,可以当做计数信号量使用。 通过对以上任务通知方式的合理使用,可以在一定场合下替代 FreeRTOS 的信号量, 队列、事件组等。
FreeRTOS的任务也有所弊端,只能有一个任务接收通知消息,因为发送通知消息的任务要指定是哪个接收任务,因此一个发送通知消息的任务只能发送通知给一个指定的接收通知消息的任务,无法发送给其它的任务。另外接收通知的任务可以被阻塞但是发送通知的任务,在任何情况下都不会因发送失败而进入阻塞。
二、任务通知的运作机制
任务通知是任务中附带的资源,在创建任务时已经就被初始化,而对于消息队列和信号量来说,我们在使用队列和信号量之前必须先对队列和信号量进行创建,其目的是为了创建队列数据结构,而任务通知就免去了这一创建工作,任务创建完毕之后,任务通知就可以直接用。 FreeRTOS 的每个任务都有一个 32 位的通知值,任务控制块中的成员变量 ulNotifiedValue 就是这个通知值。获取任务通知只能在任务中等待,不能在中断中等待任务通知。如果任务在等待的通知暂时无效,任务会根据用户指定的阻塞时间进入阻塞状态。我们可以把等待任务通知的任务看作嗷嗷待哺的大学生,把发送通知的任务看作外卖员(中断和任务都可以发送通知),当饿的到头昏的大学生拿到外卖员送的外卖,顿时就不饿了(前提是外卖不被偷!),也就相当于任务接收到通知后结束阻塞状态,进入就绪态。
三、常用API函数
1、xTaskGenericNotify()
xTaskGenericNotify()函数是一个通用的任务通知发送函数,在任务中发送通知的 API 函 数 ,如xTaskNotifyGive() 、 xTaskNotify() ,xTaskNotifyAndQuery() ,都是以 xTaskGenericNotify()为原型的,只不过指定的发送方式不同而已。
2、xTaskNotifyGive()
该函数运用于任务当中,不能再中断中使用。该函数向任务发送一个通知,并将对方的任务通知加1。该函数可以作为二值信号量和计数信号量的一种轻量级实现,速度更快,在这种情况下对象任务在等待任务 通 知 的 时 候 应 该 是 使 用 函 数 ulTaskNotifyTake() 而不是xTaskNotifyWait()。 
static void prvTask1( void *pvParameters );
static void prvTask2( void *pvParameters );
/*定义任务句柄 */
static TaskHandle_t xTask1 = NULL, xTask2 = NULL;
/* 主函数:创建两个任务,然后开始任务调度 */
void main( void )
{
xTaskCreate(prvTask1, "Task1", 200, NULL, tskIDLE_PRIORITY, &xTask1);
xTaskCreate(prvTask2, "Task2", 200, NULL, tskIDLE_PRIORITY, &xTask2);
vTaskStartScheduler();
}
/*-----------------------------------------------------------*/
static void prvTask1( void *pvParameters )
{
for ( ;; ) {
/* 向 prvTask2()发送一个任务通知,让其退出阻塞状态 */
xTaskNotifyGive( xTask2 );
/* 阻塞在 prvTask2()的任务通知上
如果没有收到通知,则一直等待*/
ulTaskNotifyTake( pdTRUE, portMAX_DELAY );
}
}
/*-----------------------------------------------------------*/
static void prvTask2( void *pvParameters )
{
for ( ;; ) {
/* 阻塞在 prvTask1()的任务通知上
如果没有收到通知,则一直等待*/
ulTaskNotifyTake( pdTRUE, portMAX_DELAY );
/* 向 prvTask1()发送一个任务通知,让其退出阻塞状态 */
xTaskNotifyGive( xTask1 );
}
}
3、vTaskNotifyGiveFromISR()
xTaskNotifyGive()的中断版,运用于中断程序当中。每次调用函数时都会增加任务的通知量,任务通过接收函数的返回值是否大于0来判断是否获取了通知,如果大于零则表示获取通知而后将任务通知量初始化为零。

static TaskHandle_t xTaskToNotify = NULL;
/* 外设驱动的数据传输函数 */
void StartTransmission( uint8_t *pcData, size_t xDataLength )
{
/* 在这个时候,xTaskToNotify 应为 NULL,因为发送并没有进行。
如果有必要,对外设的访问可以用互斥量来保护*/
configASSERT( xTaskToNotify == NULL );
/* 获取调用函数 StartTransmission()的任务的句柄 */
xTaskToNotify = xTaskGetCurrentTaskHandle();
/* 开始传输,当数据传输完成时产生一个中断 */
vStartTransmit( pcData, xDatalength );
}
/*-----------------------------------------------------------*/
/* 数据传输完成中断 */
void vTransmitEndISR( void )
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* 这个时候不应该为 NULL,因为数据传输已经开始 */
configASSERT( xTaskToNotify != NULL );
/* 通知任务传输已经完成 */
vTaskNotifyGiveFromISR( xTaskToNotify, &xHigherPriorityTaskWoken );
/* 传输已经完成,所以没有任务需要通知 */
xTaskToNotify = NULL;
/* 如果为 pdTRUE,则进行一次上下文切换 */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/*-----------------------------------------------------------*/
/* 任务:启动数据传输,然后进入阻塞态,直到数据传输完成 */
void vAFunctionCalledFromATask( uint8_t ucDataToTransmit,
size_t xDataLength )
{
uint32_t ulNotificationValue;
const TickType_t xMaxBlockTime = pdMS_TO_TICKS( 200 );
/* 调用上面的函数 StartTransmission()启动传输 */
StartTransmission( ucDataToTransmit, xDataLength );
/* 等待传输完成 */
ulNotificationValue = ulTaskNotifyTake( pdFALSE, xMaxBlockTime );
/* 当传输完成时,会产生一个中断
在中断服务函数中调用 vTaskNotifyGiveFromISR()向启动数据
传输的任务发送一个任务通知,并将对象任务的任务通知值加 1
任务通知值在任务创建的时候是初始化为 0 的,当接收到任务后就变成 1 */
if ( ulNotificationValue == 1 ) {
/* 传输按预期完成 */
} else {
/* 调用函数 ulTaskNotifyTake()超时 */
}
}
4.xTaskNotify()
等效于事件组的使用,若用来类似于二值信号量,计数信号量可用前两个介绍的函数。eAction的取值不同有着不同的含义,如下图二。
图一

图二
/* 设置任务 xTask1Handle 的任务通知值的位 8 为 1*/
xTaskNotify( xTask1Handle, ( 1UL << 8UL ), eSetBits );
/* 向任务 xTask2Handle 发送一个任务通知
有可能会解除该任务的阻塞状态,但是并不会更新该任务自身的任务通知值 */
xTaskNotify( xTask2Handle, 0, eNoAction );
/* 向任务 xTask3Handle 发送一个任务通知
并把该任务自身的任务通知值更新为 0x50
即使该任务的上一次的任务通知都没有读取的情况下
即覆盖写 */
xTaskNotify( xTask3Handle, 0x50, eSetValueWithOverwrite );
/* 向任务 xTask4Handle 发送一个任务通知
并把该任务自身的任务通知值更新为 0xfff
但是并不会覆盖该任务之前接收到的任务通知值*/
if(xTaskNotify(xTask4Handle,0xfff,eSetValueWithoutOverwrite)==pdPASS )
{
/* 任务 xTask4Handle 的任务通知值已经更新 */
} else
{
/* 任务 xTask4Handle 的任务通知值没有更新
即上一次的通知值还没有被取走*/
}
5. xTaskNotifyFromISR()
xTaskNotify的中断保护版本。
/* 中断:向一个任务发送任务通知,并根据不同的中断将目标任务的
任务通知值的相应位置 1 */
void vANInterruptHandler( void )
{
BaseType_t xHigherPriorityTaskWoken;
uint32_t ulStatusRegister;
/* 读取中断状态寄存器,判断到来的是哪个中断
这里假设了 Rx、Tx 和 buffer overrun 三个中断 */
ulStatusRegister = ulReadPeripheralInterruptStatus();
/* 清除中断标志位 */
vClearPeripheralInterruptStatus( ulStatusRegister );
/* xHigherPriorityTaskWoken 在使用之前必须初始化为 pdFALSE
如果调用函数 xTaskNotifyFromISR()解锁了解锁了接收该通知的任务
而且该任务的优先级比当前运行的任务的优先级高,那么
xHigherPriorityTaskWoken 就会自动的被设置为 pdTRUE*/
xHigherPriorityTaskWoken = pdFALSE;
/* 向任务 xHandlingTask 发送任务通知,并将其任务通知值
与 ulStatusRegister 的值相或,这样可以不改变任务通知其它位的值*/
xTaskNotifyFromISR( xHandlingTask,
ulStatusRegister,
eSetBits,
&xHigherPriorityTaskWoken );
/* 如果 xHigherPriorityTaskWoken 的值为 pdRTUE 则执行一次上下文切换*/
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/* ----------------------------------------------------------- */
/* 任务:等待任务通知,然后处理相关的事情 */
void vHandlingTask( void *pvParameters )
{
uint32_t ulInterruptStatus;
for ( ;; ) {
/* 等待任务通知,无限期阻塞(没有超时,所以没有必要检查函数返回值)*/
xTaskNotifyWait( 0x00, /* 在进入的时候不清除通知值的任何位 */
ULONG_MAX, /* 在退出的时候复位通知值为 0 */
&ulNotifiedValue, /* 任务通知值传递到变量
ulNotifiedValue 中*/
portMAX_DELAY ); /* 无限期等待 */
/* 根据任务通知值里面的各个位的值处理事情 */
if ( ( ulInterruptStatus & 0x01 ) != 0x00 ) {
/* Rx 中断 */
prvProcessRxInterrupt();
}
if ( ( ulInterruptStatus & 0x02 ) != 0x00 ) {
/* Tx 中断 */
prvProcessTxInterrupt();
}
if ( ( ulInterruptStatus & 0x04 ) != 0x00 ) {
/* 缓冲区溢出中断 */
prvClearBufferOverrun();
}
}
}

6、xTaskNotifyAndQuery()
xTaskNotifyAndQuery()通过,xTaskGenericNotify() 来 实 现 通 知 的 发 送,参 数 pulPreviousNotifyValue 用于回传接收任务的上一个通知值,xTaskNotifyAndQuery()函数不能用在中断中,而是必须使用带中断保护功能的xTaskNotifyAndQuery()FromISR 来代替。
7.xTaskNotifyAndQueryFromISR()
xTaskNotifyAndQuery()带有中断保护的版本。

以上均为发送通知的函数,任务通知在某些场景中可代替信号量,消息队列,事件等。获取任务通知函数只能运用在任务中,因此没有带中断保护的版本。两个获取任务通知的API函数:ulTaskNotifyTake() 和 xTaskNotifyWait ()。前者是为代替二值信号量和计数信号量而专门设计的,它和发送通知 API 函数 xTaskNotifyGive()、vTaskNotifyGiveFromISR()配合使用;后者是全功能版的等待 通知,可以根据不同的参数实现轻量级二值信号量、计数信号量、事件组和长度为 1 的队 列。
8、ulTaskNotifyTake( )
该函数可作为二值信号量和计数信号量的一种轻量级实现,速度更快。该函数于信号量接收函数 xSemaphoreTake() 相比较,若任务通知值为0,则对应这信号量无效,如果设置了阻塞时间,则任务被阻塞挂起。当其他任务或中断使其任务通知值不为0对于信号量有效,即任务接受到任务通知,并且在退出时候根据用户传递的第一个参数 xClearCountOnExit 选择清 零通知值或者执行减一操作。xTaskNotifyTake()在退出的时候处理任务的通知值的时候有两种方法,一种是在函数 退出时将通知值清零,这种方法适用于实现二值信号量;另外一种是在函数退出时将通知 值减 1,这种方法适用于实现计数信号量。

/* 中断服务程序:向一个任务发送任务通知 */
void vANInterruptHandler( void )
{
BaseType_t xHigherPriorityTaskWoken;
/* 清除中断 */
prvClearInterruptSource();
/* xHigherPriorityTaskWoken 在使用之前必须设置为 pdFALSE
如果调用 vTaskNotifyGiveFromISR()会解除 vHandlingTask 任务的阻塞状态,
并且 vHandlingTask 任务的优先级高于当前处于运行状态的任务,
则 xHigherPriorityTaskWoken 将会自动被设置为 pdTRUE */
xHigherPriorityTaskWoken = pdFALSE;
/* 发送任务通知,并解锁阻塞在该任务通知下的任务 */
vTaskNotifyGiveFromISR( xHandlingTask, &xHigherPriorityTaskWoken );
/* 如果被解锁的任务优先级比当前运行的任务的优先级高
则在中断退出前执行一次上下文切换,在中断退出后去执行
刚刚被唤醒的优先级更高的任务*/
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/*-----------------------------------------------------------*/
/* 任务:阻塞在一个任务通知上 */
void vHandlingTask( void *pvParameters )
{
BaseType_t xEvent;
for ( ;; ) {
/* 一直阻塞(没有时间限制,所以没有必要检测函数的返回值)
这里 RTOS 的任务通知值被用作二值信号量,所以在函数退出
时,任务通知值要被清 0 。要注意的是真正的应用程序不应该
无限期的阻塞*/
ulTaskNotifyTake( pdTRUE, /* 在退出前清 0 任务通知值 */
portMAX_DELAY ); /* 无限阻塞 */
/* RTOS 任务通知被当作二值信号量使用
当处理完所有的事情后继续等待下一个任务通知*/
do {
xEvent = xQueryPeripheral();
if ( xEvent != NO_MORE_EVENTS ) {
vProcessPeripheralEvent( xEvent );
}
} while ( xEvent != NO_MORE_EVENTS );
}
}
9.xTaskNotifyWait()
xTaskNotifyWait()函数用于实现全功能版的等待任务通知,一个全能型的任务通知接收函数,可以灵活的运用于轻量级的消息队列二值信号量,计数信号量和事件组并富有超时等待机制。
/* 这个任务展示使用任务通知值的位来传递不同的事件
这在某些情况下可以代替事件标志组。*/
void vAnEventProcessingTask( void *pvParameters )
{
uint32_t ulNotifiedValue;
for ( ;; ) {
/* 等待任务通知,无限期阻塞(没有超时,所以没有必要检查函数返回值)
这个任务的任务通知值的位由标志事件发生的任务或者中断来设置*/
xTaskNotifyWait( 0x00, /* 在进入的时候不清除通知值的任何位 */
ULONG_MAX, /* 在退出的时候复位通知值为 0 */
&ulNotifiedValue, /* 任务通知值传递到变量
ulNotifiedValue 中*/
portMAX_DELAY ); /* 无限期等待 */
/* 根据任务通知值里面的各个位的值处理事情 */
if ( ( ulNotifiedValue & 0x01 ) != 0 ) {
/* 位 0 被置 1 */
prvProcessBit0Event();
}
if ( ( ulNotifiedValue & 0x02 ) != 0 ) {
/* 位 1 被置 1 */
prvProcessBit1Event();
}
if ( ( ulNotifiedValue & 0x04 ) != 0 ) {
/* 位 2 被置 1 */
prvProcessBit2Event();
}
/* ... 等等 */
}
}
四、任务通知实验
1、代替消息队列
#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "limits.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define LED1_TASK_PRIO 2
//任务堆栈大小
#define LED1_STK_SIZE 50
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);
//任务优先级
#define Receive1_TASK_PRIO 3
//任务堆栈大小
#define Receive1_STK_SIZE 512
//任务句柄
TaskHandle_t Receive1Task_Handler;
//任务函数
void Receive1_task(void *pvParameters);
//任务优先级
#define Receive2_TASK_PRIO 4
//任务堆栈大小
#define Receive2_STK_SIZE 512
//任务句柄
TaskHandle_t Receive2Task_Handler;
//任务函数
void Receive2_task(void *pvParameters);
//任务优先级
#define Send_TASK_PRIO 5
//任务堆栈大小
#define Send_STK_SIZE 512
//任务句柄
TaskHandle_t SendTask_Handler;
//任务函数
void Send_task(void *pvParameters);
#define USE_CHAR 0 /* 测试字符串的时候配置为 1 ,测试变量配置为 0 */
/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
int main()
{
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
LED_Init();
KEY_Init();
USART1_Init(115200);
printf("FreeRTOS任务通知代替消息队列实验\r\n");
printf("按下KEY_UP或者KEY1进行任务消息通知发送 \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(); //进入临界区
//创建LED1任务
xTaskCreate((TaskFunction_t )led1_task,
(const char* )"led1_task",
(uint16_t )LED1_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED1_TASK_PRIO,
(TaskHandle_t* )&LED1Task_Handler);
//创建Receive1任务
xTaskCreate((TaskFunction_t )Receive1_task,
(const char* )"Receive1_task",
(uint16_t )Receive1_STK_SIZE,
(void* )NULL,
(UBaseType_t )Receive1_TASK_PRIO,
(TaskHandle_t* )&Receive1Task_Handler);
//创建Receive2任务
xTaskCreate((TaskFunction_t )Receive2_task,
(const char* )"Receive2_task",
(uint16_t )Receive2_STK_SIZE,
(void* )NULL,
(UBaseType_t )Receive2_TASK_PRIO,
(TaskHandle_t* )&Receive2Task_Handler);
//创建Send任务
xTaskCreate((TaskFunction_t )Send_task,
(const char* )"Send_task",
(uint16_t )Send_STK_SIZE,
(void* )NULL,
(UBaseType_t )Send_TASK_PRIO,
(TaskHandle_t* )&SendTask_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//LED1任务函数
void led1_task(void *pvParameters)
{
while(1)
{
LED1=0;
vTaskDelay(200);
LED1=1;
vTaskDelay(800);
}
}
//Receive1任务函数
void Receive1_task(void *pvParameters)
{
BaseType_t xReturn = pdTRUE;
#if USE_CHAR
char *r_char;
#else
uint32_t r_num;
#endif
while(1)
{
//获取任务通知 ,没获取到则一直等待
xReturn=xTaskNotifyWait(0x0, //进入函数的时候不清除任务bit
ULONG_MAX, //退出函数的时候清除所有的bit
#if USE_CHAR
(uint32_t *)&r_char, //保存任务通知值
#else
&r_num, //保存任务通知值
#endif
portMAX_DELAY); //阻塞时间
if( pdTRUE == xReturn )
#if USE_CHAR
printf("Receive1_Task 任务通知消息为 %s \n",r_char);
#else
printf("Receive1_Task 任务通知消息为 %d \n",r_num);
#endif
}
}
//Receive2任务函数
void Receive2_task(void *pvParameters)
{
BaseType_t xReturn = pdTRUE;
#if USE_CHAR
char *r_char;
#else
uint32_t r_num;
#endif
while(1)
{
//获取任务通知 ,没获取到则一直等待
xReturn=xTaskNotifyWait(0x0, //进入函数的时候不清除任务bit
ULONG_MAX, //退出函数的时候清除所有的bit
#if USE_CHAR
(uint32_t *)&r_char, //保存任务通知值
#else
&r_num, //保存任务通知值
#endif
portMAX_DELAY); //阻塞时间
if( pdTRUE == xReturn )
#if USE_CHAR
printf("Receive2_Task 任务通知消息为 %s \n",r_char);
#else
printf("Receive2_Task 任务通知消息为 %d \n",r_num);
#endif
}
}
//Send任务函数
void Send_task(void *pvParameters)
{
BaseType_t xReturn = pdPASS;
u8 key=0;
#if USE_CHAR
char test_str1[] = "this is a mail test 1";/* 邮箱消息test1 */
char test_str2[] = "this is a mail test 2";/* 邮箱消息test2 */
#else
uint32_t send1 = 1;
uint32_t send2 = 2;
#endif
while(1)
{
key=KEY_Scan(0);
if(key==KEY_UP_PRESS)
{
xReturn = xTaskNotify(Receive1Task_Handler, /*任务句柄*/
#if USE_CHAR
(uint32_t)&test_str1, /* 发送的数据,最大为4字节 */
#else
send1, /* 发送的数据,最大为4字节 */
#endif
eSetValueWithOverwrite );/*覆盖当前通知*/
if( xReturn == pdPASS )
printf("Receive1_Task_Handle 任务通知消息发送成功!\r\n");
}
else if(key==KEY1_PRESS)
{
xReturn = xTaskNotify(Receive2Task_Handler, /*任务句柄*/
#if USE_CHAR
(uint32_t)&test_str2, /* 发送的数据,最大为4字节 */
#else
send2, /* 发送的数据,最大为4字节 */
#endif
eSetValueWithOverwrite );/*覆盖当前通知*/
if( xReturn == pdPASS )
printf("Receive2_Task_Handle 任务通知消息发送成功!\r\n");
}
vTaskDelay(20);
}
}

2、代替二值信号量实验
#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define LED1_TASK_PRIO 2
//任务堆栈大小
#define LED1_STK_SIZE 50
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);
//任务优先级
#define Receive1_TASK_PRIO 3
//任务堆栈大小
#define Receive1_STK_SIZE 512
//任务句柄
TaskHandle_t Receive1Task_Handler;
//任务函数
void Receive1_task(void *pvParameters);
//任务优先级
#define Receive2_TASK_PRIO 4
//任务堆栈大小
#define Receive2_STK_SIZE 512
//任务句柄
TaskHandle_t Receive2Task_Handler;
//任务函数
void Receive2_task(void *pvParameters);
//任务优先级
#define Send_TASK_PRIO 5
//任务堆栈大小
#define Send_STK_SIZE 512
//任务句柄
TaskHandle_t SendTask_Handler;
//任务函数
void Send_task(void *pvParameters);
/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
int main()
{
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
LED_Init();
KEY_Init();
USART1_Init(115200);
printf("FreeRTOS任务通知代替二值信号量实验\r\n");
printf("按下KEY_UP或者KEY1进行任务与任务间的同步\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(); //进入临界区
//创建LED1任务
xTaskCreate((TaskFunction_t )led1_task,
(const char* )"led1_task",
(uint16_t )LED1_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED1_TASK_PRIO,
(TaskHandle_t* )&LED1Task_Handler);
//创建Receive1任务
xTaskCreate((TaskFunction_t )Receive1_task,
(const char* )"Receive1_task",
(uint16_t )Receive1_STK_SIZE,
(void* )NULL,
(UBaseType_t )Receive1_TASK_PRIO,
(TaskHandle_t* )&Receive1Task_Handler);
//创建Receive2任务
xTaskCreate((TaskFunction_t )Receive2_task,
(const char* )"Receive2_task",
(uint16_t )Receive2_STK_SIZE,
(void* )NULL,
(UBaseType_t )Receive2_TASK_PRIO,
(TaskHandle_t* )&Receive2Task_Handler);
//创建Send任务
xTaskCreate((TaskFunction_t )Send_task,
(const char* )"Send_task",
(uint16_t )Send_STK_SIZE,
(void* )NULL,
(UBaseType_t )Send_TASK_PRIO,
(TaskHandle_t* )&SendTask_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//LED1任务函数
void led1_task(void *pvParameters)
{
while(1)
{
LED1=0;
vTaskDelay(200);
LED1=1;
vTaskDelay(800);
}
}
//Receive1任务函数
void Receive1_task(void *pvParameters)
{
while(1)
{
//获取任务通知 ,没获取到则一直等待
ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
printf("Receive1_Task 任务通知获取成功!\n\n");
}
}
//Receive2任务函数
void Receive2_task(void *pvParameters)
{
while(1)
{
//获取任务通知 ,没获取到则一直等待
ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
printf("Receive2_Task 任务通知获取成功!\n\n");
}
}
//Send任务函数
void Send_task(void *pvParameters)
{
BaseType_t xReturn = pdPASS;
u8 key=0;
while(1)
{
key=KEY_Scan(0);
if(key==KEY_UP_PRESS)
{
xReturn = xTaskNotifyGive(Receive1Task_Handler);
if( xReturn == pdTRUE )
printf("Receive1_Task_Handle 任务通知发送成功!\r\n");
}
else if(key==KEY1_PRESS)
{
xReturn = xTaskNotifyGive(Receive2Task_Handler);
if( xReturn == pdTRUE )
printf("Receive2_Task_Handle 任务通知发送成功!\r\n");
}
vTaskDelay(20);
}
}

3、代替计数信号量实验
#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define LED1_TASK_PRIO 2
//任务堆栈大小
#define LED1_STK_SIZE 50
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);
//任务优先级
#define Take_TASK_PRIO 3
//任务堆栈大小
#define Take_STK_SIZE 512
//任务句柄
TaskHandle_t TakeTask_Handler;
//任务函数
void Take_task(void *pvParameters);
//任务优先级
#define Give_TASK_PRIO 4
//任务堆栈大小
#define Give_STK_SIZE 512
//任务句柄
TaskHandle_t GiveTask_Handler;
//任务函数
void Give_task(void *pvParameters);
/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
int main()
{
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
LED_Init();
KEY_Init();
USART1_Init(115200);
printf("FreeRTOS任务通知代替计数信号量实验\r\n");
printf("车位默认值为0个,按下KEY1申请车位,按下KEY2释放车位!\n\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(); //进入临界区
//创建LED1任务
xTaskCreate((TaskFunction_t )led1_task,
(const char* )"led1_task",
(uint16_t )LED1_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED1_TASK_PRIO,
(TaskHandle_t* )&LED1Task_Handler);
//创建Take任务
xTaskCreate((TaskFunction_t )Take_task,
(const char* )"Take_task",
(uint16_t )Take_STK_SIZE,
(void* )NULL,
(UBaseType_t )Take_TASK_PRIO,
(TaskHandle_t* )&TakeTask_Handler);
//创建Give任务
xTaskCreate((TaskFunction_t )Give_task,
(const char* )"Give_task",
(uint16_t )Give_STK_SIZE,
(void* )NULL,
(UBaseType_t )Give_TASK_PRIO,
(TaskHandle_t* )&GiveTask_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//LED1任务函数
void led1_task(void *pvParameters)
{
while(1)
{
LED1=0;
vTaskDelay(200);
LED1=1;
vTaskDelay(800);
}
}
//Give任务函数
void Give_task(void *pvParameters)
{
BaseType_t xReturn = pdPASS;
u8 key=0;
while(1)
{
key=KEY_Scan(0);
if(key==KEY2_PRESS)
{
xTaskNotifyGive(TakeTask_Handler);//发送任务通知
if ( pdPASS == xReturn )
printf( "KEY2被按下,释放1个停车位。\n" );
}
vTaskDelay(20);
}
}
//Take任务函数
void Take_task(void *pvParameters)
{
uint32_t take_num = pdTRUE;
u8 key=0;
while(1)
{
key=KEY_Scan(0);
if(key==KEY1_PRESS)
{
//获取任务通知 ,没获取到则不等待
take_num=ulTaskNotifyTake(pdFALSE,0);
if(take_num > 0)
printf( "KEY1被按下,成功申请到停车位,当前车位为 %d \n", take_num - 1);
else
printf( "KEY1被按下,车位已经没有了,请按KEY2释放车位\n" );
}
vTaskDelay(20);
}
}

4、代替事件组实验
#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "limits.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define LED1_TASK_PRIO 2
//任务堆栈大小
#define LED1_STK_SIZE 50
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);
//任务优先级
#define LED2_TASK_PRIO 3
//任务堆栈大小
#define LED2_STK_SIZE 50
//任务句柄
TaskHandle_t LED2Task_Handler;
//任务函数
void led2_task(void *pvParameters);
//任务优先级
#define KEY_TASK_PRIO 4
//任务堆栈大小
#define KEY_STK_SIZE 512
//任务句柄
TaskHandle_t KEYTask_Handler;
//任务函数
void key_task(void *pvParameters);
#define KEY1_EVENT (0x01 << 0)//设置事件掩码的位0
#define KEY2_EVENT (0x01 << 1)//设置事件掩码的位1
/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
int main()
{
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
LED_Init();
KEY_Init();
USART1_Init(115200);
printf("FreeRTOS任务通知代替事件组实验\r\n");
printf("按下KEY1|KEY2发送任务事件通知!\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(); //进入临界区
//创建LED1任务
xTaskCreate((TaskFunction_t )led1_task,
(const char* )"led1_task",
(uint16_t )LED1_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED1_TASK_PRIO,
(TaskHandle_t* )&LED1Task_Handler);
//创建LED2任务
xTaskCreate((TaskFunction_t )led2_task,
(const char* )"led2_task",
(uint16_t )LED2_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED2_TASK_PRIO,
(TaskHandle_t* )&LED2Task_Handler);
//创建KEY任务
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(); //退出临界区
}
//LED1任务函数
void led1_task(void *pvParameters)
{
while(1)
{
LED1=0;
vTaskDelay(200);
LED1=1;
vTaskDelay(800);
}
}
//LED2任务函数
void led2_task(void *pvParameters)
{
BaseType_t xReturn = pdTRUE;
uint32_t r_event = 0; /* 定义一个事件接收变量 */
uint32_t last_event = 0;/* 定义一个保存事件的变量 */
while(1)
{
//获取任务通知 ,没获取到则一直等待
xReturn = xTaskNotifyWait(0x0, //进入函数的时候不清除任务bit
ULONG_MAX, //退出函数的时候清除所有的bitR
&r_event, //保存任务通知值
portMAX_DELAY); //阻塞时间
if( pdTRUE == xReturn )
{
last_event |= r_event;
if(last_event == (KEY1_EVENT|KEY2_EVENT))
{
last_event=0;
printf ( "KEY1与KEY2都按下\n");
LED2=!LED2;
}
else
last_event = r_event;
}
}
}
//KEY任务函数
void key_task(void *pvParameters)
{
u8 key=0;
while(1)
{
key=KEY_Scan(0);
if(key==KEY1_PRESS)
{
printf ( "KEY1被按下\n" );
/* 触发一个事件1 */
xTaskNotify((TaskHandle_t )LED2Task_Handler,//接收任务通知的任务句柄
(uint32_t )KEY1_EVENT,//要触发的事件
(eNotifyAction)eSetBits);//设置任务通知值中的位
}
else if(key==KEY2_PRESS)
{
printf ( "KEY2被按下\n" );
/* 触发一个事件2 */
xTaskNotify((TaskHandle_t )LED2Task_Handler,//接收任务通知的任务句柄
(uint32_t )KEY2_EVENT,//要触发的事件
(eNotifyAction)eSetBits);//设置任务通知值中的位
}
vTaskDelay(20);
}
}


被折叠的 条评论
为什么被折叠?



