# 队列特性

队列可以保存有限个具有确定长度的数据单元。队列可以保存的最大单元数目被称为队列的 “深度” ;在队列创建时需要设定其深度和每个单元的大小。

通常情况下,队列被作为 FIFO(先进先出)使用,即数据由队列尾写入,从队列首读出;当然,由队列首写入也是可能的。

然后我们看一下队列的处理过程:

img

# 队列的 API 函数

要想调用以下函数,需要 #include "queue.h"

1、队列创建

属性API 接口实际执行函数
动态xQueueCreate()xQueueGenericCreate()
静态xQueueCreateStatic()xQueueGenericCreateStatic()

2、队列发送

入队方式API 接口实际执行函数其他
从队列尾部入队xQueueSend()xQueueGenericSend()
同上xQueueSendToBack()同上
同上xQueueOverwrite()同上仅用于消息数目为 1 的队列
从队列首部入队xQueueSendToFront()同上
从队列尾部入队 <br/> (用于中断中)xQueueSendFromISR()xQueueGenericSendFromISR()
同上xQueueSendToBackFromISR()同上
同上xQueueOverwriteFromISR()同上仅用于消息数目为 1 的队列
从队列首部入队 <br/> (用于中断中)xQueueSendToFrontFromISR()同上

3、队列接收

出队方式API 接口实际执行函数
出队并删除xQueueReceive()xQueueGenericReceive()
出队不删除xQueuePeek()同上
出队并删除 <br/> (用于中断中)xQueueReceiveFromISR()xQueueReceiveFromISR()
出队不删除 <br/> (用于中断中)xQueuePeekFromISR()xQueuePeekFromISR()

4、队列复位及删除

功能API 接口实际执行函数
复位清空内存xQueueReset()xQueueGenericReset()
删除释放内存vQueueDelete()vQueueDelete()

# 常用队列函数分析

1、xQueueCreate () API 函数

QueueHandle_t  xQueueCreate( UBaseType_t  uxQueueLength,
                             UBaseType_t  uxItemSize );

传入参数:

  • uxQueueLength:队列项长度;即队列能够存储的最大消息数目,也称为队列深度。
  • uxItemSize:信息大小;即队列中每个消息数目的数据大小,以字节为单位。

返回参数(此返回值应当保存下来,以作为操作此队列的句柄):

  • NULL:表示没有足够的堆空间分配给队列而导致创建失败。
  • 非 NULL:表示队列创建成功。

2、xQueueSendToBack () 与 xQueueSendToFront () API 函数

BaseType_t  xQueueSendToBack( QueueHandle_t  xQueue,
                              const void     *pvItemToQueue,
                              TickType_t     xTicksToWait );
BaseType_t  xQueueSendToFront( QueueHandle_t  xQueue,
                               const void     *pvItemToQueue,
                               TickType_t     xTicksToWait );

xQueueSendToFront()xQueueSendToBack() 函数参数及返回值。

传入参数:

  • xQueue:目标队列的句柄。这个句柄即是调用 xQueueCreate() 创建该队列时的返回值。
  • pvItemToQueue:发送数据的指针。其指向将要复制到目标队列中的数据单元。
  • xTicksToWait:阻塞超时时间。如果在发送时队列已满,这个时间即是任务处于阻塞态等待队列空间有效的最长等待时间;如果 xTicksToWait 设为 0 , 则 xQueueSendToFront()xQueueSendToBack() 均会立即返回;如果把 xTicksToWait 设置为 portMAX_DELAY ,那么阻塞等待将没有超时限制。

返回参数(有两个可能的返回值):

  • pdTRUE:数据被成功发送到队列中。
  • errQUEUE_FULL:由于队列已满而无法将数据写入。

3、xQueueReceive () 与 xQueuePeek () API 函数

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

xQueueReceive()xQueuePeek() 函数参数与返回值。

传入参数:

  • xQueue:被读队列的句柄。这个句柄即是调用 xQueueCreate() 创建该队列时的返回值
  • pvBuffer:接收缓存指针。其指向一段内存区域,用于接收从队列中拷贝来的数据
  • xTicksToWait:阻塞超时时间。如果在接收时队列为空,则这个时间是任务处于阻塞状态以等待队列数据有效的最长等待时间;如果 xTicksToWait 设为 0 , 则 xQueueRecieve()xQueuePeek() 均会立即返回;如果把 xTicksToWait 设置为 portMAX_DELAY ,那么阻塞等待将没有超时限制。

返回参数(有两个可能的返回值):

  • pdTRUE:成功地从队列中读到数据。
  • pdFALSE:在读取时由于队列已空而没有读到任何数据。

# 大型数据单元处理

如果队列存储的数据单元尺寸较大,那最好是利用队列来传递数据的指针而不是对数据本身在队列上一字节一字节地拷贝进或拷贝出。传递指针无论是在处理速度上还是内存空间利用上都更有效;但是,当你利用队列传递指针时,一定要十分小心地做到以下两点:

1、指针指向的内存空间的所有权必须明确

当任务间通过指针共享内存时,应该从根本上保证所不会有任意两个任务同时修改共享内存中的数据,或是以其它行为方式使得共享内存数据无效或产生一致性问题。原则上,共享内存在其指针发送到队列之前,其内容只允许被发送任务访问;共享内存指针从队列中被读出之后,其内容亦只允许被接收任务访问。

2、指针指向的内存空间必须有效

如果指针指向的内存空间是动态分配的,只应该有一个任务负责对其进行内存释放。当这段内存空间被释放之后,就不应该有任何一个任务再访问这段空间。

切忌用指针访问任务栈上分配的空间。因为当栈帧发生改变后,栈上的数据将不再有效。

# 例程测试

main.c
/* Standard includes. */
#include <stdio.h>
#include <string.h>
/* Scheduler includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
/* Library includes. */
#include "stm32f10x_it.h"
/* Private app includes. */
#include "bsp_time.h"
#include "bsp_uart.h"
#include "bsp_gpio.h"
/* Task priorities. */
#define mainCREATOR_TASK_PRIORITY           ( tskIDLE_PRIORITY + 3 )
/*----------------------------- End -----------------------------*/
/*
 * User Private Task.
 */
static void prvUser_Task( void *pvParameters );
/*
 * Configure the clocks, GPIO and other peripherals as required by the demo.
 */
static void prvSetupHardware( void );
/*----------------------------- End -----------------------------*/
/************************************************
函数名称 : main
功    能 : 主函数入口
参    数 : 无
返 回 值 : 无
*************************************************/
int main( void )
{
#ifdef DEBUG
  debug();
#endif
	prvSetupHardware();
	/* Start the tasks defined within this file/specific to this demo. */
	xTaskCreate( prvUser_Task, "prvUser_Task", configMINIMAL_STACK_SIZE, NULL, mainCREATOR_TASK_PRIORITY, NULL );
	
	/* Start the scheduler. */
	vTaskStartScheduler();
	/* Will only get here if there was not enough heap space to create the
	idle task. */
	return 0;
}
/*----------------------------- End -----------------------------*/
extern QueueHandle_t xQueueUartTx;
void vSender1_Task( void *pvParameters )
{	
	UartTx_Buff_TypeDef uart_data;
	
	strcpy((char *)uart_data.TxBuffer,"TaskA Running\r\n");
	uart_data.TxCounter = strlen("TaskA running\r\n");
	printf("Task A was created successfully....\r\n");
	uart_data.COMx = EVAL_COM1;
		
	while(1){
		/* 延时 800 个 tick */
		vTaskDelay(800);
		xQueueSend(xQueueUartTx, (void *)&uart_data, portMAX_DELAY);
	}
}
void vSender2_Task( void *pvParameters )
{	
	UartTx_Buff_TypeDef uart_data;
	strcpy((char *)uart_data.TxBuffer,"TaskB Running\r\n");
	uart_data.TxCounter = strlen("TaskB running\r\n");
	printf("Task B was created successfully....\r\n");
	uart_data.COMx = EVAL_COM1;
		
	while(1){
		/* 延时 800 个 tick */
		vTaskDelay(800);
		xQueueSend(xQueueUartTx, (void *)&uart_data, portMAX_DELAY);
	}
}
static void prvUser_Task( void *pvParameters )
{	
	/* User-defined private tasks */
	xTaskCreate( vSender1_Task, "vSender1_Task", configMINIMAL_STACK_SIZE + 300, NULL, tskIDLE_PRIORITY+2, NULL );
	xTaskCreate( vSender2_Task, "vSender2_Task", configMINIMAL_STACK_SIZE + 300, NULL, tskIDLE_PRIORITY+2, NULL );
	
	printf("delete user task\n");
	vTaskDelete(NULL);		// 删除自己
}
static void prvSetupHardware( void )
{
	/* Start with the clocks in their expected state. */
	RCC_DeInit();
	/* Enable HSE (high speed external clock). */
	RCC_HSEConfig( RCC_HSE_ON );
	/* Wait till HSE is ready. */
	while( RCC_GetFlagStatus( RCC_FLAG_HSERDY ) == RESET )
	{
	}
	/* 2 wait states required on the flash. */
	*( ( unsigned long * ) 0x40022000 ) = 0x02;
	/* HCLK = SYSCLK */
	RCC_HCLKConfig( RCC_SYSCLK_Div1 );
	/* PCLK2 = HCLK */
	RCC_PCLK2Config( RCC_HCLK_Div1 );
	/* PCLK1 = HCLK/2 */
	RCC_PCLK1Config( RCC_HCLK_Div2 );
	/* PLLCLK = 8MHz * 9 = 72 MHz. */
	RCC_PLLConfig( RCC_PLLSource_HSE_Div1, RCC_PLLMul_9 );
	/* Enable PLL. */
	RCC_PLLCmd( ENABLE );
	/* Wait till PLL is ready. */
	while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
	{
	}
	/* Select PLL as system clock source. */
	RCC_SYSCLKConfig( RCC_SYSCLKSource_PLLCLK );
	/* Wait till PLL is used as system clock source. */
	while( RCC_GetSYSCLKSource() != 0x08 )
	{
	}
	/* Configure HCLK clock as SysTick clock source. */
	SysTick_CLKSourceConfig( SysTick_CLKSource_HCLK );
	
	/*
	 * STM32 中断优先级分组为 4,即 4bit 都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* Other peripheral configuration */
//	vSetupTimer();
	vSetupUSART();
	vSetupParPort();
}
/*----------------------------- End -----------------------------*/

由总工程上看到,我们主要创建了两个 task 作为队列的发送;为什么要多此一举用队列来传输打印数据,而不直接调用 printf 函数输出呢?在 RTOS 应用中,我们知道每个任务都相当于是独立的,这样就会出现一种情况就是,Task A 在打印输出数据,而刚好 Task B 也要打印数据,那么在同一时间,输出的数据就可能出现叠加或者丢包乱码了;为了试验一下,所以在代码中把 Task A 和 Task B 的运行例子设计成一样,包括优先级;另外添加像我们以前没用 RTOS 一样直接打印数据,看会有什么情况,然后再贴上队列接收的任务:

bsp_uart.c
#include "bsp_uart.h"
/* Scheduler includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#define BAUDRATE_1		115200;			// 波特率设置	支持的波特率:115200,19200,9600,38400,57600,1200,2400,4800
#define BAUDRATE_2		115200;			// 波特率设置	支持的波特率:115200,19200,9600,38400,57600,1200,2400,4800
QueueHandle_t xQueueUartTx;
static void prvUartTx_Task( void *pvParameters )
{
	UartTx_Buff_TypeDef uart_data;
	
	for( ; ; ){
		if(xQueueReceive(xQueueUartTx, (void *)&uart_data, portMAX_DELAY) == pdPASS){
			if(uart_data.TxBuffer > 0){
				printf("len = %d\n",uart_data.TxCounter);
				USART_SendString(uart_data.COMx, uart_data.TxBuffer, uart_data.TxCounter);
			}
		}
	}
}
/************************************************
函数名称 : vSetupUSART
功    能 : UART 初始化接口
参    数 : 无
返 回 值 : 无
*************************************************/
void vSetupUSART( void )
{
	UART1_Config();
//	UART2_Config();
	
	/* Create the queue used by the Usart task. */
	xQueueUartTx = xQueueCreate((unsigned portBASE_TYPE)UART_QUEUE_TX_LENGTH, sizeof(UartTx_Buff_TypeDef));	// 目标队列的句柄
//	xQueueUart1_Rx = xQueueCreate ((unsigned portBASE_TYPE) UART_QUEUE_RX_LENGTH, sizeof (UartRx_Buff_TypeDef));	// 目标队列的句柄
	xQueueUart1_Rx = xQueueCreate((unsigned portBASE_TYPE)20, sizeof(uint8_t));	// 目标队列的句柄
	xTaskCreate( prvUartTx_Task, "prvUartTx_Task", 300, NULL, tskIDLE_PRIORITY + 3, NULL );
	xTaskCreate( prvUart1_Rx_Task, "prvUart1_Rx_Task", 500, NULL, tskIDLE_PRIORITY + 3, NULL );
}
/************************************************
函数名称 : UART1_Config
功    能 : UART1 端口配置
参    数 : 无
返 回 值 : 无
*************************************************/
void UART1_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    /* config GPIOA clock */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
    /* config USART1 clock */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    /* USART1 GPIO config */
    /* Configure USART1 Tx (PA.09) as alternate function push-pull */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    /* Configure USART1 Rx (PA.10) as input floating */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    /* Enable the USART1 Interrupt */
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 7;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    /* USART1 mode config */
    USART_InitStructure.USART_BaudRate = BAUDRATE_1;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No ;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(EVAL_COM1, &USART_InitStructure);
	USART_ITConfig(EVAL_COM1, USART_IT_RXNE, ENABLE);
    USART_Cmd(EVAL_COM1, ENABLE);
}
/************************************************
函数名称 : USART_SendByte
功    能 : 串口字符发送
参    数 : c ---- 发送的数据
返 回 值 : 无
*************************************************/
void USART_SendByte( USART_TypeDef* USARTx, uint8_t c )
{     
	USART_SendData(USARTx, c);
	
	while (USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET);
}
/************************************************
函数名称 : USART_SendString
功    能 : 串口字符串发送
参    数 : USARTx ---- 串口
			pData ---- 字符串
			Length ---- 长度
返 回 值 : 无
*************************************************/
void USART_SendString( USART_TypeDef* USARTx, const uint8_t *pData, uint16_t Length )
{
    while(Length--)
    {
        USART_SendByte(USARTx, *pData);
        pData++;
    }
}
/************************************************
函数名称 : USART_Printf
功    能 : 串口打印输出
参    数 : USARTx ---- 串口
			String	---- 字符串
返 回 值 : 无
*************************************************/
void USART_Printf( USART_TypeDef* USARTx, char *String )
{
    do
    {
        USART_SendByte(USARTx, *String);
        String++;
    }while((*String) != '\0');
}
/************************************************
函数名称 : fputc
功    能 : 重定向 c 库函数 printf 到 DEBUG_UART
参    数 : ch
返 回 值 : 无
*************************************************/
int fputc(int ch, FILE *f)
{
    /* 发送一个字节数据到 DEBUG_UART */
    USART_SendData(DEBUG_UART, (uint8_t) ch);
    /* 等待发送完毕 */
    while (USART_GetFlagStatus(DEBUG_UART, USART_FLAG_TXE) == RESET);
    return (ch);
}
/************************************************
函数名称 : fgetc
功    能 : 重定向 c 库函数 scanf 到 DEBUG_UART
参    数 : f ---- 文件
返 回 值 : 无
*************************************************/
int fgetc(FILE *f)
{
    /* 等待 DEBUG_UART 输入数据 */
    while (USART_GetFlagStatus(DEBUG_UART, USART_FLAG_RXNE) == RESET);
    return (int)USART_ReceiveData(DEBUG_UART);
}
/*---------------------------- END OF FILE ----------------------------*/
bsp_uart.h
#ifndef __BSP_UART_H
#define __BSP_UART_H
#include <stdio.h>
#include "stm32f10x.h"
#define DEBUG_UART				USART1
#define EVAL_COM1				USART1
#define EVAL_COM2				USART2
#define USART1_RX_DMA_CHANNEL	DMA1_Channel5
#define USART2_RX_DMA_CHANNEL	DMA1_Channel6
#define TxBUFFER_SIZE  	 		100
#define RxBUFFER_SIZE  		 	100
#define UART_QUEUE_RX_LENGTH	2
#define UART_QUEUE_TX_LENGTH	2
typedef struct
{
	uint8_t Receiving_Time;					// 接收时间
	uint8_t Frame_flag;						// 一帧完成标志
}EVAL_COMx_TypeDef;
typedef struct
{
	uint8_t RxBuffer[RxBUFFER_SIZE];		// 接收暂存缓冲区
	__IO uint8_t RxCounter;					// 接收数据个数
}UartRx_Buff_TypeDef;
typedef struct
{
	uint8_t TxBuffer[TxBUFFER_SIZE];		// 发送暂存缓冲区
	__IO uint8_t TxCounter;					// 发送数据个数
	USART_TypeDef* COMx;					// 串口号
}UartTx_Buff_TypeDef;
void vSetupUSART( void );
void UART1_Config(void);
void UART2_Config(void);
void USART_SendByte( USART_TypeDef* USARTx, uint8_t c );
void USART_SendString( USART_TypeDef* USARTx, const uint8_t *pData, uint16_t Length );
void USART_Printf( USART_TypeDef* USARTx, char *String );
#endif	/* __BSP_UART_H */
/*---------------------------- END OF FILE ----------------------------*/

其实对照之前的 STM32 笔记之 USART(串口) 改动并不大,只是分开处理一下,并且以任务的形式封装起来。

最后我们可以看一下他的测试结果:

img

从结果中可以看到,若是直接用 printf 输出,因为两个相同优先级的任务在同一时间输出数据,结果出现了数据叠加(这并不是我们想要的结果),而后面以队列的形式进行输出,数据是可以按照正常打印的(哪怕他们的任务优先级一样,并且运行的流程一样,他都分布好数据位置一并打印;后面测了一下,任务优先级相同,发现会按着任务创建的顺序来处理的;还有其他一些小测试,也会有很多不同的结果,具体可以自己试着探究),这里主要想说的是:为了避免在 RTOS 中多个任务同时访问公用数据(或者同一硬件),最好用队列或者利用后面要说的互斥量等来处理数据(根据实际需求用哪个)。

多任务系统中存在一种潜在的风险。当一个任务在使用某个资源的过程中,即还没有完全结束对资源的访问时,便被切出运行态,使得资源处于非一致,不完整的状态。如果这个时候有另一个任务或者中断来访问这个资源,则会导致数据损坏或是其它相似的错误,也就是像上面的一开始打印出错那样。

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

夏沫の浅雨 微信支付

微信支付

夏沫の浅雨 支付宝

支付宝