上一节学习了流缓冲区(Stream Buffer),它非常适合UART、DMA等连续字节流数据。
但是流缓冲区有一个特点:
它只保存数据
不保存每条数据的边界。
例如连续发送:
Hello
和:
FreeRTOS
接收任务看到的实际上是一串连续字节。
如果我们希望每次发送的数据都作为一条完整消息 保存,就可以使用FreeRTOS提供的消息缓冲区(Message Buffer)。
本节学习消息缓冲区的创建、发送和接收,并理解它和流缓冲区之间的区别。
一、什么是消息缓冲区
消息缓冲区可以理解成:
一条消息
↓
完整存进去
↓
完整取出来
例如发送两次:
Hello
FreeRTOS
消息缓冲区会保存成:
┌─────────┐
│ Hello │
├─────────┤
│ FreeRTOS│
└─────────┘
接收时第一次得到:
Hello
第二次得到:
FreeRTOS
两条消息不会混在一起。
二、消息缓冲区和流缓冲区有什么区别
上一节的流缓冲区:
Hello + FreeRTOS
↓
HelloFreeRTOS
它关心的是:
连续字节。
而消息缓冲区:
Hello
FreeRTOS
会分别保存。
它关心的是:
一条完整消息。
因此可以简单记:
连续字节流
↓
Stream Buffer
一条一条的数据包
↓
Message Buffer
三、创建消息缓冲区
首先包含头文件:
#include "message_buffer.h"
定义消息缓冲区句柄:
MessageBufferHandle_t MessageBuffer;
创建:
MessageBuffer = xMessageBufferCreate(
128
);
这里的:
128
表示消息缓冲区的总容量。
也就是:
128Byte
左右的存储空间。
需要注意,消息缓冲区内部还需要保存每条消息的长度信息,因此不能简单认为128Byte空间一定能够存放128Byte的有效数据。
四、发送消息
发送消息使用:
xMessageBufferSend();
例如:
char Message[] = "Hello FreeRTOS";
xMessageBufferSend(
MessageBuffer,
Message,
sizeof(Message),
portMAX_DELAY
);
第一个参数:
MessageBuffer
表示消息缓冲区句柄。
第二个参数:
Message
表示需要发送的数据地址。
第三个参数:
sizeof(Message)
表示消息长度。
第四个参数:
portMAX_DELAY
表示如果空间不足:
任务进入阻塞
等待缓冲区出现足够空间。
五、接收消息
接收使用:
xMessageBufferReceive();
例如:
char Buffer[50];
size_t Length;
Length = xMessageBufferReceive(
MessageBuffer,
Buffer,
sizeof(Buffer),
portMAX_DELAY
);
返回值:
Length
表示:
实际接收到的消息长度。
例如收到:
Hello
就会一次性取出这一整条消息,而不是只取其中几个字节。
六、完整实验
创建一个发送任务:
void SendTask(void *pvParameters)
{
char Message[] = "Hello FreeRTOS";
while(1)
{
xMessageBufferSend(
MessageBuffer,
Message,
sizeof(Message),
portMAX_DELAY
);
vTaskDelay(
pdMS_TO_TICKS(1000)
);
}
}
然后创建接收任务:
void ReceiveTask(void *pvParameters)
{
char Buffer[50];
size_t Length;
while(1)
{
Length = xMessageBufferReceive(
MessageBuffer,
Buffer,
sizeof(Buffer),
portMAX_DELAY
);
if(Length > 0)
{
printf(
"Receive: %s\r\n",
Buffer
);
}
}
}
程序运行以后:
SendTask
每隔1秒发送一条消息
接收任务:
等待消息
收到以后打印:
Receive: Hello FreeRTOS
然后继续等待下一条消息。
七、消息长度不同也可以发送
消息缓冲区并不要求每条消息长度相同。
例如第一次发送:
OK
第二次发送:
Hello
第三次发送:
STM32 FreeRTOS
消息缓冲区会分别记录:
OK
Hello
STM32 FreeRTOS
因此特别适合:
长度不固定的数据包。
这也是它相比普通队列比较方便的地方之一。
八、接收数组一定要足够大
消息缓冲区有一个很重要的注意事项。
假设发送:
20Byte
但是接收数组只有:
char Buffer[10];
那么:
接收缓冲区太小。
xMessageBufferReceive()不会只取前10Byte然后把剩余部分留到下一次读取,而是本次接收失败,消息仍然保留在消息缓冲区中。
因此实际程序中:
接收数组必须能够容纳可能出现的最大消息。
这是使用消息缓冲区时比较容易踩的坑。
九、消息缓冲区适合什么场景
例如STM32中:
UART收到一帧协议数据
可以作为:
一条Message
发送给协议处理任务。
网络通信中:
收到一个完整数据包
也可以放入:
Message Buffer
因此它比较适合:
串口协议帧
网络数据包
长度不固定的命令
任务之间的数据包传递
如果只是连续不断的原始字节:
UART DMA连续数据流
还是使用:
Stream Buffer
更加合适。
十、总结
这一节学习了FreeRTOS中的消息缓冲区。
创建消息缓冲区使用:
xMessageBufferCreate();
发送消息:
xMessageBufferSend();
接收消息:
xMessageBufferReceive();
消息缓冲区与流缓冲区最大的区别就是:流缓冲区只保存连续字节,而消息缓冲区会保留每一条消息的边界。
因此可以简单记住:
连续字节流
↓
Stream Buffer
独立的数据包
↓
Message Buffer
另外,接收消息时一定要保证接收数组足够大,否则无法成功取出完整消息。