【FreeRTOS】队列 queue(附源码)

目录

1.队列的特性

2.队列函数

3.队列内部机制(重点)

4.示例:队列的基本使用

5.示例:分辨数据源


引言:队列(queue)可以用于"任务到任务"、"任务到中断"、"中断到任务"直接传输信息。

1.队列的特性

直入主题,直接说特点

1)队列可以包含若干个数据:队列中有若干项,这被称为"长度"(length)

2)每个数据大小固定

3)创建队列时就要指定长度,数据大小

4)数据的操作采用先进先出的方法(FIFO,First In First Out):写数据时放到尾部,读数据时从头部读

流程如下所示:

并且传输数据的两种方法:
拷贝:把数据、把变量的值复制进队列里
引用:把数据、把变量的地址复制进队列里
FreeRTOS使用拷贝值的方法,这更简单:
局部变量的值可以发送到队列中,后续即使函数退出、局部变量被回收,也不会影响队列中的数据
无需分配buffer来保存数据,队列中有buffer
局部变量可以马上再次使用
发送任务、接收任务解耦:接收任务不需要知道这数据是谁的、也不需要发送任务来释放数据
如果数据实在太大,你还是可以使用队列传输它的地址
队列的空间有FreeRTOS内核分配,无需任务操心
对于有内存保护功能的系统,如果队列使用引用方法,也就是使用地址,必须确保双方任务对这个 地址都有访问权限。使用拷贝方法时,则无此限制:内核有足够的权限,把数据复制进队列、再把 数据复制出队列。

2.队列函数

使用队列的流程:创建队列、写队列、读队列、删除队列。

动态创建:

QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );

静态创建:

QueueHandle_t xQueueCreateStatic(
                           UBaseType_t uxQueueLength,
                           UBaseType_t uxItemSize,
                           uint8_t *pucQueueStorageBuffer,
                           StaticQueue_t *pxQueueBuffer
                       );

复位:

/* pxQueue : 复位哪个队列;
 * 返回值: pdPASS(必定成功)
 */
BaseType_t xQueueReset( QueueHandle_t pxQueue);

删除:

void vQueueDelete( QueueHandle_t xQueue );

写队列(涉及中断写法函数):

/* 等同于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
                                   );

读队列:

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

查询:

/*
 * 返回队列中可用数据的个数
 */
UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue );
/*
 * 返回队列中可用空间的个数
 */
UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue );

覆盖/偷看:

/* 覆盖队列
 * xQueue: 写哪个队列
 * pvItemToQueue: 数据地址
 * 返回值: pdTRUE表示成功, pdFALSE表示失败
 */
BaseType_t xQueueOverwrite(
                           QueueHandle_t xQueue,
                           const void * pvItemToQueue
                     );
BaseType_t xQueueOverwriteFromISR(
                           QueueHandle_t xQueue,
                           const void * pvItemToQueue,
                           BaseType_t *pxHigherPriorityTaskWoken
                     );
/* 偷看队列
 * xQueue: 偷看哪个队列
 * pvItemToQueue: 数据地址, 用来保存复制出来的数据
 * xTicksToWait: 没有数据的话阻塞一会
 * 返回值: pdTRUE表示成功, pdFALSE表示失败
 */
BaseType_t xQueuePeek(
                          QueueHandle_t xQueue,
                          void * const pvBuffer,
                          TickType_t xTicksToWait
                     ); 
 
 
 
BaseType_t xQueuePeekFromISR(
                                 QueueHandle_t xQueue,
                                 void *pvBuffer,
                             );

3.队列内部机制(重点)

总的来说也就是有数据读数据,切换任务状态,没有数据就休眠,让CPU空出时间片轮转的位置给别的程序全速运行,同时利用同步通信与互斥的机制,保证数据的稳定性。

内部机制流程:

1)关中断,防止给切换,导致数据有问题,也可以从汇编来看

2)环形buffer

3)链表操作方式,因为涉及链式结构,进行读以及写

4.示例:队列的基本使用

本程序会创建一个队列,然后创建2个发送任务、1个接收任务:
发送任务优先级为1,分别往队列中写入100、200
接收任务优先级为2,读队列、打印数值

mian函数:

/* 队列句柄, 创建队列时会设置这个变量 */
QueueHandle_t xQueue;
int main( void )
{
 prvSetupHardware();
 
    /* 创建队列: 长度为5,数据大小为4字节(存放一个整数) */
    xQueue = xQueueCreate( 5, sizeof( int32_t ) );
 if( xQueue != NULL )
 {
 /* 创建2个任务用于写队列, 传入的参数分别是100、200
 * 任务函数会连续执行,向队列发送数值100、200
 * 优先级为1
 */
 xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL );
 xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL );
 /* 创建1个任务用于读队列
 * 优先级为2, 高于上面的两个任务
 * 这意味着队列一有数据就会被读走
 */
 xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );
 /* 启动调度器 */
 vTaskStartScheduler();
 }
 else
 {
 /* 无法创建队列 */
 }
 /* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
 return 0;
}

发送函数如下:

static void vSenderTask( void *pvParameters )
{
 int32_t lValueToSend; 
 BaseType_t xStatus;
 /* 我们会使用这个函数创建2个任务
 * 这些任务的pvParameters不一样
 */
 lValueToSend = ( int32_t ) pvParameters;
 /* 无限循环 */
 for( ;; )
 {
 /* 写队列
 * xQueue: 写哪个队列
 * &lValueToSend: 写什么数据? 传入数据的地址, 会从这个地址把数据复制进队列
 * 0: 不阻塞, 如果队列满的话, 写入失败, 立刻返回
 */
 xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );
 if( xStatus != pdPASS )
 {
 printf( "Could not send to the queue.\r\n" );
 }
 }
}

接收函数:

static void vReceiverTask( void *pvParameters )
{
 /* 读取队列时, 用这个变量来存放数据 */
 int32_t lReceivedValue;
 BaseType_t xStatus;
 const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );
 /* 无限循环 */
 for( ;; )
 {
 /* 读队列
 * xQueue: 读哪个队列
 * &lReceivedValue: 读到的数据复制到这个地址
 * xTicksToWait: 如果队列为空, 阻塞一会
 */
 xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );
 if( xStatus == pdPASS )
 {
 /* 读到了数据 */
 printf( "Received = %d\r\n", lReceivedValue );
 }
 else
 {
 /* 没读到数据 */
 printf( "Could not receive from the queue.\r\n" );
 }
 }
}

5.示例:分辨数据源

当有多个发送任务,通过同一个队列发出数据,接收任务如何分辨数据来源?数据本身带有"来源"信 息,比如写入队列的数据是一个结构体,结构体中的lDataSouceID用来表示数据来源:
 

typedef struct {
    ID_t eDataID;
    int32_t lDataValue;
}Data_t;

main函数:

/* 定义2种数据来源(ID) */
typedef enum
{
 eMotorSpeed,
 eSpeedSetPoint
} ID_t;
/* 定义在队列中传输的数据的格式 */
typedef struct {
    ID_t eDataID;
    int32_t lDataValue;
}Data_t; 

/* 定义2个结构体 */
static const Data_t xStructsToSend[ 2 ] =
{
 { eMotorSpeed,    10 }, /* CAN任务发送的数据 */
 { eSpeedSetPoint, 5 }   /* HMI任务发送的数据 */
};
/* vSenderTask被用来创建2个任务,用于写队列
 * vReceiverTask被用来创建1个任务,用于读队列
 */
static void vSenderTask( void *pvParameters );
static void vReceiverTask( void *pvParameters );
/*-----------------------------------------------------------*/
/* 队列句柄, 创建队列时会设置这个变量 */
QueueHandle_t xQueue;
int main( void )
{
 prvSetupHardware();
 
    /* 创建队列: 长度为5,数据大小为4字节(存放一个整数) */
    xQueue = xQueueCreate( 5, sizeof( Data_t ) );
 if( xQueue != NULL )
 {
 /* 创建2个任务用于写队列, 传入的参数是不同的结构体地址
 * 任务函数会连续执行,向队列发送结构体
 * 优先级为2
 */
 xTaskCreate(vSenderTask, "CAN Task", 1000, (void *) &
(xStructsToSend[0]), 2, NULL);
 xTaskCreate(vSenderTask, "HMI Task", 1000, (void *) &( 
xStructsToSend[1]), 2, NULL);
 /* 创建1个任务用于读队列
 * 优先级为1, 低于上面的两个任务
 * 这意味着发送任务优先写队列,队列常常是满的状态
 */
 xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
 /* 启动调度器 */
 vTaskStartScheduler();
 }
 else
 {
 /* 无法创建队列 */
 }
 /* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
 return 0;
}

发送任务的函数中,不断往队列中写入数值,代码如下:
 

static void vSenderTask( void *pvParameters )
{
 BaseType_t xStatus;
 const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );
 /* 无限循环 */
 for( ;; )
 {
 /* 写队列
 * xQueue: 写哪个队列
 * pvParameters: 写什么数据? 传入数据的地址, 会从这个地址把数据复制进队列
 * xTicksToWait: 如果队列满的话, 阻塞一会
 */
 xStatus = xQueueSendToBack( xQueue, pvParameters, xTicksToWait );
 if( xStatus != pdPASS )
 {
 printf( "Could not send to the queue.\r\n" );
 }
 }
}

接收函数:

static void vReceiverTask( void *pvParameters )
{
 /* 读取队列时, 用这个变量来存放数据 */
 Data_t xReceivedStructure;
 BaseType_t xStatus;
 /* 无限循环 */
 for( ;; )
 {
 /* 读队列
 * xQueue: 读哪个队列
 * &xReceivedStructure: 读到的数据复制到这个地址
 * 0: 没有数据就即刻返回,不阻塞
 */
 xStatus = xQueueReceive( xQueue, &xReceivedStructure, 0 );
 if( xStatus == pdPASS )
 {
 /* 读到了数据 */
 if( xReceivedStructure.eDataID == eMotorSpeed )
 {
 printf( "From CAN, MotorSpeed = %d\r\n", 
xReceivedStructure.lDataValue );
 }
 else if( xReceivedStructure.eDataID == eSpeedSetPoint )
 {
 printf( "From HMI, SpeedSetPoint = %d\r\n", 
xReceivedStructure.lDataValue );
 }
 }
 else 
 {
 /* 没读到数据 */
 printf( "Could not receive from the queue.\r\n" );
 }
 }
}

<think>好的,我需要回答用户关于FreeRTOS队列源码实现的问题。首先,用户想查找队列相关的源码,根据提供的引用[1],队列的实现主要在queue.c文件中。我应该先确认这一点,然后指导用户如何找到该文件的位置。 接下来,用户可能需要了解源码的结构,比如头文件和函数。根据引用[2],FreeRTOS的代码结构包括数据类型命名规则、核心内容如队列、信号量等。我需要提到queue.c中的关键函数,比如xQueueCreate、xQueueSend、xQueueReceive,并解释它们的命名规则,比如函数名前缀x表示返回BaseType_t。 然后,我需要说明队列的数据结构,比如Queue_t,以及实现机制如环形缓冲区和任务阻塞列表。这些内容在queue.c中定义,可能需要用户查看具体的结构体成员。 另外,用户可能想知道如何分析源码,比如使用IDE的搜索功能或查看函数调用关系。这部分需要给出实际的步骤,比如搜索xQueueSend,并查看相关函数。 最后,生成相关问题,涉及其他任务通信机制、队列与全局变量的比较、环形缓冲区原理以及调试队列的方法。这些问题应该覆盖用户可能进一步感兴趣的方面,并引导他们深入理解FreeRTOS的机制。 需要确保使用正确的LaTeX格式,行内公式用$...$,独立公式用$$...$$,但在这个问题中可能涉及较少数学公式,主要关注代码和结构。同时,引用标记要正确添加,比如在提到queue.c的位置时引用[^1]。 总结回答的结构:首先指出源码文件位置,然后详细说明关键函数和数据结构,接着指导如何分析源码,最后生成相关问题。确保语言简洁,步骤清晰,帮助用户逐步找到并理解队列的实现。</think>根据FreeRTOS源码结构和引用文档[^1][^2],队列(queue)的实现主要集中在`queue.c`文件中。以下是具体实现分析步骤: ### 一、源码定位 1. **核心文件**:`FreeRTOS/Source/queue.c`(队列核心逻辑) 2. **数据结构**:`Queue_t`结构体定义在`queue.c`中 3. **依赖文件**:`list.c`(任务阻塞列表管理)、`portable.h`(硬件相关移植层) ### 二、关键函数解析 | 函数名称 | 功能描述 | 实现要点 | |--------------------|------------------------------------------------------------------------|-------------------------------------------------------------------------| | `xQueueCreate` | 创建队列时计算存储空间偏移量:$$Size = uxQueueLength \times uxItemSize$$ | 使用`pvPortMalloc`分配内存,初始化`Queue_t`控制块 | | `xQueueSend` | 实现入队操作的线程安全机制 | 调用`taskENTER_CRITICAL()`进行原子操作保护 | | `xQueueReceive` | 出队操作处理任务阻塞逻辑 | 当队列空时,调用`vTaskPlaceOnEventList`挂起任务 | | `prvCopyDataToQueue`| 数据拷贝使用内存复制:`memcpy( pxQueue->pcWriteTo, pvItemToQueue, uxItemSize )` | 处理队列满时的覆盖策略(如队列长度=1时直接替换) | ### 三、实现机制 1. **环形缓冲区**:通过`pcHead`、`pcTail`指针实现循环存储 2. **任务阻塞列表**:使用`xTasksWaitingToSend`和`xTasksWaitingToReceive`两个有序列表 3. **优先级继承**:互斥量发送时触发优先级提升(见`queue.c`第1823行) ### 四、代码分析建议 ```c // 典型调用示例(queue.c 第689行): BaseType_t xQueueGenericSend( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition ) { Queue_t * const pxQueue = xQueue; if( pxQueue->uxItemSize == ( UBaseType_t ) 0 ) { // 无数据拷贝的特殊处理 } else if( pxQueue->pcHead != NULL ) { prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition ); } // ...后续处理 } ``` 建议使用IDE的交叉引用功能追踪`xQueueGenericSend`的调用关系
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

7yewh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值