在之前的文章中有提到FreeRTOS系统中的信号量和队列,信号量是用于资源管理和线程(中断)同步,队列则是用于创建数据缓冲区。在实际的应用中,一般会有多个线程,线程之间也会有同步和通讯的问题,使用信号量和事件组可以解决同步问题,使用队列及其衍生品可以解决通讯问题。

线程间的同步可以使用二值信号量和计数信号量。创建二值信号量和计数信号量的函数原型如下:

#include “FreeRTOS.h”
#include “semphr.h”
SemaphoreHandle_t xSemaphoreCreateBinary( void );
#include “FreeRTOS.h”
#include “semphr.h”
SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount );

信号量的获取使用函数:

#include “FreeRTOS.h”
#include “semphr.h”
BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );

信号量的释放使用函数:

#include “FreeRTOS.h”
#include “semphr.h”
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );

信号量的获取和释放函数都有FromISR版本,注意在优先级低于 configMAX_SYSCALL_INTERRUPT_PRIORITY  的中断中使用FromISR版本的API函数。

参考FreeRTOS Reference manural:

Binary Semaphores – A binary semaphore used for synchronization does not need to be ‘given’ back after it has been successfully ‘taken’ (obtained). Task synchronization is implemented by having one task or interrupt ‘give’ the semaphore, and another task ‘take’ the semaphore (see the xSemaphoreGiveFromISR() documentation). Note the same functionality can often be achieved in a more efficient way using a direct to task notification.

Counting Semaphores – Counting semaphores are typically used for two things:

1. Counting events.
In this usage scenario, an event handler will ‘give’ the semaphore each time an event occurs,
and a handler task will ‘take’ the semaphore each time it processes an event.
The semaphore’s count value will be incremented each time it is ‘given’ and decremented
each time it is ‘taken’. The count value is therefore the difference between the number of
events that have occurred and the number of events that have been processed.
Semaphores created to count events should be created with an initial count value of zero,
because no events will have been counted prior to the semaphore being created.

2. Resource management.
In this usage scenario, the count value of the semaphore represents the number of resources
that are available.
To obtain control of a resource, a task must first successfully ‘take’ the semaphore. The action
of ‘taking’ the semaphore will decrement the semaphore’s count value. When the count value
reaches zero, no more resources are available, and further attempts to ‘take’ the semaphore
will fail.
When a task finishes with a resource, it must ‘give’ the semaphore. The action of ‘giving’ the
semaphore will increment the semaphore’s count value, indicating that a resource is available,
and allowing future attempts to ‘take’ the semaphore to be successful.
Semaphores created to manage resources should be created with an initial count value equal
to the number of resource that are available.

事件组(Event Groups)是FreeRTOS提供的一个事件管理机制,可以用于任务(中断)间的同步。和单片机中的中断(事件)状态寄存器的原理一样,某个事件组中的某一位被置位了表示该位对应的事件发生了,置位操作同时可以将等待该事件的任务从Blocked状态转成Ready状态。事件组的创建函数定义如下:

#include “FreeRTOS.h”
#include “event_groups.h”
EventGroupHandle_t xEventGroupCreate( void );

没有任何传入参数直接就创建了一个事件组,如果configUSE_16_BIT_TICKS的值为0,那么创建的事件组一共可以表示24个位,也就是可以表示24个事件。每个位的值可以被相应的API函数进行设置和清除。详细内容参考FreeRTOS Reference manural。

线程间的通讯可以使用队列实现,但是有时候仅仅使用队列不是很方便,例如当使用串口进行数据传输的时候,收到一个字节的数据就想队列中写入一个字节的数据,如果连续收到1024个字节的数据,就要写队列1024次,效率较低,如果能够仅调用一次函数就写入1024个字节的数据,效率就会高很多。

这时候FreeRTOS提供了两种基于队列实现的机制:Stream Buffer和Message Buffer,Stream Buffer基于Queue实现,而Message Buffer则是基于Stream Buffer实现,所以两者的“父类”都是队列。

Stream Buffer其实就是一个可以同时写入/读取多个数据的队列,创建之初需要设置Stream Buffer的可以存放的最大数据长度。其余的操作和Queue是很相似的。

Message Buffer是在Stream Buffer的基础上封装了一层,每次向Message Buffer中写入数据的时候都是按照一个Message写入的,一个Message就是一个拥有固定长度的数据帧,从Message Buffer中读取数据的时候也是以Message为基本单位的,但是由于每个Message的长度可能不一样,所以实际写入到Message Buffer的缓冲区中时,会在有效数据前添加一个长度信息,表示该Message实际拥有的数据长度,读取Message Buffer的时候,API函数会先将长度信息读取出来,然后根据长度信息读取对应的有效数据出来。

参考FreeRTOS Reference manural:

Notes

Uniquely among FreeRTOS objects, the stream buffer implementation (so also the message
buffer implementation, as message buffers are built on top of stream buffers) assumes there is
only one task or interrupt that will write to the buffer (the writer), and only one task or interrupt
that will read from the buffer (the reader). It is safe for the writer and reader to be different
tasks or interrupts, but, unlike other FreeRTOS objects, it is not safe to have multiple different
writers or multiple different readers. If there are to be multiple different writers then the
application  writer  must  place  each  call  to  a  writing  API  function  (such  as
xMessageBufferSend()) inside a critical section and must use a send block time of 0. Likewise,
if there are to be multiple different readers then the application writer must place each call to a
reading API function (such as xMessageBufferRead()) inside a critical section and must use a
receive block time of 0.

Use xMessageBufferReceive() to read from a message buffer from a task. Use
xMessageBufferReceiveFromISR() to read from a message buffer from an interrupt service
routine (ISR).
Message buffer functionality is enabled by including the FreeRTOS/source/stream_buffer.c
source file in the build (as message buffers use stream buffers).

提取上面的核心内容就是:使用Stream Buffer和Message Buffer的时候需要注意,最好只有一个任务向同一个Stream Buffer或Message Buffer中写入数据,且最好只有一个任务从Stream Buffer或Message Buffer中读取数据,如果非要有多个Writer和Reader的话,需要将读取或者写入函数放在临界区中同时将block time设置为0。