FreeRTOS 消息队列

目录

队列

 FreeRTOS队列特点:

入队阻塞:

出队阻塞:

队列结构体

当用于队列使用时: 

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

队列操作基本过程

创建队列相关API函数

往队列写入第一个消息  

往队列写入消息函数:

 从队列读取第一个消息

从队列读取消息API函数:

队列操作实验

实验设计:

 实验结果


队列

         消息队列是一种数据结构,用于在任务与任务之间、中断与任务之间传递消息。每个消息由一个特定大小的数据块组成,可以包含任何类型的数据。数据交流的一种机制消息传递

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

 读写队列做好了保护,防止多任务同时访问冲突; 

写队列:                                读队列:
xQueueSend( )                          xQueueReceive( )
{                                      {
      // 进入临界区(关中断)                 // 进入临界区(关中断)
         写队列实际操作                         读队列实际操作
     // 退出临界区(开中断)                  // 退出临界区(开中断)
}                                      }

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

 FreeRTOS队列特点:

  • 数据传递方式:FreeRTOS中队列采用实际值传递,即将数据拷贝到队列中进行传递,FreeRTOS 采用拷贝数据传递,也可以传递指针, 所以在传递较大的数据的时候采用指针传递
  • 多任务访问: 队列不属于某个任务,任何任务和中断都可以向队列发送/读取消息使用消息     队列可以,防止多任务的访问冲突。
  • 存储机制: 消息队列支持FIFO(先存先读)和LIFO(后进先读)两种存储机制,队列通常采用“先进先出”(FIFO)的数据存储缓冲机制,即先入队的数据会先从队列中被读取。
  • 超时机制:FreeRTOS消息队列支持超时机制,当任务尝试从空队列中接收消息时,可以指定一个等待时间。如果在这段时间内队列中没有消息,则任务可以自动转为就绪态,继续执行其他任务。
  1. 若阻塞时间为 0  :直接返回不会等待;
  2. 若阻塞时间为 0~port_MAX_DELAY    :等待设定的阻塞时间,若在该时间内还无法入队,超时后直接返回不再等待;
  3. 若阻塞时间为 port_MAX_DELAY    :死等,一直等到可以入队为止。出队阻塞与入队阻塞类似;

入队阻塞:

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

  1. 将该任务的状态列表项挂载在 pxDelayedTaskList
  2. 将该任务的事件列表项挂载在 xTasksWaitingToSend

出队阻塞:

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

  1. 将该任务的状态列表项挂载在 pxDelayedTaskList; 
  2. 将该任务的事件列表项挂载在 xTasksWaitingToReceive; 

队列结构体

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;			    /* 写入上锁计数器 */

当用于队列使用时: 

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

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

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

队列操作基本过程

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

创建队列相关API函数

函数

描述

xQueueCreate()

动态方式创建队列

xQueueCreateStatic()

静态方式创建队列

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

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

形参

描述

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

往队列写入第一个消息  

 往队列写入第二个消息 

函数

描述

xQueueSend()

往队列的尾部写入消息

xQueueSendToBack()

xQueueSend()

xQueueSendToFront()

往队列的头部写入消息

xQueueOverwrite()

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

xQueueSendFromISR()

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

xQueueSendToBackFromISR()

xQueueSendFromISR()

xQueueSendToFrontFromISR()

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

xQueueOverwriteFromISR()

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

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

#define queueSEND_TO_BACK                 ( ( BaseType_t ) 0 )		/* 写入队列尾部 */
#define queueSEND_TO_FRONT                ( ( BaseType_t ) 1 )		/* 写入队列头部 */
#define queueOVERWRITE                    ( ( BaseType_t ) 2 )		/* 覆写队列*/
//注意:覆写方式写入队列,只有在队列的队列长度为 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 )

往队列写入消息函数:

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

读取失败

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

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

形参

描述

xQueue

待读取的队列

pvBuffer

信息读取缓冲区

xTicksToWait

阻塞超时时间

返回值

描述

pdTRUE

读取成功

pdFALSE

读取失败

队列操作实验

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

实验设计:

将设计四个任务:start_task、task1、task2、task3

四个任务的功能如下:

  • start_task:用来创建其他的3个任务
  • task1:当按键key0或key1按下,将键值拷贝到队列key_queue(入队), 当按键key_up按下,将传输大数据,这里拷贝大数据的地址到队列big_date_queue中
  • task2:读取队列key_queue中的消息(出队),打印出接收到的键值
  • task3:从队列big_date_queue读取大数据地址,通过地址访问大数据
QueueHandle_t           key_quene;             /* 定义队列 */
QueueHandle_t           big_date_quene;        /* 定义队列 */

#define QUEUE_LENGTH    1                   /* 队列支持的消息个数 */
#define QUEUE_ITEM_SIZE sizeof(uint8_t)     /* 队列中每条消息的大小 */

char buff[100] = {"asdasfdsgasdffasdd"};


void freertos_demo(void)
{  
	/* 创建队列 */
	key_quene = xQueueCreate(QUEUE_LENGTH,QUEUE_ITEM_SIZE);
	if(key_quene != NULL)
	{
		printf("key_quene队列创建成功!!\r\n");
	}else printf("key_quene队列创建失败!!\r\n");
	
	big_date_quene = xQueueCreate(QUEUE_LENGTH,sizeof(char *));
	if(big_date_quene != NULL)
	{
		printf("big_date_quene队列创建成功!!\r\n");
	}else printf("big_date_quene队列创建失败!!\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();
}


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*  )&Task1Task_Handler);
    /* 创建任务2 */
    xTaskCreate((TaskFunction_t )task2,
                (const char*    )"task2",
                (uint16_t       )TASK2_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK2_PRIO,
                (TaskHandle_t*  )&Task2Task_Handler);
	/* 创建任务3 */
    xTaskCreate((TaskFunction_t )task3,
                (const char*    )"task3",
                (uint16_t       )TASK3_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK3_PRIO,
                (TaskHandle_t*  )&Task3Task_Handler);
    vTaskDelete(StartTask_Handler); /* 删除开始任务 */
    taskEXIT_CRITICAL();            /* 退出临界区 */
}

void task1(void *pvParameters)
{
    uint8_t key = 0;
    BaseType_t err = 0;
	
	char *buf;
	buf = buff;
    while (1)
    {
        key = key_scan(0);
		if(key == KEY0_PRES || key == KEY1_PRES)
		{
			err = xQueueSend(key_quene, &key, portMAX_DELAY);
			/* 将键值作为消息发送到队列中 */
			if(err != pdTRUE)
			{
				printf("key_quene队列发送失败\r\n");
			}else printf("key_quene队列发送成功\r\n");
		}
		else if(key == WKUP_PRES)
		{
			err = xQueueSend(big_date_quene, &buf, portMAX_DELAY);
			if(err != pdTRUE)
			{
				printf("big_date_quene队列发送失败\r\n");
			}else printf("big_date_quene队列发送成功\r\n");
		}				
       
        
        vTaskDelay(500);
    }
}


void task2(void *pvParameters)
{
    uint8_t     queue_recv  = 0;
	BaseType_t err =0;
    while (1)
    {
        err = xQueueReceive(key_quene, &queue_recv, portMAX_DELAY);
		if(err != pdTRUE)
		{
			printf("接收队列失败\r\n");
		}
		else 
		{			
			printf("key_quene队列的键值是:%u\r\n",queue_recv);
        }
		vTaskDelay(500);
    }
	
}

void task3(void *pvParameters)
{
    
	BaseType_t err =0;
    char * buf;
    while (1)
    {
        err = xQueueReceive(big_date_quene, &buf, portMAX_DELAY);
		if(err != pdTRUE)
		{
			printf("接收队列失败\r\n");
		}
		else 
		{
			printf("big_date_quene队列的数据是:%s\r\n",buf);
        }
        
      
    }
	
}

 实验结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值