Freertos——使用队列集优化数据传输

队列集

  • 通常在实际的产品设计中,我们对硬件的原始数据队列和参与操作的软件数据队列应该是分开设计
  • 这样能保证我们的业务逻辑能更加清晰独立,并且硬件读取逻辑可以再不同产品中复用
  • 会导致一个问题,我们需要在每个硬件读取后进行相应的数据转化任务,如果硬件过多会导致任务栈占据过多的内存,且使任务切换过于频繁污染了时间片
  • 因此我们可以使用到队列集,使得写入同一个业务的软件队列的原始数据队列转换达成统一,使得任务数量减少,并且更容易修改移植我们的代码

业务逻辑

红外遥控器驱动程序里,它只应该把键值记录下来、写入某个队列,它不应该把键值转换为游戏的控制键。在红外遥控器的驱动程序里,不应该有游戏相关的代码,这样,切换使用场景时,这个驱动程序还可以继续使用。把红外遥控器的按键转换为游戏的控制键,应该在游戏的任务里实现。要支持多个输入设备时,我们需要实现一个"InputTask",它读取各个设备的队列,得到数据后再分别转换为游戏的控制键。

队列集的本质也是队列,只不过里面存放的是"队列句柄"。使用过程如下:

  1. 创建队列A,它的长度是n1
  2. 创建队列B,它的长度是n2
  3. 创建队列集S,它的长度是"n1+n2"
  4. 把队列A、B加入队列集S
  5. 这样,写队列A的时候,会顺便把队列A的句柄写入队列集S
  6. 这样,写队列B的时候,会顺便把队列B的句柄写入队列集S
  7. InputTask先读取队列集S,它的返回值是一个队列句柄,这样就可以知道哪个队列有有数据了;然后InputTask再读取这个队列句柄得到数据。

内部机制

每当在一个队列写入数据,系统自动在这个队列在的队列集的队列中写入一个此队列句柄,当队列集读取时就会依次返回出他的队列中存放的句柄,然后再凭借返回的句柄操作对应的队列

创建队列集

c 复制代码
QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength )

uxQueueLength:队列集长度,可存放的队列句柄数

返回值非0:成功,返回句柄,以后使用句柄来操作队列

NULL:失败,因为内存不足

加入队列

c 复制代码
BaseType_t xQueueAddToSet(
    QueueSetMemberHandle_t xQueueOrSemaphore,
    QueueSetHandle_t xQueueSet
);

xQueueOrSemaphore:队列句柄,要放入的队列

xQueueSet:队列集句柄

返回值pdTRUE:成功

pdFALSE:失败

读取非空队列

c 复制代码
QueueSetMemberHandle_txQueueSelectFromSet(
    QueueSetHandle_t xQueueSet,
    TickType_t const xTicksToWait
);

xQueueSet:队列集句柄

xTicksToWait:如果队列集空则无法读出数据,可以让任务进入阻塞状态,如果被设为0,无法读出数据时函数会立刻返回;如果被设为portMAX_DELAY,则会一直阻塞直到有数据可写

返回值NULL:失败

队列句柄:成功

从队列集移除成员

c 复制代码
BaseType_t xQueueRemoveFromSet(
    QueueSetMemberHandle_t xQueueOrSemaphore,
    QueueSetHandle_t xQueueSet
);

xQueueOrSemaphore:队列句柄,要放入的队列

xQueueSet:队列集句柄

返回值pdTRUE:失败

pdFALSE:成功

删除队列集

c 复制代码
void vQueueDeleteSet(QueueSetHandle_t xQueueSet);

xQueueSet:队列集句柄
注意:需先移除所有成员,否则可能导致内存泄漏!!!

简单程序示例

c 复制代码
// 1. 创建队列和队列集
QueueHandle_t xKeyQueue = xQueueCreate(5, sizeof(uint8_t));  // 按键队列
QueueHandle_t xUartQueue = xQueueCreate(3, sizeof(char));    // 串口队列
QueueSetHandle_t xInputSet = xQueueCreateSet(5 + 3);          // 队列集

// 2. 将队列加入队列集
xQueueAddToSet(xKeyQueue, xInputSet);
xQueueAddToSet(xUartQueue, xInputSet);

// 3. 数据写入任务(例如:按键中断、串口中断)
void vWriteTask(void *pvParameters) {
    uint8_t key = 0x01;
    xQueueSend(xKeyQueue, &key, 0);  // 写入按键队列,自动触发队列集
}

// 4. InputTask:统一处理
void vInputTask(void *pvParameters) {
    QueueSetMemberHandle_t xActivatedMember;
    uint8_t keyData;
    char uartData;

    while (1) {
        // 等待队列集有数据
        xActivatedMember = xQueueSelectFromSet(xInputSet, portMAX_DELAY);

        // 判断是哪个队列有数据
        if (xActivatedMember == xKeyQueue) {
            xQueueReceive(xKeyQueue, &keyData, 0);
            // 处理按键数据(如转换为游戏控制键)
        } else if (xActivatedMember == xUartQueue) {
            xQueueReceive(xUartQueue, &uartData, 0);
            // 处理串口数据
        }
    }
}

注意事项

  1. 队列集长度的设置
    队列集长度需 ≥ 所有成员队列的总长度(或信号量的最大计数值之和)。例如:队列 A 长度为 5,队列 B 长度为 3,则队列集长度应 ≥ 8,最坏情况下,两个队列同时满,队列集需存放 8 个句柄
  2. 成员的 "独占性"
    队列 / 信号量加入队列集后,不应再直接读写,否则会导致队列集状态不同步。需通过队列集机制间接访问,先xQueueSelectFromSet() 获取句柄,再读写对应队列,这个函数类似于xQueueReceive()的阻塞机制
  3. 中断中使用队列集
    若在中断中操作队列集成员,需使用中断安全的队列读取函数( xQueueSendFromISR())
相关推荐
日拱一卒的小田6 分钟前
ZYNQ学习笔记2-ZYNQ的UART控制器1
单片机·嵌入式硬件
我想走路带风38 分钟前
OPENWRT-Day01
stm32·单片机·嵌入式硬件
ACP广源盛139246256731 小时前
GSV2221@ACP#DP 1.4 MST 多屏转换芯片,物理 AI 多模态交互的视觉中枢
大数据·人工智能·嵌入式硬件·gpt·spark
云栖梦泽2 小时前
Linux内核与驱动:pinctl子系统和GPIO子系统
linux·单片机·嵌入式硬件
电气_空空2 小时前
基于 LabVIEW 的单片机串口通信设计
单片机·嵌入式硬件·毕业设计·labview
逻极3 小时前
Windows 平台 Ollama AMD GPU 一键编译指南:基于 ROCm 7.1 的自动化实战
人工智能·windows·stm32·自动化·gpu·amd·ollama
caimouse4 小时前
Reactos 第 9 章 设备驱动 — 9.10 磁盘的Miniport驱动模块
windows·嵌入式硬件
xiangw@GZ4 小时前
WiFi系统BCC与LDPC纠错编码技术性能对比
单片机·嵌入式硬件
AoDeLuo5 小时前
EthercCAT软件主站方案对比
stm32·单片机·嵌入式硬件
平凡灵感码头5 小时前
半导体三大主流制程详解:Bipolar、CMOS 与 BCD
单片机·嵌入式硬件