从0到1学习FreeRTOS:FreeRTOS 内核应用开发:(二十五)任务通知 NO.3 任务通知实验
一、任务通知代替消息队列:
任务通知代替消息队列是在FreeRTOS中创建了三个任务,其中两个任务是用于接收任务通知,另一个任务发送任务通知。三个任务独立运行,发送消息任务是通过检测按键的按下情况来发送消息通知,另两个任务获取消息通知,在任务通知中没有可用的通知之前就一直等待消息。
实验步骤:
- 明确使用哪个函数
- 发送消息:xTaskNotify()
- 接收消息:xTaskNotifyWait()
- 创建任务
- 实现任务主体逻辑
实验代码:
/********************************************************************************* @file main.c* @author Sumjess* @version V1.0* @date 2019-09-xx* @brief MDK5.27******************************************************************************* @attention** 实验平台 :STM32 F429 * CSDN Blog :https://blog.csdn.net/qq_38351824* 微信公众号 :Tech云********************************************************************************//*
*************************************************************************
* 包含的头文件
*************************************************************************
*/
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h" //消息队列
#include "semphr.h" //信号量、互斥信号量
#include "event_groups.h" //事件
#include "timers.h" //软件定时器/* 开发板硬件bsp头文件 */
#include "sum_common.h"
#include "limits.h" //任务通知使用的
/**************************** 任务句柄 ********************************/
/* * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么* 这个句柄可以为NULL。*/
static TaskHandle_t AppTaskCreate_Handle = NULL;/* 创建任务句柄 */static TaskHandle_t Receive1_Task_Handle = NULL;/* Receive1_Task任务句柄 */
static TaskHandle_t Receive2_Task_Handle = NULL;/* Receive2_Task任务句柄 */
static TaskHandle_t Send_Task_Handle = NULL;/* Send_Task任务句柄 *//********************************** 内核对象句柄 *********************************/
/** 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核* 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我* 们就可以通过这个句柄操作这些内核对象。** 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,* 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数* 来完成的* *//******************************* 全局变量声明 ************************************/
/** 当我们在写应用程序的时候,可能需要用到一些全局变量。*//********************************** 宏定义 **************************************/
/** 当我们在写应用程序的时候,可能需要用到一些宏定义。*/
#define USE_CHAR 0 /* 测试字符串的时候配置为 1 ,测试变量配置为 0 *//*
*************************************************************************
* 函数声明
*************************************************************************
*/
static void AppTaskCreate(void);/* 用于创建任务 */static void Receive1_Task(void* pvParameters);/* Receive1_Task任务实现 */
static void Receive2_Task(void* pvParameters);/* Receive2_Task任务实现 */static void Send_Task(void* pvParameters);/* Send_Task任务实现 */static void BSP_Init(void);/* 用于初始化板载相关资源 *//****************************************************************** @brief 主函数* @param 无* @retval 无* @note 第一步:开发板硬件初始化 第二步:创建APP应用任务第三步:启动FreeRTOS,开始多任务调度****************************************************************/
int main(void)
{ BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS *//* 开发板硬件初始化 */BSP_Init();printf("这是一个 FreeRTOS 任务通知实验!\r\n");printf("按下KEY1或者KEY2进行任务消息通知发送 \r\n");/* 创建AppTaskCreate任务 */xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数---即任务函数的名称,需要我们自己定义并且实现。*/(const char* )"AppTaskCreate",/* 任务名字---字符串形式, 最大长度由 FreeRTOSConfig.h 中定义的configMAX_TASK_NAME_LEN 宏指定,多余部分会被自动截掉,这里任务名字最好要与任务函数入口名字一致,方便进行调试。*/(uint16_t )512, /* 任务栈大小---字符串形式, 最大长度由 FreeRTOSConfig.h 中定义的configMAX_TASK_NAME_LEN 宏指定,多余部分会被自动截掉,这里任务名字最好要与任务函数入口名字一致,方便进行调试。*/(void* )NULL,/* 任务入口函数参数---字符串形式, 最大长度由 FreeRTOSConfig.h 中定义的configMAX_TASK_NAME_LEN 宏指定,多余部分会被自动截掉,这里任务名字最好要与任务函数入口名字一致,方便进行调试。*/(UBaseType_t )1, /* 任务的优先级---优先级范围根据 FreeRTOSConfig.h 中的宏configMAX_PRIORITIES 决定, 如果使能 configUSE_PORT_OPTIMISED_TASK_SELECTION,这个宏定义,则最多支持 32 个优先级;如果不用特殊方法查找下一个运行的任务,那么则不强制要求限制最大可用优先级数目。在 FreeRTOS 中, 数值越大优先级越高, 0 代表最低优先级。*/(TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针---在使用内存的时候,需要给任务初始化函数xTaskCreateStatic()传递预先定义好的任务控制块的指针。在使用动态内存的时候,任务创建函数 xTaskCreate()会返回一个指针指向任务控制块,该任务控制块是 xTaskCreate()函数里面动态分配的一块内存。*/ /* 启动任务调度 */ if(pdPASS == xReturn)vTaskStartScheduler(); /* 启动任务,开启调度 */elsereturn -1; while(1); /* 正常不会执行到这里 */
}/************************************************************************ @ 函数名 : AppTaskCreate* @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面* @ 参数 : 无 * @ 返回值 : 无**********************************************************************/
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */taskENTER_CRITICAL(); //进入临界区/// /* 创建Receive1_Task任务 */xReturn = xTaskCreate((TaskFunction_t )Receive1_Task, /* 任务入口函数 */(const char* )"Receive1_Task",/* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL, /* 任务入口函数参数 */(UBaseType_t )2, /* 任务的优先级 */(TaskHandle_t* )&Receive1_Task_Handle);/* 任务控制块指针 */if(pdPASS == xReturn)printf("创建Receive1_Task任务成功!\r\n");/* 创建Receive2_Task任务 */xReturn = xTaskCreate((TaskFunction_t )Receive2_Task, /* 任务入口函数 */(const char* )"Receive2_Task",/* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL, /* 任务入口函数参数 */(UBaseType_t )3, /* 任务的优先级 */(TaskHandle_t* )&Receive2_Task_Handle);/* 任务控制块指针 */if(pdPASS == xReturn)printf("创建Receive2_Task任务成功!\r\n");/* 创建Send_Task任务 */xReturn = xTaskCreate((TaskFunction_t )Send_Task, /* 任务入口函数 */(const char* )"Send_Task",/* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL,/* 任务入口函数参数 */(UBaseType_t )4, /* 任务的优先级 */(TaskHandle_t* )&Send_Task_Handle);/* 任务控制块指针 */ if(pdPASS == xReturn)printf("创建Send_Task任务成功!\r\n");
///vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务taskEXIT_CRITICAL(); //退出临界区
}/*********************************************************************** @ 函数名 : Receive_Task* @ 功能说明: Receive_Task任务主体* @ 参数 : * @ 返回值 : 无********************************************************************/
static void Receive1_Task(void* parameter)
{ BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
#if USE_CHARchar *r_char;
#elseuint32_t r_num;
#endifwhile (1){/* BaseType_t xTaskNotifyWait(uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait ); * ulBitsToClearOnEntry:当没有接收到任务通知的时候将任务通知值与此参数的取反值进行按位与运算,当此参数为Oxfffff或者ULONG_MAX的时候就会将任务通知值清零。* ulBits ToClearOnExit:如果接收到了任务通知,在做完相应的处理退出函数之前将任务通知值与此参数的取反值进行按位与运算,当此参数为0xfffff或者ULONG MAX的时候就会将任务通知值清零。* pulNotification Value:此参数用来保存任务通知值。* xTick ToWait:阻塞时间。** 返回值:pdTRUE:获取到了任务通知。pdFALSE:任务通知获取失败。*///获取任务通知 ,没获取到则一直等待xReturn=xTaskNotifyWait(0x0, //进入函数的时候不清除任务bitULONG_MAX, //退出函数的时候清除所有的bit
#if USE_CHAR(uint32_t *)&r_char, //保存任务通知值
#else&r_num, //保存任务通知值
#endif portMAX_DELAY); //阻塞时间if( pdTRUE == xReturn )
#if USE_CHARprintf("Receive1_Task 任务通知消息为 %s \r\n",r_char);
#elseprintf("Receive1_Task 任务通知消息为 %d \r\n",r_num);
#endif LED1_TOGGLE;}
}/*********************************************************************** @ 函数名 : Receive_Task* @ 功能说明: Receive_Task任务主体* @ 参数 : * @ 返回值 : 无********************************************************************/
static void Receive2_Task(void* parameter)
{ BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
#if USE_CHARchar *r_char;
#elseuint32_t r_num;
#endifwhile (1){/* BaseType_t xTaskNotifyWait(uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait ); * ulBitsToClearOnEntry:当没有接收到任务通知的时候将任务通知值与此参数的取反值进行按位与运算,当此参数为Oxfffff或者ULONG_MAX的时候就会将任务通知值清零。* ulBits ToClearOnExit:如果接收到了任务通知,在做完相应的处理退出函数之前将任务通知值与此参数的取反值进行按位与运算,当此参数为0xfffff或者ULONG MAX的时候就会将任务通知值清零。* pulNotification Value:此参数用来保存任务通知值。* xTick ToWait:阻塞时间。** 返回值:pdTRUE:获取到了任务通知。pdFALSE:任务通知获取失败。*///获取任务通知 ,没获取到则一直等待xReturn=xTaskNotifyWait(0x0, //进入函数的时候不清除任务bitULONG_MAX, //退出函数的时候清除所有的bit
#if USE_CHAR(uint32_t *)&r_char, //保存任务通知值
#else&r_num, //保存任务通知值
#endif portMAX_DELAY); //阻塞时间if( pdTRUE == xReturn )
#if USE_CHARprintf("Receive2_Task 任务通知消息为 %s \r\n",r_char);
#elseprintf("Receive2_Task 任务通知消息为 %d \r\n",r_num);
#endif LED2_TOGGLE;}
}/*********************************************************************** @ 函数名 : Send_Task* @ 功能说明: Send_Task任务主体* @ 参数 : * @ 返回值 : 无********************************************************************/
static void Send_Task(void* parameter)
{ BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
#if USE_CHARchar test_str1[] = "this is a mail test 1";/* 邮箱消息test1 */char test_str2[] = "this is a mail test 2";/* 邮箱消息test2 */
#elseuint32_t send1 = 1;uint32_t send2 = 2;
#endifwhile (1){/* KEY1 被按下 */if( Key_Scan(KEY1_GPIO_PORT,KEY1_PIN) == KEY_ON ){/* 原型:BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction ); * eNoAction = 0,通知任务而不更新其通知值。* eSetBits, 设置任务通知值中的位。* eIncrement, 增加任务的通知值。* eSetvaluewithoverwrite,覆盖当前通知* eSetValueWithoutoverwrite 不覆盖当前通知* * pdFAIL:当参数eAction设置为eSetValueWithoutOverwrite的时候,* 如果任务通知值没有更新成功就返回pdFAIL。* pdPASS: eAction 设置为其他选项的时候统一返回pdPASS。*/xReturn = xTaskNotify( Receive1_Task_Handle, /*任务句柄*/
#if USE_CHAR (uint32_t)&test_str1, /* 发送的数据,最大为4字节 */
#elsesend1, /* 发送的数据,最大为4字节 */
#endifeSetValueWithOverwrite );/*覆盖当前通知*/if( xReturn == pdPASS )printf("Receive1_Task_Handle 任务通知消息发送成功!\r\n");} /* KEY2 被按下 */if( Key_Scan(KEY2_GPIO_PORT,KEY2_PIN) == KEY_ON ){xReturn = xTaskNotify( Receive2_Task_Handle, /*任务句柄*/
#if USE_CHAR (uint32_t)&test_str2, /* 发送的数据,最大为4字节 */
#elsesend2, /* 发送的数据,最大为4字节 */
#endifeSetValueWithOverwrite );/*覆盖当前通知*//* 此函数只会返回pdPASS */if( xReturn == pdPASS )printf("Receive2_Task_Handle 任务通知消息发送成功!\r\n");}vTaskDelay(20);}
}/************************************************************************ @ 函数名 : BSP_Init* @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面* @ 参数 : * @ 返回值 : 无*********************************************************************/
static void BSP_Init(void)
{/** STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,* 都统一用这个优先级分组,千万不要再分组,切忌。*/NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );/* LED 初始化 */LED_GPIO_Config();/* 串口初始化 */Debug_USART_Config();/* 按键初始化 */Key_GPIO_Config();
}/********************************END OF FILE****************************/
实验现象:
二、任务通知代替二值信号量:
任务通知代替消息队列是在FreeRTOS中创建了三个任务,其中两个任务是用于接收任务通知,另一个任务发送任务通知。三个任务独立运行,发送通知任务是通过检测按键的按下情况来发送通知,另两个任务获取通知,在任务通知中没有可用的通知之前就一直等待任务通知,获取到通知以后就将通知值清0,这样子是为了代替二值信号量,任务同步成功则继续执行。
实验步骤:
- 明确使用哪个函数
- 发送信号量:xTaskNotifyGive()
- 获取信号量:ulTaskNotifyTake()
- 创建任务
- 实现任务主体逻辑
实验代码:
/********************************************************************************* @file main.c* @author Sumjess* @version V1.0* @date 2019-09-xx* @brief MDK5.27******************************************************************************* @attention** 实验平台 :STM32 F429 * CSDN Blog :https://blog.csdn.net/qq_38351824* 微信公众号 :Tech云********************************************************************************//*
*************************************************************************
* 包含的头文件
*************************************************************************
*/
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h" //消息队列
#include "semphr.h" //信号量、互斥信号量
#include "event_groups.h" //事件
#include "timers.h" //软件定时器/* 开发板硬件bsp头文件 */
#include "sum_common.h"
#include "limits.h" //任务通知使用的
/**************************** 任务句柄 ********************************/
/* * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么* 这个句柄可以为NULL。*/
static TaskHandle_t AppTaskCreate_Handle = NULL;/* 创建任务句柄 */static TaskHandle_t Receive1_Task_Handle = NULL;/* Receive1_Task任务句柄 */
static TaskHandle_t Receive2_Task_Handle = NULL;/* Receive2_Task任务句柄 */
static TaskHandle_t Send_Task_Handle = NULL;/* Send_Task任务句柄 *//********************************** 内核对象句柄 *********************************/
/** 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核* 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我* 们就可以通过这个句柄操作这些内核对象。** 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,* 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数* 来完成的* *//******************************* 全局变量声明 ************************************/
/** 当我们在写应用程序的时候,可能需要用到一些全局变量。*//********************************** 宏定义 **************************************/
/** 当我们在写应用程序的时候,可能需要用到一些宏定义。*//*
*************************************************************************
* 函数声明
*************************************************************************
*/
static void AppTaskCreate(void);/* 用于创建任务 */static void Receive1_Task(void* pvParameters);/* Receive1_Task任务实现 */
static void Receive2_Task(void* pvParameters);/* Receive2_Task任务实现 */static void Send_Task(void* pvParameters);/* Send_Task任务实现 */static void BSP_Init(void);/* 用于初始化板载相关资源 *//****************************************************************** @brief 主函数* @param 无* @retval 无* @note 第一步:开发板硬件初始化 第二步:创建APP应用任务第三步:启动FreeRTOS,开始多任务调度****************************************************************/
int main(void)
{ BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS *//* 开发板硬件初始化 */BSP_Init();printf("这是一个 FreeRTOS 任务通知实验!\r\n");printf("按下KEY1或者KEY2进行任务消息通知发送 \r\n");/* 创建AppTaskCreate任务 */xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数---即任务函数的名称,需要我们自己定义并且实现。*/(const char* )"AppTaskCreate",/* 任务名字---字符串形式, 最大长度由 FreeRTOSConfig.h 中定义的configMAX_TASK_NAME_LEN 宏指定,多余部分会被自动截掉,这里任务名字最好要与任务函数入口名字一致,方便进行调试。*/(uint16_t )512, /* 任务栈大小---字符串形式, 最大长度由 FreeRTOSConfig.h 中定义的configMAX_TASK_NAME_LEN 宏指定,多余部分会被自动截掉,这里任务名字最好要与任务函数入口名字一致,方便进行调试。*/(void* )NULL,/* 任务入口函数参数---字符串形式, 最大长度由 FreeRTOSConfig.h 中定义的configMAX_TASK_NAME_LEN 宏指定,多余部分会被自动截掉,这里任务名字最好要与任务函数入口名字一致,方便进行调试。*/(UBaseType_t )1, /* 任务的优先级---优先级范围根据 FreeRTOSConfig.h 中的宏configMAX_PRIORITIES 决定, 如果使能 configUSE_PORT_OPTIMISED_TASK_SELECTION,这个宏定义,则最多支持 32 个优先级;如果不用特殊方法查找下一个运行的任务,那么则不强制要求限制最大可用优先级数目。在 FreeRTOS 中, 数值越大优先级越高, 0 代表最低优先级。*/(TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针---在使用内存的时候,需要给任务初始化函数xTaskCreateStatic()传递预先定义好的任务控制块的指针。在使用动态内存的时候,任务创建函数 xTaskCreate()会返回一个指针指向任务控制块,该任务控制块是 xTaskCreate()函数里面动态分配的一块内存。*/ /* 启动任务调度 */ if(pdPASS == xReturn)vTaskStartScheduler(); /* 启动任务,开启调度 */elsereturn -1; while(1); /* 正常不会执行到这里 */
}/************************************************************************ @ 函数名 : AppTaskCreate* @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面* @ 参数 : 无 * @ 返回值 : 无**********************************************************************/
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */taskENTER_CRITICAL(); //进入临界区/// /* 创建Receive1_Task任务 */xReturn = xTaskCreate((TaskFunction_t )Receive1_Task, /* 任务入口函数 */(const char* )"Receive1_Task",/* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL, /* 任务入口函数参数 */(UBaseType_t )2, /* 任务的优先级 */(TaskHandle_t* )&Receive1_Task_Handle);/* 任务控制块指针 */if(pdPASS == xReturn)printf("创建Receive1_Task任务成功!\r\n");/* 创建Receive2_Task任务 */xReturn = xTaskCreate((TaskFunction_t )Receive2_Task, /* 任务入口函数 */(const char* )"Receive2_Task",/* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL, /* 任务入口函数参数 */(UBaseType_t )3, /* 任务的优先级 */(TaskHandle_t* )&Receive2_Task_Handle);/* 任务控制块指针 */if(pdPASS == xReturn)printf("创建Receive2_Task任务成功!\r\n");/* 创建Send_Task任务 */xReturn = xTaskCreate((TaskFunction_t )Send_Task, /* 任务入口函数 */(const char* )"Send_Task",/* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL,/* 任务入口函数参数 */(UBaseType_t )4, /* 任务的优先级 */(TaskHandle_t* )&Send_Task_Handle);/* 任务控制块指针 */ if(pdPASS == xReturn)printf("创建Send_Task任务成功!\r\n");
///vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务taskEXIT_CRITICAL(); //退出临界区
}/*********************************************************************** @ 函数名 : Receive_Task* @ 功能说明: Receive_Task任务主体* @ 参数 : * @ 返回值 : 无********************************************************************/
static void Receive1_Task(void* parameter)
{ while (1){/* uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait ); * xClearCountOnExit:pdTRUE 在退出函数的时候任务任务通知值清零,类似二值信号量* pdFALSE 在退出函数ulTaskNotifyTakeO的时候任务通知值减一,类似计数型信号量。*///获取任务通知 ,没获取到则一直等待ulTaskNotifyTake(pdTRUE,portMAX_DELAY);printf("Receive1_Task 任务通知获取成功!\r\n");LED1_TOGGLE;}
}/*********************************************************************** @ 函数名 : Receive_Task* @ 功能说明: Receive_Task任务主体* @ 参数 : * @ 返回值 : 无********************************************************************/
static void Receive2_Task(void* parameter)
{ while (1){/* uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait ); * xClearCountOnExit:pdTRUE 在退出函数的时候任务任务通知值清零,类似二值信号量* pdFALSE 在退出函数ulTaskNotifyTakeO的时候任务通知值减一,类似计数型信号量。*///获取任务通知 ,没获取到则一直等待ulTaskNotifyTake(pdTRUE,portMAX_DELAY);printf("Receive2_Task 任务通知获取成功!\r\n");LED2_TOGGLE;}
}/*********************************************************************** @ 函数名 : Send_Task* @ 功能说明: Send_Task任务主体* @ 参数 : * @ 返回值 : 无********************************************************************/
static void Send_Task(void* parameter)
{ BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */while (1){/* KEY1 被按下 */if( Key_Scan(KEY1_GPIO_PORT,KEY1_PIN) == KEY_ON ){/* 原型:BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); */xReturn = xTaskNotifyGive(Receive1_Task_Handle);/* 此函数只会返回pdPASS */if( xReturn == pdTRUE )printf("Receive1_Task_Handle 任务通知发送成功!\r\n");} /* KEY2 被按下 */if( Key_Scan(KEY2_GPIO_PORT,KEY2_PIN) == KEY_ON ){xReturn = xTaskNotifyGive(Receive2_Task_Handle);/* 此函数只会返回pdPASS */if( xReturn == pdPASS )printf("Receive2_Task_Handle 任务通知发送成功!\r\n");}vTaskDelay(20);}
}/************************************************************************ @ 函数名 : BSP_Init* @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面* @ 参数 : * @ 返回值 : 无*********************************************************************/
static void BSP_Init(void)
{/** STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,* 都统一用这个优先级分组,千万不要再分组,切忌。*/NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );/* LED 初始化 */LED_GPIO_Config();/* 串口初始化 */Debug_USART_Config();/* 按键初始化 */Key_GPIO_Config();
}/********************************END OF FILE****************************/
实验现象:
三、任务通知代替计数信号量:
任务通知代替计数信号量是基于计数型信号量实验修改而来,模拟停车场工作运行。并且在FreeRTOS中创建了两个任务:一个是获取任务通知,一个是发送任务通知,两个任务独立运行,获取通知的任务是通过按下KEY1按键获取,模拟停车场停车操作,其等待时间是0;发送通知的任务则是通过检测KEY2按键按下进行通知的发送,模拟停车场取车操作。
实验步骤:
- 明确使用哪个函数
- 发送信号量:xTaskNotifyGive()
- 获取信号量:ulTaskNotifyTake()
- 创建任务
- 实现任务主体逻辑
实验代码:
/********************************************************************************* @file main.c* @author Sumjess* @version V1.0* @date 2019-09-xx* @brief MDK5.27******************************************************************************* @attention** 实验平台 :STM32 F429 * CSDN Blog :https://blog.csdn.net/qq_38351824* 微信公众号 :Tech云********************************************************************************//*
*************************************************************************
* 包含的头文件
*************************************************************************
*/
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h" //消息队列
#include "semphr.h" //信号量、互斥信号量
#include "event_groups.h" //事件
#include "timers.h" //软件定时器/* 开发板硬件bsp头文件 */
#include "sum_common.h"
#include "limits.h" //任务通知使用的
/**************************** 任务句柄 ********************************/
/* * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么* 这个句柄可以为NULL。*/
static TaskHandle_t AppTaskCreate_Handle = NULL;/* 创建任务句柄 */static TaskHandle_t Take_Task_Handle = NULL;/* Take_Task任务句柄 */
static TaskHandle_t Give_Task_Handle = NULL;/* Give_Task任务句柄 *//********************************** 内核对象句柄 *********************************/
/** 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核* 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我* 们就可以通过这个句柄操作这些内核对象。** 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,* 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数* 来完成的* */
SemaphoreHandle_t CountSem_Handle =NULL;/******************************* 全局变量声明 ************************************/
/** 当我们在写应用程序的时候,可能需要用到一些全局变量。*//********************************** 宏定义 **************************************/
/** 当我们在写应用程序的时候,可能需要用到一些宏定义。*//*
*************************************************************************
* 函数声明
*************************************************************************
*/
static void AppTaskCreate(void);/* 用于创建任务 */static void Take_Task(void* pvParameters);/* Take_Task任务实现 */
static void Give_Task(void* pvParameters);/* Give_Task任务实现 */static void BSP_Init(void);/* 用于初始化板载相关资源 *//****************************************************************** @brief 主函数* @param 无* @retval 无* @note 第一步:开发板硬件初始化 第二步:创建APP应用任务第三步:启动FreeRTOS,开始多任务调度****************************************************************/
int main(void)
{ BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS *//* 开发板硬件初始化 */BSP_Init();printf("这是一个 FreeRTOS 任务通知实验!\r\n");printf("车位默认值为0个,按下KEY1申请车位,按下KEY2释放车位! \r\n");/* 创建AppTaskCreate任务 */xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数---即任务函数的名称,需要我们自己定义并且实现。*/(const char* )"AppTaskCreate",/* 任务名字---字符串形式, 最大长度由 FreeRTOSConfig.h 中定义的configMAX_TASK_NAME_LEN 宏指定,多余部分会被自动截掉,这里任务名字最好要与任务函数入口名字一致,方便进行调试。*/(uint16_t )512, /* 任务栈大小---字符串形式, 最大长度由 FreeRTOSConfig.h 中定义的configMAX_TASK_NAME_LEN 宏指定,多余部分会被自动截掉,这里任务名字最好要与任务函数入口名字一致,方便进行调试。*/(void* )NULL,/* 任务入口函数参数---字符串形式, 最大长度由 FreeRTOSConfig.h 中定义的configMAX_TASK_NAME_LEN 宏指定,多余部分会被自动截掉,这里任务名字最好要与任务函数入口名字一致,方便进行调试。*/(UBaseType_t )1, /* 任务的优先级---优先级范围根据 FreeRTOSConfig.h 中的宏configMAX_PRIORITIES 决定, 如果使能 configUSE_PORT_OPTIMISED_TASK_SELECTION,这个宏定义,则最多支持 32 个优先级;如果不用特殊方法查找下一个运行的任务,那么则不强制要求限制最大可用优先级数目。在 FreeRTOS 中, 数值越大优先级越高, 0 代表最低优先级。*/(TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针---在使用内存的时候,需要给任务初始化函数xTaskCreateStatic()传递预先定义好的任务控制块的指针。在使用动态内存的时候,任务创建函数 xTaskCreate()会返回一个指针指向任务控制块,该任务控制块是 xTaskCreate()函数里面动态分配的一块内存。*/ /* 启动任务调度 */ if(pdPASS == xReturn)vTaskStartScheduler(); /* 启动任务,开启调度 */elsereturn -1; while(1); /* 正常不会执行到这里 */
}/************************************************************************ @ 函数名 : AppTaskCreate* @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面* @ 参数 : 无 * @ 返回值 : 无**********************************************************************/
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */taskENTER_CRITICAL(); //进入临界区/// /* 创建Take_Task任务 */xReturn = xTaskCreate((TaskFunction_t )Take_Task, /* 任务入口函数 */(const char* )"Take_Task",/* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL, /* 任务入口函数参数 */(UBaseType_t )2, /* 任务的优先级 */(TaskHandle_t* )&Take_Task_Handle);/* 任务控制块指针 */if(pdPASS == xReturn)printf("创建Take_Task任务成功!\r\n");/* 创建Give_Task任务 */xReturn = xTaskCreate((TaskFunction_t )Give_Task, /* 任务入口函数 */(const char* )"Give_Task",/* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL,/* 任务入口函数参数 */(UBaseType_t )3, /* 任务的优先级 */(TaskHandle_t* )&Give_Task_Handle);/* 任务控制块指针 */ if(pdPASS == xReturn)printf("创建Give_Task任务成功!\r\n");
///vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务taskEXIT_CRITICAL(); //退出临界区
}/*********************************************************************** @ 函数名 : Take_Task* @ 功能说明: Take_Task任务主体* @ 参数 : * @ 返回值 : 无********************************************************************/
static void Take_Task(void* parameter)
{ uint32_t take_num = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS *//* 任务都是一个无限循环,不能返回 */while (1){//如果KEY1被单击if( Key_Scan(KEY1_GPIO_PORT,KEY1_PIN) == KEY_ON ) {/* uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait ); * xClearCountOnExit:pdTRUE 在退出函数的时候任务任务通知值清零,类似二值信号量* pdFALSE 在退出函数ulTaskNotifyTakeO的时候任务通知值减一,类似计数型信号量。*///获取任务通知 ,没获取到则不等待take_num=ulTaskNotifyTake(pdFALSE,0);//if(take_num > 0)printf( "KEY1被按下,成功申请到停车位,当前车位为 %d \r\n", take_num - 1);elseprintf( "KEY1被按下,车位已经没有了,请按KEY2释放车位\r\n" ); }vTaskDelay(20); //每20ms扫描一次 }
}/*********************************************************************** @ 函数名 : Give_Task* @ 功能说明: Give_Task任务主体* @ 参数 : * @ 返回值 : 无********************************************************************/
static void Give_Task(void* parameter)
{ BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS *//* 任务都是一个无限循环,不能返回 */while (1){//如果KEY2被单击if( Key_Scan(KEY2_GPIO_PORT,KEY2_PIN) == KEY_ON ) {/* 原型:BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); *//* 释放一个任务通知 */xTaskNotifyGive(Take_Task_Handle);//发送任务通知/* 此函数只会返回pdPASS */if ( pdPASS == xReturn ) printf( "KEY2被按下,释放1个停车位。\r\n" );}vTaskDelay(20); //每20ms扫描一次 }
}/************************************************************************ @ 函数名 : BSP_Init* @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面* @ 参数 : * @ 返回值 : 无*********************************************************************/
static void BSP_Init(void)
{/** STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,* 都统一用这个优先级分组,千万不要再分组,切忌。*/NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );/* LED 初始化 */LED_GPIO_Config();/* 串口初始化 */Debug_USART_Config();/* 按键初始化 */Key_GPIO_Config();
}/********************************END OF FILE****************************/
实验现象:
四、任务通知代替事件组:
任务通知代替事件组实验是在事件标志组实验基础上进行修改,实验任务通知替代事件实现事件类型的通信,该实验是在FreeRTOS中创建了两个任务,一个是发送事件通知任务,一个是等待事件通知任务,两个任务独立运行,发送事件通知任务通过检测按键的按下情况设置不同的通知值位,等待事件通知任务则获取这任务通知值,并且根据通知值判断两个事件是否都发生,如果是则输出相应信息,LED进行翻转。等待事件通知任务的等待时间是portMAX_DELAY,一直在等待事件通知的发生,等待获取到事件之后清除对应的任务通知值的位。
实验步骤:
- 明确使用哪个函数
- 发送消息:xTaskNotify()
- 接收消息:xTaskNotifyWait()
- 定义事件位
- 创建任务
- 实现任务主体逻辑
实验代码:
/********************************************************************************* @file main.c* @author Sumjess* @version V1.0* @date 2019-09-xx* @brief MDK5.27******************************************************************************* @attention** 实验平台 :STM32 F429 * CSDN Blog :https://blog.csdn.net/qq_38351824* 微信公众号 :Tech云********************************************************************************//*
*************************************************************************
* 包含的头文件
*************************************************************************
*/
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h" //消息队列
#include "semphr.h" //信号量、互斥信号量
#include "event_groups.h" //事件
#include "timers.h" //软件定时器/* 开发板硬件bsp头文件 */
#include "sum_common.h"
#include "limits.h" //任务通知使用的
/**************************** 任务句柄 ********************************/
/* * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么* 这个句柄可以为NULL。*/
static TaskHandle_t AppTaskCreate_Handle = NULL;/* 创建任务句柄 */static TaskHandle_t LED_Task_Handle = NULL;/* LED_Task任务句柄 */
static TaskHandle_t KEY_Task_Handle = NULL;/* KEY_Task任务句柄 *//********************************** 内核对象句柄 *********************************/
/** 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核* 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我* 们就可以通过这个句柄操作这些内核对象。** 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,* 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数* 来完成的* *//******************************* 全局变量声明 ************************************/
/** 当我们在写应用程序的时候,可能需要用到一些全局变量。*//********************************** 宏定义 **************************************/
/** 当我们在写应用程序的时候,可能需要用到一些宏定义。*/
#define KEY1_EVENT (0x01 << 0)//设置事件掩码的位0
#define KEY2_EVENT (0x01 << 1)//设置事件掩码的位1/*
*************************************************************************
* 函数声明
*************************************************************************
*/
static void AppTaskCreate(void);/* 用于创建任务 */static void LED_Task(void* pvParameters);/* LED_Task 任务实现 */
static void KEY_Task(void* pvParameters);/* KEY_Task 任务实现 */static void BSP_Init(void);/* 用于初始化板载相关资源 *//****************************************************************** @brief 主函数* @param 无* @retval 无* @note 第一步:开发板硬件初始化 第二步:创建APP应用任务第三步:启动FreeRTOS,开始多任务调度****************************************************************/
int main(void)
{ BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS *//* 开发板硬件初始化 */BSP_Init();printf("这是一个 FreeRTOS 任务通知实验!\r\n");printf("按下KEY1|KEY2发送任务事件通知! \r\n");/* 创建AppTaskCreate任务 */xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数---即任务函数的名称,需要我们自己定义并且实现。*/(const char* )"AppTaskCreate",/* 任务名字---字符串形式, 最大长度由 FreeRTOSConfig.h 中定义的configMAX_TASK_NAME_LEN 宏指定,多余部分会被自动截掉,这里任务名字最好要与任务函数入口名字一致,方便进行调试。*/(uint16_t )512, /* 任务栈大小---字符串形式, 最大长度由 FreeRTOSConfig.h 中定义的configMAX_TASK_NAME_LEN 宏指定,多余部分会被自动截掉,这里任务名字最好要与任务函数入口名字一致,方便进行调试。*/(void* )NULL,/* 任务入口函数参数---字符串形式, 最大长度由 FreeRTOSConfig.h 中定义的configMAX_TASK_NAME_LEN 宏指定,多余部分会被自动截掉,这里任务名字最好要与任务函数入口名字一致,方便进行调试。*/(UBaseType_t )1, /* 任务的优先级---优先级范围根据 FreeRTOSConfig.h 中的宏configMAX_PRIORITIES 决定, 如果使能 configUSE_PORT_OPTIMISED_TASK_SELECTION,这个宏定义,则最多支持 32 个优先级;如果不用特殊方法查找下一个运行的任务,那么则不强制要求限制最大可用优先级数目。在 FreeRTOS 中, 数值越大优先级越高, 0 代表最低优先级。*/(TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针---在使用内存的时候,需要给任务初始化函数xTaskCreateStatic()传递预先定义好的任务控制块的指针。在使用动态内存的时候,任务创建函数 xTaskCreate()会返回一个指针指向任务控制块,该任务控制块是 xTaskCreate()函数里面动态分配的一块内存。*/ /* 启动任务调度 */ if(pdPASS == xReturn)vTaskStartScheduler(); /* 启动任务,开启调度 */elsereturn -1; while(1); /* 正常不会执行到这里 */
}/************************************************************************ @ 函数名 : AppTaskCreate* @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面* @ 参数 : 无 * @ 返回值 : 无**********************************************************************/
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */taskENTER_CRITICAL(); //进入临界区/// /* 创建LED_Task任务 */xReturn = xTaskCreate((TaskFunction_t )LED_Task, /* 任务入口函数 */(const char* )"LED_Task",/* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL, /* 任务入口函数参数 */(UBaseType_t )2, /* 任务的优先级 */(TaskHandle_t* )&LED_Task_Handle);/* 任务控制块指针 */if(pdPASS == xReturn)printf("创建LED_Task任务成功!\r\n");/* 创建KEY_Task任务 */xReturn = xTaskCreate((TaskFunction_t )KEY_Task, /* 任务入口函数 */(const char* )"KEY_Task",/* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL,/* 任务入口函数参数 */(UBaseType_t )3, /* 任务的优先级 */(TaskHandle_t* )&KEY_Task_Handle);/* 任务控制块指针 */ if(pdPASS == xReturn)printf("创建KEY_Task任务成功!\r\n");
///vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务taskEXIT_CRITICAL(); //退出临界区
}/*********************************************************************** @ 函数名 : LED_Task* @ 功能说明: LED_Task任务主体* @ 参数 : * @ 返回值 : 无********************************************************************/
static void LED_Task(void* parameter)
{ uint32_t r_event = 0; /* 定义一个事件接收变量 */uint32_t last_event = 0;/* 定义一个保存事件的变量 */BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS *//* 任务都是一个无限循环,不能返回 */while (1){/* BaseType_t xTaskNotifyWait(uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait ); * ulBitsToClearOnEntry:当没有接收到任务通知的时候将任务通知值与此参数的取反值进行按位与运算,当此参数为Oxfffff或者ULONG_MAX的时候就会将任务通知值清零。* ulBits ToClearOnExit:如果接收到了任务通知,在做完相应的处理退出函数之前将任务通知值与此参数的取反值进行按位与运算,当此参数为0xfffff或者ULONG MAX的时候就会将任务通知值清零。* pulNotification Value:此参数用来保存任务通知值。* xTick ToWait:阻塞时间。** 返回值:pdTRUE:获取到了任务通知。pdFALSE:任务通知获取失败。*///获取任务通知 ,没获取到则一直等待xReturn = xTaskNotifyWait(0x0, //进入函数的时候不清除任务bitULONG_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都按下\r\n"); LED1_TOGGLE; //LED1 反转 }else /* 否则就更新事件 */last_event = r_event; /* 更新上一次触发的事件 */}}
}/*********************************************************************** @ 函数名 : KEY_Task* @ 功能说明: KEY_Task任务主体* @ 参数 : * @ 返回值 : 无********************************************************************/
static void KEY_Task(void* parameter)
{ /* 任务都是一个无限循环,不能返回 */while (1){if( Key_Scan(KEY1_GPIO_PORT,KEY1_PIN) == KEY_ON ) {printf ( "KEY1被按下\r\n" );/* 原型:BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction ); * eNoAction = 0,通知任务而不更新其通知值。* eSetBits, 设置任务通知值中的位。* eIncrement, 增加任务的通知值。* eSetvaluewithoverwrite,覆盖当前通知* eSetValueWithoutoverwrite 不覆盖当前通知* * pdFAIL:当参数eAction设置为eSetValueWithoutOverwrite的时候,* 如果任务通知值没有更新成功就返回pdFAIL。* pdPASS: eAction 设置为其他选项的时候统一返回pdPASS。*//* 触发一个事件1 */xTaskNotify((TaskHandle_t )LED_Task_Handle,//接收任务通知的任务句柄(uint32_t )KEY1_EVENT, //要触发的事件(eNotifyAction)eSetBits); //设置任务通知值中的位}if( Key_Scan(KEY2_GPIO_PORT,KEY2_PIN) == KEY_ON ) {printf ( "KEY2被按下\r\n" ); /* 触发一个事件2 */xTaskNotify((TaskHandle_t )LED_Task_Handle,//接收任务通知的任务句柄(uint32_t )KEY2_EVENT, //要触发的事件(eNotifyAction)eSetBits); //设置任务通知值中的位 }vTaskDelay(20); //每20ms扫描一次 }
}/************************************************************************ @ 函数名 : BSP_Init* @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面* @ 参数 : * @ 返回值 : 无*********************************************************************/
static void BSP_Init(void)
{/** STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,* 都统一用这个优先级分组,千万不要再分组,切忌。*/NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );/* LED 初始化 */LED_GPIO_Config();/* 串口初始化 */Debug_USART_Config();/* 按键初始化 */Key_GPIO_Config();
}/********************************END OF FILE****************************/
实验现象: