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

FreeRTOS学习系列·二值信号量

目录

1.  信号量的基本概念

2.  二值信号量

3.  应用场景

4.  运作机制

5.  信号量控制块

6.  常用信号量函数接口API

6.1  创建二值信号量 xSemaphoreCreateBinary()

6.2  信号量删除函数 vSemaphoreDelete()

6.3  信号量释放函数

6.3.1  xSemaphoreGive()

6.3.2  xSemaphoreGiveFromISR()


1.  信号量的基本概念

        在了解信号量的概念之前,我们先来回忆一下,当我们在逻辑编程的时候,时候使用过这样一个变量:用于标记某个事件是否发生,或者标志一下某个东西是否正在被使用,如果是被占用了的或者没发生,我们就不对它进行操作。

        那么当我们来到操作系统,信号量的概念就是:信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务之间同步或临界资源的互斥访问,常用于协助一组相互竞争的任务来访问临界资源。在多任务系统中,各任务之间需要同步或互斥实现临界资源的保护,信号量功能可以为用户提供这方面的支持。

        抽象的来讲,信号量是一个非负整数,所有获取它的任务都会将该整数减一(获取它当然是为了使用资源),当该整数值为零时,所有试图获取它的任务都将处于阻塞状态。通常一个信号量的计数值用于对应有效的资源数,表示剩下的可被占用的互斥资源数。其值的含义分两种情况:

  • 0:表示没有积累下来的释放信号量操作,且有可能有在此信号量上阻塞的任务。
  • 正值,表示有一个或多个释放信号量操作。

2.  二值信号量

        二值信号量既可以用于临界资源访问,也可用于同步功能。

        二值信号量是一种特殊的信号量,其内部状态只有两种:被释放(释放状态)或被占用(占用状态)。可以将二值信号量看作只有一个消息的队列,因此这个队列只能为空或满(因此称为二
值),我们在运用的时候只需要知道队列中是否有消息即可,而无需关注消息是什么。

        二值信号量和互斥信号量(以下使用互斥量表示互斥信号量)非常相似,但是有一些细微差别:互斥量有优先级继承机制,二值信号量则没有这个机制。这使得二值信号量更偏向应用于同步功能(任务与任务间的同步或任务和中断间同步),而互斥量更偏向应用于临界资源的访问。

        用作同步时,信号量在创建后应被置为空,任务 1 获取信号量而进入阻塞,任务 2 在某种条件发生后,释放信号量,于是任务 1 获得信号量得以进入就绪态,如果任务 1 的优先级是最高的,那么就会立即切换任务,从而达到了两个任务间的同步。同样的,在中断服务函数中释放信号量,任务 1 也会得到信号量,从而达到任务与中断间的同步。

        还记得我们经常说的中断要快进快出吗,在裸机开发中我们经常是在中断中做一个标记,然后在退出的时候进行轮询处理,这个就是类似我们使用信号量进行同步的,当标记发生了,我们再做其他事情。在 FreeRTOS 中我们用信号量用于同步,任务与任务的同步,中断与任务的同步,可以大大提高效率。

3.  应用场景

        在嵌入式操作系统中二值信号量是任务间、任务与中断间同步的重要手段,信号量使用最多的一般都是二值信号量与互斥信号量(互斥信号量在下一章讲解)。为什么叫二值信号量呢?因为信号量资源被获取了,信号量值就是 0,信号量资源被释放,信号量值就是 1,把这种只有 0 和 1 两种情况的信号量称之为二值信号量。

        在多任务系统中,我们经常会使用这个二值信号量,比如,某个任务需要等待一个标记,那么任务可以在轮询中查询这个标记有没有被置位,但是这样子做,就会很消耗 CPU资源并且妨碍其它任务执行,更好的做法是任务的大部分时间处于阻塞状态(允许其它任务执行),直到某些事件发生该任务才被唤醒去执行。可以使用二进制信号量实现这种同步,当任务取信号量时,因为此时尚未发生特定事件,信号量为空,任务会进入阻塞状态;当事件的条件满足后,任务/中断便会释放信号量,告知任务这个事件发生了,任务取得信号量便被唤醒去执行对应的操作,任务执行完毕并不需要归还信号量,这样子的 CPU 的效率可以大大提高,而且实时响应也是最快的。

        二值信号量在任务与任务中同步的应用场景:假设我们有一个温湿度的传感器,假设是 1s 采集一次数据,那么我们让他在液晶屏中显示数据出来,这个周期也是要 1s 一次的,如果液晶屏刷新的周期是 100ms 更新一次,那么此时的温湿度的数据还没更新,液晶屏根本无需刷新,只需要在 1s 后温湿度数据更新的时候刷新即可,否则 CPU 就是白白做了多次的无效数据更新,CPU的资源就被刷新数据这个任务占用了大半,造成 CPU 资源浪费,如果液晶屏刷新的周期是 10s更新一次,那么温湿度的数据都变化了 10 次,液晶屏才来更新数据,那拿这个产品有啥用,根本就是不准确的,所以,还是需要同步协调工作,在温湿度采集完毕之后,进行液晶屏数据的刷新,这样子,才是最准确的,并且不会浪费 CPU的资源。

        同理,二值信号量在任务与中断同步的应用场景:我们在串口接收中,我们不知道啥时候有数据发送过来,有一个任务是做接收这些数据处理,总不能在任务中每时每刻都在任务查询有没有数据到来,那样会浪费 CPU 资源,所以在这种情况下使用二值信号量是很好的办法,当没有数据到来的时候,任务就进入阻塞态,不参与任务的调度,等到数据到来了,释放一个二值信号量,任务就立即从阻塞态中解除,进入就绪态,然后运行的时候处理数据,这样子系统的资源就会很好的被利用起来。

4.  运作机制

        创建信号量时,系统会为创建的信号量对象分配内存,并把可用信号量初始化为用户自定义的个数,二值信号量的最大可用信号量个数为 1。

        二值信号量获取,任何任务都可以从创建的二值信号量资源中获取一个二值信号量,获取成功则返回正确,否则任务会根据用户指定的阻塞超时时间来等待其它任务/中断释放信号量。在等待这段时间,系统将任务变成阻塞态,任务将被挂到该信号量的阻塞等待列表中。

        在二值信号量无效的时候,假如此时有任务获取该信号量的话,那么任务将进入阻塞状态。

信号量无效时候获取: 

        假如某个时间中断/任务释放了信号量,那么,由于获取无效信号量而进入阻塞态的任务将获得信号量并且恢复为就绪态。

中断、任务释放信号量:

二值信号量运作机制: 

5.  信号量控制块

        信号量 API 函数实际上都是宏,它使用现有的队列机制,这些宏定义在 semphr.h 文件中,如果使用信号量或者互斥量,需要包含 semphr.h 头文件。所以 FreeRTOS 的信号量控制块结构体与消息队列结构体是一模一样的,只不过结构体中某些成员变量代表的含义不一样而已:

/** 定义调度器使用的队列。* 项目通过复制而非引用排队。请参见以下链接了解详细原因:http://www.freertos.org/Embedded-RTOS-Queues.html*/
typedef struct QueueDefinition
{int8_t *pcHead;                  /*< 指向队列存储区域的开始位置。 */int8_t *pcTail;                  /*< 指向队列存储区域末尾的字节。一次分配比实际需要的多一个字节,以作为标记。 */int8_t *pcWriteTo;              /*< 指向存储区域中下一个可写的位置。 */union                            /* 使用联合体是为了确保两个互斥的结构成员不会同时出现(避免浪费内存)。 */{int8_t *pcReadFrom;         /*< 当结构体被用作队列时,指向最后一个读取项目的位置。 */UBaseType_t uxRecursiveCallCount; /*< 当结构体用作互斥量时,维护递归获取互斥量的次数。 */} u;List_t xTasksWaitingToSend;       /*< 阻塞等待向此队列发送项目的任务列表,按优先级排序。 */List_t xTasksWaitingToReceive;    /*< 阻塞等待从此队列接收项目的任务列表,按优先级排序。 */volatile UBaseType_t uxMessagesWaiting; /*< 当前队列中项目的数量。 */UBaseType_t uxLength;             /*< 队列的长度,定义为队列可以容纳的项目数,而不是字节数。 */UBaseType_t uxItemSize;           /*< 队列将持有的每个项目的大小。 */volatile int8_t cRxLock;          /*< 存储在队列锁定时从队列中接收的项目数量(从队列中移除)。队列未锁定时设置为 queueUNLOCKED。 */volatile int8_t cTxLock;          /*< 存储在队列锁定时传输到队列的项目数量(添加到队列中)。队列未锁定时设置为 queueUNLOCKED。 */#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )uint8_t ucStaticallyAllocated; /*< 如果队列的内存是静态分配的,设置为 pdTRUE,以确保不尝试释放内存。 */#endif#if ( configUSE_QUEUE_SETS == 1 )struct QueueDefinition *pxQueueSetContainer; /*< 如果启用了队列集功能,指向包含此队列的队列集。 */#endif#if ( configUSE_TRACE_FACILITY == 1 )UBaseType_t uxQueueNumber;  /*< 队列编号,用于跟踪功能。 */uint8_t ucQueueType;        /*< 队列类型,用于跟踪功能。 */#endif} xQUEUE;/* 旧的 xQUEUE 名称在上面维护,然后重新定义为新的 Queue_t 名称,以便支持旧的内核调试工具。 */
typedef xQUEUE Queue_t;

6.  常用信号量函数接口API

6.1  创建二值信号量 xSemaphoreCreateBinary()

        xSemaphoreCreateBinary()用于创建一个二值信号量,并返回一个句柄。其实二值信号量和互斥量都共同使用一个类型 SemaphoreHandle_t 的句柄(.h 文件 79 行),该句柄的原型是一个 void 型的指针。使用该函数创建的二值信号量是空的,在使用函数 xSemaphoreTake()获取之前必须先调用函数 xSemaphoreGive()释放后才可以获取。如果是使用老式的函数 vSemaphoreCreateBinary()创建的二值信号量,则为 1,在使用之前不用先释放 。 要想使用该函数必须在 FreeRTOSConfig.h 中把宏 configSUPPORT_DYNAMIC_ALLOCATION 定义为 1,即开启动态内存分配。其实该宏在 FreeRTOS.h 中默认定义为 1,即所有 FreeRTOS 的对象在创建的时候都默认使用动态内存分配方案。

xSemaphoreCreateBinary()函数原型:

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )#define xSemaphoreCreateBinary() \xQueueGenericCreate( \( UBaseType_t ) 1, \ (1)semSEMAPHORE_QUEUE_ITEM_LENGTH, \ (2)queueQUEUE_TYPE_BINARY_SEMAPHORE ) (3)#endif

(1):uxQueueLength 为 1 表示创建的队列长度为 1,其实用作信号量就表示信号量的最大可用个数,从前面的知识点我们就知道,二值信号量的非空即满,长度为 1 不正是这样子的表示吗。

(2):semSEMAPHORE_QUEUE_ITEM_LENGTH 其实是一个宏定义,其值为 0,见文知义,它表示创建的消息空间(队列项)大小为 0,因为这个所谓的“消息队列”其实并不是用于存储消息的,而是被用作二值信号量,因为我们根本无需关注消息内容是什么,只要知道有没有信号量就行了。

(3):ucQueueType 表示的是创建消息队列的类型,在 queue.h 中有定义, 现在创建的是二值信号量,其类型就是queueQUEUE_TYPE_BINARY_SEMAPHORE。

ucQueueType 可选类型:

#define queueQUEUE_TYPE_BASE ( ( uint8_t ) 0U )
#define queueQUEUE_TYPE_SET ( ( uint8_t ) 0U )
#define queueQUEUE_TYPE_MUTEX ( ( uint8_t ) 1U )
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE ( ( uint8_t ) 2U )
#define queueQUEUE_TYPE_BINARY_SEMAPHORE ( ( uint8_t ) 3U )
#define queueQUEUE_TYPE_RECURSIVE_MUTEX ( ( uint8_t ) 4U )

简单来说,就是创建一个没有消息存储空间的队列。

        可能很多人会问了,创建一个没有消息存储空间的队列,信号量用什么表示?其实二值信号量的释放和获取都是通过操作队列结控制块构体成员 uxMessageWaiting 来实现的,它表示信号量中当前可用的信号量个数。在信号量创建之后,变量 uxMessageWaiting 的值为 0,这说明当前信号量处于无效状态,此时的信号量是无法被获取的,在获取信号之前,应先释放一个信号量。后面讲到信号量释放和获取时还会详细介绍。

        我们来举个实例,首先我们继续使用之前移植好的工程模版:

STM32F103ZET6的FreeRTOS工程移植模版_freertos移植stm32资源-CSDN文库

        引入头文件:

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"

        创建一个任务句柄,其类型为SemaphoreHandle_t:

SemaphoreHandle_t BinarySem_Handle =NULL;

        对任务创建函数进行修改:

//任务创建函数
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */taskENTER_CRITICAL();           //进入临界区/* 创建 BinarySem */BinarySem_Handle = xSemaphoreCreateBinary();	 if(NULL != BinarySem_Handle)printf("BinarySem_Handle二值信号量创建成功!\r\n");vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务taskEXIT_CRITICAL();            //退出临界区
}

        完整代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"#include "LED.h"
#include "Usart.h"
#include "Key.h"  /* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/* * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么* 这个句柄可以为NULL。*/
static TaskHandle_t AppTaskCreate_Handle = NULL; /* 创建任务句柄 *//********************************** 内核对象句柄 *********************************/
/** 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核* 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我* 们就可以通过这个句柄操作这些内核对象。** 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,* 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数* 来完成的* */
SemaphoreHandle_t BinarySem_Handle =NULL;/******************************* 宏定义 ************************************/
/** 当我们在写应用程序的时候,可能需要用到一些宏定义。*///一些函数声明
static void AppTaskCreate(void);/* 用于创建任务 */static void All_Function_Init(void);/* 用于初始化板载相关资源 */int main(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */All_Function_Init();//硬件初始化while (1){/* 创建AppTaskCreate任务 */xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,  /* 任务入口函数 */(const char*    )"AppTaskCreate",/* 任务名字 */(uint16_t       )512,  /* 任务栈大小 */(void*          )NULL,/* 任务入口函数参数 */(UBaseType_t    )1, /* 任务的优先级 */(TaskHandle_t*  )&AppTaskCreate_Handle);/* 任务控制块指针 */ /* 启动任务调度 */           if(pdPASS == xReturn)vTaskStartScheduler();   /* 启动任务,开启调度 */elsereturn -1;  }
}//任务创建函数
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */taskENTER_CRITICAL();           //进入临界区/* 创建 BinarySem */BinarySem_Handle = xSemaphoreCreateBinary();	 if(NULL != BinarySem_Handle)printf("BinarySem_Handle二值信号量创建成功!\r\n");vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务taskEXIT_CRITICAL();            //退出临界区
}//初始化声明
static void All_Function_Init(void)
{/** STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,* 都统一用这个优先级分组,千万不要再分组,切忌。*/NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );/* LED 初始化 */LED_GPIO_Config();/* 串口初始化	*/USART_Config();//按键初始化Key_GPIO_Config();}

运行结果

        完整工程:

FreeRTOS二值信号量创建函数的运用.zip资源-CSDN文库

6.2  信号量删除函数 vSemaphoreDelete()

        vSemaphoreDelete()用于删除一个信号量,包括二值信号量,计数信号量,互斥量和递归互斥量。如果有任务阻塞在该信号量上,那么不要删除该信号量。

函数原型void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
功能删除一个信号量
参数xSemaphore信号量句柄
返回值

        删除信号量过程其实就是删除消息队列过程,因为信号量其实就是消息队列,只不过是无法存储消息的队列而已。

6.3  信号量释放函数

        与消息队列的操作一样,信号量的释放可以在任务、中断中使用,所以需要有不一样的 API 函数在不一样的上下文环境中调用。

        在前面的讲解中,我们知道,当信号量有效的时候,任务才能获取信号量,那么,是什么函数使得信号量变得有效?其实有两个方式,一个是在创建的时候进行初始化,将它可用的信号量个数设置一个初始值;在二值信号量中,该初始值的范围是 0~1(旧版本的FreeRTOS 中创建二值信号量默认是有效的,而新版本则默认是无效),假如初始值为 1 个可用的信号量的话,被申请一次就变得无效了,那就需要我们释放信号量,FreeRTOS 提供了信号量释放函数,每调用一次该函数就释放一个信号量。但是有个问题,能不能一直释放?很显然,这是不能的,无论是你的信号量是二值信号量还是计数信号量,都要注意可用信号量的范围,当用作二值信号量的时候,必须确保其可用值在 0~1 范围内;而用作计数信号量的话,其范围是由用户在创建时指定 uxMaxCount,其最大可用信号量不允许超出uxMaxCount,这代表我们不能一直调用信号量释放函数来释放信号量,其实一直调用也是无法释放成功的,在写代码的时候,我们要注意代码的严谨性罢了。

6.3.1  xSemaphoreGive()

        xSemaphoreGive()是一个用于释放信号量的宏,真正的实现过程是调用消息队列通用发送函数。释放的信号量对象必须是已经被创建的,可以用于二值信号量、计数信号量、互斥量的释放,但不能释放由函数xSemaphoreCreateRecursiveMutex()创建的递归互斥量。此外该函数不能在中断中使用。

xSemaphoreGive()函数原型:

#define xSemaphoreGive( xSemaphore )xQQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ),NULL,semGIVE_BLOCK_TIME, queueSEND_TO_BACK)

        从该宏定义可以看出释放信号量实际上是一次入队操作,并且是不允许入队阻塞,因为阻塞时间为 semGIVE_BLOCK_TIME,该宏的值为 0。

        通过消息队列入队过程分析,我们可以将释放一个信号量的过程简化:如果信号量未满,控制块结构体成员 uxMessageWaiting 就会加 1,然后判断是否有阻塞的任务,如果有的话就会恢复阻塞的任务,然后返回成功信息(pdPASS);如果信号量已满,则返回错误代码(err_QUEUE_FULL),我们在上面工程的基础上进行更改,首先添加一个释放任务的任务句柄:

static TaskHandle_t Send_Task_Handle = NULL;

        然后根据上面描述创建一个任务,任务主体如下:

static void Send_Task(void* parameter)
{	 BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */while (1){/* K1 被按下 */if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ){xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量if( xReturn == pdTRUE )printf("BinarySem_Handle二值信号量释放成功!\r\n");elseprintf("BinarySem_Handle二值信号量释放失败!\r\n");} /* K2 被按下 */if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ){xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量if( xReturn == pdTRUE )printf("BinarySem_Handle二值信号量释放成功!\r\n");elseprintf("BinarySem_Handle二值信号量释放失败!\r\n");}vTaskDelay(20);}
}

        在AppTaskCreate()任务中创建相关参数:

  /* 创建Send_Task任务 */xReturn = xTaskCreate((TaskFunction_t )Send_Task,  /* 任务入口函数 */(const char*    )"Send_Task",/* 任务名字 */(uint16_t       )512,  /* 任务栈大小 */(void*          )NULL,/* 任务入口函数参数 */(UBaseType_t    )3, /* 任务的优先级 */(TaskHandle_t*  )&Send_Task_Handle);/* 任务控制块指针 */ if(pdPASS == xReturn)printf("创建Send_Task任务成功!\r\n");

        完整代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"#include "LED.h"
#include "Usart.h"
#include "Key.h"  /* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/* * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么* 这个句柄可以为NULL。*/
static TaskHandle_t AppTaskCreate_Handle = NULL; /* 创建任务句柄 */
static TaskHandle_t Send_Task_Handle = NULL;/********************************** 内核对象句柄 *********************************/
/** 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核* 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我* 们就可以通过这个句柄操作这些内核对象。** 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,* 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数* 来完成的* */
SemaphoreHandle_t BinarySem_Handle =NULL;/******************************* 宏定义 ************************************/
/** 当我们在写应用程序的时候,可能需要用到一些宏定义。*///一些函数声明
static void AppTaskCreate(void);/* 用于创建任务 */static void Send_Task(void* pvParameters);/* Send_Task任务实现 */static void All_Function_Init(void);/* 用于初始化板载相关资源 */int main(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */All_Function_Init();//硬件初始化while (1){/* 创建AppTaskCreate任务 */xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,  /* 任务入口函数 */(const char*    )"AppTaskCreate",/* 任务名字 */(uint16_t       )512,  /* 任务栈大小 */(void*          )NULL,/* 任务入口函数参数 */(UBaseType_t    )1, /* 任务的优先级 */(TaskHandle_t*  )&AppTaskCreate_Handle);/* 任务控制块指针 */ /* 启动任务调度 */           if(pdPASS == xReturn)vTaskStartScheduler();   /* 启动任务,开启调度 */elsereturn -1;  }
}//任务创建函数
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */taskENTER_CRITICAL();           //进入临界区/* 创建 BinarySem */BinarySem_Handle = xSemaphoreCreateBinary();	 if(NULL != BinarySem_Handle)printf("BinarySem_Handle二值信号量创建成功!\r\n");/* 创建Send_Task任务 */xReturn = xTaskCreate((TaskFunction_t )Send_Task,  /* 任务入口函数 */(const char*    )"Send_Task",/* 任务名字 */(uint16_t       )512,  /* 任务栈大小 */(void*          )NULL,/* 任务入口函数参数 */(UBaseType_t    )3, /* 任务的优先级 */(TaskHandle_t*  )&Send_Task_Handle);/* 任务控制块指针 */ if(pdPASS == xReturn)printf("创建Send_Task任务成功!\r\n");vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务taskEXIT_CRITICAL();            //退出临界区
}static void Send_Task(void* parameter)
{	 BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */while (1){/* K1 被按下 */if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ){xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量if( xReturn == pdTRUE )printf("BinarySem_Handle二值信号量释放成功!\r\n");elseprintf("BinarySem_Handle二值信号量释放失败!\r\n");} /* K2 被按下 */if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ){xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量if( xReturn == pdTRUE )printf("BinarySem_Handle二值信号量释放成功!\r\n");elseprintf("BinarySem_Handle二值信号量释放失败!\r\n");}vTaskDelay(20);}
}//初始化声明
static void All_Function_Init(void)
{/** STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,* 都统一用这个优先级分组,千万不要再分组,切忌。*/NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );/* LED 初始化 */LED_GPIO_Config();/* 串口初始化	*/USART_Config();//按键初始化Key_GPIO_Config();}

        运行结果,可以看到由于我们将二值信号量释放后,再次释放此时二值信号量已经没有可以释放的值了,再次释放将会导致释放失败:

        完整工程:

FreeRTOS信号量-二值信号量-释放函数的运用.zip资源-CSDN文库

6.3.2  xSemaphoreGiveFromISR()

        用于释放一个信号量,带中断保护。被释放的信号量可以是二进制信号量和计数信号量。和普通版本的释放信号量 API 函数有些许不同,它不能释放互斥量,这是因为互斥量不可以在中断中使用,互斥量的优先级继承机制只能在任务中起作用,而在中断中毫无意义。带中断保护的信号量释放其实也是一个宏,真正调用的函数是 xQueueGiveFromISR ():

#define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken )xQueueGiveFromISR((QueueHandle_t)( xSemaphore ),(pxHigherPriorityTaskWoken ))

        如果可用信号量未满,控制块结构体成员 uxMessageWaiting 就会加 1,然后判断是否有阻塞的任务,如果有的话就会恢复阻塞的任务,然后返回成功信息(pdPASS),如果恢复的任务优先级比当前任务优先级高,那么在退出中断要进行任务切换一次;如果信号量满,则返回错误代码(err_QUEUE_FULL),表示信号量满。

FreeRTOS实时操作系统_时光の尘的博客-CSDN博客

相关文章:

  • TCP 与 UDP报文
  • 《数字图像处理(面向新工科的电工电子信息基础课程系列教材)》封面颜色空间一图的选图历程
  • linux系统基本操作命令
  • asyncio:Python的异步编程
  • Lua 元表和元方法
  • 【ArcGIS Pro微课1000例】0066:多边形要素添加折点,将曲线线段(贝塞尔、圆弧和椭圆弧)替换为线段?
  • Webug4.0靶场通关笔记16- 第20关文件上传(截断上传)
  • 【NLP】 31. Retrieval-Augmented Generation(RAG):KNN-LM, RAG、REALM、RETRO、FLARE
  • # 从零构建一个简单的卷积神经网络:手写数字识别
  • 【Unity】AssetBundle热更新
  • HTML 元素
  • 冷启动算法简介和示例
  • 【了解】数字孪生网络(Digital Twin Network,DTN)
  • 代码随想录算法训练营第60期第二十七天打卡
  • ABC 404
  • sudo useradd -r -s /bin/false -U -m -d /usr/share/ollama ollama解释这行代码的含义
  • 机器人强化学习入门学习笔记(二)
  • HTML05:超链接标签及应用
  • 永磁同步电机控制算法--基于PI和前馈的位置伺服控制
  • 告别(Python)if elif else错误使用方法
  • 今年五一档电影票房已破7亿
  • 国铁:今天预计发送旅客2110万人次,加开列车1896列
  • 当AI开始谋财害命:从骗钱到卖假药,人类该如何防范?
  • 浙江医生举报3岁男童疑遭生父虐待,妇联:已跟爷爷奶奶回家
  • 三亚再回应游客骑摩托艇出海遇暴雨:俱乐部未配备足额向导人员,停业整改
  • 多地景区发公告称售票达接待峰值,有景区暂停网络和线下售票