1 Queue队列
队列是任务间通信的最基础形式,也是最灵活的方式。操作系统中队列是以项(item)为基本单元。
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,
UBaseType_t uxItemSize );
BaseType_t xQueueSend( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void *pvBuffer,
TickType_t xTicksToWait );
从其创建函数中传参可以看出,对列需要一个固定的长度,并且每个项的大小也是固定的。从发送和接收函数中可以看出,其默认传递的方式为拷贝,将指针指向的内容拷贝到自己的内存中。所以发送完成后可以修改原来的数据存储区,同样接收完成后,数据会从队列存储区中删除。当然,可以通过传递指针的指针解决大数据量拷贝慢的问题,同样也会存在其它问题(例如接收任务接收到数据之前,该区域内容不得更改等),这里不再详述。
2 StreamBuffer流缓冲区
流式缓冲区是在队列的基础上,针对单一生产者和消费者场景,优化的一种更适合的数据结构。 流缓冲区允许将字节流从中断服务例程传递到任务,或从一个任务传递到另一个任务。字节流可以是任意长度,并且不一定具有开头或结尾。可以一次写入任意数量的字节,并且可以一次读取任意数量的字节。数据通过复制传递 – 数据由发送方复制到缓冲区中,并通过读取从缓冲区中复制出来。
StreamBufferHandle_t xStreamBufferCreate( size_t xBufferSizeBytes,
size_t xTriggerLevelBytes );
size_t xStreamBufferSend( StreamBufferHandle_t xStreamBuffer,
const void *pvTxData,
size_t xDataLengthBytes,
TickType_t xTicksToWait );
size_t xStreamBufferReceive( StreamBufferHandle_t xStreamBuffer,
void *pvRxData,
size_t xBufferLengthBytes,
TickType_t xTicksToWait );
上面简单列举了创建,发送和接收的函数,不难看出,流缓冲区可以任意长度读写数据,并且支持阻塞式访问。同时提供了一种辅助的触发方式(可以设定在多少字节时触发)。相比较队列而言在串口等数据调试和传输环境中具有更佳的可用性。
与大多数其他FreeRTOS API不同的是,流缓冲器针对单个读取器单写入器场景进行了优化,例如将数据从中断服务例程传递到任务,或者从双核CPU上的一个微控制器核心传递到另一个。在多任务读写的环境中,需要将该函数相关的调用置于关键区域内(taskENTER_CRITICAL和taskEXIT_CRITICAL),也可以使用互斥信号量来解决。我感觉关键区域的方式是最简单的。
NOTICE:
这里有个小问题,手册中在taskENTER_CRITICAL中明确说明了关键区域内不允许调用FreeRTOS API函数,那么和这一数据结构的使用有些冲突。下面是我对这一问题在官方论坛上询问和解答。
- whzh – 23 hours ago
I see the following content in the manual, are these two paragraphs contradictory?
Manual P365
If there are to be multiple different writers then the application writer must place each call to a writing API function (such as xStreamBufferSend()) inside a critical section and use a send block time of 0.
Manual P59
FreeRTOS API functions must not be called from within a critical section.
Richard Damon – 21 hours ago
Both those statements are in my mind a bit simplified.
First, Streambuffers don’t need a critical section, but do need some form of protection that you NEVER have multiple tasks trying to read or write the buffer at a given time. A critical section is one simple way to do this, having a Mutex protect the access should also work (and that gets around the need for 0 block time.
Second, the limitiation isn’t so much that they can’t be called from a critical section, but that no FreeRTOS API call should block or attempt to change the running task inside a critical section. (some ports actually don’t have a problem with it, but some do).
三 MessageBuffer 消息缓冲区
消息缓冲区是在流式缓冲区的基础上实现的,其进一步针对“消息”进行设计改进。MessageBuffer每一条消息的写入增加了一个字节用来表示该条消息的长度。读取时需要一次性读出至少一条消息,否则会返回
MessageBufferHandle_t xMessageBufferCreate( size_t xBufferSizeBytes );
size_t xMessageBufferSend( MessageBufferHandle_t xMessageBuffer,
const void *pvTxData,
size_t xDataLengthBytes,
TickType_t xTicksToWait );
size_t xMessageBufferReceive( MessageBufferHandle_t xMessageBuffer,
void *pvRxData,
size_t xBufferLengthBytes,
TickType_t xTicksToWait );
综上,对比了三种数据结构的区别,每一种数据结构都是在上一中简单结构中的增强和针对性的改进。确定的场景中选用合适的数据结构将事半功倍。