寫在前面:
本文章旨在總結備份、友善以後查詢,由于是個人總結,如有不對,歡迎指正;另外,内容大部分來自網絡、書籍、和各類手冊,如若侵權請告知,馬上删帖緻歉。
目錄
一、隊列特性
二、隊列的 API函數
三、常用隊列函數分析
四、大型資料單元處理
五、例程測試
一、隊列特性
隊列可以儲存有限個具有确定長度的資料單元。隊列可以儲存的最大單元數目被稱為隊列的 “ 深度 ” ;在隊列建立時需要設定其深度和每個單元的大小
通常情況下,隊列被作為 FIFO(先進先出)使用,即資料由隊列尾寫入,從隊列首讀出;當然,由隊列首寫入也是可能的
然後我們看一下隊列的處理過程:

二、隊列的 API函數
要想調用以下函數,需要 #include "queue.h"
1、隊列建立
屬性 | API 接口 | 實際執行函數 |
動态 | xQueueCreate() | xQueueGenericCreate() |
靜态 | xQueueCreateStatic() | xQueueGenericCreateStatic() |
2、隊列發送
入隊方式 | API 接口 | 實際執行函數 | 其他 |
從隊列尾部入隊 | xQueueSend() | xQueueGenericSend() | |
xQueueSendToBack() | |||
xQueueOverwrite() | 僅用于消息數目為 1的隊列 | ||
從隊列首部入隊 | xQueueSendToFront() | ||
從隊列尾部入隊 (用于中斷中) | xQueueSendFromISR() | xQueueGenericSendFromISR() | |
xQueueSendToBackFromISR() | |||
xQueueOverwriteFromISR() | 僅用于消息數目為 1的隊列 | ||
從隊列首部入隊 (用于中斷中) | xQueueSendToFrontFromISR() |
3、隊列接收
出隊方式 | API 接口 | 實際執行函數 |
出隊并删除 | xQueueReceive() | xQueueGenericReceive() |
出隊不删除 | xQueuePeek() | |
出隊并删除 (用于中斷中) | xQueueReceiveFromISR() | xQueueReceiveFromISR() |
出隊不删除 (用于中斷中) | 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(序列槽)改動并不大,隻是分開處理一下,并且以任務的形式封裝起來
最後我們可以看一下他的測試結果:
從結果中可以看到,若是直接用 printf輸出,因為兩個相同優先級的任務在同一時間輸出資料,結果出現了資料疊加(這并不是我們想要的結果),而後面以隊列的形式進行輸出,資料是可以按照正常列印的(哪怕他們的任務優先級一樣,并且運作的流程一樣,他都分布好資料位置一并列印;後面測了一下,任務優先級相同,發現會按着任務建立的順序來處理的;還有其他一些小測試,也會有很多不同的結果,具體可以自己試着探究),這裡主要想說的是:為了避免在 RTOS中多個任務同時通路公用資料(或者同一硬體),最好用隊列或者利用後面要說的互斥量等來處理資料(根據實際需求用哪個)
多任務系統中存在一種潛在的風險。當一個任務在使用某個資源的過程中,即還沒有完全結束對資源的通路時,便被切出運作态,使得資源處于非一緻,不完整的狀态。如果這個時候有另一個任務或者中斷來通路這個資源,則會導緻資料損壞或是其它相似的錯誤,也就是像上面的一開始列印出錯那樣