13 FreeRTOS消息队列

1 队列简介

队列是任务到任务、任务到中断、中断到任务数据交流的一种机制(消息传递)。

1)使用全局变量

假设有一个全局变量 a = 0,现有两个任务都在写这个变量a

全局变量的弊端:数据无保护,导致数据不安全,当多个任务同时对该变量操作时,数据易受损。

2)使用队列

FreeRTOS基于队列,实现了多种功能,其中包括队列集、互斥信号量、计数型信号量、二值信号量、递归互斥信号量,因此需要深入了解FreeRTOS的队列。

读写队列做好了保护(通过关中断和开中断),防止多任务同时访问冲突,只需要直接调用API函数即可。

在队列中可以存储数量有限、大小固定的数据。队列中的每一个数据叫做“队列项目”,队列能够存储“队列项目”的最大数量称为队列的长度。

在创建队列时,就要指定队列长度以及队列项目的大小

队列特点:

1)数据入队出队方式:队列通常采用“先进先出”(FIFO)的数据缓存机制,即先入队的数据会先从队列中被读取,FreeRTS中也可以配置为“后进先出”LIFO方式。

2)数据传递方式:FreeRTOS中队列采用实际值传递,即数据拷贝到队列中进行传递,FreeRTOS采用拷贝数据,也可以传递指针,所以在传递较大的数据的时候采用指针传递

3)多任务访问:队列不属于某个任务,任何任务和中断都可以向队列发送、读取消息

4)出队、入队阻塞:当任务向一个队列发送消息时,假设此时当队列已满无法入队,可以指定一个阻塞时间:

A 若阻塞时间为0:直接返回不会等待;

B 若阻塞时间为0~port_MAX_DELAY :等待设定的阻塞时间,若在该时间内还无法入队,超时后直接返回不再等待;

C 若阻塞时间为port_MAX_DELAY :死等,一直等到可以入队为止。出队阻塞和入队阻塞类似。

入队阻塞:

队列满了,此时写不进去数据:

1)将该任务的任务状态列表挂载在pxDelayedTaskList

2)将该任务的事件列表项挂载在xTaskWaitingToSend

出队阻塞:

列队为空,此时读取不了数据:

1)将该任务的任务状态列表项挂载在pxDelayedTaskList;

2)将任务的事件列表项挂载在xTaskWaitingToReceive;

问题:当多个任务写入消息给一个“满队列”时,这些任务都会进入阻塞状态,也就是说有多个任务在等待同一个队列的空间,那当队列中有空间时,哪个任务会进入就绪态?

1)优先级最高的任务

2)如果大家的优先级相同,那等待时间最久的任务会进入就绪态

队列操作基本过程:

 2 队列结构体介绍

typedef struct QueueDefinition 
{
    int8_t * pcHead					        /* 存储区域的起始地址 */
    int8_t * pcWriteTo;        				/* 下一个写入的位置 */
    union
    {
        QueuePointers_t  xQueue; 
	    SemaphoreData_t  xSemaphore; 
    } u ;
    List_t xTasksWaitingToSend; 			/* 等待发送列表 */
    List_t xTasksWaitingToReceive;			/* 等待接收列表 */
    volatile UBaseType_t uxMessagesWaiting; 	/* 非空闲队列项目的数量 */
    UBaseType_t uxLength;			        /* 队列长度 */
    UBaseType_t uxItemSize;                 /* 队列项目的大小 */
    volatile int8_t cRxLock; 				/* 读取上锁计数器 */
    volatile int8_t cTxLock;		    	/* 写入上锁计数器 */
   /* 其他的一些条件编译 */
} xQUEUE;

当用于队列使用时:

typedef struct QueuePointers
{
     int8_t * pcTail; 				/* 存储区的结束地址 */
     int8_t * pcReadFrom;			/* 最后一个读取队列的地址 */
} QueuePointers_t;

当用于互斥信号量和递归互斥信号量时:

typedef struct SemaphoreData
{
    TaskHandle_t xMutexHolder;		    /* 互斥信号量持有者 */
    UBaseType_t uxRecursiveCallCount;	/* 递归互斥信号量的获取计数器 */
} SemaphoreData_t;

3 队列相关API函数介绍

使用队列的主要流程:创建队列——>写队列——>读队列

创建队列相关API函数介绍:

函数描述
xQueueCreate()动态方式创建队列
xQueueCreateStatic()静态方式创建队列

动态和静态创建队列之间的区别:队列所需的内存空间由FReeeRTOS从FreeRTOS管理的堆中分配,而静态创建需要用户自行分配内存。

1)动态方式创建队列

#define xQueueCreate ( uxQueueLength, uxItemSize )   												
	    xQueueGenericCreate( (uxQueueLength ), (uxItemSize), (queueQUEUE_TYPE_BASE)) 

        此函数用于使用动态方式创建队列,队列所需的内存空间由FreeRTOS从FreeRTOS管理的堆中分配。

形参描述
uxQueueLength队列长度
uxItemSize队列项目的大小
返回值描述
NULL队列创建失败
其他值队列创建成功,返回队列句柄

FreeRTOS基于队列实现了多种功能,每一种功能对应一种队列类型,队列类型的queue.h文件中有定义:

#define queueQUEUE_TYPE_BASE                  	( ( uint8_t ) 0U )	/* 队列 */
#define queueQUEUE_TYPE_SET                  	( ( uint8_t ) 0U )	/* 队列集 */
#define queueQUEUE_TYPE_MUTEX                 	( ( uint8_t ) 1U )	/* 互斥信号量 */
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE    	( ( uint8_t ) 2U )	/* 计数型信号量 */
#define queueQUEUE_TYPE_BINARY_SEMAPHORE     	( ( uint8_t ) 3U )	/* 二值信号量 */
#define queueQUEUE_TYPE_RECURSIVE_MUTEX       	( ( uint8_t ) 4U )	/* 递归互斥信号量 

2)往队列写入消息API函数:

函数描述
xQueueSend()往队列的尾部写入消息
xQueueSendToBack()同xQueueSend()
xQueueSendToFront()往队列的头部写入消息
xQueueOverwrite()覆写队列消息(只用于队列长度为1 的情况)
xQueueSendFromISR()在中断中往队列的尾部写入消息
xQueueSendToBackFromISR()同xQueueSendFromISR()
xQueueSendToFrontISR()在中断中往队列的头部写入消息
xQueueOverwriteFromISR()在中断中覆写写队列消息(只用于队列长度为1 的情况)

以下几个写入函数调用的是同一个函数xQueueGenericsend(),只是指定了不同的写入位置:

#define  xQueueSend(xQueue, pvItemToQueue, xTicksToWait)	 					    
	     xQueueGenericSend((xQueue), (pvItemToQueue), (xTicksToWait),queueSEND_TO_BACK)

#define  xQueueSendToBack(xQueue,pvItemToQueue,xTicksToWait)					    
	     xQueueGenericSend((xQueue), (pvItemToQueue), (xTicksToWait), queueSEND_TO_BACK)

#define  xQueueSendToFront(xQueue,pvItemToQueue,xTicksToWait) 					
	     xQueueGenericSend((xQueue), (pvItemToQueue), (xTicksToWait), queueSEND_TO_FRONT)

#define  xQueueOverwrite( xQueue,   pvItemToQueue  ) 							   
	     xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), 0, queueOVERWRITE )

/*队列一共有3种写入位置*/
/*注意:覆写方式写入队列,只有在队列的队列长度为1时,才能够使用*/
#define queueSEND_TO_BACK            ( ( BaseType_t ) 0 )		/* 写入队列尾部 */
#define queueSEND_TO_FRONT           ( ( BaseType_t ) 1 )		/* 写入队列头部 */
#define queueOVERWRITE               ( ( BaseType_t ) 2 )		/* 覆写队列*/

/*往队列写入消息函数入口参数解析*/
BaseType_t     xQueueGenericSend( QueueHandle_t       xQueue,
                                  const void * const  pvItemToQueue,
					              TickType_t 		  xTicksToWait,					   
                                  const BaseType_t    xCopyPosition   );
//xQueue 为待写入的队列
//pvItemToQueue 为待写入的消息
//xTicksToWait 为阻塞超时时间
//xCopyPosition 为写入的位置
//返回值为pdTURE 表示队列写入成功,errQUEUE_FULL表示队列写入失败

3)从队列读取消息API函数:

函数描述
xQueueReceive()从队列头部读取消息,并删除消息
xQueuePeek()从队列头部读取消息
xQueueReceiveFromISR()在中断中从队列头部读取消息,并删除消息
xQueuePeekFromISR()在中断中从队列头部读取消息
BaseType_t xQueueReceive(QueueHandle_t xQueue,
                         void * const pvBuffer,
                         TickType_t   xTicksToWait )
//xQueue为待读取的队列
//pvBuffer为信息读取缓冲区
//xTickToWait 为阻塞超时时间
//返回值为pdTRUE为读取成功。pdFALSE为读取失败



BaseType_t  xQueuePeek(QueueHandle_t xQueue,   
                       void * const   pvBuffer,   
                       TickType_t   xTicksToWait)
//xQueue为待读取的队列
//pvBuffer为信息读取缓冲区
//xTickToWait为阻塞超时时间
//返回值为pdTRUE为读取成功。pdFALSE为读取失败

4 队列操作实验

实验设计:设计4个任务:start_task、task1、task2、task3

四个任务的功能如下:

start_task:用来创建task1、task2、task3

task1:当按键key0或key1按下,将键值拷贝到队列key_queue(入队)

            当按键key_up按下,将传输大数据,这里拷贝大数据的地址到队列big_data_queue中

task2:读取队列key_queue中的消息(出队),打印出接收到的键值
task3:从队列big_data_queue读取大数据地址,通过地址访问大数据

#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
#include "./MALLOC/malloc.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

/******************************************************************************************************/
/*FreeRTOS配置*/

/* START_TASK 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define START_TASK_PRIO         1
#define START_TASK_STACK_SIZE   128
TaskHandle_t    start_task_handler;
void start_task( void * pvParameters );

/* TASK1 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define TASK1_PRIO         2
#define TASK1_STACK_SIZE   128
TaskHandle_t    task1_handler;
void task1( void * pvParameters );


/* TASK2 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define TASK2_PRIO         3
#define TASK2_STACK_SIZE   128
TaskHandle_t    task2_handler;
void task2( void * pvParameters );

/* TASK3 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define TASK3_PRIO         4
#define TASK3_STACK_SIZE   128
TaskHandle_t    task3_handler;
void task3( void * pvParameters );

QueueHandle_t key_queue;       //小数据句柄
QueueHandle_t big_data_queue;  //大数据句柄
char buffer[100] = {"这是一个大数组!!!!!!!"};
/******************************************************************************************************/

/**
 * @brief       FreeRTOS例程入口函数
 * @param       无
 * @retval      无
 */
void freertos_demo(void)
{   /*队列的创建*/
		key_queue = xQueueCreate( 2, sizeof(uint8_t) );
		if(key_queue != NULL)
		{
			printf("key_queue队列创建成功!!\r\n");
		}
		else
			printf("key_queue队列创建失败!!\r\n");

		
		big_data_queue = xQueueCreate( 1, sizeof(char *) );
		if(big_data_queue != NULL)
		{
			printf("big_data_queue队列创建成功!!\r\n");
		}
		else
			printf("big_data_queue队列创建失败!!\r\n");	
		
    xTaskCreate((TaskFunction_t         )   start_task,
                (char *                 )   "start_task",
                (configSTACK_DEPTH_TYPE )   START_TASK_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   START_TASK_PRIO,
                (TaskHandle_t *         )   &start_task_handler );
    vTaskStartScheduler();
		
}


void start_task( void * pvParameters )
{
    taskENTER_CRITICAL();               /* 进入临界区 */
    xTaskCreate((TaskFunction_t         )   task1,
                (char *                 )   "task1",
                (configSTACK_DEPTH_TYPE )   TASK1_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK1_PRIO,
                (TaskHandle_t *         )   &task1_handler );
                
    xTaskCreate((TaskFunction_t         )   task2,
                (char *                 )   "task2",
                (configSTACK_DEPTH_TYPE )   TASK2_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK2_PRIO,
                (TaskHandle_t *         )   &task2_handler );
								
		xTaskCreate((TaskFunction_t         )   task3,
                (char *                 )   "task3",
                (configSTACK_DEPTH_TYPE )   TASK3_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK3_PRIO,
                (TaskHandle_t *         )   &task3_handler );
								
    vTaskDelete(NULL);
    taskEXIT_CRITICAL();                /* 退出临界区 */
}

/* 任务一,实现入队 */
void task1( void * pvParameters )
{
	  uint8_t key = 0;
	  char* buf;
	  //buf = buffer; /*buf = &buffer[0];*/
	  BaseType_t err = 0 ;
    while(1)
    {
       key = key_scan(0);
			 if(key == KEY0_PRES || key == KEY1_PRES)
			 {
				 err = xQueueSend( key_queue, &key, portMAX_DELAY );
				 if(err != pdTRUE)
				 {
					 printf("key_queue队列写入失败!\r\n");
				 }
				 else if(key = WKUP_PRES)
				 {
					 err = xQueueSend(big_data_queue,&buf,portMAX_DELAY);
				   if(err != pdTRUE)
					{
					   printf("big_data_queue队列写入失败!\r\n");
					}
				 }
			 }
       vTaskDelay(10);
    }
}

/* 任务二,实现小数据出队*/
void task2( void * pvParameters )
{
    uint8_t key = 0;
		BaseType_t err = 0;
    while(1)
    {
       err = xQueueReceive( key_queue,&key,portMAX_DELAY);
       if(err != pdTRUE)
       {
				 printf("key_queue队列出队失败!\r\n");
			 }else
			 {
				 printf("读取队列成功,数据:%d\r\n",key);
			 }				 
    }
}
//可以加延时函数也可以不加延时函数,因为task2的优先级高于task1,但是由于key_queue中没有数据可以出队
//因此task2会进入阻塞态

/* 任务三,实现小数据出队*/
void task3( void * pvParameters )
{
    BaseType_t err = 0;
	  char *buf;
    while(1)
    {
       err = xQueueReceive( big_data_queue,&buf,portMAX_DELAY);
       if (err != pdTRUE)
			 {
					printf("big_data_queue队列读取失败\r\n");
			 }else
			 {
				 printf("big_data_queue队列读取成功,数据:%s\r\n",&buffer);
			 }				 
		}
}

//如果task1没有按按键的话,task3和task2是读取不到数据的,因为此时创建的队列是没有数据的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值