freertos队列学习

队列的概念

队列用于任务间通信的数据结构,通过消息队列服务,任务或中断服务将消息放入消息队列中。其他任务或者自身从消息队列中获得消息。实现队列可以在任务与任务间、中断和任务间传递信息。队列操作支持阻塞等待,向已经填满的队列发送数据或者从空队列读出数据,都会导致阻塞,时间自定义。消息队列的运作过程具如下:

队列创建

xQueueCreate()用于创建一个新的队列并返回可用于访问这个队列的句柄。队列句柄其实就是一个指向队列数据结构类型的指针。

  1. master_queue = xQueueCreate(50, sizeof(track_task_message_struct_t));

创建队列,占用50个单元,每个单元为sizeof(track_task_message_struct_t)字节,和 malloc比较类似。

其最终使用的函数是 xQueueGenericCreate(),后续信号量等也是使用它创建,只是最后的队列类型不同。

申请内存后,xQueueGenericReset再对其进行初始化,队列的结构体xQUEUE成员:

  1. typedef struct QueueDefinition /* The old naming convention is used to prevent breaking kernel aware debuggers. */

  2. {

  3. int8_t * pcHead; /*< Points to the beginning of the queue storage area. */

  4. int8_t * pcWriteTo; /*< Points to the free next place in the storage area. */

  5. //类型

  6. union

  7. {

  8. QueuePointers_t xQueue; /*< Data required exclusively when this structure is used as a queue. */

  9. SemaphoreData_t xSemaphore; /*< Data required exclusively when this structure is used as a semaphore. */

  10. } u;

  11. //当前向队列写数据阻塞的任务列表或者从队列取数阻塞的链表

  12. List_t xTasksWaitingToSend;

  13. List_t xTasksWaitingToReceive;

  14. //队列里有多少个单元被占用,应用中需要

  15. volatile UBaseType_t uxMessagesWaiting;

  16. UBaseType_t uxLength; /*< The length of the queue defined as the number of items it will hold, not the number of bytes. */

  17. UBaseType_t uxItemSize; /*< The size of each items that the queue will hold. */

  18. /******/

  19. } xQUEUE;

队列删除

队列删除函数 vQueueDelete()需传入要删除的消息队列的句柄即可,删除之后这个消息队列的所有信息都会被系统回收清空,而且不能再次使用这个消息队列了。实际应用中很少使用。

向队列发送消息

任务或者中断服务程序都可以给消息队列发送消息,当发送消息时,如果队列未满或者允许覆盖入队,FreeRTOS 会将消息拷贝到消息队列队尾,否则,会根据用户指定的超时时间进行阻塞,消息发送接口很多,最简单的是 xQueueSend(),用于向队列尾部发送一个队列消息。消息以拷贝的形式入队,该函数绝对不能在中断服务程序里面被调用,中断中必须使用带有中断保护功能的 xQueueSendFromISR()来代替。

BaseType_t xQueueSend(QueueHandle_t xQueue,const void* pvItemToQueue, TickType_t xTicksToWait);

用于向队列尾部发送一个队列消息

参数

xQueue 队列句柄

pvItemToQueue 指针,指向要发送到队列尾部的队列消息。

xTicksToWait 队列满时,等待队列空闲的最大超时时间。如果队列满并且xTicksToWait 被设置成 0,函数立刻返回。超时时间的单位为系统节拍周期 tick,延时为 portMAX_DELAY 将导致任务挂起(没有超时)。

返回值

消息发送成功成功返回 pdTRUE,否则返回 errQUEUE_FULL

xQueueSendToBack与xQueueSend完全相同, xQueueSendFromISR()与 xQueueSendToBackFromISR(),带FromISR表示只能在中断中使用,freeRTOS所以带这个后缀的都是这个含义。

xQueueSendToFront()和QueueSendToFrontFromISR()用于向队列队首发送一个消息。 这些在任务中发送消息的函数都是 xQueueGenericSend()展开的宏定义。

  1. BaseType_t xQueueGenericSend( QueueHandle_t xQueue, //任务句柄

  2. const void * const pvItemToQueue, //指向要发送到队列的数据项的指针

  3. TickType_t xTicksToWait, //函数将等待队列有空位的最长时间(以系统节拍为单位)

  4. const BaseType_t xCopyPosition ) //发送数据到消息队列的位置

一般使用xQueueSend和xQueueSendFromISR,如不确定当前运行的是系统服务,还是中断服务,一般ARM都支持查询中断状态寄存器判断,可以封装一层接口,只管发消息,内部判断是否使用支持中断嵌套的版本,UIS8910就是如此。

特殊情况下,如发送网络数据包未收到服务器响应,期望立刻入队再次发送它,可以xQueueSendToFront向队头发消息。

从队列读取消息

当任务试图读队列中的消息时,可以指定一个阻塞超时时间,当且仅当消息队列中有消息的时候,任务才能读取到消息。如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当其它任务或中断服务程序往其等待的队列中写入了数据,该任务将自动由阻塞态转为就绪态。当任务等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态。所有的task主入口while循环体都是按这个执行。例如:

  1. static void track_master_task_main()

  2. {

  3. track_task_message_struct_t queue_item = {0};

  4. /****/

  5. while(1)

  6. {

  7. if(xQueueReceive(master_queue, &queue_item, portMAX_DELAY))//阻塞等待

  8. {

  9. track_master_task_msg_handler(&queue_item);

  10. }

  11. }

  12. }

xQueueReceive()用于从一个队列中接收消息并把消息从队列中删除。

如果不想删除消息的话,就调用 xQueuePeek()函数。

xQueueReceiveFromISR()与xQueuePeekFromISR()是中断版本,用于在中断服务程序中接收一个队列消息并把消息。这两个函数只能用于中断,是不带有阻塞机制的,实际项目没有使用。

查询队列使用情况

uxQueueMessagesWaiting()查询队列中存储的信息数目,具有中断保护的版本为uxQueueMessagesWaitingFromISR()。

查询队列的空闲数目uxQueueSpacesAvailable()。

队列使用注意点

使用队列函数需要注意以下几点:

1、中断中必须使用带FromISR后缀的接口;

2、发送或者是接收消息都是以拷贝的方式进行,如果消息内容过于庞大,可以将消息的地址作为消息进行发送、接收。

  1. typedef struct

  2. {

  3. TaskHandle_t src_mod_id;

  4. int message_id;

  5. int32_t param;

  6. union

  7. {

  8. int32_t result;

  9. int32_t socket_id;

  10. };

  11. void* pvdata; //大数据使用动态申请内存保存,队列只传递指针

  12. } track_task_message_struct_t;

3、队列并不属于任何任务,所有任务都可以向同一队列写入和读出,一个队列可以由多任务或中断读写。 4、队列的深度要结合实际,可以多申请点,前提是每个队列单元尽可能小。 5、队列存在一定限制,在队头没有取出来之前,是无法取出第二个,和以前使用的STL存在差异。

创建任务和队列

#define UART_TASK_PRIO 1 //任务优先级

#define UART_TASK_STK_SIZE 80 //任务堆栈大小

TaskHandle_t UARTTaskHandler; //任务句柄

void UARTTaskFunc(void *pvParameters); //任务函数

#define TEST_TASK0_PRIO 2 //任务优先级

#define TEST_TASK0_STK_SIZE 50 //任务堆栈大小

TaskHandle_t TestTask0Handler; //任务句柄

void TestTask0Func(void *pvParameters); //任务函数

#define TEST_TASK1_PRIO 3 //任务优先级

#define TEST_TASK1_STK_SIZE 80 //任务堆栈大小

TaskHandle_t TestTask1Handler; //任务句柄

void TestTask1Func(void *pvParameters); //任务函数

#define QUEUE_TASK_PRIO 4 //任务优先级

#define QUEUE_TASK_STK_SIZE 80 //任务堆栈大小

TaskHandle_t QueueTaskHandler; //任务句柄

void QueueTaskFunc(void *pvParameters); //任务函数

//LCD显示任务当前计数,并把当前计数写入TaskToTaskQueue队列函数

void UARTTaskFunc(void *pvParameters);

//Test0任务函数,读取 TaskToTaskQueue的数据并打印

void TestTask0Func(void *pvParameters);

//Test1任务函数

void TestTask1Func(void *pvParameters);

//从队列读取串口收到的数据并打印任务函数

void QueueTaskFunc(void *pvParameters);

QueueHandle_t TaskToIrqQueue; //信息队列句柄

QueueHandle_t TaskToTaskQueue; //信息队列句柄

void MX_FREERTOS_Init(void) {

/* USER CODE BEGIN Init /

* /* USER CODE END Init */

/* USER CODE BEGIN RTOS_MUTEX /

* /* add mutexes, ... /

* /* USER CODE END RTOS_MUTEX */

/* USER CODE BEGIN RTOS_SEMAPHORES /

* /* add semaphores, ... /

* /* USER CODE END RTOS_SEMAPHORES */

/* USER CODE BEGIN RTOS_TIMERS /

* /* start timers, add new ones, ... /

* /* USER CODE END RTOS_TIMERS */

/* USER CODE BEGIN RTOS_QUEUES /

* /* add queues, ... /

* /* USER CODE END RTOS_QUEUES */

/* Create the thread(s) /

* /* creation of defaultTask */

//defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);

/* USER CODE BEGIN RTOS_THREADS /

* /* add threads, ... */

BaseType_t ret;

TaskToIrqQueue=xQueueCreate(1,255); //创建一个队列,队列有1项,每个项长为255byte。

TaskToTaskQueue=xQueueCreate(5,2); //创建一个队列,队列有5项,每个项长为2byte

ret=xTaskCreate((TaskFunction_t )UARTTaskFunc, (const char* )"lcdtask", (uint16_t )UART_TASK_STK_SIZE,

(void* )NULL, (UBaseType_t )UART_TASK_PRIO, (TaskHandle_t* )&UARTTaskHandler);

ret=xTaskCreate((TaskFunction_t )TestTask0Func, (const char* )"testtask0", (uint16_t )TEST_TASK0_STK_SIZE,

(void* )NULL, (UBaseType_t )TEST_TASK0_PRIO, (TaskHandle_t* )&TestTask0Handler);

ret=xTaskCreate((TaskFunction_t )TestTask1Func, (const char* )"testtask1", (uint16_t )TEST_TASK1_STK_SIZE,

(void* )NULL,

(UBaseType_t )TEST_TASK1_PRIO,

(TaskHandle_t* )&TestTask1Handler);

ret=xTaskCreate((TaskFunction_t )QueueTaskFunc, (const char* )"queue task", (uint16_t )QUEUE_TASK_STK_SIZE,

(void* )NULL,

(UBaseType_t )QUEUE_TASK_PRIO,

(TaskHandle_t* )&QueueTaskHandler);

xTaskCreate(LED_Test,"LED",254,NULL,osPriorityNormal,&LED123);

//xTaskCreate(Key_Test,"KEY",254,NULL,osPriorityNone,&KEY123);

vTaskStartScheduler();

/* USER CODE END RTOS_THREADS */

/* USER CODE BEGIN RTOS_EVENTS /

* /* add events, ... /

* /* USER CODE END RTOS_EVENTS */

}

各个任务函数:

//显示任务当前计数,并把当前计数写入TaskToTaskQueue队列函数

void UARTTaskFunc(void *pvParameters)

{

static uint8_t i=0;

for(;;)

{

i++; xQueueSend(TaskToTaskQueue,&i,20);

vTaskDelay(500); //延时500ms,也就是500个时钟节拍 }

}

//Test0任务函数,读取 TaskToTaskQueue的数据并通过串口打印出来

void TestTask0Func(void *pvParameters)

{

uint8_t i;

char a[20];

while(1)

{ if(TaskToTaskQueue!=NULL) { xQueueReceive(TaskToTaskQueue,&i,portMAX_DELAY);

sprintf((char *)a,"%d",i);

printf("%s\r\n",a); }

}

}

//Test1任务函数

void TestTask1Func(void *pvParameters)

{

while(1)

{

printf("task1\r\n");

vTaskDelay(1000);

}

}

//从队列读取串口收到的数据并打印任务函数

void QueueTaskFunc(void *pvParameters)

{

for(;;)

{

if(TaskToIrqQueue!=NULL)

{

if(xQueueReceive(TaskToIrqQueue,rx_buf,10))

{

printf("serial:%s\r\n",rx_buf);

}

}

}

}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){

HAL_UART_Receive_IT(&huart1,&rx_data,1);

rx_buf[rx_pointer++] = rx_data;

BaseType_t pxHigherPriorityTaskWoken=pdFALSE;

xQueueSendFromISR(TaskToIrqQueue,rx_buf,&pxHigherPriorityTaskWoken);

//向队列写入数据,如果写队列使某个任务就绪,并且这个任务是当前所有任务中优先级最高的,那么pxHigherPriorityTaskWoken将会被设置为pdTRUE

portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);

//如果pxHigherPriorityTaskWoken为真将进行任务切换

}

实现效果:

<think>好的,用户想了解FreeRTOS中的队列集合操作或实现。我需要先回忆一下FreeRTOS队列相关机制。FreeRTOS队列是任务间通信的基础,通常用于传递消息。队列集合可能指的是多个队列的组合操作,比如同时监听多个队列,等待其中一个有数据到达。 记得在FreeRTOS中,有一个功能叫做Queue Sets,也就是队列集合。它允许任务同时阻塞在多个队列或信号量上,直到其中一个有数据可用。这个功能对于需要处理多个输入源的任务非常有用,比如同时等待网络数据和用户输入的情况。 首先,我需要确认Queue Sets的具体实现方式。根据FreeRTOS的文档,队列集合通过xQueueCreateSet()函数创建,然后使用xQueueAddToSet()将队列或信号量添加到集合中。任务可以调用xQueueSelectFromSet()来等待集合中的任何一个成员有数据到达。这个过程类似于select系统调用,但针对的是FreeRTOS队列和信号量。 接下来,我需要考虑用户可能的实际应用场景。比如,在嵌入式系统中,一个任务可能需要同时监听多个传感器的数据队列,或者同时处理多个通信接口的消息。使用队列集合可以简化任务的设计,避免使用多个单独的信号量或事件组。 同时,要注意队列集合的一些限制。例如,添加到集合中的队列必须是在创建时允许使用队列集合的,也就是在创建队列时需要设置uxItemSize参数为0,或者确保队列项的大小符合要求。此外,队列集合可能会增加内存开销,因为每个被监听的队列都需要维护额外的信息。 用户可能还想知道如何具体使用这些API函数。需要给出示例代码,展示创建队列集合、添加队列、等待数据到达的流程。例如,创建两个队列,将它们添加到集合中,然后在任务中等待任意一个队列有数据,处理相应的数据。 另外,性能方面的考虑也很重要。队列集合的效率如何?在高频率的数据处理场景下是否适用?可能需要提到,队列集合适用于低频率的同步操作,如果对实时性要求很高,可能需要其他机制,如直接的中断服务例程处理。 还要注意,在FreeRTOS的配置中,队列集合功能默认可能未被启用,需要在FreeRTOSConfig.h中设置configUSE_QUEUE_SETS为1。如果用户遇到编译错误,可能需要检查这个配置项。 最后,需要总结队列集合的适用场景和优缺点,帮助用户决定是否在自己的项目中使用该功能。同时,提供相关的问题,引导用户进一步学习,比如如何优化队列集合的性能,或者替代方案有哪些。</think>### FreeRTOS队列集合的实现与操作 FreeRTOS队列集合(Queue Sets)允许任务同时监听多个队列或信号量的数据到达事件,类似于操作系统中的`select()`系统调用。其核心实现依赖于以下API和数据结构: #### 1. **队列集合的创建** 使用`xQueueCreateSet()`创建集合容器,参数为集合可容纳的最大队列/信号量数量: ```c QueueSetHandle_t xQueueCreateSet(const UBaseType_t uxEventQueueLength); ``` 此函数返回一个队列集合句柄,用于后续操作[^1]。 #### 2. **添加成员到集合** 通过`xQueueAddToSet()`将队列或信号量添加到集合中: ```c BaseType_t xQueueAddToSet(QueueSetMemberHandle_t xQueueOrSemaphore, QueueSetHandle_t xQueueSet); ``` 注意:只有**空队列**或**满队列**可触发事件通知[^1]。 #### 3. **等待数据到达** 任务通过`xQueueSelectFromSet()`阻塞等待集合中任意成员有数据: ```c QueueSetMemberHandle_t xQueueSelectFromSet(QueueSetHandle_t xQueueSet, TickType_t xTicksToWait); ``` 返回值指明触发事件的队列句柄。 #### 4. **代码示例** ```c // 创建两个队列和一个集合 QueueHandle_t xQueue1 = xQueueCreate(5, sizeof(int)); QueueHandle_t xQueue2 = xQueueCreate(3, sizeof(float)); QueueSetHandle_t xSet = xQueueCreateSet(5 + 3); // 添加队列到集合 xQueueAddToSet(xQueue1, xSet); xQueueAddToSet(xQueue2, xSet); void vTask(void *pvParams) { QueueSetMemberHandle_t xActivated; while(1) { xActivated = xQueueSelectFromSet(xSet, portMAX_DELAY); if(xActivated == xQueue1) { int data; xQueueReceive(xQueue1, &data, 0); // 处理队列1数据 } else if(xActivated == xQueue2) { float data; xQueueReceive(xQueue2, &data, 0); // 处理队列2数据 } } } ``` #### 5. **实现原理** - 每个队列包含`cTxLock`和`cRxLock`计数器,记录阻塞任务数 - 当队列状态变化时,检查关联的队列集合并发送通知 - 集合内部使用二进制信号量同步事件 #### 6. **性能特征** $$ T_{response} = O(n) \quad (n=集合成员数) $$ 适合低频事件监听,高频场景建议使用直接任务通知。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值