——————(正点原子FreeRTOS学习笔记)
开始学习FreeRTOS,学习参考书籍和视频来自正点原子FreeRTOS源码详解与应用开发,北京航空航天大学出版社
1 二值信号量
1.1 二值信号量简介
二值信号量通常用于互斥访问或同步,二值信号量和互斥信号量非常类似,但是还是有一
些细微的差别,互斥信号量拥有优先级继承机制,二值信号量没有优先级继承。因此二值信号
另更适合用于同步(任务与任务或任务与中断的同步),而互斥信号量适合用于简单的互斥访问。
和队列一样,信号量 API 函数允许设置一个阻塞时间,阻塞时间是当任务获取信号量的时
候由于信号量无效从而导致任务进入阻塞态的最大时钟节拍数。如果多个任务同时阻塞在同
一个信号量上的话那么优先级最高的哪个任务优先获得信号量,这样当信号量有效的时候高优
先级的任务就会解除阻塞状态。
二值信号量其实就是一个只有一个队列项的队列,这个特殊的队列要么是满的,要么是空
的,这不正好就是二值的吗? 任务和中断使用这个特殊队列不用在乎队列中存的是什么消息,只需要知道这个队列是满的还是空的。可以利用这个机制来完成任务与中断之间的同步。
使用二值信号量来完成中断与任务同步的这个机制中,任务优先级确保了外设能够得到及
时的处理,这样做相当于推迟了中断处理过程。也可以使用队列来替代二值信号量,在外设事
件的中断服务函数中获取相关数据,并将相关的数据通过队列发送给任务。如果队列无效的话
任务就进入阻塞态,直至队列中有数据,任务接收到数据以后就开始相关的处理过程。下面几
个步骤演示了二值信号量的工作过程。
1 、二值信号量无效
在图 14.2.1.1 中任务 Task 通过函数 xSemaphoreTake()获取信号量,但是此时二值信号量无
效,所以任务 Task 进入阻塞态。
2 、中断 释放 信号量
此时中断发生了,在中断服务函数中通过函数 xSemaphoreGiveFromISR()释放信号量,因此信
号量变为有效。
3、任务获取信号量成功
由于信号量已经有效了,所以任务 Task 获取信号量成功,任务从阻塞态解除,开始执行相
关的处理过程
4、任务再次进入阻塞态
由于任务函数一般都是一个大循环,所以在任务做完相关的处理以后就会再次调用函数
xSemaphoreTake()获取信号量。在执行完第三步以后二值信号量就已经变为无效的了,所以任务
将再次进入阻塞态,和第一步一样,直至中断再次发生并且调用函数 xSemaphoreGiveFromISR()
释放信号量。
1.2 创建二值信号量
同队列一样,要想使用二值信号量就必须先创建二值信号量,二值信号量创建函数 如表 14.2.2
所示:
1 、函数
vSemaphoreCreateBinary ()
此函数是老版本 FreeRTOS 中的创建二值信号量函数,新版本已经不再使用了,新版本的
FreeRTOS 使用 xSemaphoreCreateBinary()来替代此函数,这里还保留这个函数是为了兼容那些
基于老版本 FreeRTOS 而做的应用层代码。此函数是个宏,具体创建过程是由函数
xQueueGenericCreate()来完成的,在文件 semphr.h 中有如下定义:
void vSemaphoreCreateBinary( SemaphoreHandle_t xSemaphore )
参数:
xSemaphore:保存创建成功的二值信号量句柄。
返回值:
NULL: 二值信号量创建失败。
其他值: 二值信号量创建成功。
2 、函数
xSemaphoreCreateBinary()
此函数是 vSemaphoreCreateBinary()的新版本,新版本的 FreeRTOS 中统一用此函数来创建
二值信号量。使用此函数创建二值信号量的话信号量所需要的 RAM 是由 FreeRTOS 的内存管
理部分来动态分配的。此函数创建好的二值信号量默认是空的,也就是说刚创建好的二值信号
量使用函数 xSemaphoreTake()是获取不到的,此函数也是个宏,具体创建过程是由函数
xQueueGenericCreate()来完成的,函数原型如下:
SemaphoreHandle_t xSemaphoreCreateBinary( void )
参数:
无。
返回值:
NULL: 二值信号量创建失败。
其他值: 创建成功的二值信号量的句柄。
3 、函数
xSemaphoreCreateBinaryStatic()
此函数也是创建二值信号量的,只不过使用此函数创建二值信号量的话信号量所需要的
RAM 需要由用户来分配,此函数是个宏,具体创建过程是通过函数 xQueueGenericCreateStatic()
来完成的,函数原型如下:
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer )
参数:
pxSemaphoreBuffer: :此参数指向一个 StaticSemaphore_t 类型的变量,用来保存信号量结构体。
返回值:
NULL: 二值信号量创建失败。
其他值: 创建成功的二值信号量句柄。
1.3 二值信号量创建过程分析
上一小节讲了三个用于二值信号量创建的函数,两个动态的创建函数和一个静态的创建函
数。本节就来分析一下这两个动态的创建函数,静态创建函数和动态的类似,就不做分析了。
首先来看一下老版本的二值信号量动态创建函数 vSemaphoreCreateBinary(),函数代码如下:
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define vSemaphoreCreateBinary( xSemaphore ) \
{ \
( xSemaphore ) = xQueueGenericCreate( ( UBaseType_t ) 1, \ (1)
semSEMAPHORE_QUEUE_ITEM_LENGTH, \
queueQUEUE_TYPE_BINARY_SEMAPHORE ); \
if( ( xSemaphore ) != NULL ) \
{ \
( void ) xSemaphoreGive( ( xSemaphore ) ); \ (2)
} \
}
#endif
(1)、上面说了二值信号量是在队列的基础上实现的,所以创建二值信号量就是创建队列的
过程。这里使用函数 xQueueGenericCreate()创建了一个队列,队列长度为 1,队列项长度为 0,
队列类型为 queueQUEUE_TYPE_BINARY_SEMAPHORE,也就是二值信号量。
(2)、当二值信号量创建成功以后立即调用函数 xSemaphoreGive()释放二值信号量,此时新
创建的二值信号量有效。
在来看一下新版本的二值信号量创建函数 xSemaphoreCreateBinary(),函数代码如下:
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateBinary() \
xQueueGenericCreate( ( UBaseType_t ) 1, \
semSEMAPHORE_QUEUE_ITEM_LENGTH, \
queueQUEUE_TYPE_BINARY_SEMAPHORE ) \
#endif
可以看出新版本的二值信号量创建函数也是使用函数 xQueueGenericCreate()来创建一个类
型为queueQUEUE_TYPE_BINARY_SEMAPHORE、长度为 1、队列项长度为 0 的队列。这一步
和老版本的二值信号量创建函数一样,唯一不同的就是新版本的函数在成功创建二值信号量以
后不会立即调用函数 xSemaphoreGive()释放二值信号量。也就是说新版函数创建的二值信号量
默认是无效的,而老版本是有效的。
大家注意看,创建的队列是个没有存储区的队列,前面说了使用队列是否为空来表示二值
信号量,而队列是否为空可以通过队列结构体的成员变量 uxMessagesWaiting 来判断。
1.4 释放信号量
释放信号量的函数有两个,如表 14.2.4.1 所示:
同队列一样,释放信号量也分为任务级和中断级。还有!不管是二值信号量、计数型信号
量还是互斥信号量,它们都使用表 14.2.4.1 中的函数释放信号量,递归互斥信号量有专用的释
放函数。
1、数 函数
xSemaphoreGive()
此函数用于释放二值信号量、计数型信号量或互斥信号量,此函数是一个宏,真正释放信
号量的过程是由函数 xQueueGenericSend()来完成的,函数原型如下:
BaseType_t xSemaphoreGive( xSemaphore )
参数:
xSemaphore :要释放的信号量句柄。
返回值:
pdPASS: 释放信号量成功。
errQUEUE_FULL: 释放信号量失败。
我们再来看一下函数 xSemaphoreGive()的具体内容,此函数在文件 semphr.h 中有如下定义:
#define xSemaphoreGive( xSemaphore ) \
xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), \
NULL, \
semGIVE_BLOCK_TIME, \
queueSEND_TO_BACK ) \
可以看出任务级释放信号量就是向队列发送消息的过程,只是这里并没有发送具体的消息,
阻塞时间为 0(宏 semGIVE_BLOCK_TIME 为 0),入队方式采用的后向入队。具体入队过程第十
三章已经做了详细的讲解,入队的时候队列结构体成员变量 uxMessagesWaiting 会加一,对于二
值信号量通过判断 uxMessagesWaiting 就可以知道信号量是否有效了,当 uxMessagesWaiting 为
1 的话说明二值信号量有效,为 0 就无效。如果队列满的话就返回错误值 errQUEUE_FULL,提
示队列满,入队失败。
2 、函数
xSemaphoreGiveFromISR()
此函数用于在中断中释放信号量,此函数只能用来释放二值信号量和计数型信号量,绝对
不能用来在中断服务函数中释放互斥信号量!此函数是一个宏,真正执行的是函数
xQueueGiveFromISR(),此函数原型如下:
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,
BaseType_t * pxHigherPriorityTaskWoken)
参数:
xSemaphore: 要释放的信号量句柄。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这
三个函数来设置的,用户不用进行设置,用户只需要提供一
个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退
出中断服务函数之前一定要进行一次任务切换。
返回值:
pdPASS: 释放信号量成功。
errQUEUE_FULL: 释放信号量失败。
在中断中释放信号量真正使用的是函数 xQueueGiveFromISR(),此函数和中断级通用入队
函数 xQueueGenericSendFromISR()极其类似!只是针对信号量做了微小的改动。函数
xSemaphoreGiveFromISR()不能用于在中断中释放互斥信号量,因为互斥信号量涉及到优先级继
承的问题,而中断不属于任务,没法处理中断优先级继承。大家可以参考第十三章分析函数
xQueueGenericSendFromISR()的过程来分析 xQueueGiveFromISR()。
1.5 获取信号量
获取信号量也有两个函数,如表 14.2.5.1 所示:
同释放信号量的 API 函数一样,不管是二值信号量、计数型信号量还是互斥信号量,它们
都使用表 14.2.5.1 中的函数获取信号量。
1 、函数
xSemaphoreTake()
此函数用于获取二值信号量、计数型信号量或互斥信号量,此函数是一个宏,真正获取信
号量的过程是由函数 xQueueGenericReceive ()来完成的,函数原型如下:
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,
TickType_t xBlockTime)
参数:
xSemaphore :要获取的信号量句柄。
xBlockTime: 阻塞时间。
返回值:
pdTRUE: 获取信号量成功。
pdFALSE: 超时,获取信号量失败。
再来看一下函数 xSemaphoreTake ()的具体内容,此函数在文件 semphr.h 中有如下定义:
#define xSemaphoreTake( xSemaphore, xBlockTime ) \
xQueueGenericReceive( ( QueueHandle_t ) ( xSemaphore ), \
NULL, \
( xBlockTime ), \
pdFALSE ) \
获取信号量的过程其实就是读取队列的过程,只是这里并不是为了读取队列中的消息。在
第十三章讲解函数 xQueueGenericReceive()的时候说过如果队列为空并且阻塞时间为 0 的话就
立即返回 errQUEUE_EMPTY,表示队列满。如果队列为空并且阻塞时间不为 0 的话就将任务
添加到延时列表中。如果队列不为空的话就从队列中读取数据(获取信号量不执行这一步),数
据读取完成以后还需要将队列结构体成员变量 uxMessagesWaiting 减一,然后解除某些因为入
队而阻塞的任务,最后返回 pdPASS 表示出对成功。互斥信号量涉及到优先级继承,处理方式
不同,后面讲解互斥信号量的时候在详细的讲解。