队列简介
队列是任务到任务、任务到中断、中断到任务数据交流的一种机制。
队列可以容纳有限数量的固定大小的数据项。一个队列可以容纳的最大项目数称为它的长度。
- 数据入队出队方式:队列通常用作先进先出(FIFO)缓冲区,其中数据被写入队列的末尾,并从队列的头部删除。FreeRTOS中也可以配置为“后进先出”的方式。
- 数据传递方式:FreeRTOS中队列采用实际值传递,即将数据拷贝到队列中进行传递,FreeRTOS采用拷贝数据传递,也可以传递指针,所以在传递较大数据的时候采用指针传递。
- 多任务访问:队列不属于某个任务,任何任务和中断都可以向队列发送/读取消息。
- 可阻塞:当任务向一个队列发送消息时,可以指定一个阻塞时间,假设此时当队列已满无法入队,若阻塞时间为0,直接返回不会等待;若阻塞时间为0~port_MAX_DELAY,则等待设定的阻塞时间,若在该时间内还无法入队,超时后直接返回不再等待;若阻塞时间为port_MAX_DELAY,则一直等到可以入队为止。出队阻塞与入队阻塞类似。
当一个任务试图从队列中读取数据时,它可以指定一个“阻塞”时间。
队列为空的时候无法读取,这个时候就会阻塞,当另一个任务或中断将数据放入队列中时,处于阻塞态的任务将自动移动到就绪态。如果指定的阻塞时间到了,还是没有数据,任务也将自动移到就绪态。
可能存在多个任务读取队列,因此单个队列上可能阻塞了多个等待数据的任务。在这种情况下,当有数据时,只有一个任务将被解除阻塞。被解除阻塞的任务将始终是等待数据的任务中优先级最高的那个任务。如果阻塞的任务具有相同的优先级,则等待数据时间最长的任务将被解除阻塞。
队列使用
BaseType_t xQueueSend(QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait);BaseType_t xQueueSend(QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait);BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);BaseType_t xQueueReceive(QueueHandle_t xQueue,void *pvBuffer,TickType_t xTicksToWait
);BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,void *pvBuffer,BaseType_t *pxHigherPriorityTaskWoken);BaseType_t xQueuePeek(QueueHandle_t xQueue,void *pvBuffer,TickType_t xTicksToWait);BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue,void *pvBuffer,);
这些函数的使用就不做实例了,可以直接参考官网。我们重点进入下一章节。
为什么要有xQueueSendFromISR函数
xQueueSendFromISR执行完后不会立即进入任务调度,而是会标记,等退出中断后再进行任务调度。这样做的原因是中断服务程序的执行时间需要尽可能短,以避免影响其他中断的处理和任务的执行。
此外,xQueueSendFromISR函数在发送消息时不需要设置阻塞时间值,因为它是在中断服务程序中调用的,而不是在任务中。如果队列已满,xQueueSendFromISR会返回错误码errQUEUE_FULL,否则返回pdTRUE表示消息发送成功。
在实际应用中,使用FromISR函数时需要注意以下几点:
- 中断函数的执行时间应尽可能短,避免影响其他中断的响应。
- 推荐不在中断中处理消息,而是在中断服务程序中发送消息通知任务,在任务重处理消息,以保证中断的实时响应。
流缓冲区简介
流缓冲区专用于传输流式数据,如音频和视频等大数据块。它通过一个环形缓冲区来存储数据。流缓冲区允许将字节流从中断服务函数传递到任务重,或从一个任务传递到另一个任务重。字节流可以是任意长度,并且不一定具有开头或结尾。可以一次写入任意数量的字节,并且可以一次读取任意数量的字节。数据通过复制传递,数据有发送发复制到缓冲区中,并通过读取从缓冲区中复制出来。
触发级别是指定在缓冲区中必须有多少字节数据时,因等待数据而阻塞的任务会被解除阻塞。流缓冲区针对单一生产者和单一消费者场景进行了优化。如果有多个不同的写入者或读取者,应用程序设计者必须确保每次对流缓冲区的访问都在临界区内,并使用非阻塞调用 。流缓冲区在每次发送和接收操作完成后可以执行回调函数。这些回调函数可以通过sbSEND_COMPLETED和sbRECEIVE_COMPLETED宏进行自定义实现。
// 创建流缓冲区
// xBufferSizeBytes表示流缓冲区容量(字节)
// xTriggerLevelBytes读取等待字节数(触发等级),如果写0会被重置为1
StreamBufferHandle_t xStreamBufferCreate( size_t xBufferSizeBytes, size_t xTriggerLevelBytes );// 删除流缓冲区
void vStreamBufferDelete( StreamBufferHandle_t xStreamBuffer );// 向流缓冲区写数据,如果空间不足无法写入将阻塞直至超时
// pvTxData为要写入的消息首指针
// xDataLengthBytes为要写入的消息长度,写入时会额外占用sizeof(size_t)字节空间,对于32架构来说将额外占用4字节
// 返回写入字节数
size_t xStreamBufferSend( StreamBufferHandle_t xStreamBuffer, const void *pvTxData, size_t xDataLengthBytes, TickType_t xTicksToWait );
// 写数据中断版本,不会阻塞
size_t xStreamBufferSendFromISR( StreamBufferHandle_t xStreamBuffer, const void *pvTxData, size_t xDataLengthBytes, BaseType_t *pxHigherPriorityTaskWoken );// 从流缓冲区读数据,如果没有数据可读取将阻塞直至超时
// pvRxData为要接收数据的数组首指针
// xBufferLengthBytes接收消息的数组长度
// 返回读取字节数
size_t xStreamBufferReceive( StreamBufferHandle_t xStreamBuffer, void *pvRxData, size_t xBufferLengthBytes, TickType_t xTicksToWait );
// 读数据中断版本,不会阻塞
size_t xStreamBufferReceiveFromISR( StreamBufferHandle_t xStreamBuffer, void *pvRxData, size_t xBufferLengthBytes, BaseType_t *pxHigherPriorityTaskWoken );// 返回缓冲区可读字节数
size_t xStreamBufferBytesAvailable( StreamBufferHandle_t xStreamBuffer );
// 返回缓冲区可写字节数
size_t xStreamBufferSpacesAvailable( StreamBufferHandle_t xStreamBuffer );
// 查看缓冲区是否为空
BaseType_t xStreamBufferIsEmpty( StreamBufferHandle_t xStreamBuffer );
// 查看缓冲区是否已满
BaseType_t xStreamBufferIsFull( StreamBufferHandle_t xStreamBuffer );// 设置缓冲区触发等级
BaseType_t xStreamBufferSetTriggerLevel( StreamBufferHandle_t xStreamBuffer, size_t xTriggerLevel );
// 将缓冲区重置为空,只有缓冲区没有在使用时才可设置
BaseType_t xStreamBufferReset( StreamBufferHandle_t xStreamBuffer );
消息缓冲区简介
消息缓冲区(Message Buffer)是一种用于在任务或中断服务例程之间传递变长离散消息的数据结构。与流缓冲区(Stream Buffer)不同,消息缓冲区在存储消息内容之前,会先存储消息的长度信息,这样在读取时就必须以完整的消息为单位进行。
发送消息到消息缓冲区时,可以使用xMessageBufferSend函数,该函数允许发送任意长度的消息,只要缓冲区有足够的空间。当消息被写入时,会先写入消息的长度,然后再写入消息内容本身。消息的长度信息通常占用4个字节(这取决于架构,32位架构上通常为4字节)。
开发者在使用消息缓冲区时,应根据具体需求调整缓冲区大小,并优化数据处理流程,以实现最佳性能。消息缓冲区适用于需要按消息包进行传输的场景,例如长度为10、20和123字节的消息都可以在同一个消息缓冲区写入或读取。发送和接收完成回调函数可以通过sbSEND_COMPLETED和sbRECEIVE_COMPLETED宏进行定义。
// 创建消息缓冲区
MessageBufferHandle_t xMessageBufferCreate( size_t xBufferSizeBytes );// 删除消息缓冲区
void vMessageBufferDelete( MessageBufferHandle_t xMessageBuffer );// 向消息缓冲区写数据,如果空间不足无法写入将阻塞直至超时
// pvTxData为要写入的消息首指针
// xDataLengthBytes为要写入的消息长度,每次写入时会额外占用sizeof(size_t)字节空间,对于32架构来说将额外占用4字节
size_t xMessageBufferSend( MessageBufferHandle_t xMessageBuffer, const void *pvTxData, size_t xDataLengthBytes, TickType_t xTicksToWait );
// 写数据中断版本,不会阻塞
size_t xMessageBufferSendFromISR( MessageBufferHandle_t xMessageBuffer, const void *pvTxData, size_t xDataLengthBytes, BaseType_t *pxHigherPriorityTaskWoken );// 从消息冲区读数据,如果没有数据可读取将阻塞直至超时
// pvRxData为要接收数据的数组首指针
// xBufferLengthBytes接收消息的数组长度
// 返回读取字节数
// 从消息缓冲区中读取数据是以一个个消息为单位进行读取的
size_t xMessageBufferReceive( MessageBufferHandle_t xMessageBuffer, void *pvRxData, size_t xBufferLengthBytes, TickType_t xTicksToWait );
// 读数据中断版本,不会阻塞
size_t xMessageBufferReceiveFromISR( MessageBufferHandle_t xMessageBuffer, void *pvRxData, size_t xBufferLengthBytes, BaseType_t *pxHigherPriorityTaskWoken );// 返回缓冲区可写字节数
size_t xMessageBufferSpacesAvailable( MessageBufferHandle_t xMessageBuffer );
// 查看缓冲区是否为空
BaseType_t xMessageBufferIsEmpty( MessageBufferHandle_t xMessageBuffer );
// 查看缓冲区是否已满
BaseType_t xMessageBufferIsFull( MessageBufferHandle_t xMessageBuffer );// 将缓冲区重置为空,只有缓冲区没有在使用时才可设置
BaseType_t xMessageBufferReset( MessageBufferHandle_t xMessageBuffer );