13_FreeRTOS消息队列

目录

队列简介

FreeRTOS队列特点

队列操作基本过程 

队列结构体介绍

队列结构体整体示意图

队列相关API函数介绍

创建队列相关API函数介绍

往队列写入消息API函数

往队列写入消息函数入口参数解析 

从队列读取消息API函数

实验源码


队列简介

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

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

假设在任务1完成2步骤修改数据的时候,此时任务2优先级高打断任务1,a还是0因为任务a还没有写数据,任务2执行了加1,在回到任务1时,下一步是执行写数据此时r0是任务2执行完时候的1,任务1在吧R0赋值给a,相当于r0等于1在给a,并没有等于2。这样就照成了数据的受损。

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

使用队列的情况如下:

 读写队列做好了保护,防止多任务同时访问冲突;FreeRTOS基于队列,实现了多种功能,其中包括队列集、互斥信号量、计数型信号量、二值信号量、递归互斥信号量,因此很有必要深入了解 FreeRTOS的队列。

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

 队列长度为:5个

队列项目大小为:10字节

队列长度和队列项目不是固定的是创建的时候自己指定的。

FreeRTOS队列特点

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

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

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

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

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

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

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

入队阻塞:

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

①将该任务的状态列表项挂载在pxDelayedTaskList;(阻塞列表)

②将该任务的事件列表项挂载在xTasksWaitingToSend;(等待发送列表)

出队阻塞:

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

①将该任务的状态列表项挂载在pxDelayedTaskList;(阻塞列表)

②将该任务的事件列表项挂载在xTasksWaitingToReceive;(等待接收列表)

当多个任务写入消息给一个“满队列”时,这些任务都会进入阻塞状态,也就是说有多个任务在等待同一 个队列的空间。那当队列中有空间时,优先级最高的任务会进入就绪态,如果优先级相同,那等待时间最久的任务回进入就绪态。

队列操作基本过程 

1.创建队列

2.往队列写入第一个消息

 3.往队列写入第二个消息

4.从队列读取第一个消息

队列结构体介绍

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  uxltemSize;		/*队列项目的大小*/
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;

队列结构体整体示意图

队列相关API函数介绍

创建队列相关API函数介绍

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

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

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

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

#define queueQUEUE_TYPE_BASE    ((uint8_t) OU)/* 队列 */
#define queueQUEUE_TYPE_SET	  ((uint8_t) OU)/* 队列集*/
#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)/* 递归互斥信号量*/

往队列写入消息API函数

 任务级往队列写入消息

/*往队列的尾部写入消息*/
#define xQueueSend( xQueue, pvltemToQueue, xTicksToWait)
xQueueGenericSend( (xQueue ), ( pvltemToQueue ), (xTicksToWait), queueSEND_TO_BACK)

/*往队列的尾部写入消息*/
#define xQueueSendToBack( xQueue, pvltemToQueue, xTicksToWait)
xQueueGenericSend((×Queue ), ( pvltemToQueue ), (xTicksToWait), queueSEND_TO_BACK)



/*往队列的头部写入消息*/
#define xQueueSendToFront( xQueue, pvltemToQueue, xTicksToWait)
xQueueGenericSend((xQueue), (pvltemToQueue), (xTicksToWait), queueSEND_TO_FRONT)

/*覆写队列消息(只用于队列长度为1的情况)*/
#define xQueueOverwrite( xQueue, pvitemToQueue )
xQueueGenericSend( (xQueue ), ( pvitemToQueue ), 0, queueOVERWRITE )

可以看到这几个写入函数调用的是同一个函数xQueueGenericSend(),只是指定了不同的写入位置!

队列一共有 3 种写入位置:

#define queueSEND_TO_BACK		((BaseType_t) 0)/*写入队列尾部*/
#define queueSEND_TO_FRONT 	((BaseType_t) 1)/*写入队列头部*/
#define queueOVERWRITE 			 ((BaseType_t) 2)/*覆写队列*/

注意:覆写方式写入队列,只有在队列的队列长度为1时,才能够使用

往队列写入消息函数入口参数解析

BaseType_t xQueueGenericSend(QueueHandle_t  xQueue, 
QueueHandle_t  xQueue,
TickType_t xTicksToWait,
const BaseType_t  xCopyPosition );

从队列读取消息API函数

/*从队列头部读取消息,并删除消息*/
BaseType_t xQueueReceive(
QueueHandle_t xQueue, 
void* const pvBuffer, 
TickType_t xTicksToWait )

此函数用于在任务中,从队列中读取消息,并且消息读取成功后,会将消息从队列中移除。

/*从队列头部读取消息不会删除*/
BaseType_t xQueuePeek(
QueueHandle_t xQueue, 
void*const pvBuffer, 
TickType_t xTicksToWait)

此函数用于在任务中,从队列中读取消息,但与函数xQueueReceive()不同,此函数在成功读取消息后,并不会移除已读取的消息!

实验源码

start_task  用来创建task1和task2以及task3任务

task1 当按键key0或key1按下,将键值拷贝到队列key_queue(入队)当按键kev_up按  下,将传输大数据,这里拷贝大数据的地址到队列big_date_queue中

task2 读取队列key_queue中的消息(出队)打印出接收到的键值

task3 从队列big_date_queue读取大数据地址,通过地址访问大数据

/**
  ******************************************************************************
  * @file           : user_mian.h
  * @brief          : V1.00
  ******************************************************************************
  * @attention
  *
  ******************************************************************************
  */

/* Include 包含---------------------------------------------------------------*/
#include "stm32f10x.h"
#include <stdbool.h>
#include "user_gpio.h"
#include "user_delay.h"
#include "user_rcc_config.h"
#include "user_uart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "user_key.h"
/* Typedef 类型----------------------------------------------------------------*/
/* Define  定义----------------------------------------------------------------*/
/* Macro   宏------------------------------------------------------------------*/
/* Variables 变量--------------------------------------------------------------*/ 

QueueHandle_t  key_queue;  		/*小数据句柄*/
QueueHandle_t  big_date_queue;  /*大数据句柄*/
char buff[100] = {" 大数组 12324164985465484684866"};
/* Constants 常量--------------------------------------------------------------*/
/* Function  函数--------------------------------------------------------------*/

//任务优先级
#define START_TASK_PRIO		1
//任务堆栈大小	
#define START_STK_SIZE 		128  
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);


//任务优先级
#define TASK1_PRIO			2
//任务堆栈大小	
#define TASK1_STK_SIZE 		100  
//任务句柄
TaskHandle_t Task1_Handler;
//任务函数
void task1(void *pvParameters);


//任务优先级
#define TASK2_PRIO			2
//任务堆栈大小	
#define TASK2_STK_SIZE 		100  
//任务句柄
TaskHandle_t Task2_Handler;
//任务函数
void task2(void *pvParameters);



//任务优先级
#define TASK3_PRIO			2
//任务堆栈大小	
#define TASK3_STK_SIZE 		100  
//任务句柄
TaskHandle_t Task3_Handler;
//任务函数
void task3(void *pvParameters);

 int main(void)
 {	

	/*配置系统中断分组为4位抢占*/
	 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
	 /*延时函数初始化*/
	 delay_init();
	/*RCC配置*/
	 Rcc_config();
	/*GPIO初始化*/ 
	 Gpio_Init();
	/*USART1初始化*/
	 Uart1_Init(9600);
	 /*创建小数据队列*/ 
	 key_queue = xQueueCreate(2,sizeof(uint8_t));
	 if(key_queue != NULL)
	 {
		printf("key_queue队列创建!!\r\n\r\n");
	 }else 
	 {
		printf("key_queue队列创建失败!!\r\n\r\n");	 
	 }
	 /*创建大数据队列*/	 
	 big_date_queue = xQueueCreate(1,sizeof(char *));
	 if(key_queue != NULL)
	 {
		printf("big_date_queue队列创建!!\r\n\r\n");
	 }else 
	 {
		printf("big_date_queue队列创建失败!!\r\n\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();          //开启任务调度
		

}
 

/*!
	\brief		开始任务函数
	\param[in]	传递形参,创建任务时用户自己传入
	\param[out]	none
	\retval 	none
*/
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();           //进入临界区
    //创建任务1
    xTaskCreate((TaskFunction_t )task1,     	
                (const char*    )"task1",   	
                (uint16_t       )TASK1_STK_SIZE, 
                (void*          )NULL,				
                (UBaseType_t    )TASK1_PRIO,	
                (TaskHandle_t*  )&Task1_Handler);   
    //创建任务2
    xTaskCreate((TaskFunction_t )task2,     
                (const char*    )"task2",   
                (uint16_t       )TASK2_STK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )TASK2_PRIO,
                (TaskHandle_t*  )&Task2_Handler); 
	//创建任务3
    xTaskCreate((TaskFunction_t )task3,     
                (const char*    )"task3",   
                (uint16_t       )TASK3_STK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )TASK3_PRIO,
                (TaskHandle_t*  )&Task3_Handler);

				
    vTaskDelete(StartTask_Handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
}


/*!
	\brief		task1实现入队
	\param[in]	传递形参,创建任务时用户自己传入
	\param[out]	none
	\retval 	none
*/
void task1(void *pvParameters)
{
	uint8_t key = 0;
	BaseType_t err = 0;
	char * buf;
	buf = &buff[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\r\n");
			}
		}else if(key == WKUP_PRES)
		{	
			/*写入大数据,队列满就死等*/
			err = xQueueSend(big_date_queue,&buf,portMAX_DELAY);
			
			if(err != pdTRUE)
			{
				printf("big_date_queue队列大数据发送失败\r\n\r\n");
			}
		}
		vTaskDelay(100);
    }
} 


/*!
	\brief		task2实现小数据出队
	\param[in]	传递形参,创建任务时用户自己传入
	\param[out]	none
	\retval 	none
*/
void task2(void *pvParameters)
{
	uint8_t key = 0;
	uint8_t err = 0;
    while(1)
    {
		/*接受小数据,队列空死等*/	
		err = xQueueReceive(key_queue,&key,portMAX_DELAY);
		
		if(err != pdTRUE)
		{
			printf("key_queue队列小数据读取失败\r\n\r\n");
		}else
		{
			printf("读取key_queue队列成功,数据:%d\r\n\r\n",key);
		}
    }
}

/*!
	\brief		task3实现大数据出队
	\param[in]	传递形参,创建任务时用户自己传入
	\param[out]	none
	\retval 	none
*/
void task3(void *pvParameters)
{
	char * buf;
	uint8_t err = 0;
    while(1)
    {
		/*接受大数据,队列空死等*/	
		err = xQueueReceive(big_date_queue,&buf,portMAX_DELAY);
		
	 	if(err != pdTRUE)
		{
			printf("big_date_queue队列大数据读取失败\r\n\r\n");
		}else
		{
			printf("读取big_date_queue队列成功,数据:%s\r\n\r\n",buf);
		}		
    }
}

 /************************************************************** END OF FILE ****/

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FreeRTOS消息队列是一种用于任务间通信的机制,可以在任务之间传递消息队列中可以存储有限的、大小固定的数据项目,任务与任务、任务与中断之间要交流的数据保存在队列中,叫做队列项目。队列所能保存的最大数据项目数量叫做队列的长度,创建队列的时候会指定数据项目的大小和队列的长度。FreeRTOS中的信号量也是依据队列实现的,所以有必要深入了解FreeRTOS的队列。 在FreeRTOS中,可以使用xQueueCreate()函数创建一个队列,该函数需要传入两个参数:队列的长度和每个队列项目的大小。创建队列后,可以使用xQueueSend()函数向队列中发送数据,使用xQueueReceive()函数从队列中接收数据。此外,还可以使用xQueuePeek()函数查看队列中的下一个数据项,而不将其从队列中删除。 下面是一个简单的示例,演示如何在FreeRTOS中使用消息队列: ```c #include "FreeRTOS.h" #include "task.h" #include "queue.h" #define QUEUE_LENGTH 5 #define ITEM_SIZE sizeof(int) void vSenderTask(void *pvParameters) { QueueHandle_t xQueue; int i = 0; // 创建一个长度为QUEUE_LENGTH,每个项目大小为ITEM_SIZE的队列 xQueue = xQueueCreate(QUEUE_LENGTH, ITEM_SIZE); while (1) { // 向队列中发送数据 xQueueSend(xQueue, &i, 0); // 延时一段时间 vTaskDelay(pdMS_TO_TICKS(500)); i++; } } void vReceiverTask(void *pvParameters) { QueueHandle_t xQueue; int iReceivedValue; // 获取发送任务创建的队列句柄 xQueue = (QueueHandle_t)pvParameters; while (1) { // 从队列中接收数据 xQueueReceive(xQueue, &iReceivedValue, portMAX_DELAY); // 处理接收到的数据 printf("Received value: %d\n", iReceivedValue); } } int main(void) { QueueHandle_t xQueue; // 创建一个长度为QUEUE_LENGTH,每个项目大小为ITEM_SIZE的队列 xQueue = xQueueCreate(QUEUE_LENGTH, ITEM_SIZE); // 创建发送任务 xTaskCreate(vSenderTask, "Sender", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL); // 创建接收任务 xTaskCreate(vReceiverTask, "Receiver", configMINIMAL_STACK_SIZE, (void *)xQueue, tskIDLE_PRIORITY + 1, NULL); // 启动调度器 vTaskStartScheduler(); return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值