1 信号量概念及应用

信号灯解决的问题:
行人、车辆共享一个马路共享资源的问题,第二个解决通过的问题。
计数停车位的问题:
解决共享资源停车位计数的问题,因为车位是有限的,客流是无限的,计数可以很好的解决这个问题。

FreeRTOS提供了3种信号量:二值信号量、计数信号量、互斥信号量。三种信号量都是基于消息队列开发的,因为消息队列既能计数又能阻塞。

二值信号量: 二值信号量是最简单的信号量类型,只有两个状态:0和1。它常用于控制共享资源的访问权。当一个任务想要访问临界资源时,首先会尝试获取二值信号量。如果信号量的值为1,表示资源可用,任务可以继续执行;如果信号量的值为0,表示资源被占用,任务会被阻塞等待。当资源释放后,持有该信号量的任务会将信号量的值置为1,唤醒等待的任务。

计数信号量: 计数信号量可以有多个状态值,用于控制多个任务对共享资源的访问。当一个任务想要访问临界资源时,它会尝试获取计数信号量。如果信号量的值大于0,则表示资源可用,任务可以继续执行,并将信号量的值减少1。如果信号量的值为0,则表示资源暂时不可用,任务会被阻塞等待。当资源释放后,持有该信号量的任务会将信号量的值增加,并唤醒等待的任务。
2 二值信号量函数应用
2.1 功能需求
- 修改按键功能
 - 当按键按下触发打印一次CPU利用率
 - 使用二值信号量实现按键与任务间同步
 
2.2 API介绍



这里errQUEUQ_FULL代表信号量释放失败,已经信号量可用了,与上面有所区别。

 
2.3 功能实现
利用FreeRTOS创建一个信号量

创建信号量,创建函数在MX_FREERTOS_Init中
void MX_FREERTOS_Init(void) {
  /* definition and creation of CpuPrintfBinarySem */
  osSemaphoreDef(CpuPrintfBinarySem);
  CpuPrintfBinarySemHandle = osSemaphoreCreate(osSemaphore(CpuPrintfBinarySem), 1);
   
    //其他业务省略
} 
按键按下时给出信号量
#include "gpio.h"
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
teKeyStatus KeyStatus;
extern osSemaphoreId CpuPrintfBinarySemHandle;   //扩展引用信号量句柄
void MX_GPIO_Init(void)
{
  //略
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){
	if(Key3_Pin == GPIO_Pin){
		if(HAL_GPIO_ReadPin(Key3_GPIO_Port,Key3_Pin) == GPIO_PIN_RESET){
			HAL_Delay(10);
			if(HAL_GPIO_ReadPin(Key3_GPIO_Port,Key3_Pin) == GPIO_PIN_RESET){
			 
				printf("key3 is down!\r\n");
                //按键按下时,给出信号量(中断中)
				xSemaphoreGiveFromISR(CpuPrintfBinarySemHandle,NULL);
			
			}
		
		}else{
				HAL_Delay(10);
				if(HAL_GPIO_ReadPin(Key3_GPIO_Port,Key3_Pin) == GPIO_PIN_SET){
			    printf("key3 is up!\r\n");
			
			}
		}
	}
} 
Key任务中获取信号量(任务中去除了OsDelay(10),CPU使用率得到了提升)
void Key_Task(void const * argument)
{
  KeyStatus = KEY_RESET;
  for(;;)
  {
    //获取信号量后执行函数
	if(xSemaphoreTake(CpuPrintfBinarySemHandle,portMAX_DELAY) == pdPASS){
		memset(u8TaskListBuff,0,400);
		vTaskGetRunTimeStats((char*)u8TaskListBuff);
		printf("Name      Abs Time        Time\r\n");
		printf("******************************************************\r\n");
		printf("%s",u8TaskListBuff);
		printf("******************************************************\r\n");
		KeyStatus = KEY_RESET;
	}
  }
} 
3 计数信号量函数应用
3.1功能需求
- 修改按键功能,模拟停车位出入功能(创建信号量)
 - 当按键K3按下获取车位(获取信号量)
 - 当按键K4按下释放车位(释放信号量)
 
3.2API介绍

 
3.3功能实现
CubeMX配置使能计数信号量

创建信号量
 
  //FreeRTOS会根据CubeMX帮我们创建任务
  osSemaphoreDef(KeyCountingSem);
  KeyCountingSemHandle = osSemaphoreCreate(osSemaphore(KeyCountingSem), 4); 
KEY3按下获取信号量,KEY4按下释放信号量
//按键检测回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){
	//是不是KEY3
	if(Key3_Pin == GPIO_Pin)
    {
		//key3是否按下
		if(HAL_GPIO_ReadPin(Key3_GPIO_Port,Key3_Pin) == GPIO_PIN_RESET)
        {
			//软件去抖动
			HAL_Delay(10);
			if(HAL_GPIO_ReadPin(Key3_GPIO_Port,Key3_Pin) == GPIO_PIN_RESET)
            {
			 
				//建立一个标志位
				printf("key3 is down!\r\n");
				KeyStatus = KEY_DOWN;
				if(xSemaphoreTakeFromISR(KeyCountingSemHandle,NULL) == pdPASS)
                {
					
					printf("获取车位成功!\r\n");
				
				}
				else{
					printf("获取车位失败! 车位已经占满!\r\n");
				
				}
			}
		}
        else
        {
		    
		    //软件去抖动
			HAL_Delay(10);
			if(HAL_GPIO_ReadPin(Key3_GPIO_Port,Key3_Pin) == GPIO_PIN_SET)
            {
				//建立一个标志位
			    printf("key3 is up!\r\n");
				KeyStatus = KEY_UP;
			}
		}
	}
	//是不是Key4
	if(Key4_Pin == GPIO_Pin){
		//Key4是否按下
		if(HAL_GPIO_ReadPin(Key4_GPIO_Port,Key4_Pin) == GPIO_PIN_RESET)
        {
			//软件去抖动
			HAL_Delay(10);
			if(HAL_GPIO_ReadPin(Key4_GPIO_Port,Key4_Pin) == GPIO_PIN_RESET)
            {
				//建立一个标志位
				printf("Key4 is down!\r\n");
				KeyStatus = KEY_DOWN;
				if(xSemaphoreGiveFromISR(KeyCountingSemHandle,NULL) == pdPASS)
                {
					printf("释放车位成功!\r\n");
				}
				else
                {
					printf("释放车位失败! 车位为空!\r\n");
				}
			}
		}
        else
        {
			//软件去抖动
			HAL_Delay(10);
			if(HAL_GPIO_ReadPin(Key4_GPIO_Port,Key4_Pin) == GPIO_PIN_SET
            {
				//建立一个标志位
			    printf("Key4 is up!\r\n");
				KeyStatus = KEY_UP;
			
			}
		}
	}
} 
4 信号量实现原理
创建和删除

计数信号量创建内部也是调用xQueueGenericCreate,这个源码在消息队列中已经介绍过,只要分析消息队列长度、队列的类型
创建源码分析(删除参考消息队列)
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
	#define xSemaphoreCreateBinary() 
	/*
		1、队列长度=1 二值信号的值 无非就是0和1
		2、队列项的长度=semSEMAPHORE_QUEUE_ITEM_LENGTH   = ( ( uint8_t ) 0U )消息空间没有意义
		3、队列的类型=queueQUEUE_TYPE_BINARY_SEMAPHORE   =( ( uint8_t ) 3U ) 只用于调试使用
	*/
	xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )
#endif
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
	/*
		内部调用消息队列计数信号量的创建,重点分析它
	*/
	#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) 
	xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )
#endif
	/*
		1、计数信号量的最大值
		2、计数信号量的初始值
	
	*/
	QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount )
	{
	QueueHandle_t xHandle;
		//调用消息队列的创建
		xHandle = xQueueGenericCreate( uxMaxCount, queueSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_COUNTING_SEMAPHORE );
		if( xHandle != NULL )
		{
			//uxMessagesWaiting:将要处理的消息个数,这个去接收,是不会进入阻塞态的
			//赋值为计数信号量的初始值的目的,创建之后,就可以获取信号量,代表可用的资源数量
			( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;
			traceCREATE_COUNTING_SEMAPHORE();
		}
		else
		{
			traceCREATE_COUNTING_SEMAPHORE_FAILED();
		}
		return xHandle;
	}
 
信号量释放

信号量获取
 
信号量发送和接收源码分析
/*
	消息队列的发送和接收,都有阻塞任务的功能
	信号量的释放,却没有阻塞参数?????
	参数:
	1、信号量的句柄
	2、发送的缓冲区  NULL
	3、阻塞等待时间 = semGIVE_BLOCK_TIME -=  ( ( TickType_t ) 0U )???
		信号量释放是一个紧急的事件,当信号量资源已经到达最大值时,就不需要再等待其他任务使用
		所以不需要阻塞
	4、队列插入方式=queueSEND_TO_BACK 队尾
*/	
#define xSemaphoreGive( xSemaphore )		
xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )
/*
	1、再次封装了消息队列在中断中的发送接口xQueueGiveFromISR
	2、区别就是 give没有copy的功能  -----因为信号量不占用内存空间
*/
#define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken )	
xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ), ( pxHigherPriorityTaskWoken ) )
/*
	1、句柄
	2、接收缓冲区  = NULL 
	3、阻塞等待时间
	4、是否允许 删除消息空间 = pdFALSE,不删除给其他任务使用
*/
#define xSemaphoreTake( xSemaphore, xBlockTime )		
xQueueGenericReceive( ( QueueHandle_t ) ( xSemaphore ), NULL, ( xBlockTime ), pdFALSE )
/*
	1、句柄
	2、接收缓冲区  = NULL 
	3、NULL
*/
#define xSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken )	
xQueueReceiveFromISR( ( QueueHandle_t ) ( xSemaphore ), NULL, ( pxHigherPriorityTaskWoken ) ) 
问: 消息队列的发送和接收,都有阻塞任务的功能,信号量的释放,却没有阻塞参数?
信号量释放是一个紧急的事件,当信号量资源已经到达最大值时,就不需要再等待其他任务使用,所以不需要阻塞










![Vue [Day2]](https://img-blog.csdnimg.cn/167e8c4bf9514b6d8c4c2047a94f9973.png)








