实时操作系统-7-[FreeRTOS]信号量·二值信号量与计数信号量

信号量控制块

信号量 API 函数实际上都是宏,它使用现有的队列机制,这些宏定义在 semphr.h 文件中,如果使用信号量或者互斥量,需要包含 semphr.h 头文件:

/*
 * 定义调度器使用的队列。
 * 项目通过复制而非引用排队。请参见以下链接了解详细原因: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;

其中对于参数:

    volatile UBaseType_t uxMessagesWaiting; /*< 当前队列中项目的数量。 */
    UBaseType_t uxLength;             /*< 队列的长度,定义为队列可以容纳的项目数,而不是字节数。 */
    UBaseType_t uxItemSize;           /*< 队列将持有的每个项目的大小。 */

对于第一行:

  • 如果控制块结构体是用于消息队列:uxMessagesWaiting 用来记录当前消息队列的消息个数;
  • 如果控制块结构体被用于信号量的时候,这个值就表示有效信号量个数。

有以下两种情况:

  • 如果信号量是二值信号量、互斥信号量,这个值是 1 则表示有可用信号量,如果是 0 则表示没有可用信号量。
  • 如果是计数信号量,这个值表示可用的信号量个数,在创建计数信号量的时候会被初始化一个可用信号量个数 uxInitialCount,最大不允许超过创建信号量的初始值 uxMaxCount。

对于第二行:

  • 如果控制块结构体是用于消息队列:uxLength 表示队列的长度,也就是能存放多少消息;
  • 如果控制块结构体被用于信号量的时候,uxLength 表示最大的信号量可用个数。

会有以下两种情况:

  • 如果信号量是二值信号量、互斥信号量,uxLength 最大为 1,因为信号量要么是有效的,要么是无效的。
  • 如果是计数信号量,这个值表示最大的信号量个数,在创建计数信号量的时候将由用户指定这个值 uxMaxCount。

对于第三行:

  • 如果控制块结构体是用于消息队列:uxItemSize 表示单个消息的大小;
  • 如果控制块结构体被用于信号量的时候,则无需存储空间,为 0 即可。

从这里我们不难看出,在 FreeRTOS中,信号量控制块结构体与消息队列结构体是一模一样的,只不过结构体中某些成员变量代表的含义不一样而已。

常用信号量函数接口

创建信号量函数

创建二值信号量 xSemaphoreCreateBinary()

xSemaphoreCreateBinary()用于创建一个二值信号量,并返回一个句柄。其实二值信号量和互斥量都共同使用一个类 型 SemaphoreHandle_t 的句柄(.h 文件 79 行),该句柄的原型是一个 void 型的指针。
image

image

使用该函数创建的二值信号量是空的,在使用函数 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

在这里我们可以发现对于二值信号量创建函数 xSemaphoreCreateBinary() 其中所使用的函数 xQueueGenericCreate() 实际上和消息队列创建函数中所调用的是同一个函数:
image

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

二值信号量创建完成示意图:
image

举个使用的例子
首先引入头文件,前面我们也说了信号量的相关参数都存放在 semphr.h 将其引入:

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

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

SemaphoreHandle_t BinarySem_Handle = NULL;

对任务创建函数AppTaskCreate()进行修改,调用xSemaphoreCreateBinary()函数,进行创建二值信号量,若是创建成功则返回一个有效值,即 BinarySem_Handle 不为NULL,若是创建失败则返回 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();            //退出临界区
}

main函数完整代码:

#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();   /* 启动任务,开启调度 */
		else
			return -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();
  
}

运行结果:
image

创建计数信号量 xSemaphoreCreateCounting()

xSemaphoreCreateCounting ()用于创建一个计数信号量。要想使用该函数必须在 FreeRTOSConfig.h 中把宏 configSUPPORT_DYNAMIC_ALLOCATION 定义为 1,即开启动态内存分配。其实该宏在 FreeRTOS.h 中默认定义为 1,即所有 FreeRTOS 的对象在创建的时候都默认使用动态内存分配方案。

其实计数信号量跟二值信号量的创建过程都差不多,其实也是间接调用 xQueueGenericCreate()函数进行创建:
image

创建计数信号量 xQueueCreateCountingSemaphore()函数原型:

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
	#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )
#endif

源码:

#if( ( configUSE_COUNTING_SEMAPHORES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
 
	QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount )
	{
	QueueHandle_t xHandle;
 
		configASSERT( uxMaxCount != 0 );
		configASSERT( uxInitialCount <= uxMaxCount );
 
		xHandle = xQueueGenericCreate( uxMaxCount, queueSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_COUNTING_SEMAPHORE );
 
		if( xHandle != NULL )
		{
			( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;
 
			traceCREATE_COUNTING_SEMAPHORE();
		}
		else
		{
			traceCREATE_COUNTING_SEMAPHORE_FAILED();
		}
 
		return xHandle;
	}
 
#endif /* ( ( configUSE_COUNTING_SEMAPHORES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) ) */

从中我们可以看出创建计数信号量仍然调用通用队列创建函数 xQueueGenericCreate()来创建一个计数信号量,信号量最大个数由参数 uxMaxCount 指定,每个消息空间的大小由宏 queueSEMAPHORE_QUEUE_ITEM_LENGTH 指定,这个宏被定义为 0,也就是说创建的计数信号量只有消息队列控制块结构体存储空间而没有消息存储空间,这一点与二值信号量一致,创建的信号量类型是计数信号量 queueQUEUE_TYPE_COUNTING_SEMAPHORE。如果创建成功,还会将消息队列控制块中的 uxMessagesWaiting 成员变量赋值为用户指定的初始可用信号量个数 uxInitialCount,如果这个值大于 0,则表示此时有 uxInitialCount 个计数信号量是可用的,这点与二值信号量的创建不一样,二值信号量在创建成功的时候是无效的(FreeRTOS 新版源码,旧版源码在创建成功默认是有效的 )。

如果我们创建一个最大计数值为 5,并且默认有效的可用信号量个数为 5 的计数信号量,那么计数信号量创建成功的示意图:
image

举个使用的例子
找到FreeRTOSConfig.h文件,找到 configUSE_COUNTING_SEMAPHORES 将其置为1:

#define configUSE_COUNTING_SEMAPHORES		1

image

同样的为计数信号量创建一个句柄:

SemaphoreHandle_t CountSem_Handle =NULL;

然后对任务创建函数AppTaskCreate()进行修改,原理和二值信号量差不多:

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

main函数源码:

#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 CountSem_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();   /* 启动任务,开启调度 */
		else
			return -1;  
	}
}
 
//任务创建函数
static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  taskENTER_CRITICAL();           //进入临界区
 
  /* 创建Test_Queue */
  CountSem_Handle = xSemaphoreCreateCounting(5,5);	 
  if(NULL != CountSem_Handle)
    printf("CountSem_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();
  
}

运行结果:
image

信号量删除函数 vSemaphoreDelete()

vSemaphoreDelete()用于删除一个信号量,包括二值信号量,计数信号量,互斥量和递归互斥量。
如果有任务阻塞在该信号量上,那么不要删除该信号量。意思就是如果有任务想要接收一个信号量的值,另一个任务你给删了,那么想要接收信号量的任务将接收不到信号量了,所以有任务想要接收信号量,不要去删除它。
image

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

我们来验证一下,这里以二值信号量为例,我们先使用上面二值信号量的代码,在其基础上进行更改,这里主要是对任务创建函数 AppTaskCreate() 的更改,我们调用删除函数 vSemaphoreDelete(),删除二值信号量的句柄 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");
 
	vSemaphoreDelete(BinarySem_Handle);
 
	BinarySem_Handle = NULL;
 
  if(NULL == BinarySem_Handle)
    printf("BinarySem_Handle二值信号量删除成功!\r\n");
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}

image

注意,这里手动置NULL的原因是 vSemaphoreDelete() 只负责释放信号量的内存,但不会修改传入的句柄变量。必须手动将 BinarySem_Handle = NULL,否则后续误用该句柄可能导致崩溃。

信号量释放函数

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

前面我们知道只有在信号量有效的时候,任务才能获取信号量,那么,是什么函数使得信号量变得有效?我们可以在创建的时候进行初始化,将它可用的信号量个数设置一个初始值。

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

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");
      else
        printf("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");
      else
        printf("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");

main 完整代码:

#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();   /* 启动任务,开启调度 */
		else
			return -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");
      else
        printf("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");
      else
        printf("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();
  
}

运行结果:
image

可以看出当我们是放过一次后,由于此时没有获取信号量的代码,此时的信号量并没有被消耗,是满的状态,当再次释放就会释放失败。

计数信号量举例
这里我们以停车场的模型为例,主要完成功能开始时创建拥有五个停车位的停车场,且全部被占满,我们通过按键按下,按下一次释放一个停车位,同时向外界提示还有多少停车位。

相当于创建一个初始全部被占用的计数信号量,点击一下释放一个
首先,老规矩创建一个任务句柄,用于指向按键释放的任务:

static TaskHandle_t Give_Task_Handle = NULL;/* Give_Task任务句柄 */

然后创建释放的任务主体,通过调用xSemaphoreGive()获取计数信号量句柄,进行释放,同时通过调用uxSemaphoreGetCount()获取信号量当前计数值的 API 函数,其核心作用是实时查询信号量的可用资源数量:

//Give_Task任务主体
static void Give_Task(void* parameter)
{	 
  BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
  /* 任务都是一个无限循环,不能返回 */
  while (1)
  {
    //如果KEY2被单击
		if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )       
		{
			/* 获取一个计数信号量 */
      xReturn = xSemaphoreGive(CountSem_Handle);//给出计数信号量                  
			if ( pdTRUE == xReturn ) 
			{
				printf( "KEY2被按下,释放1个停车位!\r\n" );
				printf("当前剩余车位:%d\r\n", uxSemaphoreGetCount(CountSem_Handle));
			}
			else
				printf( "KEY2被按下,但已无车位可以释放!\r\n" );							
		}
		vTaskDelay(20);     //每20ms扫描一次	
  }
}

然后对创建函数进行修改,其中创建函数创建计数信号量最大值为5,可用初始值为0:

//任务创建函数
static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  taskENTER_CRITICAL();           //进入临界区
 
  /* 创建Test_Queue */
  CountSem_Handle = xSemaphoreCreateCounting(5,0);	 
  if(NULL != CountSem_Handle)
    printf("CountSem_Handle计数信号量创建成功!\r\n");
 
  /* 创建Give_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Give_Task,  /* 任务入口函数 */
                        (const char*    )"Give_Task",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )3, /* 任务的优先级 */
                        (TaskHandle_t*  )&Give_Task_Handle);/* 任务控制块指针 */ 
  if(pdPASS == xReturn)
    printf("创建Give_Task任务成功!\r\n");
 
	printf("当前剩余车位:%d\r\n", uxSemaphoreGetCount(CountSem_Handle));
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}

完整main函数代码:

#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 Give_Task_Handle = NULL;/* Give_Task任务句柄 */
 
/********************************** 内核对象句柄 *********************************/
/*
 * 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
 * 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
 * 们就可以通过这个句柄操作这些内核对象。
 *
 * 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
 * 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
 * 来完成的
 * 
 */
SemaphoreHandle_t CountSem_Handle =NULL;
 
/******************************* 宏定义 ************************************/
/*
 * 当我们在写应用程序的时候,可能需要用到一些宏定义。
 */
 
 
//一些函数声明
static void AppTaskCreate(void);/* 用于创建任务 */
 
static void Give_Task(void* pvParameters);/* Give_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();   /* 启动任务,开启调度 */
		else
			return -1;  
	}
}
 
//任务创建函数
static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  taskENTER_CRITICAL();           //进入临界区
 
  /* 创建Test_Queue */
  CountSem_Handle = xSemaphoreCreateCounting(5,0);	 
  if(NULL != CountSem_Handle)
    printf("CountSem_Handle计数信号量创建成功!\r\n");
 
  /* 创建Give_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Give_Task,  /* 任务入口函数 */
                        (const char*    )"Give_Task",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )3, /* 任务的优先级 */
                        (TaskHandle_t*  )&Give_Task_Handle);/* 任务控制块指针 */ 
  if(pdPASS == xReturn)
    printf("创建Give_Task任务成功!\r\n");
 
	printf("当前剩余车位:%d\r\n", uxSemaphoreGetCount(CountSem_Handle));
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}
 
//Give_Task任务主体
static void Give_Task(void* parameter)
{	 
  BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
  /* 任务都是一个无限循环,不能返回 */
  while (1)
  {
    //如果KEY2被单击
		if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )       
		{
			/* 获取一个计数信号量 */
      xReturn = xSemaphoreGive(CountSem_Handle);//给出计数信号量                  
			if ( pdTRUE == xReturn ) 
			{
				printf( "KEY2被按下,释放1个停车位!\r\n" );
				printf("当前剩余车位:%d\r\n", uxSemaphoreGetCount(CountSem_Handle));
			}
			else
				printf( "KEY2被按下,但已无车位可以释放!\r\n" );							
		}
		vTaskDelay(20);     //每20ms扫描一次	
  }
}
 
//初始化声明
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();
  
}

运行结果:
image

xSemaphoreGiveFromISR() (中断)

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

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

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

信号量获取函数

与释放信号量对应的是获取信号量,我们知道,当信号量有效的时候,任务才能获取信号量,当任务获取了某个信号量的时候,该信号量的可用个数就减一,当它减到 0 的时候,任务就无法再获取了,并且获取的任务会进入阻塞态(假如用户指定了阻塞超时时间的话)。如果某个信号量中当前拥有 1 个可用的信号量的话,被获取一次就变得无效了,那么此时另外一个任务获取该信号量的时候,就会无法获取成功,该任务便会进入阻塞态,阻塞时间由用户指定。

xSemaphoreTake() (非中断)

xSemaphoreTake()函数用于获取信号量,不带中断保护。获取的信号量对象可以是二值信号量、计数信号量和互斥量,但是递归互斥量并不能使用这个 API 函数获取。其实获取信号量是一个宏,真正调用的函数是 xQueueGenericReceive ()。该宏不能在中断使用,而是必须由具体中断保护功能的 xQueueReceiveFromISR()版本代替。
image

从该宏定义可以看出释放信号量实际上是一次消息出队操作,阻塞时间由用户指定 xBlockTime,当有任务试图获取信号量的时候,当且仅当信号量有效的时候,任务才能读获取到信号量。如果信号量无效,在用户指定的阻塞超时时间中,该任务将保持阻塞状态以等待信号量有效。当其它任务或中断释放了有效的信号量,该任务将自动由阻塞态转移为就绪态。当任务等待的时间超过了指定的阻塞时间,即使信号量中还是没有可用信号量,任务也会自动从阻塞态转移为就绪态。

二值信号量举例
上面我们在二值信号量举例的时候,知道当信号量被释放后没有被获取,二值信号量就无法继续释放,那么下面我们在其中加入获取的代码,首先创建一个获取的任务句柄:

static TaskHandle_t Receive_Task_Handle = NULL;

创建一个接收任务,这里我们等待实现设置为无限等待:

//Receive_Task任务主体
static void Receive_Task(void* parameter)
{	
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  while (1)
  {		
    //获取二值信号量 xSemaphore,没获取到则一直等待
		xReturn = xSemaphoreTake(BinarySem_Handle,/* 二值信号量句柄 */
                              portMAX_DELAY); /* 等待时间 */
    if(pdTRUE == xReturn)
      printf("BinarySem_Handle二值信号量获取成功!\r\n");
		Toggle_LED_R();
		printf("当前可用:%d\r\n", uxSemaphoreGetCount(BinarySem_Handle));
  }
}

其中为了实验现象更加明显的观看到,我们在其中加入了Toggle_LED_R();,这是自己声明的一个电平翻转函数,函数模型:

void Toggle_LED_R(void)
{
	BitAction LED_R = (BitAction)(1 - GPIO_ReadOutputDataBit(LED1_GPIO_PORT, LED1_GPIO_PIN));
	GPIO_WriteBit(LED1_GPIO_PORT, LED1_GPIO_PIN, LED_R);
}

当成功获取一次电平翻转一次。

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

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

完整main函数:

#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 Receive_Task_Handle = NULL;
static TaskHandle_t Send_Task_Handle = NULL;
 
/********************************** 内核对象句柄 *********************************/
/*
 * 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
 * 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
 * 们就可以通过这个句柄操作这些内核对象。
 *
 * 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
 * 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
 * 来完成的
 * 
 */
SemaphoreHandle_t BinarySem_Handle =NULL;
 
/******************************* 宏定义 ************************************/
/*
 * 当我们在写应用程序的时候,可能需要用到一些宏定义。
 */
 
 
//一些函数声明
static void AppTaskCreate(void);/* 用于创建任务 */
 
static void Receive_Task(void* pvParameters);/* Receive_Task任务实现 */
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();   /* 启动任务,开启调度 */
		else
			return -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");
 
  /* 创建Receive_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Receive_Task, /* 任务入口函数 */
                        (const char*    )"Receive_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,	/* 任务入口函数参数 */
                        (UBaseType_t    )2,	    /* 任务的优先级 */
                        (TaskHandle_t*  )&Receive_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建Receive_Task任务成功!\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();            //退出临界区
}
 
//Receive_Task任务主体
static void Receive_Task(void* parameter)
{	
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  while (1)
  {		
    //获取二值信号量 xSemaphore,没获取到则一直等待
		xReturn = xSemaphoreTake(BinarySem_Handle,/* 二值信号量句柄 */
                              portMAX_DELAY); /* 等待时间 */
    if(pdTRUE == xReturn)
      printf("BinarySem_Handle二值信号量获取成功!\r\n");
		Toggle_LED_R();
		printf("当前可用:%d\r\n", uxSemaphoreGetCount(BinarySem_Handle));
  }
}
 
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 )
    {
      printf("\r\n按键K1被按下!\r\n");
      xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量
      if( xReturn == pdTRUE )
			{
        printf("BinarySem_Handle二值信号量释放成功!\r\n");
				printf("当前可用:%d\r\n", uxSemaphoreGetCount(BinarySem_Handle));
			}
      else
        printf("BinarySem_Handle二值信号量释放失败!\r\n");
    } 
    /* K2 被按下 */
    if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
    {
      printf("\r\n按键K2被按下!\r\n");
      xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量
      if( xReturn == pdTRUE )
			{
        printf("BinarySem_Handle二值信号量释放成功!\r\n");
				printf("当前可用:%d\r\n", uxSemaphoreGetCount(BinarySem_Handle));
			}
      else
        printf("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();
  
}

运行结果:
image

计数信号量举例
同样的我们在计数信号量释放函数的基础上进行修改,创建一个接收的句柄:

static TaskHandle_t Take_Task_Handle = NULL;/* Take_Task任务句柄 */

通过点击KEY1进行车位的获取,按一下获取一个,当没有是提示以无法获取:

//Take_Task任务主体
static void Take_Task(void* parameter)
{	
  BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
  /* 任务都是一个无限循环,不能返回 */
  while (1)
  {
    //如果KEY1被单击
		if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )       
		{
			/* 获取一个计数信号量 */
      xReturn = xSemaphoreTake(CountSem_Handle,	/* 计数信号量句柄 */
                             0); 	/* 等待时间:0 */
			if ( pdTRUE == xReturn ) 
			{
				printf( "\r\nKEY1被按下,成功申请到停车位!\r\n" );
			  printf("当前剩余车位:%d\r\n", uxSemaphoreGetCount(CountSem_Handle));
			}
			else
				printf( "\r\nKEY1被按下,不好意思,现在停车场已满!\r\n" );							
		}
		vTaskDelay(20);     //每20ms扫描一次		
  }
}

在任务创建函数AppTaskCreate()中增加接收任务的相关参数设定:

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

完整main代码:

#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 Take_Task_Handle = NULL;/* Take_Task任务句柄 */
static TaskHandle_t Give_Task_Handle = NULL;/* Give_Task任务句柄 */
 
/********************************** 内核对象句柄 *********************************/
/*
 * 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
 * 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
 * 们就可以通过这个句柄操作这些内核对象。
 *
 * 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
 * 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
 * 来完成的
 * 
 */
SemaphoreHandle_t CountSem_Handle =NULL;
 
/******************************* 宏定义 ************************************/
/*
 * 当我们在写应用程序的时候,可能需要用到一些宏定义。
 */
 
 
//一些函数声明
static void AppTaskCreate(void);/* 用于创建任务 */
 
static void Take_Task(void* pvParameters);/* Take_Task任务实现 */
static void Give_Task(void* pvParameters);/* Give_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();   /* 启动任务,开启调度 */
		else
			return -1;  
	}
}
 
//任务创建函数
static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  taskENTER_CRITICAL();           //进入临界区
 
  /* 创建Test_Queue */
  CountSem_Handle = xSemaphoreCreateCounting(5,0);	 
  if(NULL != CountSem_Handle)
    printf("CountSem_Handle计数信号量创建成功!\r\n");
 
  /* 创建Take_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Take_Task, /* 任务入口函数 */
                        (const char*    )"Take_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,	/* 任务入口函数参数 */
                        (UBaseType_t    )2,	    /* 任务的优先级 */
                        (TaskHandle_t*  )&Take_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建Take_Task任务成功!\r\n");
 
  /* 创建Give_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Give_Task,  /* 任务入口函数 */
                        (const char*    )"Give_Task",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )3, /* 任务的优先级 */
                        (TaskHandle_t*  )&Give_Task_Handle);/* 任务控制块指针 */ 
  if(pdPASS == xReturn)
    printf("创建Give_Task任务成功!\r\n");
 
	printf("当前剩余车位:%d\r\n", uxSemaphoreGetCount(CountSem_Handle));
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}
 
//Take_Task任务主体
static void Take_Task(void* parameter)
{	
  BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
  /* 任务都是一个无限循环,不能返回 */
  while (1)
  {
    //如果KEY1被单击
		if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )       
		{
			/* 获取一个计数信号量 */
      xReturn = xSemaphoreTake(CountSem_Handle,	/* 计数信号量句柄 */
                             0); 	/* 等待时间:0 */
			if ( pdTRUE == xReturn ) 
			{
				printf( "\r\nKEY1被按下,成功申请到停车位!\r\n" );
			  printf("当前剩余车位:%d\r\n", uxSemaphoreGetCount(CountSem_Handle));
			}
			else
				printf( "\r\nKEY1被按下,不好意思,现在停车场已满!\r\n" );							
		}
		vTaskDelay(20);     //每20ms扫描一次		
  }
}
 
//Give_Task任务主体
static void Give_Task(void* parameter)
{	 
  BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
  /* 任务都是一个无限循环,不能返回 */
  while (1)
  {
    //如果KEY2被单击
		if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )       
		{
			/* 获取一个计数信号量 */
      xReturn = xSemaphoreGive(CountSem_Handle);//给出计数信号量                  
			if ( pdTRUE == xReturn ) 
			{
				printf( "\r\nKEY2被按下,释放1个停车位!\r\n" );
				printf("当前剩余车位:%d\r\n", uxSemaphoreGetCount(CountSem_Handle));
			}
			else
				printf( "\r\nKEY2被按下,但已无车位可以释放!\r\n" );							
		}
		vTaskDelay(20);     //每20ms扫描一次	
  }
}
 
//初始化声明
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();
  
}

运行结果:
image

中断获取函数 xSemaphoreTakeFromISR()

xSemaphoreTakeFromISR()是函数 xSemaphoreTake()的中断版本,用于获取信号量,是一个不带阻塞机制获取信号量的函数,获取对象必须由是已经创建的信号量,信号量类型可以是二值信号量和计数信号量,它与 xSemaphoreTake()函数不同,它不能用于获取互斥量,因为互斥量不可以在中断中使用,并且互斥量特有的优先级继承机制只能在任务中起作用,而在中断中毫无意义。
image

posted @ 2026-09-12 13:20  灵垚克府  阅读(5)  评论(0)    收藏  举报