当前位置: 首页 > news >正文

STM32F1+HAL库+FreeTOTS学习14——数值信号量

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),其他等待事件发生的任务获取计数信号量(资源数-1),等待事件发生的任务就可以在成功获取到计数信号量之后执行相应的动作。在这种场合下,计数信号量的资源数一般在创建时设置为0。

  1. 资源管理

在这种场合下,计数信号量的资源数代表共享资源的可用数量,一个任务想要访问共享资源,就必须先获取这个共享资源的计数信号量,获取成功之后,才可以对这个共享资源进行操作,在使用完这个共享资源之后,也需要释放它,在这中场合下,计数信号量的资源数一般在创建时设置为其管理的资源最大数。

2. 相关API函数

计数信号量的使用过程:创建计数信号量->释放信号量-> 获取信号量 -> 删除信号量 ( 可选 ),下面我们围绕几个步骤介绍计数信号量的相关API函数
常用的二值信号量API函数如下表:

函数描述
xSemaphoreCreateCounting()使用动态方式创建数值信号量
xSemaphoreCreateCountingStatic()使用静态方式创建数值信号量
xSemaphoreTake()获取信号量
xSemaphoreTakeFromISR()在中断中获取信号量
xSemaphoreGive()释放信号量
xSemaphoreGiveFromISR()在中断中释放信号量
vSemaphoreDelete()删除信号量
uxSemaphoreGetCount()获取信号量的计数值

【注】:获取和释放信号量的函数和前面二值信号量的获取和释放是一模一样的。

2.1 创建计数信号量

  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);
  1. 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 uxInitialCountStaticSemaphore_t *pxSemaphoreBuffer );

2.2 获取信号量

  1. 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 );
  1. 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 释放信号量

  1. 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 );
  1. 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 删除信号量

  1. 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 获取信号量的计数值

  1. 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框架之按键扫描新思路 ,这里不做过多介绍。我们直接来看代码:
  1. 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);}
}
  1. 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 */

运行结果

在这里插入图片描述
运行结果如上图,我们来简单的解释一下,为什么是这样:

  1. 数值信号量创建成功后,打印对应信息
  2. 任务2优先级大于任务1,但是由于信号量资源数初始化为0,所以无法获取信号量,任务2进入阻塞,执行任务1。
  3. 执行任务1的过程中,按键0按下(我这里是连续按下3次按键0),在识别到第一次按下(还没来得及大于释放成功的信息),信号量资源数+1,任务2恢复就绪态并执行。
  4. 任务2执行获取信号量,并且打印计数值为0,后再次进入阻塞态,切换回任务1,打印第一次数值信号量释放成功。
  5. 紧接着第二、三次按键按下,打印第二、三次数值信号量释放成功。
  6. 1s后,切换会任务2,释放幸好了,打印计数值为1。
  7. 再过1s,打印计数值为0。

以上就是本期使用到的核心代码,其他部分我这里就不做展示,直接去看我往期的内容,源代码都有的。至于按键的配置,可以参考:夜深人静学32系列9——GPIO驱动数码管/蜂鸣器/按键/LED

相关文章:

  • 【Go】-Websocket的使用
  • ThinkPHP一对多的关联模型运用
  • ClickHouse | 入门
  • 2024 年实验室设备管理系统的选择指南
  • 第四章-课后练习5:修正指数曲线模型——excel和python应用(2)
  • 力扣 简单 104.二叉树的最大深度
  • Llama 系列简介与 Llama3 预训练模型推理
  • springboot实战学习(9)(配置mybatis“驼峰命名“和“下划线命名“自动转换)(postman接口测试统一添加请求头)(获取用户详细信息接口)
  • 【数据治理-设计数据标准】
  • py-mmcif包pdbx_struct_assembly对象介绍
  • 困扰我们的,不是如何过上更幸福的生活,而是如何过上比别人更幸福的生活
  • Linux之实战命令18:col应用实例(五十二)
  • 开启争对目标检测的100类数据集-信息收集
  • 深入理解 Nuxt.js 中的 app:data:refresh 钩子
  • 「实战应用」如何用DHTMLX Gantt集成工具栏部件更好完成项目管理?
  • 【108天】Java——《Head First Java》笔记(第1-4章)
  • 2017届校招提前批面试回顾
  • 2018以太坊智能合约编程语言solidity的最佳IDEs
  • android高仿小视频、应用锁、3种存储库、QQ小红点动画、仿支付宝图表等源码...
  • express + mock 让前后台并行开发
  • fetch 从初识到应用
  • JavaScript 基础知识 - 入门篇(一)
  • PHP那些事儿
  • Spring Boot MyBatis配置多种数据库
  • 阿里云容器服务区块链解决方案全新升级 支持Hyperledger Fabric v1.1
  • 工作踩坑系列——https访问遇到“已阻止载入混合活动内容”
  • 关于Android中设置闹钟的相对比较完善的解决方案
  • 机器学习学习笔记一
  • 解析带emoji和链接的聊天系统消息
  • 面试总结JavaScript篇
  • 深度学习中的信息论知识详解
  • 网页视频流m3u8/ts视频下载
  • k8s使用glusterfs实现动态持久化存储
  • mysql面试题分组并合并列
  • RDS-Mysql 物理备份恢复到本地数据库上
  • ​Linux Ubuntu环境下使用docker构建spark运行环境(超级详细)
  • ​一些不规范的GTID使用场景
  • #define MODIFY_REG(REG, CLEARMASK, SETMASK)
  • #if 1...#endif
  • #我与Java虚拟机的故事#连载16:打开Java世界大门的钥匙
  • #我与虚拟机的故事#连载20:周志明虚拟机第 3 版:到底值不值得买?
  • $.ajax()参数及用法
  • (C#)Windows Shell 外壳编程系列4 - 上下文菜单(iContextMenu)(二)嵌入菜单和执行命令...
  • (C++17) optional的使用
  • (HAL库版)freeRTOS移植STMF103
  • (每日一问)基础知识:堆与栈的区别
  • (入门自用)--C++--抽象类--多态原理--虚表--1020
  • (一)Linux+Windows下安装ffmpeg
  • (幽默漫画)有个程序员老公,是怎样的体验?
  • **PyTorch月学习计划 - 第一周;第6-7天: 自动梯度(Autograd)**
  • ../depcomp: line 571: exec: g++: not found
  • .bat批处理(四):路径相关%cd%和%~dp0的区别
  • .naturalWidth 和naturalHeight属性,
  • .net core webapi 部署iis_一键部署VS插件:让.NET开发者更幸福
  • .net oracle 连接超时_Mysql连接数据库异常汇总【必收藏】