当前位置: 首页 > 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博客

相关文章:

FreeRTOS学习系列·二值信号量

目录 1. 信号量的基本概念 2. 二值信号量 3. 应用场景 4. 运作机制 5. 信号量控制块 6. 常用信号量函数接口API 6.1 创建二值信号量 xSemaphoreCreateBinary() 6.2 信号量删除函数 vSemaphoreDelete() 6.3 信号量释放函数 6.3.1 xSemaphoreGive() 6.3.2 …...

二叉搜索树 AVL树 红黑树 的性质

二叉搜索树 如何判断一棵树是否是二叉搜索树&#xff1f; 1.方法一 左子树的所有节点值 < 当前节点值&#xff0c;右子树的所有节点值 > 当前节点值&#xff0c;左右子树也必须满足 。 2.方法二 中序遍历&#xff0c;得到的序列是有序的 红黑树 红黑树的性质 1.根…...

分析 Docker 磁盘占用

以下是分析 Docker 磁盘占用的详细步骤和工具指南&#xff0c;帮助开发者快速定位和清理冗余数据&#xff1a; 1. 查看 Docker 磁盘使用概览 docker system df 输出说明&#xff1a; TYPE TOTAL ACTIVE SIZE RECLAIMABLE Images 15 …...

LLM提示词设计及多轮对话优化策略在心理健康咨询场景中的应用研究

多轮对话策略:设计LLM提示词并优化多轮对话输出 LLM提示词设计及多轮对话优化策略在心理健康咨询场景中的应用研究 摘要 本文针对大语言模型(LLM)在心理健康问题咨询对话场景中的应用,系统研究提示词设计方法及多轮对话优化策略。通过分析提示词核心原理,构建包含任务指…...

Kubernetes(k8s)学习笔记(七)--KubeSphere 最小化安装

前情提要 可视化操作面板对于开发、运维绝对是提升工作效率的一大利器&#xff0c;因此很有必要搭建一套可视化操作来管理Kubernetes。 可视化面板有多种&#xff1a; 1.Kubernetes官方提供的默认面板&#xff1a;dashboard&#xff0c;用处不大&#xff0c;放弃&#xff1b…...

面向对象与过程介绍

一、面向对象具体介绍 &#xff08;1&#xff09;基本概念 1.对象 在面向对象编程中&#xff0c;对象是对现实世界中事物的抽象&#xff0c;它具有状态&#xff08;属性&#xff09;和行为&#xff08;方法&#xff09;。在 TypeScript 中&#xff0c;我们可以通过类、接口等…...

UniGetUI 使用指南:轻松管理 Windows 软件(包括CUDA)

UniGetUI&#xff08;前身为 WingetUI&#xff09;是一款专门为 Windows 10&#xff08;x64&#xff09;和 Windows 11 系统打造的图形化包管理器界面工具。它集成了 Winget、Scoop、Chocolatey、Npm、Pip、Cargo、vcpkg、.NET Tool 和 PowerShell 等多种常用包管理器的功能&am…...

使用 NGINX 实现 HTTP Basic 认证ngx_http_auth_basic_module 模块

一、前言 在 Web 应用中&#xff0c;对部分资源进行访问控制是十分常见的需求。除了基于 IP 限制、JWT 验证、子请求校验等方式外&#xff0c;最经典也最简单的一种方式便是 HTTP Basic Authentication。NGINX 提供的 ngx_http_auth_basic_module 模块支持基于用户名和密码的基…...

014枚举之指针尺取——算法备赛

枚举是数据结构与算法中基本的操作&#xff0c;常用于解决序列的区间问题。算法界将"双指针"视为其重要分支&#xff0c;类似地当然还有"三指针"&#xff0c;“四指针”&#xff0c;最常见的还是“双指针”&#xff0c;我认为它们应统称为“指针尺取”。 双…...

Python小酷库系列:bidict,可以双向查询的dict

bidict&#xff0c;可以双向查询的dict 基本使用创建双向字典正向查找&#xff08;key → value&#xff09;反向查找&#xff08;value → key&#xff09;添加新项删除项 进阶功能使用 ~ &#xff08;.inverse&#xff09;获取逆映射使用 namedbidict 创建具名双向字典批量更…...

Android Compose 中 CompositionLocal 的全面解析与最佳实践

CompositionLocal 在 Android Compose 中的详细总结 核心概念 CompositionLocal 是 Jetpack Compose 提供的一种隐式数据传递机制&#xff0c;允许数据在组件树中向下传递&#xff0c;而无需显式地通过每个 Composable 函数的参数传递。 两种创建方式 1. staticComposition…...

Qt开发:容器组控件的介绍和使用

文章目录 一、Group Box&#xff08;分组框&#xff09;1.1 QGroupBox 简介1.2 基本用法1.3 设置为可勾选&#xff08;可启用/禁用子控件&#xff09;1.4 信号与槽连接&#xff08;监控勾选状态&#xff09;1.5 布局示例&#xff08;完整&#xff09; 二、Scroll Area&#xff…...

JS逆向入门案例1——集思录登录

JS逆向入门案例1——集思录登录 前言声明网站流程分析总结 前言 由于这段时间本职工作比较繁忙&#xff0c;没有很多空余的时间去研究各大厂的加密风控了&#xff0c;想起来自己刚接触js逆向走过坎坷&#xff0c;所以决定出一期js入门案例分析&#xff0c;为刚接触js逆向的小伙…...

ARM子程序和栈

微处理器中的栈由栈指针指向存储器中的栈顶来实现&#xff0c;当数据项入栈时&#xff0c;栈 指针向上移动&#xff0c;当数据项出栈时&#xff0c;栈指针向下移动。 实现栈时需要做出两个决定&#xff1a;一是当数据项进栈时是向低位地址方向向上生 长&#xff08;图a和图b&a…...

笔试专题(十五)

文章目录 排序子序列题解代码 消减整数题解代码 最长公共子序列(二)题解代码 排序子序列 题目链接 题解 1. 贪心 模拟 2. 1 2 3 2 2 应该是有两个排列子序列的&#xff0c;所以i n-1时ret 3. 把水平的位置和上升部分&#xff0c;水平位置和下降部分分为一个排列子序列 代…...

使用OpenCV 和 Dlib 进行卷积神经网络人脸检测

文章目录 引言1.准备工作2.代码解析2.1 导入必要的库2.2 加载CNN人脸检测模型2.3 加载并预处理图像2.4 进行人脸检测2.5 绘制检测结果2.6 显示结果 3.完整代码4.性能考虑5.总结 引言 人脸检测是计算机视觉中最基础也最重要的任务之一。今天我将分享如何使用dlib库中的CNN人脸检…...

某信服EDR3.5.30.ISO安装测试(一)

一、前言 1.某信服EDR3.5.30 以下简称“EDR3.5”&#xff0c;即统一端点安全管理系统aES(终端检测响应EDR)&#xff0c; 官网最新版&#xff1a;aES6.0.1R2&#xff0c;可下载的最低版本&#xff1a;EDR3.7.11R3&#xff0c; 下载地址&#xff1a;统一端点安全管理系统aES-…...

Dify 快速构建和部署基于LLM的应用程序

本文先对Dify做一个初步的认识&#xff0c;然后以一个实际的简单金融问答案例&#xff0c;配置chatflow 工作流。 一、Dify简介 如果你是第一次接触Dify&#xff0c;可以先创建一个简单的聊天助手&#xff0c;初步感觉一下&#xff0c;Dify在构建聊天问答类应用的过程。 比如…...

精益数据分析(40/126):移动应用商业模式的关键指标与盈利策略

精益数据分析&#xff08;40/126&#xff09;&#xff1a;移动应用商业模式的关键指标与盈利策略 在创业和数据分析的探索之路上&#xff0c;我们持续挖掘不同商业模式的内在规律&#xff0c;以寻求更好的发展机遇。今天&#xff0c;我们依旧秉持共同进步的理念&#xff0c;深…...

JavaScript 实现输入框的撤销功能

在 Web 开发中&#xff0c;为输入框添加撤销功能可以极大地提升用户体验&#xff0c;方便用户快速回滚到之前的输入状态。本文将通过一段简单的 HTML、CSS 和 JavaScript 代码&#xff0c;详细介绍如何实现输入框的撤销功能。 整体实现思路 利用 JavaScript 监听输入框的inpu…...

【C++】类和对象(一)

前言 类和对象第一部分知识包括定义访问限定符类域实例化this指针 本人其他文章&#xff1a;恋风诗 文章中的源码[gitte]&#xff1a;mozhengy 类和对象&#xff08;一&#xff09; 前言1. 类的定义引例1.1 类定义格式1.2 类的访问限定符1.3 类域 2. 实例化2.1 实例化概念2.2 …...

【Vue】Vue3源码解析与实现原理

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Vue 文章目录 1. Vue 3 架构概览1.1 模块化设计1.2 整体流程 2. 响应式系统2.1 响应式原理2.2 ref 和 reactive2.3 依赖收集与触发更新 3. 渲染系统3.1 虚拟DOM设计3.2 渲染管线3.3 Patch算法与Diff优化 4. 组件系统4.1 组件创建…...

黑马点评day02(缓存)

2、商户查询缓存 2.1 什么是缓存? 前言:什么是缓存? 就像自行车,越野车的避震器 举个例子:越野车,山地自行车,都拥有"避震器",防止车体加速后因惯性,在酷似"U"字母的地形上飞跃,硬着陆导致的损害,像个弹簧一样; 同样,实际开发中,系统也需要"避震…...

数据库MySQL学习——day9(聚合函数与分组数据)

文章目录 1. 聚合函数1.1 COUNT() 函数1.2 SUM() 函数1.3 AVG() 函数1.4 MIN() 函数1.5 MAX() 函数 2. GROUP BY 子句2.1 使用 GROUP BY 进行数据分组2.2 结合聚合函数 3. HAVING 子句3.1 使用 HAVING 过滤分组数据3.2 HAVING 和 WHERE 的区别 4. 实践任务4.1 创建一个销售表4.…...

为React组件库引入自动化测试:从零到完善的实践之路

为什么我们需要测试&#xff1f; 我们的ReactTypeScript业务组件库已经稳定运行了一段时间&#xff0c;主要承载各类UI展示组件&#xff0c;如卡片、通知等。项目初期&#xff0c;迫于紧张的开发周期&#xff0c;我们暂时搁置了自动化测试的引入。当时团队成员对组件逻辑了如指…...

数据结构——算法复杂度

一、数据结构定义 数据结构(Data Structure)是计算机存储、组织数据的⽅式&#xff0c;指相互之间存在⼀种或多种特定关系的数据元素的集合。没有⼀种单⼀的数据结构对所有⽤途都有⽤&#xff0c;所以我们要学各式各样的数据结构&#xff0c;如&#xff1a;线性表、树、图、哈希…...

Vue3响应式原理那些事

文章目录 1 响应式基础:Proxy 与 Reflect1.1 Proxy 代理拦截1.2 Reflect 确保 `this` 指向正确1.2.1 修正 `this` 指向问题1.2.2 统一的操作返回值1.3 与 Vue2 的对比2 依赖收集与触发机制2.1 全局依赖存储结构:WeakMap → Map → Set2.2 依赖收集触发时机2.3 依赖收集核心实…...

记9(Torch

目录 1、Troch 1、Troch 函数说明举例torch.tensor()torch.arange()创建张量创建一个标量&#xff1a;torch.tensor(42)创建一个一维张量&#xff1a;torch.tensor([1, 2, 3])创建一个二维张量&#xff1a;torch.tensor([[1, 2], [3, 4]])生成一维等差张量&#xff1a;语法&am…...

机器学习模型训练模块技术文档

一、模块结构概览 import numpy as np from sklearn.model_selection import cross_validate, learning_curve from sklearn.pipeline import make_pipeline from sklearn.svm import SVC from sklearn.neighbors import KNeighborsClassifier from sklearn.preprocessing imp…...

健康养生:从微小改变开始

养生不必大刀阔斧&#xff0c;几个微小改变&#xff0c;就能让健康慢慢扎根生活。晨起别急着洗漱&#xff0c;先花 5 分钟靠墙站立&#xff0c;拉伸脊柱、调整体态&#xff0c;唤醒身体。早餐把白米粥换成杂粮粥&#xff0c;搭配水煮蛋和一小碟凉拌黄瓜&#xff0c;营养更全面。…...

某信服EDR3.5.30.ISO安装测试(二)

一、物理机启动EDR 1、修复dracut 使用DiskGenius克隆虚拟磁盘到物理磁盘&#xff0c;将虚拟机移植到物理机&#xff0c;因为磁盘UUID变化等原因&#xff0c;首次默认启动失败&#xff0c;提示&#xff1a; Starting Dracut Emergency Shell... Warning:/dev/centos/root doe…...

Leetcode:回文链表

1、题目描述 给定一个链表的 头节点 head &#xff0c;请判断其是否为回文链表。 如果一个链表是回文&#xff0c;那么链表节点序列从前往后看和从后往前看是相同的。 示例 1&#xff1a; 输入: head [1,2,3,3,2,1] 输出: true 示例 2&#xff1a; 输入: head [1,2] 输出: …...

IL2CPP 技术深度解析

IL2CPP 是 Unity 开发的高性能脚本后端&#xff0c;它将 .NET 的中间语言 (IL) 转换为 C 代码&#xff0c;再编译为原生平台二进制文件。以下是 IL2CPP 的全面技术剖析。 一、架构设计原理 1. 整体编译流程 C# 源代码 → Roslyn 编译器 → IL (.NET DLL)→ IL2CPP 转换器 →…...

AI图片修复工具,一键操作,图片更清晰!

泛黄的老相册里藏着一座记忆博物馆&#xff0c;每张照片都是时光长河中的琥珀。祖父军装照上的折痕里藏着抗美援朝的故事&#xff0c;父母结婚照褪色的红唇映照着八十年代的风尚&#xff0c;童年抓拍照上模糊的身影正重演着我们成长的轨迹。这些承载着集体记忆的影像&#xff0…...

国内短剧 vs. 海外短剧系统:如何选择?2025年深度对比与SEO优化指南

在短剧市场爆发式增长的背景下&#xff0c;国内与海外短剧系统成为创业者与企业的热门选择。本文将从市场潜力、开发成本、内容创作、盈利模式及风险五大维度&#xff0c;结合最新行业数据与案例&#xff0c;深度解析两者的优劣势&#xff0c;助您做出最优决策。 一、市场前…...

linux crash工具详解

crash 是 Linux 系统中用于分析内核转储文件&#xff08;如 vmcore 或 kdump 生成的 dump 文件&#xff09;的核心工具。它结合了调试符号和内核数据结构&#xff0c;能够直观地查看崩溃时的系统状态。以下是其详细使用方法及核心功能解析&#xff1a; 一、安装与准备 1. 安装…...

Scala day6(Class,field,Single Object)

Foreword【こんにちは】 Today, we continue to learn Scala Language, though the Scala isn’t easy for Begainner, Big Data Technology need Scala Language, Spark【Distributed Calculation FrameWork】is based on Scala Language designed. I know the learning road…...

不小心把当前的环境变量路径覆盖掉怎么办

起因 配置环境变量&#xff08;~/.bashrc&#xff09;的时候没加:$PATH&#xff0c;导致 source ~/.bashrc之后只剩下刚刚配置的环境变量了。连vim都打不开 解决 添加临时环境变量export PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH再重新修改…...

unity TMP字体使用出现乱码方框

参考文章&#xff1a; Unity 设置默认字体&#xff08;支持老版及新版TMP&#xff09;_unity tmp字体-CSDN博客 原因是导入的项目package包没有连着tmp一起&#xff0c;这样在新工程中导入的tmp字体默认的是tmp自己的&#xff0c;解决方案就是替换成自己需要的tmp字体就行 替换…...

14.网络钓鱼实战

网络钓鱼实战 第一部分&#xff1a;网络钓鱼攻击详解第二部分&#xff1a;设计与分析钓鱼攻击第三部分&#xff1a;钓鱼攻击防范实践总结 目标: • 深入理解网络钓鱼攻击的实施过程 • 掌握设计和识别钓鱼攻击的技巧 • 通过模拟实践提升防范钓鱼攻击的能力 第一部分&#xf…...

pyqt写一个单片机配置界面

已经实现以下功能 1.可以选择单片机架构 2.选择完单片机架构后第二个框可以选择常见单片机型号 3.选择完常见单片机型号后第三个框可以选择内部资源如adc等&#xff08;可以选择多个内部资源&#xff09;4.选择完内部资源如adc等&#xff08;可以选择多个内部资源&#xff09;后…...

「Mac畅玩AIGC与多模态20」开发篇16 - 使用结构化输出字段控制后续流程示例

一、概述 本篇介绍如何在工作流中使用结构化输出字段作为判断依据&#xff0c;实现前后节点联动控制。通过执行 LLM 节点输出结构化 JSON&#xff0c;并使用其中的字段驱动后续判断节点执行不同路径&#xff0c;开发人员将掌握结构化字段在工作流中的引用方式与分支控制技巧。…...

Unity-Shader详解-其四

今天我们来聊Unity特有的表面着色器以及很少提到的几何着色器。 表面着色器 在前文关于光照的计算中&#xff0c;我们学会了很多&#xff1a;我们学习了一系列光照模型&#xff0c;比如专门针对漫反射的兰伯特模型和改进的半兰伯特模型&#xff0c;又比如由高光、漫反射和环境…...

Lua 元表和元方法

元表(Metatable)和元方法(Metamethod)是Lua中实现面向对象编程、操作符重载和自定义行为的重要机制。 元表 元表是一个普通的Lua表&#xff0c;可以附加到另一个表上&#xff0c;用于定义或修改该表的行为。每个表都可以有自己的元表。 setmetatable(tab,metatab) 将metatab设…...

GESP2024年3月认证C++八级( 第二部分判断题(6-10))

海伦公式参考程序&#xff1a; #include <iostream> #include <cmath> // 引入cmath库以使用sqrt函数using namespace std;double calculateTriangleArea(int a, int b, int c) {// 使用海伦公式double s (a b c) / 2.0; // 半周长return sqrt(s * (s - a) *…...

Nacos源码—3.Nacos集群高可用分析一

大纲 1.Nacos集群的几个问题 2.单节点对服务进行心跳健康检查和同步检查结果 3.集群新增服务实例时如何同步给其他节点 4.集群节点的健康状态变动时的数据同步 5.集群新增节点时如何同步已有服务实例数据 1.Nacos集群的几个问题 问题一&#xff1a;在单机模式下&#xff…...

信息系统项目管理师-软考高级(软考高项)​​​​​​​​​​​2025最新(九)

个人笔记整理---仅供参考 第九章项目范围管理 9.1管理基础 9.2项目范围管理过程 9.3规划范围管理 9.4收集需求 9.5定义范围 9.6创建WBS 9.7确认范围 9.8控制范围...

DeepSeek学术论文写作全流程指令

一、选题与领域界定 研究热点捕捉 指令: “在[研究领域]中,现有文献对[具体问题]的[哪方面]研究不足?基于近5年文献归纳3个待突破方向,需结合高频关键词和交叉学科维度。” 示例: “在深度学习医疗影像分析中,现有文献对小样本训练的泛化性研究不足?基于2019-2023年顶会…...

【ArUco boards】标定板检测

之前定位用的Charuco标定板做的&#xff08;https://blog.csdn.net/qq_45445740/article/details/143897238&#xff09;&#xff0c;因为实际工况中对标定板的尺寸有要求&#xff0c;大概是3cm*2cm这个尺寸&#xff0c;加上选用的是ChAruco标定板&#xff0c;导致每一个aruco码…...

2025 年 408 真题及答案

2025 年 408 真题 历年408真题及答案下载直通车 1、以下 C 代码的时间复杂度是多少&#xff1f;&#xff08;&#xff09; int count 0; for (int i0; i*i<n; i)for (int j0; j<i; j)count;A O(log2n)B O(n)C O(nlogn)D O(n2) 2、对于括号匹配问题&#xff0c;符号栈…...