STM32移植FreeRTOS系列十七:队列

目录

1、队列的简介

2、队列的特点

2.1 、数据入队出队方式

2.2、数据传递方式

2.3、多任务访问

2.4、出队、入队阻塞

3、队列结构体介绍

4、队列相关API函数介绍

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

往队列写入消息API函数:

从队列读取消息API函数:

5、队列操作实验


1、队列的简介

应用中一个任务或者中断服务需要和另外一个任务进行“沟通交流”, 这个“沟通交流”的过程其实就是消息传递的过程。在没有操作系统的时候两个应用程序进行 消息传递一般使用全局变量的方式,但是如果在使用操作系统的应用中用全局变量来传递消息 就会涉及到“资源管理”的问题(全局变量的弊端数据无保护,导致数据不安全,当多个任务同时对该变量操作时,数据易受损)。FreeRTOS 对此提供了一个叫做“队列”的机制来完成任务与 任务、任务与中断之间的消息传递。

队列是为了任务与任务、任务与中断之间的通信而准备的,可以在任务与任务、任务与中 断之间传递消息,队列中可以存储有限的、大小固定的数据项目。任务与任务、任务与中断之 间要交流的数据保存在队列中,叫做队列项目。队列所能保存的最大数据项目数量叫做队列的长度,创建队列的时候会指定数据项目的大小和队列的长度。由于队列用来传递消息的,所以也称为消息队列。FreeRTOS 中的信号量的也是依据队列实现的!所以有必要深入的了解 FreeRTOS 的队列。

简单来说队列的概念:队列是任务到任务、任务到中断、中断到任务数据交流的一种机制(消息传递

2、队列的特点

2.1 、数据入队出队方式

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

2.2、数据传递方式

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

 数据发送到队列中会导致数据拷贝,也就是将要发送的数据拷贝到队列中,这就意味着在队列中存储的是数据的原始值,而不是原数据的引用(即只传递数据的指针),这个也叫做值传递。学过 UCOS 的同学应该知道,UCOS 的消息队列采用的是引用传递,传递的是消息指针。 采用引用传递的话消息内容就必须一直保持可见性,也就是消息内容必须有效,那么局部变量这种可能会随时被删掉的东西就不能用来传递消息,但是采用引用传递可以节省时间。因为不用进行数据拷贝。 采用值传递的话虽然会导致数据拷贝,会浪费一点时间,但是一旦将消息发送到队列中原始的数据缓冲区就可以删除掉或者覆写,这样的话这些缓冲区就可以被重复的使用。FreeRTOS 中使用队列传递消息的话虽然使用的是数据拷贝,但是也可以使用引用来传递消息,操作办法是直接往队列中发送指向这个消息的地址指针。这样当我发送的消息数据太大的时候就可以直接发送消息缓冲区的地址指针,比如在网络应用环境中,网络的数据量往往都很大的, 采用数据拷贝的话就不现实。

2.3、多任务访问

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

2.4、出队、入队阻塞

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

①若阻塞时间为0  :直接返回不会等待;
②若阻塞时间为0~port_MAX_DELAY  :等待设定的阻塞时间,若在该时间内还无法入队,超时后直接返回不再等待;
③若阻塞时间为port_MAX_DELAY  :死等,一直等到可以入队为止。出队阻塞与入队阻塞类似;
入队阻塞和出队阻塞的简单流程
STM32移植FreeRTOS系列十七:队列_第1张图片

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

答:

  1、优先级最高的任务

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

STM32移植FreeRTOS系列十七:队列_第2张图片

图 中任务 B 从队列中读取消息,并将读取到的消息值赋值给 y,这样 y 就等于 10 了。任务 B 从队列中读取消息完成以后可以选择清除掉这个消息或者不清除。当选择清除这个 消息的话其他任务或中断就不能获取这个消息了,而且队列剩余大小就会加一,变成 3。如果不清除的话其他任务或中断也可以获取这个消息,而队列剩余大小依旧是 2

3、队列结构体介绍

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;

STM32移植FreeRTOS系列十七:队列_第3张图片

4、队列相关API函数介绍

使用队列的主要流程:创建队列 ------ 写队列 ------- 读队列。

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

函数

描述

xQueueCreate()

动态方式创建队列

xQueueCreateStatic()

静态方式创建队列

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

动态创建队列宏定义
#define xQueueCreate (  uxQueueLength,   uxItemSize  )   						 \						
	   xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), (queueQUEUE_TYPE_BASE )) 

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

形参

描述

uxQueueLength

队列长度

uxItemSize

队列项目的大小

返回值

描述

NULL

队列创建失败

其他值

队列创建成功,返回队列句柄

 xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), (queueQUEUE_TYPE_BASE ))
 

前面说 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 )	/* 递归互斥信号量 */

往队列写入消息API函数:

函数

描述

xQueueSend()

往队列的尾部写入消息

xQueueSendToBack()

xQueueSend()

xQueueSendToFront()

往队列的头部写入消息

xQueueOverwrite()

覆写队列消息(只用于队列长度为 1 的情况)

xQueueSendFromISR()

在中断中往队列的尾部写入消息

xQueueSendToBackFromISR()

xQueueSendFromISR()

xQueueSendToFrontFromISR()

在中断中往队列的头部写入消息

xQueueOverwriteFromISR()

在中断中覆写队列消息(只用于队列长度为 1 的情况)

任务级就这四个
#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 )

可以看到这几个写入函数调用的是同一个函数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,
					               const void * const 	pvItemToQueue,					       
                                   TickType_t 		xTicksToWait,					        
                                   const BaseType_t 	xCopyPosition   ); 

形参

描述

xQueue

待写入的队列

pvItemToQueue

待写入消息

xTicksToWait

阻塞超时时间

xCopyPosition

写入的位置

返回值

描述

pdTRUE

队列写入成功

errQUEUE_FULL

队列写入失败

从队列读取消息API函数:

函数

描述

xQueueReceive()

从队列头部读取消息,并删除消息

xQueuePeek()

从队列头部读取消息

xQueueReceiveFromISR()

在中断中从队列头部读取消息,并删除消息

xQueuePeekFromISR()

在中断中从队列头部读取消息

BaseType_t    xQueueReceive( QueueHandle_t   xQueue,  void *   const pvBuffer,  TickType_t   xTicksToWait )

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

形参

描述

xQueue

待读取的队列

pvBuffer

信息读取缓冲区

xTicksToWait

阻塞超时时间

返回值

描述

pdTRUE

读取成功

pdFALSE

读取失败

BaseType_t   xQueuePeek( QueueHandle_t   xQueue,   void * const   pvBuffer,   TickType_t   xTicksToWait )

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

形参

描述

xQueue

待读取的队列

pvBuffer

信息读取缓冲区

xTicksToWait

阻塞超时时间

返回值

描述

pdTRUE

读取成功

pdFALSE

读取失败

5、队列操作实验

1、实验目的:学习 FreeRTOS 的队列相关API函数的使用 ,实现队列的入队和出队操作。

2、实验设计:将设计四个任务:start_tasktask1task2task3

四个任务的功能如下:

STM32移植FreeRTOS系列十七:队列_第4张图片
PS:
char buff[100] = {"我是一个大数组124214 uhsidhaksjhdklsadhsaklj"}; big_date_queue = xQueueCreate( 1, sizeof(char *) );
big_date_queue = xQueueCreate( 1, sizeof(char *) ); 
这里为什么不是sizeof(char )

sizeof(char *) 是用来获取指针的大小,而不是字符的大小。 xQueueCreate() 函数需要知道队列元素的大小,所以我们使用 sizeof(char *) 来表示指向 char 的指针的大小。如果我们使用 sizeof(char),它将返回 1,因为 char 类型的大小是 1 字节

在这个特定的代码片段中,xQueueCreate() 函数使用 sizeof(char *) 来确定队列元素的大小。这是因为队列是通过指向元素的指针来操作的,而不是直接存储元素本身。在这种情况下,队列中的每个元素都是一个指向 char 的指针,由于 sizeof(char *) 表示指针的大小,所以它可以用作队列元素的大小。实际上,这个队列将存储指向字符数组的指针。这种设计允许队列能够容纳任意大小的数据,并且只需要存储指针本身的大小,而不需要复制整个数据。这对于节省内存空间和提高效率是有益的。

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer.h"
#include "lcd.h"
#include "key.h"
#include "beep.h"
#include "malloc.h"
#include "string.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
/************************************************
 ALIENTEK 战舰STM32F103开发板 FreeRTOS实验13-1
 FreeRTOS队列操作实验-库函数版本
 技术支持:www.openedv.com
 淘宝店铺:http://eboard.taobao.com 
 关注微信公众平台微信号:"正点原子",免费获取STM32资料。
 广州市星翼电子科技有限公司  
 作者:正点原子 @ALIENTEK
************************************************/

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

//任务优先级
#define TASK1_TASK_PRIO		6
//任务堆栈大小	
#define TASK1_STK_SIZE 		256  
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);

//任务优先级
#define KEYPROCESS_TASK_PRIO 3
//任务堆栈大小	 
#define KEYPROCESS_STK_SIZE  256 
//任务句柄
TaskHandle_t Keyprocess_Handler;
//任务函数
void Keyprocess_task(void *pvParameters);

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


//按键消息队列的数量
#define KEYMSG_Q_NUM    2  		//按键消息队列的数量  
#define MESSAGE_Q_NUM   1   	//发送数据的消息队列的数量 
QueueHandle_t Key_Queue;   		//按键值消息队列句柄
QueueHandle_t Message_Queue;	//信息队列句柄
char buff[100] = {"我是一个大数组,大大的数组 124214 uhsidhaksjhdklsadhsaklj"};

LCD刷屏时使用的颜色
//int lcd_discolor[14]={	WHITE, BLACK, BLUE,  BRED,      
//						GRED,  GBLUE, RED,   MAGENTA,       	 
//						GREEN, CYAN,  YELLOW,BROWN, 			
//						BRRED, GRAY };

//用于在LCD上显示接收到的队列的消息
//str: 要显示的字符串(接收到的消息)
void disp_str(u8* str)
{
//	LCD_Fill(5,230,110,245,WHITE);					//先清除显示区域
//	LCD_ShowString(5,230,100,16,16,str);
}

//加载主界面
void freertos_load_main_ui(void)
{

}

//查询Message_Queue队列中的总队列数量和剩余队列数量
void check_msg_queue(void)
{
    u8 *p;
	u8 msgq_remain_size;	//消息队列剩余大小
    u8 msgq_total_size;     //消息队列总大小
    
    taskENTER_CRITICAL();   //进入临界区
    msgq_remain_size=uxQueueSpacesAvailable(Message_Queue);//得到队列剩余大小
    msgq_total_size=uxQueueMessagesWaiting(Message_Queue)+uxQueueSpacesAvailable(Message_Queue);//得到队列总大小,总大小=使用+剩余的。
	p=mymalloc(SRAMIN,20);	//申请内存
	sprintf((char*)p,"Total Size:%d",msgq_total_size);	//显示DATA_Msg消息队列总的大小
//	LCD_ShowString(10,150,100,16,16,p);
	sprintf((char*)p,"Remain Size:%d",msgq_remain_size);	//显示DATA_Msg剩余大小
//	LCD_ShowString(10,190,100,16,16,p);
	myfree(SRAMIN,p);		//释放内存
    taskEXIT_CRITICAL();    //退出临界区
}

int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4	 
	delay_init();	    				//延时函数初始化	 
	uart_init(115200);					//初始化串口
	LED_Init();		  					//初始化LED
	KEY_Init();							//初始化按键
	//BEEP_Init();						//初始化蜂鸣器
	LCD_Init();							//初始化LCD
	//TIM2_Int_Init(5000,7200-1);			//初始化定时器2,周期500ms
	my_mem_init(SRAMIN);            	//初始化内部内存池
    freertos_load_main_ui();        	//加载主UI
	
	//创建开始任务
    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();           //进入临界区
	
	//创建消息队列
    Key_Queue=xQueueCreate(KEYMSG_Q_NUM,sizeof(u8));        //创建消息Key_Queue
    Message_Queue=xQueueCreate(MESSAGE_Q_NUM,sizeof(char *)); //创建消息Message_Queue,队列项长度是串口接收缓冲区长度
	
    //创建TASK1任务
    xTaskCreate((TaskFunction_t )task1_task,             
                (const char*    )"task1_task",           
                (uint16_t       )TASK1_STK_SIZE,        
                (void*          )NULL,                  
                (UBaseType_t    )TASK1_TASK_PRIO,        
                (TaskHandle_t*  )&Task1Task_Handler);   
    //创建TASK2任务
    xTaskCreate((TaskFunction_t )Keyprocess_task,     
                (const char*    )"keyprocess_task",   
                (uint16_t       )KEYPROCESS_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )KEYPROCESS_TASK_PRIO,
                (TaskHandle_t*  )&Keyprocess_Handler); 
    //创建TASK3任务
    xTaskCreate((TaskFunction_t )task3,     
                (const char*    )"task3",   
                (uint16_t       )TASK3_STACK_SIZE,
                (void*          )NULL,
                (UBaseType_t    ) TASK3_PRIO,
                (TaskHandle_t*  )&task3_handler);               
    vTaskDelete(StartTask_Handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
}

//task1任务函数,实现入队
void task1_task(void *pvParameters)
{
	u8 key=0;
   BaseType_t   err = 0;
	char * buf;
	buf=&buff[0];
	while(1)
	{
		
		key=KEY_Scan(0);            	//扫描按键
        if(key == KEY2_PRES || key == KEY1_PRES)   	//消息队列Key_Queue创建成功,并且按键被按下
        {
            err=xQueueSend(Key_Queue,&key,10);
            if(err==errQUEUE_FULL)   	//发送按键值
            {
                printf("队列Key_Queue已满,数据发送失败!\r\n");
            }
        }else if(key == WKUP_PRES)
        {
            err = xQueueSend( Message_Queue, &buf, portMAX_DELAY );
            if(err != pdTRUE)
            {
                printf("key_queue队列发送失败\r\n");
            }
        }
        
       
	}
}


//Keyprocess_task函数
void Keyprocess_task(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("key_queue读取队列成功,数据:%d\r\n",key);
        }
    }
}

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


链接:https://pan.baidu.com/s/1is1OMw4j2lWD_VSJ9YqQAw?pwd=rtos 
提取码:rtos

你可能感兴趣的:(stm32,嵌入式硬件,单片机)