八、流缓冲区和消息缓冲区
8.1 流缓冲区
通过流缓冲区,可以将字节流从中断服务程序传递到任务, 也可以将其从一项任务传递到另一项任务。字节流可以是任意长度,不一定 有开头或结尾。可以一次写入任意数量的字节, 也可以一次读取任意数量的字节。数据通过复制的方式传递:数据由发送方复制到缓冲区中, 然后由接收方从缓冲区中读取。
8.1.1 API说明
函数名 | 功能 | 备注 |
---|---|---|
xStreamBufferCreate | 创建一个流数据缓冲区 | 返回一个缓冲区句柄,供后续使用 |
xStreamBufferSend | 向流缓冲区写入数据 | 如果缓冲区已满,可选择阻塞或立即返回 |
xStreamBufferReceive | 从流缓冲区读取数据 | 如果缓冲区为空,可选择阻塞或立即返回 |
xStreamBufferReset | 重置流缓冲区,清空所有数据 | 需确保无任务正在操作此缓冲区 |
xStreamBufferSpacesAvailable | 查询缓冲区中剩余可写空间 | 返回缓冲区剩余字节数 |
xStreamBufferBytesAvailable | 查询缓冲区中可读数据量 | 返回缓冲区中尚未读取的字节数 |
vStreamBufferDelete | 删除流缓冲区并释放内存 | 清理操作,需在确保无任务使用的情况下执行 |
以下是流数据缓冲区相关函数的详细说明,包含函数原型、参数解释和返回值:
xStreamBufferCreate
功能: 创建一个流数据缓冲区,用于存储和传输数据。
原型:
StreamBufferHandle_t xStreamBufferCreate(size_t xBufferSizeBytes, size_t xTriggerLevelBytes);
参数:
xBufferSizeBytes
:缓冲区的总大小(以字节为单位)。xTriggerLevelBytes
:触发读取任务的最小字节数(推荐设为 1)。
返回值:
成功:返回创建的流缓冲区句柄。 失败:返回 NULL。
xStreamBufferSend
功能: 向流缓冲区写入数据。
原型:
size_t xStreamBufferSend(StreamBufferHandle_t xStreamBuffer, const void *pvTxData, size_t xDataLengthBytes, TickType_t xTicksToWait);
参数:
xStreamBuffer
:目标流缓冲区的句柄。pvTxData
:指向要写入数据的指针。xDataLengthBytes
:要写入的数据长度(字节数)。xTicksToWait
:写入时的最大阻塞时间(Tick 数,0 表示立即返回)。
返回值:
成功:实际写入的字节数。 失败:0 表示写入失败(缓冲区已满)。
xStreamBufferReceive
功能: 从流缓冲区中读取数据。
原型:
size_t xStreamBufferReceive(StreamBufferHandle_t xStreamBuffer, void *pvRxData, size_t xBufferLengthBytes, TickType_t xTicksToWait);
参数:
xStreamBuffer
:目标流缓冲区的句柄。pvRxData
:指向接收数据的缓冲区指针。xBufferLengthBytes
:接收缓冲区的大小(字节数)。xTicksToWait
:读取时的最大阻塞时间(Tick 数,0 表示立即返回)。
返回值:
成功:实际读取的字节数。 失败:0 表示读取失败(缓冲区为空)。
xStreamBufferReset
功能: 重置流缓冲区,清空所有数据。 原型:
BaseType_t xStreamBufferReset(StreamBufferHandle_t xStreamBuffer);
参数:
xStreamBuffer
:目标流缓冲区的句柄。 返回值:pdPASS
:重置成功。pdFAIL
:重置失败(通常是因为缓冲区正在被任务或中断使用)。
xStreamBufferSpacesAvailable
功能: 查询流缓冲区中剩余的可写空间。
原型:
size_t xStreamBufferSpacesAvailable(StreamBufferHandle_t xStreamBuffer);
参数:
- xStreamBuffer:目标流缓冲区的句柄。 返回值:剩余可写空间的字节数。
xStreamBufferBytesAvailable
功能: 查询流缓冲区中可读取的数据量。 原型:
size_t xStreamBufferBytesAvailable(StreamBufferHandle_t xStreamBuffer);
参数:
xStreamBuffer
:目标流缓冲区的句柄。 返回值:当前缓冲区中可读取的数据字节数。
vStreamBufferDelete
功能: 删除一个流缓冲区并释放其内存。 原型:
void vStreamBufferDelete(StreamBufferHandle_t xStreamBuffer);
参数:
xStreamBuffer
:目标流缓冲区的句柄。 返回值:无返回值。
8.1.2 示例代码
// 任务通知值按位判断-代替队列邮箱或者事件组
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/stream_buffer.h"
static const char *TAG = "main";
StreamBufferHandle_t xStreamBuffer;
void task1(void *pvParameters)
{
ESP_LOGI(TAG, "-------------------------------");
ESP_LOGI(TAG, "task1启动!");
char tx_buf[50];
int i = 0;
while (1)
{
i++;
sprintf(tx_buf, "Hello World!-%d", i);
// 写入数据到流缓冲区
int send_len = xStreamBufferSend(xStreamBuffer, (void *)tx_buf, sizeof(tx_buf), portMAX_DELAY);
ESP_LOGI(TAG, "task1发送数据长度: %d", send_len);
vTaskDelay(pdMS_TO_TICKS(3000));
}
}
void task2(void *pvParameters)
{
ESP_LOGI(TAG, "-------------------------------");
ESP_LOGI(TAG, "task2启动!");
char rx_buf[50];
while (1)
{
memset(rx_buf, 0, sizeof(rx_buf));
// 读取数据
int recv_len = xStreamBufferReceive(xStreamBuffer, (void *)rx_buf, sizeof(rx_buf), portMAX_DELAY);
if (recv_len > 0)
{
rx_buf[recv_len] = 0;
ESP_LOGI(TAG, "task2接收数据: %s ", rx_buf);
}
}
}
// 监控缓冲区
void task3(void *pvParameters)
{
while (1)
{
// 读取数据
size_t xBytesAvailable = xStreamBufferBytesAvailable(xStreamBuffer);
if (xBytesAvailable > 0)
{
ESP_LOGI(TAG, "监控缓冲区中有数据: %d", xBytesAvailable);
}
else
{
ESP_LOGI(TAG, "监控缓冲区中无数据");
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void app_main(void)
{
xStreamBuffer = xStreamBufferCreate(1024, 300); // 创建一个 1024 字节的流缓冲区,每次可读取 1 字节
if (xStreamBuffer != NULL)
{
ESP_LOGI(TAG, "创建流缓冲区成功");
xTaskCreate(task1, "task1", 1024 * 4, NULL, 1, NULL);
xTaskCreate(task2, "task2", 1024 * 4, NULL, 1, NULL);
xTaskCreate(task3, "task3", 1024 * 2, NULL, 1, NULL);
}
else
{
ESP_LOGI(TAG, "创建流缓冲区失败");
}
}
8.2 消息缓冲区
消息缓冲区允许长度可变的离散消息从中断服务程序传递至 一个任务,或从一个任务传递至另一个任务。例如,长度为 10、20 和 123 字节的消息 都可以在同一个消息缓冲区写入或读取 。与使用流缓冲区不同的是, 长度为 10 个字节的消息只能作为 10 个字节的消息读取,而不能 以单独的字节读取。消息缓冲区构建在流缓冲区之上(即它们使用 流缓冲区实现)。
4.6.1 API说明
以下是消息缓冲区的核心 API,以及其功能和参数说明。
函数名 | 功能 | 备注 |
---|---|---|
xMessageBufferCreate | 创建一个消息缓冲区 | 返回一个缓冲区句柄,供后续使用 |
xMessageBufferSend | 向消息缓冲区写入数据 | 如果缓冲区空间不足,可选择阻塞或立即返回 |
xMessageBufferReceive | 从消息缓冲区读取数据 | 如果缓冲区为空,可选择阻塞或立即返回 |
xMessageBufferSpaceAvailable | 查询消息缓冲区中剩余可用空间 | 返回剩余字节数 |
xMessageBufferReset | 重置消息缓冲区,清空所有数据 | 需确保没有任务正在操作缓冲区 |
vMessageBufferDelete | 删除消息缓冲区并释放内存 | 必须在没有任务使用此缓冲区的情况下执行 |
xMessageBufferCreate
功能: 创建一个消息缓冲区,用于传输带长度前缀的消息。
原型:
MessageBufferHandle_t xMessageBufferCreate(size_t xBufferSizeBytes);
参数:
xBufferSizeBytes
:缓冲区的总大小(字节)。
返回值:
成功:返回创建的消息缓冲区句柄。 失败:返回 NULL。
xMessageBufferSend
功能: 向消息缓冲区中写入数据。
原型:
size_t xMessageBufferSend(MessageBufferHandle_t xMessageBuffer, const void *pvTxData, size_t xDataLengthBytes, TickType_t xTicksToWait);
参数:
- xMessageBuffer:目标消息缓冲区的句柄。
- pvTxData:指向要写入数据的指针。
- xDataLengthBytes:要写入的数据长度(字节)。
- xTicksToWait:写入时的最大阻塞时间(Tick 数,0 表示立即返回)。
返回值:
- 成功:实际写入的字节数。
- 失败:0 表示写入失败(缓冲区空间不足)。
xMessageBufferReceive
功能: 从消息缓冲区中读取数据。 原型:
size_t xMessageBufferReceive(MessageBufferHandle_t xMessageBuffer, void *pvRxData, size_t xBufferLengthBytes, TickType_t xTicksToWait);
参数:
- xMessageBuffer:目标消息缓冲区的句柄。
- pvRxData:指向接收数据的缓冲区指针。
- xBufferLengthBytes:接收缓冲区的大小(字节)。
- xTicksToWait:读取时的最大阻塞时间(Tick 数,0 表示立即返回)。
返回值:
- 成功:实际读取的字节数。
- 失败:0 表示读取失败(缓冲区为空)。
注意,当接收缓冲区小于消息长度时,无法接收到消息。
xMessageBufferSpaceAvailable
功能: 查询消息缓冲区中剩余的可用空间。 原型:
size_t xMessageBufferSpaceAvailable(MessageBufferHandle_t xMessageBuffer);
参数:
- xMessageBuffer:目标消息缓冲区的句柄。 返回值:剩余可用空间的字节数。
xMessageBufferReset
功能: 重置消息缓冲区,清空所有数据。
原型:
BaseType_t xMessageBufferReset(MessageBufferHandle_t xMessageBuffer);
参数:- xMessageBuffer:目标消息缓冲区的句柄。 返回值:
- pdPASS:重置成功。
- pdFAIL:重置失败(缓冲区正在被使用)。
vMessageBufferDelete
功能: 删除一个消息缓冲区并释放其内存。
原型:
void vMessageBufferDelete(MessageBufferHandle_t xMessageBuffer);
参数:xMessageBuffer:目标消息缓冲区的句柄。
返回值:无返回值。
4.6.2 示例代码
// 任务通知值按位判断-代替队列邮箱或者事件组
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/message_buffer.h"
static const char *TAG = "main";
MessageBufferHandle_t xMessageBuffer;
void task1(void *pvParameters)
{
ESP_LOGI(TAG, "-------------------------------");
ESP_LOGI(TAG, "task1启动!");
char tx_buf[50];
for (int i = 0; i < 3; i++)
{
sprintf(tx_buf, "Hello World!-%d", i);
// 写入数据到流缓冲区
int send_len = xMessageBufferSend(xMessageBuffer, (void *)tx_buf, sizeof(tx_buf), portMAX_DELAY);
ESP_LOGI(TAG, "task1发送数据长度: %d", send_len);
}
vTaskDelete(NULL);
}
void task2(void *pvParameters)
{
ESP_LOGI(TAG, "-------------------------------");
ESP_LOGI(TAG, "task2启动!");
char rx_buf[200];
vTaskDelay(pdMS_TO_TICKS(3000));
while (1)
{
memset(rx_buf, 0, sizeof(rx_buf));
// 读取数据
int recv_len = xMessageBufferReceive(xMessageBuffer, (void *)rx_buf, sizeof(rx_buf), portMAX_DELAY);
if (recv_len > 0)
{
rx_buf[recv_len] = 0;
ESP_LOGI(TAG, "task2接收数据: %s ", rx_buf);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
}
void app_main(void)
{
xMessageBuffer = xMessageBufferCreate(1024); // 创建消息缓冲区
if (xMessageBuffer != NULL)
{
ESP_LOGI(TAG, "创建消息缓冲区成功");
xTaskCreate(task1, "task1", 1024 * 4, NULL, 1, NULL);
xTaskCreate(task2, "task2", 1024 * 4, NULL, 1, NULL);
}
else
{
ESP_LOGI(TAG, "创建流缓冲区失败");
}
}