FreeRTOS 计数信号量

计数信号量的基本用法

计数信号量和二进制信号量的用法基本相同(共享同一组API),区别在于计数信号量有一个内禀的计数值。

二进制信号量计数信号量
创建xSemaphoreCreateBinary()xSemaphoreCreateCounting()
删除vSemaphoreDelete()vSemaphoreDelete()
给信号vSemaphoreGive()vSemaphoreGive()
FromISR版本vSemaphoreGiveFromISR()vSemaphoreGiveFromISR()
取信号vSemaphoreTake()vSemaphoreTake()
FromISR版本vSemaphoreTakeFromISR()vSemaphoreTakeFromISR()

可见,只有创建信号量的API不同。

“内禀计数值”指的就是信号量内部维护的那个计数值(Count Value),它直接代表了当前可用的“资源”数量或“未处理事件”的数量。

使用计数信号量改进按钮音效

之前使用的二进制信号量编写按钮音效,存在一个问题:即如果在蜂鸣器鸣叫时按下了多次按钮,蜂鸣器只响一次,信号丢失了。
时序图
上图,在蜂鸣器鸣叫期间,按下多次按钮,由于没有取信号,二进制信号量的值始终是1,按下多次按钮只计算了一次。
这里需要使用计数信号量改进该程序。

12345
// @作用:创建计数信号量// @参数:uxMaxCount - 最大计数值// @参数:uxInitialCount - 初始信号数量// @返回:新创建的信号量句柄,创建失败时返回NULL,一般占70到80字节。SemaphoreHandle_t xSemaphoreCreatCounting( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount );

注意:uxMaxCount为32位数字,最大是0xffffffff,uxMaxCount增大不会消耗额外内存。

修改后的Buzzer_Task.c

1234567891011121314151617181920212223242526272829303132333435
/* * Buzzer_Task.c * *  Created on: 2026年9月27日 *      Author: Miles */ #include "FreeRTOS.h"#include "semphr.h"#include "main.h" static SemaphoreHandle_t xBuzzerSemphr; void Buzzer_Init(){	xBuzzerSemphr = xSemaphoreCreateCounting(10,0);//创建一个计数信号量	HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, GPIO_PIN_SET);}void vBuzzerTask(void *pvParameters){	for(;;)	{		xSemaphoreTake(xBuzzerSemphr,portMAX_DELAY);		HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, GPIO_PIN_RESET);		vTaskDelay(pdMS_TO_TICKS(100));		HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, GPIO_PIN_SET);		vTaskDelay(pdMS_TO_TICKS(1000));	} }void Buzzer_Beep(){	xSemaphoreGive(xBuzzerSemphr);} 

上面的代码,只有创建信号量时从创建一个二进制信号量改为创建了一个计数信号量。

计数信号量的两种典型用法

计数信号量和二进制信号量的一个重要区别就是有内禀计数值。有两种常见用法:

事件计数守护资源
事件发生,计数值+1
事件处理,计数值-1
使用资源,计数值+1
归还资源,计数值-1
使用内禀计数值统计尚未处理事件的数量,目的是防丢使用内禀计数值记录剩余资源的数量,目的是有序访问有限的资源
一般xSemaphoreCreateCounting(n,0)
初始计数写0,创建信号量之前没有事件发生
一般xSemaphoreCreateCounting(n,n)
初始计数写n,初始状态下全部资源可用

例:使用计数信号量守护停车位
1、创建一个3个停车位的停车场:xSemaphoreCreateCounting(3,3)
2、每次入场前先取信号,vSemaphoreTake,内禀计数值-1
3、每次出厂前先给信号,vSemaphoreGive,内禀计数值+1
4、内禀计数值位0时,需要等待,超时则失败。
使用4个按钮代表4辆车,按一次入场,再按一次出场,串口打印数据,如此循环。

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
/* Private includes ----------------------------------------------------------*//* USER CODE BEGIN Includes */#include "FreeRTOS.h"#include "task.h"#include "Key_Task.h"#include "Buzzer_Task.h"#include "semphr.h"#include <string.h>/* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*//* USER CODE BEGIN PTD *///创建一个结构体typedef struct{	GPIO_TypeDef *GPIO_Port;	uint16_t GPIO_Pin;	const char *Name;} Car_TypeDef;/* USER CODE END PTD */ /* USER CODE BEGIN PV */const Car_TypeDef carA = {.GPIO_Port = KEY1_GPIO_Port, .GPIO_Pin = KEY1_Pin, .Name = "汽车A"};const Car_TypeDef carB = {.GPIO_Port = KEY2_GPIO_Port, .GPIO_Pin = KEY2_Pin, .Name = "汽车B"};const Car_TypeDef carC = {.GPIO_Port = KEY3_GPIO_Port, .GPIO_Pin = KEY3_Pin, .Name = "汽车C"};const Car_TypeDef carD = {.GPIO_Port = KEY4_GPIO_Port, .GPIO_Pin = KEY4_Pin, .Name = "汽车D"}; SemaphoreHandle_t  xSemaphore1;/* USER CODE END PV */ /* USER CODE BEGIN PFP */static void WaitForKeyClick(GPIO_TypeDef *GPIO_Port,uint16_t GPIO_Pin);static void PrintString2(const char *Str1,const char *Str2);/* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*//* USER CODE BEGIN 0 */static void WaitForKeyClick(GPIO_TypeDef *GPIO_Port,uint16_t GPIO_Pin){	GPIO_PinState current,previous = GPIO_PIN_SET;	for(;;)	{		current = HAL_GPIO_ReadPin(GPIO_Port, GPIO_Pin);		if(current != previous && current == GPIO_PIN_SET)		{			return;		}		previous = current;		vTaskDelay(pdMS_TO_TICKS(10));	}} static void vCarTask(void *pvParameters){	Car_TypeDef *car = (Car_TypeDef *)pvParameters;	for(;;)	{		//1.第一次按按钮,入场		WaitForKeyClick(car->GPIO_Port, car->GPIO_Pin);		//取信号		if(xSemaphoreTake(xSemaphore1,pdMS_TO_TICKS(10000)) == pdPASS)		{			PrintString2(car->Name,"入场\n");		}		else		{			PrintString2(car->Name,"入场失败\n");			continue;//入场失败,提前结束,进下一次循环		} 		//2.第二次按按钮,出场		WaitForKeyClick(car->GPIO_Port, car->GPIO_Pin);		//给信号		xSemaphoreGive(xSemaphore1);		PrintString2(car->Name,"出场\n");	}}static void PrintString2(const char *Str1,const char *Str2){	vTaskSuspendAll();	HAL_UART_Transmit(&huart1, (const uint8_t *)Str1,strlen(Str1),HAL_MAX_DELAY);	HAL_UART_Transmit(&huart1, (const uint8_t *)Str2,strlen(Str2),HAL_MAX_DELAY);	xTaskResumeAll();}/* USER CODE END 0 */   /* USER CODE BEGIN 2 */  xSemaphore1 = xSemaphoreCreateCounting(3,3);//创建计数信号量  Buzzer_Init();  xTaskCreate( KeyTask, "Key", 128, NULL, 3, NULL );  xTaskCreate( vBuzzerTask, "BUZZER", 32, NULL, 1, NULL );   xTaskCreate( vCarTask, "A", 64, (void *)&carA, 1, NULL );  xTaskCreate( vCarTask, "B", 64, (void *)&carB, 1, NULL );  xTaskCreate( vCarTask, "C", 64, (void *)&carC, 1, NULL );  xTaskCreate( vCarTask, "D", 64, (void *)&carD, 1, NULL );  vTaskStartScheduler();//启用调度器  /* USER CODE END 2 */

实验现象:
实验现象

🔍 ×