如前所述,队列虽然提供了任务之间传递数据的功能,但没有对通知机制进行优化,即不方便实现多次采集不同长度的数据,然后触发一次通知接收的机制。
Streambuffer 的中文含意是“流式缓冲区”,其特点可以概述如下:
1)发送消息的一方(发送方)与获取消息的一方(接收方)之间可以按任意长度的字节流的方式进行数据传递。
2)可以设置触发唤醒通知的字节数,仅在缓冲区中的数据达到一定长度时,才唤醒接收方接收数据。
3)适用于仅有一个发送方、一个接收方的场景。如果有多个发送方、接收方,则需要在发送、接收处添加互斥保护,特别地,多个接收方时应将接收阻塞时间设置为0。
Streambuffer 的基本结构如图所示,主要由消息缓冲区、管理消息缓冲区的相关字段、解阻塞字节数标记、以及两个阻塞延时列表组成。一定程度上,Streambuffer 可以看作在触发接收的机制上改良的队列。
// 创建新的流缓冲区
StreamBufferHandle_t xStreamBufferCreate(xBufferSizeBytes, // 流缓冲区的总字节数。
xTriggerLevelBytes) // 触发唤醒的字节数,流缓冲区中必须存在不少该字节数的数据才能唤醒等待数据的任务(设置为0时,将按照如1的方式处理,设置的数比buffer_size还大时,创建无效)
// 向流缓冲区去发送数据
size_t xStreamBufferSend(StreamBufferHandle_t xStreamBuffer, // 要从中发送字节的流缓冲区的句柄。
const void *pvTxData, // 指向要复制到流缓冲区中的字节的缓冲区的指针。
size_t xDataLengthBytes, // 从 pvTxData 复制到流缓冲区的最大字节数。
TickType_t xTicksToWait) // 若无法完成发送 xDataLengthBytes 个字节数据的目的,则等待该延时时间
// 从流缓冲区中接收数据
size_t xStreamBufferReceive(StreamBufferHandle_t xStreamBuffer, // 要从中接收字节的流缓冲区的句柄。
void *pvRxData, // 指向将接收的字节复制到的缓冲区的指针。
size_t xBufferLengthBytes, // 设置一个要接收的最大字节数。
TickType_t xTicksToWait) // 如果流缓冲区为空,则任务应保持“blocked”状态以等待数据变为可用的最长时间,时间以tick周期为基本单位。
// 重置流缓冲区
// 将流缓冲区重置为其初始空状态。流缓冲区中的任何数据都将被丢弃。仅当没有任务被阻止等待发送到流缓冲区或从流缓冲区接收时,才能重置流缓冲区。
BaseType_t xStreamBufferReset(StreamBufferHandle_txStreamBuffer) //
注意,Receive 返回的情况有两种可能:
1)获取到不小于唤醒长度的数据。
2)超时返回,此时实际获取的数据长度为 [0,TriggerLevel]。
API 参考:stream buffer API, 读者可以通过点击网页自行查看每个 API 的使用方法和参数。
示例创建了一个 TriggerLevel 为 5 的streambuf,来实现 task1 中每产生五个数据就自动唤醒 task2 处理数据的逻辑。
示例输出:
This is esp32 chip with 2 CPU core(s), WiFi/BT/BLE, Minimum free heap size: 295348 bytes
TASK1: flag=0
TASK2: timeout and read some data
TASK2: The buffer data is as follows:00
TASK1: flag=1
TASK1: flag=2
TASK1: flag=3
TASK1: flag=4
TASK1: flag=5
TASK2: read triggle level bytes
TASK2: The buffer data is as follows:01 02 03 04 05
TASK1: flag=6
TASK1: flag=7
TASK1: flag=8
TASK1: flag=9
TASK1: flag=10
TASK2: read triggle level bytes
TASK2: The buffer data is as follows:06 07 08 09 0a
这种自动的触发机制,比上节使用队列的情况要简单了一些。
Streambuffer 虽然实现了可以发送不定长度的数据串,并规定了一定的触发唤醒机制:即数据量至少达到 TriggerLevel 才唤醒等待数据的任务。
但是一些情况下,发送的数据是不定长度的数据块,每个数据块都具有指定的格式,具备不同的长度,并且不能被拆分和组合。
比如发送数据的任务发送人的身份证号、手机号码。接收数据的任务要准确地识别这些数据,必须知道数据的长度,否则无法区分那部分数据属于身份证数据、手机号码数据。
流式缓冲区无法描述这些不定长数据块的具体长度。我们将在下一节介绍处理不定长离散数据块的通信组件。
1)Streambuffer 可看作针对单一生产者、消费者传输不定长数据通信场景而优化的 queue。
2)Streambuffer 还优化了唤醒机制,可以设置触发唤醒通知的字节数,仅在缓冲区中的数据达到一定长度时,才唤醒接收方接收数据。
3)Streambuffer 可以传输没有固定结构的不定长数据,但是它无法描述这些不定长数据块的具体长度。
1)Learning-FreeRTOS-with-esp32 系列博客介绍
2)对应示例的 code 链接 (点击直达代码仓库)
3)下一篇: