FreeRTOS Queue消息队列-笔记

发布于:2025-05-11 ⋅ 阅读:(19) ⋅ 点赞:(0)

一、进程间通信(IPC)概述

1. 进程的定义

在使用RTOS时有多个任务,还可以有多个中断的ISR。任务和ISR可以统称为进程。
任务与任务之间或者任务与ISR之间有时候需要进行通信或者同步,这称为进程间通信。
在这里插入图片描述
总结就是在FreeRTOS中,任务(Tasks)和中断服务程序(ISRs)统称为进程。多任务系统中,进程之间常需要数据传递和状态同步
例如:
在实际的ADC连续采集中,一般使用双缓中区,一个缓冲区存满之后用于读取和处理,而另一个缓冲区继续用于保存ADC转换结果数据。两个缓冲区交替使用以保证采集和处理的连续性。

进程间通信就是ADC中断ISR与数据处理任务之间的通信,在ADC中断ISR向缓冲区写入数据后,如果发现缓冲区满了,就可以发出一个标志信号,通知数据处理任务一直在阻塞状态下,等待这个信号的数据处理任务就可以退出。阻塞状态被调度为运行状态后,就可以及时的读取缓冲区的数据并进行处理。

2. 进程间通信的实现方式

FreeRTOS提供以下机制:

在这里插入图片描述

在这里插入图片描述

补充一个是在事件组后面:任务通知task notification。使用任务通知不需要创建任何的中间对象,可以直接从任务向任务或者从ISR向任务发送通知,传递一个通知值。任务通知可以模拟二值信号量、计数信号量或者长度为一的消息队列。使用任务通知通常效率更高,消耗内存更少好。


二、队列的核心概念与作用

1. 队列的作用

  • 数据缓冲:解决生产者-消费者问题(如ADC采样与数据处理的速率不匹配)。
  • 任务同步:通过阻塞等待数据,避免忙等待浪费CPU资源。
  • 优先级处理:支持FIFO或优先级插入。

2. 双缓冲区在ADC采样中的应用

  • 双缓冲区工作原理
    • 一个缓冲区用于采集数据,另一个用于处理数据。
    • 缓冲区满时通过队列通知任务处理,切换缓冲区继续采集。
  • 队列的作用:在ADC中断与数据处理任务之间传递缓冲区切换信号。

三、队列的创建

在FreeRTOS创建对象,如任务队列、信号量等,都有静态分配内存和动态分配内存两种方式。
在这里插入图片描述

函数原型
在这里插入图片描述

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
    #define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )
#endif

在这里插入图片描述

xQueueCreate实际上是一个宏函数,它调用的函数xQueueGenericCreate,这个函数是创建队列、信号量、互斥量等对象的通用函数。

QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength,UBaseType_t uxItemSize);

在这里插入图片描述

四、队列的发送与接收操作

1. 向队列发送数据

在这里插入图片描述
可以把数据写到队列头部,也可以写到尾部,这些函数有两个版本:在任务中使用、在ISR中使用。函数原型如下:

/* 等同于xQueueSendToBack 
往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait 
*/
BaseType_t xQueueSend(
                 QueueHandle_t    xQueue,                                
                 const void       *pvItemToQueue,                                
                 TickType_t       xTicksToWait                            
              	);  

/*
* 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait 
*/
BaseType_t xQueueSendToBack(                                
					QueueHandle_t    xQueue,                                
					const void       *pvItemToQueue,                                
					TickType_t       xTicksToWait                            
					);
/*
* 往队列尾部写入数据,此函数可以在中断函数中使用,不可阻塞 
*/
BaseType_t xQueueSendToBackFromISR(                                      
					QueueHandle_t xQueue,                                     
					const void *pvItemToQueue,                                      
					BaseType_t *pxHigherPriorityTaskWoken                                   
					); 
/* 
* 往队列头部写入数据,如果没有空间,阻塞时间为xTicksToWait 
*/

BaseType_t xQueueSendToFront(                                
				QueueHandle_t    xQueue,                                
				const void       *pvItemToQueue,                                
				TickType_t       xTicksToWait                            
				);
/*  
往队列头部写入数据,此函数可以在中断函数中使用,不可阻塞 
*/
BaseType_t xQueueSendToFrontFromISR(
                     QueueHandle_t xQueue,                                      
                     const void *pvItemToQueue,                                      
                     BaseType_t *pxHigherPriorityTaskWoken                                   
                   );

这些函数用到的参数是类似的,统一说明如下:
在这里插入图片描述

2. 从队列接收数据

使用 xQueueReceive() 函数读队列,读到一个数据后,队列中该数据会被移除。
这个函数有两个版本:在任务中使用、在ISR中使用。函数原型如下:

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

BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue, void *pvBuffer, BaseType_t *pxTaskWoken);

参数说明如下:
在这里插入图片描述

五、队列的典型应用场景

1. ADC连续采样与双缓冲区

代码示例
// 定义双缓冲区
#define BUFFER_SIZE 100
uint16_t buffer1[BUFFER_SIZE];
uint16_t buffer2[BUFFER_SIZE];

// 队列用于缓冲区切换信号
QueueHandle_t xADCQueue = xQueueCreate(1, sizeof(uint16_t*));

// ADC中断服务程序
void ADC_IRQHandler(void) {
    static uint16_t *currentBuffer = buffer1;
    static uint16_t index = 0;

    // 读取ADC数据
    uint16_t data = ADC_GetValue();

    // 写入当前缓冲区
    currentBuffer[index++] = data;

    // 判断是否填满缓冲区
    if (index >= BUFFER_SIZE) {
        index = 0;
        // 切换缓冲区
        if (currentBuffer == buffer1) {
            currentBuffer = buffer2;
        } else {
            currentBuffer = buffer1;
        }
        // 通过队列通知任务处理数据
        xQueueSendToBackFromISR(xADCQueue, &currentBuffer, NULL);
    }
}

// 数据处理任务
void vDataProcessingTask(void *pvParameters) {
    uint16_t *pBuffer;
    while (1) {
        // 等待缓冲区切换信号
        xQueueReceive(xADCQueue, &pBuffer, portMAX_DELAY);
        // 处理缓冲区数据
        ProcessData(pBuffer);
    }
}

六、任务通知(Task Notification)的替代方案

1. 任务通知的优势

  • 无需创建中间对象:直接从ISR或任务向任务发送通知。
  • 内存占用低:每个任务自带一个通知值,无需额外内存。
  • 效率更高:减少队列操作的开销。

2. 任务通知的使用

从ISR发送通知
BaseType_t xTaskNotifyFromISR(
    TaskHandle_t xTaskToNotify,
    uint32_t ulValue,
    eNotifyAction eAction,
    BaseType_t *pxHigherPriorityTaskWoken
);
从任务接收通知
BaseType_t xTaskNotifyWait(
    uint32_t ulBitsToClearOnEntry,
    uint32_t ulBitsToClearOnExit,
    uint32_t *pulNotificationValue,
    TickType_t xTicksToWait
);
示例:ADC中断通知任务
// 中断中发送通知
void ADC_IRQHandler(void) {
    xTaskNotifyFromISR(xDataProcessingTask, 0, eSetValueWithOverwrite, NULL);
}

// 任务中等待通知
void vDataProcessingTask(void *pvParameters) {
    while (1) {
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
        // 处理ADC数据
    }
}

功能 关键函数 适用场景
队列创建 xQueueCreate() / xQueueCreateStatic() 数据缓冲、任务同步
队列发送 xQueueSend() / xQueueSendToBackFromISR() 任务与ISR间的数据传递
队列接收 xQueueReceive() / xQueueReceiveFromISR() 任务等待数据处理
任务通知 xTaskNotifyFromISR() / xTaskNotifyWait() 轻量级同步替代方案

网站公告

今日签到

点亮在社区的每一天
去签到