一、二值信号量简介
二值信号量的本质是一个队列长度为 1 的队列,该队列就只有空和满两种情况,也就是 0 和 1。通常用于互斥访问或任务同步,与互斥信号量比较类似,但是二值信号量有可能会导致优先级反转问题,所以二值信号量更适用于同步。
二、二值信号量相关的API函数
使用二值信号量的过程:创建二值信号量 - 释放信号量 - 获取信号量。下面表格是二值信号量关于任务间的API函数
函数 | 描述 |
---|---|
xSemaphoreCreateBinary( ) |
使用动态方式创建二值信号量 |
xSemaphoreCreateBinaryStatic( ) |
使用静态方式创建二值信号量 |
xSemaphoreTake( ) |
获取信号量 |
xSemaphoreGive( ) |
释放信号量 |
2.1.动态方式创建二值信号量
动态创建的内存由 FreeRTOS 自动分配,静态创建的内存是由用户分配,该函数是一个宏,而且它与队列所调用的函数xQueueGenericCreate( )
是相同的,只不过最后一个参数不一样:
#define xSemaphoreCreateBinary()
xQueueGenericCreate(( UBaseType_t ) 1,
semSEMAPHORE_QUEUE_ITEM_LENGTH,
queueQUEUE_TYPE_BINARY_SEMAPHORE)
下面表格是它的返回值:
返回值 | 描述 |
---|---|
NULL | 创建失败 |
其他值 | 创建成功返回二值信号量的句柄 |
2.2.获取信号量
此函数用于获取信号量,如果信号量处于没有资源的状态,那么此函数可以选择将任务进行阻塞,如果成功获取了信号量,那信号量的资源数将会减1,资源数就是操作xQueueSemaphoreTake( )
里面的uxMessagesWaiting
变量。该函数实际上是一个宏定义,在 semphr.h 文件中有定义,具体的代码如下所示:
#define xSemaphoreTake(xSemaphore, xBlockTime)
xQueueSemaphoreTake((xSemaphore),
(xBlockTime))
下面表格是它的形参和描述:
形参 | 描述 |
---|---|
xSemaphore | 要获得的信号量句柄 |
xBlockTime | 阻塞时间 |
下面表格是它的返回值:
返回值 | 描述 |
---|---|
pdTRUE | 获取信号量成功 |
pdFALSE | 超时,获取信号量失败 |
2.3.释放信号量
此函数用于释放信号量,如果信号量处于资源满的状态,那么此函数可续选择将任务进行阻塞,如果成功释放了信号量,那信号量的资源数将会加1。该函数实际上是一个宏定义,在 semphr.h 文件中有定义,具体的代码如下所示:
#define xSemaphoreGive(xSemaphore)
xQueueGenericSend(( QueueHandle_t ) ( xSemaphore ),
NULL,
semGIVE_BLOCK_TIME,
queueSEND_TO_BACK)
#define semGIVE_BLOCK_TIME ((TickType_t)0U)
下面表格是它的形参和描述:
形参 | 描述 |
---|---|
xSemaphore | 要释放的信号量句柄 |
下面表格是它的返回值:
返回值 | 描述 |
---|---|
pdPASS | 释放信号量成功 |
errQUEUE_FULL | 释放信号量失败 |
三、实验
3.1.实验设计
本实验将设计三个任务:
- start_task:用来创建 task1 和 task2
- task1:用于按键扫描,当检测到按键 KEY0 被按下时,释放二值信号量
- task2:获取二值信号量,当成功获取后打印提示信息
3.2.软件设计
在入口函数里面创建二值信号量:
void freertos_demo(void)
{
semphr_handle = xSemaphoreCreateBinary();
if(semphr_handle != NULL)
{
printf("二值信号量创建成功\r\n");
}
xTaskCreate((TaskFunction_t) start_task,
(char*) "start_task",
(uint16_t) START_TASK_STACK_SIZE,
(void*) NULL,
(UBaseType_t) START_TASK_PRIO,
(TaskHandle_t*) &start_task_handler);
vTaskStartScheduler();
}
接下来编写 task1:
void task1(void *pvParameters)
{
uint8_t key = 0;
BaseType_t err = 0;
while(1)
{
key = key_scan(0);
if(key == KEY0_PRES)
{
err = xSemaphoreGive(semphr_handle);
if(err == pdPASS)
{
printf("信号量释放成功\r\n");
}
}
}
}
接下来是 task2,里面实现了将时间限制在 3s 之内,如果 3s 之内不按下 KEY0 按键来释放信号量,就提示超时多少次,直到按下按键才提示获取信号量成功:
void task2(void *pvParameters)
{
BaseType_t err = 0;
uint8_t i = 0;
while(1)
{
err = xSemaphoreTake(semphr_handle, 3000);
if(err == pdFALSE)
{
printf("超时,%d\r\n",++i);
}
else if(err == pdTRUE)
{
printf("获取信号量成功\r\n");
}
}
}
下图是结果图,因为本实验将 task2 的任务优先级设置为 3,task1 的任务优先级设置为 2,所以出现了先获取信号量,再出现信号量释放成功,当程序执行到 task1 的xSemaphoreGive( )
函数,task2 就抢占了CPU了,导致还没将 task1 的printf
打印出来: