队列又称消息队列,是一种常用于任务间通信的数据结构。队列可以在任务与任务间、中断和任务间传递信息,实现了任务接收来自其他任务或中断的不固定长度的消息,任务能够从队列里面读取消息,当队列中的消息是空时,读取消息的任务将被阻塞,用户还可以指定阻塞的任务时间 xTicksToWait,在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。 当队列中有新消息时, 被阻塞的任务会被唤醒并处理新消息;当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转为就绪态。 消息队列是一种异步的通信方式。
通过消息队列服务,任务或中断服务例程可以将一条或多条消息放入消息队列中。同样,一个或多个任务可以从消息队列中获得消息。当有多个消息发送到消息队列时,通常是将先进入消息队列的消息先传给任务,也就是说,任务先得到的是最先进入消息队列的消息,即先进先出原则(FIFO),但是也支持后进先出原则(LIFO) 。
创建消息队列时 FreeRTOS 会先给消息队列分配一块内存空间,这块内存的大小等于消息队列控制块大小加上单个消息空间大小与消息队列长度的乘积。每个消息队列都与消息空间在同一段连续的内存空间中,在创建成功的时候,这些内存就被占用了,只有删除了消息队列的时候,这段内存才会被释放掉,创建成功的时候就已经分配好每个消息空间与消息队列的容量,无法更改。当消息队列不再被使用时,应该删除它以释放系统资源,一旦操作完成, 消息队列将被永久性的删除。
一般来说我们创建的队列,是每个任务都可以去对他进行读写操作。为了保护每个任务对它进行读写操作的过程,必须要有阻塞机制。在很多时候,我们创建的队列,每个任务都可以去对他进行读写操作的。
假设有一个任务 A 对某个队列进行读操作的时候(也就是我们所说的出队),发现它没有消息,那么此时任务 A 有 3 个选择:
①A不进入阻塞,直接进行后面的任务或者下一个任务。
②A进入阻塞状态,若在等待这段时间中得到消息A就会从阻塞态变为就绪态,如果A 的优先级比当前执行的任务优先级高则执行A。如果等待x个tick后,若还是没有消息到来就从阻塞态中唤醒,返回一个没等到消息的错误代码,然后继续执行任务 A 的其他代码。
③A死等消息,一直阻塞,直到完成读取队列的消息。
中断中发送消息不允许带有阻塞机制的,需要调用在中断中发送消息的 API 函数接口,因为发送消息的上下文环境是在中断中,不允许有阻塞的情况。
假如有多个任务阻塞在一个消息队列中,那么这些阻塞的任务将按照任务优先级进行排序,优先级高的任务将优先获得队列的访问权。
xQueueCreate()用于创建一个新的队列并返回可用于访问这个队列的队列句柄。 队列句柄其实就是一个指向队列数据结构类型的指针。
void vQueueDelete( QueueHandle_t xQueue )消息队列删除函数
xQueue 消息队列句柄
队列删除函数是根据消息队列句柄直接删除的,删除之后这个消息队列的所有信息都会被系统回收清空,而且不能再次使用这个消息队列了
xQueueSend()向消息队列队尾发送消息,该函数绝不能在中断函数中调用
xQueueSendFromISR()该宏是 xQueueSend()的中断保护版本,用于在中断服务程序中向队列尾部发送一个队列消息,等价于 xQueueSendToBackFromISR()
xQueueSendToFront()用于向队列队首发送一个消息(发送紧急消息)。消息以拷贝的形式入队,而不是以引
用的形式。该函数绝不能在中断服务程序里面被调用, 而是必须使用带有中断保护功能的
xQueueSendToFrontFromISR ()来代替。
xQueueSendToFrontFromISR() 是 一 个 宏 , 宏 展 开 是 调 用 函 数
xQueueGenericSendFromISR()。 该宏是 xQueueSendToFront()的中断保护版本,用于在中断
服务程序中向消息队列队首发送一个消息。
xQueueReceive() 消息队列读取函数
从一个队列中接收消息并把消息从队列中删除。 接收的消息是以拷贝的形式进行的, 所以我们必须提供一个足够大空间的缓冲区。该函数绝不能在中断服务程序里面被调用, 而是必须使用带有中断保护功能的 xQueueReceiveFromISR ()来代替
xQueuePeek() 消息队列读取函数
从一个队列中接收消息但不会把消息从队列中删除。 接收的消息是以拷贝的形式进行的, 所以我们必须提供一个足够大空间的缓冲区。该函数绝不能在中断服务程序里面被调用, 而是必须使用带有中断保护功能的 xQueueReceiveFromISR ()来代替
xQueueReceiveFromISR()是 xQueueReceive ()的中断版本,用于在中断服务程序中接收
一个队列消息并把消息从队列中删除; xQueuePeekFromISR()是 xQueuePeek()的中断版本,
用于在中断中从一个队列中接收消息, 但并不会把消息从队列中移除。
在FreeRTOS中创建两个任务,一个发送消息任务,一个接收消息任务。发送消息任务中通过按键发送消息,接收消息任务一直等待,直到接收到消息后在串口上打印“接收到消息,消息内容为message=%d\r\n”
创建消息队列Test_Queue = xQueueCreate(QUEUE_LEN,QUEUE_SIZE);
创建发送任务和接收任务。main.c如下
#include "stm32f10x.h"
#include "EXC_Gpio.h"
#include "EXC_Usart.h"
#include "EXC_Led.h"
#include "EXC_Key.h"
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#define QUEUE_LEN 4 /* 队列的长度,最大可包含多少个消息 */
#define QUEUE_SIZE 4 /* 队列中每个消息大小(字节) */
QueueHandle_t Test_Queue =NULL;
/**************************** 任务句柄 ********************************/
/*
* 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
* 这个句柄可以为NULL。
*/
/* 创建任务句柄 */
static TaskHandle_t AppTaskCreate_Handle;
/* 消息发送任务句柄 */
static TaskHandle_t MessageSend_Task_Handle;
/* 消息接收任务句柄 */
static TaskHandle_t MessageRecv_Task_Handle;
/********************************** 内核对象句柄 *********************************/
/*
* 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
* 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
* 们就可以通过这个句柄操作这些内核对象。
*
* 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
* 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
* 来完成的
*
*/
/*
*************************************************************************
* 函数声明
*************************************************************************
*/
static void AppTaskCreate(void);/* 用于创建任务 */
static void MessageSend_Task(void* pvParameters);/* MessageSend_Task任务实现 */
static void MessageRecv_Task(void* pvParameters);/* MessageRecv_Task任务实现 */
static void BSP_Init(void);/* 用于初始化板载相关资源 */
/*****************************************************************
* @brief 主函数
* @param 无
* @retval 无
* @note 第一步:开发板硬件初始化
第二步:创建APP应用任务
第三步:启动FreeRTOS,开始多任务调度
****************************************************************/
int main(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
BSP_Init();
/* 创建 AppTaskCreate 任务 */
xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, //任务函数
(const char* )"AppTaskCreate", //任务名称
(uint32_t )256, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )3, //任务优先级
(TaskHandle_t* )AppTaskCreate_Handle); //任务控制块指针
//任务控制块
if(pdPASS == xReturn)/* 创建成功 */
{
vTaskStartScheduler(); /* 启动任务,开启调度 */
}
else
{
return -1;
}
while(1)
{
}
}
/***********************************************************************
* @ 函数名 : AppTaskCreate
* @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
* @ 参数 : 无
* @ 返回值 : 无
**********************************************************************/
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
taskENTER_CRITICAL(); //进入临界区
Test_Queue = xQueueCreate(QUEUE_LEN,QUEUE_SIZE);
/* 创建MessageSend_Task任务 */
xReturn = xTaskCreate((TaskFunction_t )MessageSend_Task, //任务函数
(const char* )"MessageSend_Task", //任务名称
(uint32_t )128, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )4, //任务优先级
(TaskHandle_t* )&MessageSend_Task_Handle); //任务控制块指针
if(pdPASS == xReturn)/* 创建成功 */
printf("MessageSend_Task任务创建成功!\n");
else
printf("MessageSend_Task任务创建失败!\n");
/* 创建xTaskCreate任务 */
xReturn = xTaskCreate((TaskFunction_t )MessageRecv_Task, //任务函数
(const char* )"MessageRecv_Task", //任务名称
(uint32_t )128, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )3, //任务优先级
(TaskHandle_t* )&MessageRecv_Task_Handle); //任务控制块指针
if(pdPASS == xReturn)/* 创建成功 */
printf("LCD_Task任务创建成功!\n");
else
printf("LCD_Task任务创建失败!\n");
vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
taskEXIT_CRITICAL(); //退出临界区
}
/**********************************************************************
* @ 函数名 : LED_Task
* @ 功能说明: LED_Task任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
static void MessageSend_Task(void* parameter)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
u32 message1 = 1;
u32 message2 = 2;
while (1)
{
if(Key_Scan(K1) == KEY_ON)
{
xReturn = xQueueSend(Test_Queue,&message1,10);
if(xReturn == pdPASS)
{
printf("message1 发送成功\r\n");
}
else
{
printf("message1 发送失败\r\n");
}
}
if(Key_Scan(K2) == KEY_ON)
{
xReturn = xQueueSend(Test_Queue,&message2,10);
if(xReturn == pdPASS)
{
printf("message2 发送成功\r\n");
}
else
{
printf("message2 发送失败\r\n");
}
}
vTaskDelay(20);
}
}
/**********************************************************************
* @ 函数名 : MessageRecv_Task
* @ 功能说明: MessageRecv_Taskr任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
static void MessageRecv_Task(void* parameter)
{
BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为 pdPASS */
u32 message=0;
while (1)
{
xReturn = xQueueReceive(Test_Queue,&message,portMAX_DELAY); //死等任务消息
if(xReturn == pdTRUE)
{
printf("接收到消息,消息内容为message=%d\r\n",message);
}
else
{
printf("接收消息失败\r\n");
}
vTaskDelay(20);
}
}
/***********************************************************************
* @ 函数名 : BSP_Init
* @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
* @ 参数 :
* @ 返回值 : 无
*********************************************************************/
static void BSP_Init(void)
{
/*
* STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
* 都统一用这个优先级分组,千万不要再分组,切忌。
*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
/* LED 初始化 */
LED3Color_Set(LEDBLUE);
USART_init(SYSTEM_UART,115200,1);
printf("串口初始化成功\r\n");
Key_Init(K1);
Key_Init(K2);
}
/********************************END OF FILE****************************/
实验现象
按下K1发送message1 接收到message立刻显示message=1
按下K2发送message2 接收到message立刻显示message=2