STM32F1+HAL库+FreeTOTS学习13——数值信号量
- 1. 数值信号量
- 2. 相关API函数
- 2.1 创建计数信号量
- 2.2 获取信号量
- 2.3 释放信号量
- 2.4 删除信号量
- 2.5 获取信号量的计数值
- 3. 操作实验
- 1. 实验内容
- 2. 代码实现:
- 运行结果
上一期我们学习了二值信号量 ,这一期学习计数信号量
1. 数值信号量
和二值信号量类似的,数值信号量也是队列的一种特殊情况。只不过二值信号量是队列长度为1的队列,而数值信号量则是队列长度大于0的队列(可以为1,但是这样的话就和二值信号量无异),数值信号量能够容纳多个资源,其资源最大数在创建时就已经确定,一般适用于一下两个场合:
- 事件计数
在这种场合下,每次事件发生后,在事件处理函数中释放计数信号量(资源数+1),其他等待事件发生的任务获取计数信号量(资源数-1),等待事件发生的任务就可以在成功获取到计数信号量之后执行相应的动作。在这种场合下,计数信号量的资源数一般在创建时设置为0。
- 资源管理
在这种场合下,计数信号量的资源数代表共享资源的可用数量,一个任务想要访问共享资源,就必须先获取这个共享资源的计数信号量,获取成功之后,才可以对这个共享资源进行操作,在使用完这个共享资源之后,也需要释放它,在这中场合下,计数信号量的资源数一般在创建时设置为其管理的资源最大数。
2. 相关API函数
计数信号量的使用过程:创建计数信号量->释放信号量-> 获取信号量 -> 删除信号量 ( 可选 ),下面我们围绕几个步骤介绍计数信号量的相关API函数
常用的二值信号量API函数如下表:
函数 | 描述 |
---|---|
xSemaphoreCreateCounting() | 使用动态方式创建数值信号量 |
xSemaphoreCreateCountingStatic() | 使用静态方式创建数值信号量 |
xSemaphoreTake() | 获取信号量 |
xSemaphoreTakeFromISR() | 在中断中获取信号量 |
xSemaphoreGive() | 释放信号量 |
xSemaphoreGiveFromISR() | 在中断中释放信号量 |
vSemaphoreDelete() | 删除信号量 |
uxSemaphoreGetCount() | 获取信号量的计数值 |
【注】:获取和释放信号量的函数和前面二值信号量的获取和释放是一模一样的。
2.1 创建计数信号量
- xSemaphoreCreateCounting()
此函数用于动态方式创建计数信号量,创建所需要的内存,有FreeRTOS自动分配,该函数实际上是一个宏定义,在semphr.h文件中有定义,具体的代码如下:
#define xSemaphoreCreateCounting( uxMaxCount, \
uxInitialCount ) \
xQueueCreateCountingSemaphore( ( uxMaxCount ), \
( uxInitialCount ))
可以看到xSemaphoreCreateCounting() 内部是调用了xQueueCreateCountingSemaphore() ,由于该函数不经常使用,我们这里不赘述。所以我们直接把xSemaphoreCreateCounting() 当作一个函数介绍(实际上是一个宏,我们当作函数来使用,FreeRTOS的官方文档也是这样的),下面是函数原型:
/**
* @brief xSemaphoreCreateCounting
* @param uxMaxCount:计数信号量的资源数最大值
* @param uxInitialCount:计数信号量创建时分配的资源数
* @retval 返回值为NULL,表示创建失败,其他值表示为创建数值信号量的句柄
*/
SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount);
- xSemaphoreCreateCountingStatic()
此函数用于静态方式创建数值信号量,创建二值信号量所需要的内存,需要用户手动分配,该函数实际上是一个宏定义,在semphr.h文件中有定义,具体的代码如下:
#define xSemaphoreCreateCountingStatic( uxMaxCount, \
uxInitialCount, \
pxSemaphoreBuffer) \
xQueueCreateCountingSemaphoreStatic( ( uxMaxCount ), \
( uxInitialCount ), \
( pxSemaphoreBuffer ))
可以看到xSemaphoreCreateCountingStatic() 内部是调用了xQueueCreateCountingSemaphoreStatic() ,有用该函数不经常使用,我们这里不赘述。所以我们直接把xSemaphoreCreateCountingStatic() 当作一个函数介绍(实际上是一个宏,我们当作函数来使用,FreeRTOS的官方文档也是这样的),下面是函数原型:
/**
* @brief xSemaphoreCreateCountingStatic
* @param uxMaxCount:计数信号量的资源数最大值
* @param uxInitialCount:计数信号量创建时分配的资源数
* @param pxSemaphoreBuffer:指向StaticSemaphore_t 类型的指针,存放创建后的信号量结构体
* @retval 返回值为NULL,表示创建失败,其他值表示为创建数值信号量的句柄
*/
SemaphoreHandle_t xSemaphoreCreateCountingStatic(
UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount
StaticSemaphore_t *pxSemaphoreBuffer );
2.2 获取信号量
- xSemaphoreTake()
此函数用于获取信号量,如果信号量处于没有资源的状态,那么可以选择将任务进入阻塞状态,如果成功获取到了信号量,那么信号的资源数减1,该函数实际上是一个宏定义,在semphr.h文件中有定义,具体的代码如下:
#define xSemaphoreTake( xSemaphore, \
xBlockTime) \
xQueueSemaphoreTake( ( xSemaphore ), \
( xBlockTime ))
可以看到xSemaphoreTake() 内部是调用了xQueueSemaphoreTake() ,关于xQueueSemaphoreTake函数的定义和使用,我们这里不赘述。所以我们直接把xSemaphoreTake() 当作一个函数介绍(实际上是一个宏,我们当作函数来使用,FreeRTOS的官方文档也是这样的),下面是函数原型:
/**
* @brief xSemaphoreTake
* @param xSemaphore:需要获取信号量的句柄
* @param xTicksToWait:阻塞时间
* @retval 返回值为pdTRUE,表示获取成功,如果返回值为pdFALSE,表示获取失败。
*/
BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait );
- xSemaphoreTakeFromISR()
此函数用于在中断中获取信号量,该函数实际上是一个宏定义,在semphr.h文件中有定义,具体的代码如下:
#define xSemaphoreTakeFromISR( xSemaphore, \
pxHigherPriorityTaskWoken) \
xQueueReceiveFromISR( ( QueueHandle_t ) \
( xSemaphore ), \
NULL, \
( pxHigherPriorityTaskWoken ))
可以看到xSemaphoreTakeFromISR() 内部是调用了xQueueReceiveFromISR() ,关于xQueueReceiveFromISR函数的定义和使用,我们这里不赘述。所以我们直接把xSemaphoreTakeFromISR() 当作一个函数介绍(实际上是一个宏,我们当作函数来使用,FreeRTOS的官方文档也是这样的),下面是函数原型:
/**
* @brief xSemaphoreTakeFromISR
* @param xSemaphore:需要获取信号量的句柄
* @param pxHigherPriorityTaskWoken:获取信号量之后是否需要任务切换,需要切换则 *pxHigherPriorityTaskWoken = pdTRUE
* @retval 返回值为pdTRUE,表示获取成功,如果返回值为pdFALSE,表示获取失败。
*/
BaseType_t xSemaphoreTakeFromISR ( SemaphoreHandle_t xSemaphore, signed BaseType_t *pxHigherPriorityTaskWoken );
需要注意的是:
- xSemaphoreTake()可以用来获取二值信号量、计数信号量、互斥信号量。
- xSemaphoreTakeFromISR() 只能用来获取二值信号量和计数信号量,不能用于互斥信号量。
2.3 释放信号量
- xSemaphoreGive()
此函数用于释放信号量,如果信号量处于资源满的状态,那么可以选择将任务进入阻塞状态,如果成功释放了信号量,那么信号的资源数加1,该函数实际上是一个宏定义,在semphr.h文件中有定义,具体的代码如下:
#define xSemaphoreGive( xSemaphore) \
xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), \
NULL, \
semGIVE_BLOCK_TIME, \
queueSEND_TO_BACK)
可以看到xSemaphoreGive() 内部是调用了xQueueGenericSend() ,该函数在 STM32F1+HAL库+FreeTOTS学习12——队列 中有介绍,我们这里不赘述。所以我们直接把xSemaphoreGive() 当作一个函数介绍(实际上是一个宏,我们当作函数来使用,FreeRTOS的官方文档也是这样的),下面是函数原型:
/**
* @brief xSemaphoreGive
* @param xSemaphore:需要释放信号量的句柄
* @retval 返回值为pdTRUE,表示释放成功,如果返回值为pdFALSE,表示释放失败。
*/
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
- xSemaphoreGiveFromISR()
此函数用于在中断中释放信号量,该函数实际上是一个宏定义,在semphr.h文件中有定义,具体的代码如下:
#define xSemaphoreGiveFromISR( xSemaphore, \
pxHigherPriorityTaskWoken) \
xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ), \
( pxHigherPriorityTaskWoken ))
可以看到xSemaphoreGiveFromISR() 内部是调用了xQueueGiveFromISR() ,关于xQueueGiveFromISR函数的定义和使用,我们这里不赘述。所以我们直接把xSemaphoreGiveFromISR() 当作一个函数介绍(实际上是一个宏,我们当作函数来使用,FreeRTOS的官方文档也是这样的),下面是函数原型:
/**
* @brief xSemaphoreGiveFromISR
* @param xSemaphore:需要释放信号量的句柄
* @param pxHigherPriorityTaskWoken:释放信号量之后是否需要任务切换,需要切换则 *pxHigherPriorityTaskWoken = pdTRUE
* @retval 返回值为pdTRUE,表示释放成功,如果返回值为pdFALSE,表示释放失败。
*/
BaseType_t xSemaphoreGiveFromISR ( SemaphoreHandle_t xSemaphore, signed BaseType_t *pxHigherPriorityTaskWoken )
需要注意的是:
- xSemaphoreGive()可以用来释放二值信号量、计数信号量、互斥信号量。
- xSemaphoreGiveFromISR () 只能用来释放二值信号量和计数信号量,不能用于互斥信号量。因为互斥信号量会有优先级继承的处理,而中断不属于任务,没有办法进行优先级继承。
2.4 删除信号量
- vSemaphoreDelete()
此函数用于删除已创建的信号量。该函数实际上是一个宏定义,在 semphr.h 文件中有定义,具体的代码如下所示
#define vSemaphoreDelete(xSemaphore) \
vQueueDelete ( QueueHandle_t ) \
( xSemaphore ))
可以看到vSemaphoreDelete() 内部是调用了vQueueDelete () ,关于vQueueDelete 函数的定义和使用,我们这里不赘述。所以我们直接把vSemaphoreDelete() 当作一个函数介绍(实际上是一个宏,我们当作函数来使用,FreeRTOS的官方文档也是这样的),下面是函数原型:
/**
* @brief vSemaphoreDelete
* @param xSemaphore :需要删除信号量的句柄
* @retval 无
*/
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
2.5 获取信号量的计数值
- uxSemaphoreGetCount()
此函数用于获取信号量的计数值。该函数实际上是一个宏定义,在 semphr.h 文件中有定义,具体的代码如下所示
#define uxSemaphoreGetCount( xSemaphore ) uxQueueMessagesWaiting( ( QueueHandle_t ) ( xSemaphore ) )
可以看到uxSemaphoreGetCount() 内部是调用了uxQueueMessagesWaiting() ,关于uxQueueMessagesWaiting函数的定义和使用,我们这里不赘述。所以我们直接把uxSemaphoreGetCount() 当作一个函数介绍(实际上是一个宏,我们当作函数来使用,FreeRTOS的官方文档也是这样的),下面是函数原型:
/**
* @brief uxSemaphoreGetCount
* @param xSemaphore :需要查询的信号量句柄
* @retval 计数信号量返回当前的计数值,二值信号量返回0表示信号量可以,返回1表示不可用
*/
UBaseType_t uxSemaphoreGetCount( SemaphoreHandle_t xSemaphore );
3. 操作实验
1. 实验内容
在STM32F103RCT6上运行FreeRTOS,通过按键控制,完成对应的数值信号量操作,具体要求如下:
- 定义一个数值信号量,资源数最大为100,初始为0
- 定义任务1:每次按下按键0,释放数值信号量
- 定义任务2:每1s获取1次数值信号量
2. 代码实现:
- 由于本期内容涉及到按键扫描,会用到: STM32框架之按键扫描新思路 ,这里不做过多介绍。我们直接来看代码:
- freertos_demo.c
#include "freertos_demo.h"
#include "main.h"
#include "queue.h" //需要包含队列和任务相关的头文件
#include "task.h"
#include "key.h" //包含按键相关头文件
/*FreeRTOS*********************************************************************************************/
/******************************************************************************************************/
/*FreeRTOS配置*/
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小
*/
#define TASK1_PRIO 1 /* 任务优先级 */
#define TASK1_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task1Task_Handler; /* 任务句柄 */
void task1(void *pvParameters); /*任务函数*/
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小
*/
#define TASK2_PRIO 2 /* 任务优先级 */
#define TASK2_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task2Task_Handler; /* 任务句柄 */
void task2(void *pvParameters); /*任务函数*/
SemaphoreHandle_t SemaphoreCount; /* 定义数值信号量 */
/******************************************************************************************************/
/**
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
taskENTER_CRITICAL(); /* 进入临界区,关闭中断,此时停止任务调度*/
SemaphoreCount = xSemaphoreCreateCounting(100,0);
if(SemaphoreCount == NULL)
{
printf("数值信号量创建失败\r\n");
}
else
{
printf("数值信号量创建成功\r\n");
}
/* 创建任务1 */
xTaskCreate((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
/* 创建任务2 */
xTaskCreate((TaskFunction_t )task2,
(const char* )"task2",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
taskEXIT_CRITICAL(); /* 退出临界区,重新开启中断,开启任务调度 */
vTaskStartScheduler(); //开启任务调度
}
/**
* @brief task1:用于按键扫描,检测按键0按下时,释放计数型信号量(资源数+1)
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task1(void *pvParameters)
{
while(1)
{
Key_One_Scan(Key_Name_Key0,Key0_Up_Task,Key0_Down_Task); /* 按键0扫描,按下后释放数值信号量 */
}
}
/**
* @brief task2:每1s获取计数信号量(资源数-1),当获取成功后,打印信号量的计数值(资源数)
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task2(void *pvParameters)
{
BaseType_t errMessage; /* 错误信息 */
while(1)
{
errMessage = xSemaphoreTake(SemaphoreCount,portMAX_DELAY); /* 获取计数信号量 */
if(errMessage == pdTRUE)
{
printf("信号量的计数值为:%d\r\n",(int)uxSemaphoreGetCount(SemaphoreCount));
}
else
{
printf("获取信号量失败\r\n");
}
vTaskDelay(1000);
}
}
- key.c
/* USER CODE BEGIN 2 */
#include "freertos_demo.h"
#include "key.h"
#include "usart.h"
extern QueueHandle_t SemaphoreCount; /* 声明外部的计数信号量 */
void Key0_Down_Task(void)
{
BaseType_t errMessage; /* 错误信息 */
errMessage = xSemaphoreGive(SemaphoreCount); /* 释放计数信号量 */
if(errMessage == pdTRUE)
{
printf("数值信号量释放成功\r\n");
}
else
{
printf("数值信号量释放失败\r\n");
}
}
void Key0_Up_Task(void)
{
}
void Key1_Down_Task(void)
{
}
void Key1_Up_Task(void)
{
}
void Key2_Down_Task(void)
{
}
void Key2_Up_Task(void)
{
}
void WKUP_Down_Task(void)
{
}
void WWKUP_Up_Task(void)
{
}
void Key_One_Scan(uint8_t KeyName ,void(*OnKeyOneUp)(void), void(*OnKeyOneDown)(void))
{
static uint8_t Key_Val[Key_Name_Max]; //按键值的存放位置
static uint8_t Key_Flag[Key_Name_Max]; //KEY0~2为0时表示按下,为1表示松开,WKUP反之
Key_Val[KeyName] = Key_Val[KeyName] <<1; //每次扫描完,将上一次扫描的结果左移保存
switch(KeyName)
{
case Key_Name_Key0: Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin)); //读取Key0按键值
break;
case Key_Name_Key1: Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin)); //读取Key1按键值
break;
case Key_Name_Key2: Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin)); //读取Key2按键值
break;
// case Key_Name_WKUP: Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(WKUP_GPIO_Port, WKUP_Pin)); //读取WKUP按键值
// break;
default:
break;
}
// if(KeyName == Key_Name_WKUP) //WKUP的电路图与其他按键不同,所以需要特殊处理
// {
// //WKUP特殊情况
// //当按键标志为1(松开)是,判断是否按下,WKUP按下时为0xff
// if(Key_Val[KeyName] == 0xff && Key_Flag[KeyName] == 1)
// {
// (*OnKeyOneDown)();
// Key_Flag[KeyName] = 0;
// }
// //当按键标志位为0(按下),判断按键是否松开,WKUP松开时为0x00
// if(Key_Val[KeyName] == 0x00 && Key_Flag[KeyName] == 0)
// {
// (*OnKeyOneUp)();
// Key_Flag[KeyName] = 1;
// }
// }
// else //Key0~2按键逻辑判断
// {
//Key0~2常规判断
//当按键标志为1(松开)是,判断是否按下
if(Key_Val[KeyName] == 0x00 && Key_Flag[KeyName] == 1)
{
(*OnKeyOneDown)();
Key_Flag[KeyName] = 0;
}
//当按键标志位为0(按下),判断按键是否松开
if(Key_Val[KeyName] == 0xff && Key_Flag[KeyName] == 0)
{
(*OnKeyOneUp)();
Key_Flag[KeyName] = 1;
}
}
//}
/* USER CODE END 2 */
运行结果
运行结果如上图,我们来简单的解释一下,为什么是这样:
- 数值信号量创建成功后,打印对应信息
- 任务2优先级大于任务1,但是由于信号量资源数初始化为0,所以无法获取信号量,任务2进入阻塞,执行任务1。
- 执行任务1的过程中,按键0按下(我这里是连续按下3次按键0),在识别到第一次按下(还没来得及大于释放成功的信息),信号量资源数+1,任务2恢复就绪态并执行。
- 任务2执行获取信号量,并且打印计数值为0,后再次进入阻塞态,切换回任务1,打印第一次数值信号量释放成功。
- 紧接着第二、三次按键按下,打印第二、三次数值信号量释放成功。
- 1s后,切换会任务2,释放幸好了,打印计数值为1。
- 再过1s,打印计数值为0。
以上就是本期使用到的核心代码,其他部分我这里就不做展示,直接去看我往期的内容,源代码都有的。至于按键的配置,可以参考:夜深人静学32系列9——GPIO驱动数码管/蜂鸣器/按键/LED