【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" );
 }
 }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

7yewh

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

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

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

打赏作者

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

抵扣说明:

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

余额充值