消息队列应用示例MessageQueues-STM32CubeMX-FreeRTOS《嵌入式系统设计》P343-P347
消息队列
使用信号量、事件标志组和线标志进行任务同步时,只能提供同步的时刻信息,无法在任务之间进行数据传输。要实现任务间的数据传输,一般使用两种方式:
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 */
}