消息隊列
使用信號量、事件標志組和線標志進行任務同步時,只能提供同步的時刻信息,無法在任務之間進行數據傳輸。要實現任務間的數據傳輸,一般使用兩種方式:
1. 全局變量
在 RTOS 中使用全局變量時,必須保證每個任務對全局變量的互斥訪問,一般借助互斥量來實現。另一個方法是在任務設計時,設計成只有一個任務修改這個全局變量,其他任務只是讀取這個全局變量,而不修改它的值,并在全局變量前面加上 volatile 的關鍵字修飾,以避免編澤器的優化。
2. 消息隊列
消息隊列類似于一個數據緩沖區,可以保存有限個、具有確定大小的數據。通常情況下,消息隊列按照 FI FO(先進先出)的模式使用,即數據由隊尾寫入,從隊首讀出。
任務向消息隊列中放人消息時,需要判斷消息隊列是否有多余的空間:如果有空間,則放人一個新的消息;如果消息隊列已經存滿,該任務將進人到阻塞態,直到消息隊列中有多余的空間。
任務從消息隊列中獲取消息時,需要判斷消息隊列是否有消息:如果消息隊列中沒有消息,該任務將進人到阻塞態。當消息隊列中有新的消息時,處于阻塞態的任務將被喚醒并獲得該消息。任務在獲取消息時,需要提前定義存放消息的緩沖區,這個緩沖區的大小不能小于消息隊列中單個消息的大小。
注意: FreeRTOS 利用消息隊列進行消息傳遞時,放入消息隊列的是實際的數據,而不是數據的地址。例如,串口一次接收 10 字節的數據,如果使用消息隊列來傳遞串口接收的數據,則應該將消息隊列的單個消息大小設置為 10 字節,以便一次性存放串口接收的10 字節數據。
消息隊列和全局變量相比,解決了多任務訪問共享資源的沖突問題,還提供了任務的同步和超時處理等機制,并且可以實現中斷服務程序和任務之間的數據傳遞。例如,多個任務都要使用串口進行數據傳輸時,可以采用兩種方法:一種方法是利用互斥量實現對串口的互斥訪問;另一種方法是創建一個消息隊列和一個負責串口數據收發的任務。任務 A 發送的數據放人消息隊列,任務 B 發送的數據也放人消息隊列,串口發送任務則按照 FIFO 的原則從消息隊列中取出消息發送。
在實際應用時,由于消息隊列采用數據復制的方式傳輸數據,而不是傳輸存放數據的地址。如果任務間傳輸的數據量較大時,使用消息隊列的效率會比較低。這時,可以考慮使用全局變量來實現任務間的通信,只是要注意全局變量的互斥訪問(利用互斥量實現)。
應用示例
利用消息隊列傳輸串口接收的數據。串口采用中斷方式接收 10 字節的數據,并放入消息隊列。數據處理任務從消息隊列中取出數據并發送到 PC 顯示。消息隊列設置為可以容納 5 個消息,每個消息的大小為10 字節。
// main.h
/*** @file main.h* @brief 主程序頭文件,包含系統所需的基本定義和聲明*//* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h> // 標準輸入輸出頭文件,用于printf和fputc
#include "cmsis_os2.h" // CMSIS RTOS2,支持FreeRTOS
#include "usart.h" // USART頭文件,包含串口相關函數
#define LENGTH 10 // 緩沖區長度
extern uint8_t RxBuf[LENGTH]; // 串口接收緩沖區,用于存儲接收到的數據
extern uint8_t TxBuf[LENGTH]; // 串口發送緩沖區,用于存儲待發送的數據
extern osMessageQueueId_t ComQueueHandle; // RTOS消息隊列句柄,用于任務間通信
/* USER CODE END Includes */
// main.c
/*** @file main.c* @brief 主程序源文件,包含系統初始化和中斷回調函數*//* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/*** @brief 重定向printf函數相關配置* @note 支持不同編譯器(ARMCC、GNUC)下的printf功能實現*/
/* suport printf function, usemicrolib is unnecessary */
#if (__ARMCC_VERSION > 6000000)
__asm (".global __use_no_semihosting\n\t");
void _sys_exit(int x)
{x = x;
}
/* __use_no_semihosting was requested, but _ttywrch was */
void _ttywrch(int ch)
{ch = ch;
}
FILE __stdout;
#else
#ifdef __CC_ARM
#pragma import(__use_no_semihosting)
struct __FILE
{int handle;
};
FILE __stdout;
void _sys_exit(int x)
{x = x;
}
#endif
#endif
#if defined ( __GNUC__ ) && !defined (__clang__)
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif/*** @brief Retargets the C library printf function to the USART.* @param None* @retval None*/
PUTCHAR_PROTOTYPE
{ // 采用輪詢方式發送1字節數據,使用阻塞方式,超時時間設置為無限等待HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);return ch;
}int fgetc(FILE *f)
{ // 采用輪詢方式接收1字節數據,使用阻塞方式,超時時間設置為無限等待uint8_t ch; HAL_UART_Receive( &huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY );return ch;
}
/* USER CODE END 0 *//* USER CODE BEGIN 2 */printf("/** FreeRTOS for task creat **/\n");HAL_UART_Receive_IT(&huart1,RxBuf,LENGTH); // 啟動串口中斷接收功能,接收長度為 LENGTH 的數據到 RxBuf 緩沖區
/* USER CODE END 2 *//* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{ // 串口接收完成中斷回調函數,當串口接收到指定長度的數據后,會觸發此回調函數if(huart->Instance==USART1) // 檢查觸發中斷的串口是否為 USART1{ // 將收到的數據放入消息隊列 ComQueueHandle中osMessageQueuePut(ComQueueHandle, (void *)RxBuf,0,0); HAL_UART_Receive_IT(&huart1,RxBuf,LENGTH); // 重新開始串口中斷接收,準備接收下一組數據}
}
/* USER CODE END 4 */
// app_freertos.c/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
uint8_t RxBuf[LENGTH]; // 串口接收緩沖區
uint8_t TxBuf[LENGTH]; // 串口發送緩沖區
/* USER CODE END Variables *//* USER CODE BEGIN Header_StartProcessTask */
/**
* @brief Function implementing the ProcessTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartProcessTask */
void StartProcessTask(void *argument)
{/* USER CODE BEGIN StartProcessTask *//* Infinite loop */for(;;){ // 從消息隊列中獲取數據,無限等待if(osMessageQueueGet(ComQueueHandle,(void *)TxBuf,NULL,osWaitForever)==osOK){ // 通過UART1發送數據HAL_UART_Transmit(&huart1,(void *)&TxBuf,LENGTH,HAL_MAX_DELAY);}}/* USER CODE END StartProcessTask */
}