news 2026/9/16 7:59:37

二进制信号量和计数信号量区别(FreeRTOS)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
二进制信号量和计数信号量区别(FreeRTOS)

目录

前言

一. FreeRTOS信号量

1.1 核心函数

1.1.1 创建信号量

1.1.2 释放信号量

1.1.3 获取信号量

1.2 STM32Cubenx初始化

1.2.1 创建3个任务

1.2.2 创建1个二进制信号量

1.3 使用演示

1.3.1 (二进制)信号量示例

1.3.2 (计数)信号量示例

二. 使用二进制信号量示例

2.1 task任务间交互使用二进制信号量

三. 使用计数信号量示例

3.1 task任务间交互使用计数信号量


前言

在 FreeRTOS 中,二进制信号量和计数信号量最核心的区别在于“计数范围”和“用途”。它们虽然都用于任务间同步或互斥,但设计目的不同。

特性二进制信号量计数信号量
计数值范围0 或 10 到 最大值(创建时指定)
典型用途同步(如中断通知任务)、互斥(尽量不用)资源计数、事件计数
“Give”操作值变为 1(若已为 1,则失败或覆盖)值加 1(达到上限则失败)
“Take”操作值变为 0(若已为 0,则阻塞)值减 1(若已为 0,则阻塞)
创建方式xSemaphoreCreateBinary()xSemaphoreCreateCounting()
是否支持优先级继承(互斥请用 Mutex)

详细说明
二进制信号量
本质是一个长度为 1 的队列,只有“有”和“无”两种状态。
常用于中断服务程序(ISR)通知任务:ISR 中 Give,任务中 Take 等待。
也常用于任务间同步:一个任务 Give,另一个任务 Take。
注意:它不适合做互斥锁,因为没有优先级继承机制,可能导致优先级翻转。互斥应使用 xSemaphoreCreateMutex()。

计数信号量
本质是一个长度为 N 的队列,可以记录“事件发生了多少次”或“还有多少个资源可用”。
常用于资源管理:例如管理 5 个相同的缓冲区,任务取走一个计数减 1,归还一个计数加 1。
也用于事件计数:中断每发生一次 Give 一次,任务可以累计处理多个事件。

选择建议
只需要“通知一次”或“二值状态” → 用二进制信号量。
需要记录数量或管理多个同类资源 → 用计数信号量。
需要互斥保护共享资源 → 两者都不要用,请用互斥量(Mutex)。

一. FreeRTOS信号量

1.1 核心函数

1.1.1 创建信号量
/* Definitions for myBinarySem01 */ osSemaphoreId_t myBinarySem01Handle; const osSemaphoreAttr_t myBinarySem01_attributes = { .name = "myBinarySem01" }; myBinarySem01Handle = osSemaphoreNew(1, 1, &myBinarySem01_attributes);
1.1.2 释放信号量

osSemaphoreRelease(释放信号量)
作用:往信号量里放回一个令牌(使其计数 +1),最多放回创建时的初始最大数量。
典型场景:
任务 A 用完后释放资源,通知其他任务"资源空出来了,可以用"
中断服务程序(ISR)里释放信号量,唤醒等待的任务(常用于中断→任务间的同步)

注意:如果没有任务在等待、且计数已达最大值,释放会失败返回osErrorResource

status = osSemaphoreRelease(myBinarySem01Handle);// 计数 +1,唤醒可能正在等待的任务
1.1.3 获取信号量

osSemaphoreAcquire(获取信号量)
作用:尝试取走一个令牌(计数 -1)。如果篮子里没令牌了,就阻塞等待,直到:
有别的任务/中断释放了令牌(拿到后返回 osOK)
等待超时(返回 osErrorTimeout)

timeout参数:0表示不等待(拿不到立即返回),osWaitForever表示无限期等待。

osStatus_t status = osSemaphoreAcquire(sem_id, 1000); // 最多等 1000ms if (status == osOK) { // 拿到了令牌,可以安全访问共享资源 use_shared_resource(); osSemaphoreRelease(sem_id); // 用完归还 }

1.2 STM32Cubenx初始化

1.2.1 创建3个任务

使用stm32cubemx快速生成3个任务:其中

任务1(默认任务):作用:往信号量里放回一个令牌

osThreadId_t defaultTaskHandle; const osThreadAttr_t defaultTask_attributes = { .name = "defaultTask", .stack_size = 128 * 4, .priority = (osPriority_t) osPriorityNormal, }; /* Definitions for myTask02 */ osThreadId_t myTask02Handle; const osThreadAttr_t myTask02_attributes = { .name = "myTask02", .stack_size = 128 * 4, .priority = (osPriority_t) osPriorityLow, }; /* Definitions for myTask03 */ osThreadId_t myTask03Handle; const osThreadAttr_t myTask03_attributes = { .name = "myTask03", .stack_size = 128 * 4, .priority = (osPriority_t) osPriorityLow, }; defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes); /* creation of myTask02 */ myTask02Handle = osThreadNew(StartTask02, NULL, &myTask02_attributes); /* creation of myTask03 */ myTask03Handle = osThreadNew(StartTask03, NULL, &myTask03_attributes);
1.2.2 创建1个二进制信号量

最大计数 1:令牌"篮子"里最多只能装 1 个令牌,

所以叫"二值"—令牌数要么是 0,要么是 1

/* Definitions for myBinarySem01 */ osSemaphoreId_t myBinarySem01Handle; const osSemaphoreAttr_t myBinarySem01_attributes = { .name = "myBinarySem01" }; myBinarySem01Handle = osSemaphoreNew(1, 1, &myBinarySem01_attributes);

1.3 使用演示

1.3.1 (二进制)信号量示例

使用3个任务:1个二进制信号量,进行测试

myBinarySem01Handle = osSemaphoreNew(1, 1, &myBinarySem01_attributes); // 最大计数=1, 初始计数=1
任务优先级对信号量的操作
Task01 (defaultTask)NormalKEY_AosSemaphoreRelease(放令牌 +1)
Task02 (myTask02)LowKEY_BosSemaphoreAcquire(timeout=0)(取令牌 -1,不等待)
Task03 (myTask03)Low每 1 秒osSemaphoreGetCount(只读令牌数)
1.3.2 (计数)信号量示例

使用使用3个任务和1个计数信号量

最大计数上限 = 5,初始可用令牌 = 5。

上电:信号量初始令牌 = 5
1. 按一次 KEY_A → Release,令牌 + 1(已经是 5 → 失败,提示到达上限)
2. 按一次 KEY_B → Acquire,令牌变成 4
3. 任务 3 打印出当前 count=4
4. 再按 KEY_B 多次,令牌持续减少,直到 0,再按 KEY_B 就会提示 `Acquire fail: sem empty`
5. 此时按 KEY_A,令牌 + 1,又可以继续按 B 获取
注意:三个任务都是 500ms 延时,所以按键检测响应最慢 500ms,属于轮询检测,不是中断方式。

二. 使用二进制信号量示例

2.1 task任务间交互使用二进制信号量

具体现象为:

这份代码其实是信号量 API 的"功能演示板",它不是典型的资源保护用法(没有共享资源被保护),而是刻意展示了三个状态:
1. Acquire 成功:有令牌时拿走
2. Acquire 失败(非阻塞):timeout=0,拿不到立刻返回 osErrorResource,任务不会被挂起
3. Release 失败:二值信号量已满,再放返回 osErrorResource
4. 查询:osSemaphoreGetCount 随时读当前令牌数

串口打印:

/* USER CODE BEGIN Header */ /** ****************************************************************************** * File Name : freertos.c * Description : Code for freertos applications ****************************************************************************** * @attention * * Copyright (c) 2026 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "FreeRTOS.h" #include "task.h" #include "main.h" #include "cmsis_os.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "stdio.h" #include "string.h" #include "queue.h" #include "semphr.h" #include "cmsis_os2.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN Variables */ /* USER CODE END Variables */ /* Definitions for defaultTask */ osThreadId_t defaultTaskHandle; const osThreadAttr_t defaultTask_attributes = { .name = "defaultTask", .stack_size = 128 * 4, .priority = (osPriority_t) osPriorityNormal, }; /* Definitions for myTask02 */ osThreadId_t myTask02Handle; const osThreadAttr_t myTask02_attributes = { .name = "myTask02", .stack_size = 128 * 4, .priority = (osPriority_t) osPriorityLow, }; /* Definitions for myTask03 */ osThreadId_t myTask03Handle; const osThreadAttr_t myTask03_attributes = { .name = "myTask03", .stack_size = 128 * 4, .priority = (osPriority_t) osPriorityLow, }; /* Definitions for myBinarySem01 */ osSemaphoreId_t myBinarySem01Handle; const osSemaphoreAttr_t myBinarySem01_attributes = { .name = "myBinarySem01" }; /* Private function prototypes -----------------------------------------------*/ /* USER CODE BEGIN FunctionPrototypes */ /* USER CODE END FunctionPrototypes */ void StartDefaultTask(void *argument); void StartTask02(void *argument); void StartTask03(void *argument); void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */ /** * @brief FreeRTOS initialization * @param None * @retval None */ void MX_FREERTOS_Init(void) { /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* USER CODE BEGIN RTOS_MUTEX */ /* add mutexes, ... */ /* USER CODE END RTOS_MUTEX */ /* Create the semaphores(s) */ /* creation of myBinarySem01 */ myBinarySem01Handle = osSemaphoreNew(1, 1, &myBinarySem01_attributes); /* USER CODE BEGIN RTOS_SEMAPHORES */ /* add semaphores, ... */ /* USER CODE END RTOS_SEMAPHORES */ /* USER CODE BEGIN RTOS_TIMERS */ /* start timers, add new ones, ... */ /* USER CODE END RTOS_TIMERS */ /* USER CODE BEGIN RTOS_QUEUES */ /* add queues, ... */ /* USER CODE END RTOS_QUEUES */ /* Create the thread(s) */ /* creation of defaultTask */ defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes); /* creation of myTask02 */ myTask02Handle = osThreadNew(StartTask02, NULL, &myTask02_attributes); /* creation of myTask03 */ myTask03Handle = osThreadNew(StartTask03, NULL, &myTask03_attributes); /* USER CODE BEGIN RTOS_THREADS */ /* add threads, ... */ /* USER CODE END RTOS_THREADS */ /* USER CODE BEGIN RTOS_EVENTS */ /* add events, ... */ /* USER CODE END RTOS_EVENTS */ } /* USER CODE BEGIN Header_StartDefaultTask */ /** * @brief Function implementing the defaultTask thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_StartDefaultTask */ void StartDefaultTask(void *argument) { /* USER CODE BEGIN StartDefaultTask */ // uint8_t key = 0; osStatus_t status; for(;;) { printf("Task01:\r\n"); if(!HAL_GPIO_ReadPin(KEY_A_GPIO_Port, KEY_A_Pin)) // 按键A按下 { osDelay(5); // 消抖 if(!HAL_GPIO_ReadPin(KEY_A_GPIO_Port, KEY_A_Pin)) { printf("KEY_A \r\n"); status = osSemaphoreRelease(myBinarySem01Handle); if(status == osOK) { // printf("release ok, key = %d\r\n", key); // key++; printf("Release ok status = %d\r\n",status); } else if(status == osErrorResource) { // ,再 Release 无效 printf("release fail: sem already full status = %d\r\n", status); } else { printf("release fail, status = %d\r\n", status); } } } osDelay(500); } /* USER CODE END StartDefaultTask */ } /* USER CODE BEGIN Header_StartTask02 */ /** * @brief Function implementing the myTask02 thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_StartTask02 */ void StartTask02(void *argument) { /* USER CODE BEGIN StartTask02 */ osStatus_t status; for(;;) { printf("Task02:\r\n"); if(!HAL_GPIO_ReadPin(KEY_B_GPIO_Port, KEY_B_Pin)) // 按键B按下 { osDelay(5); if(!HAL_GPIO_ReadPin(KEY_B_GPIO_Port, KEY_B_Pin)) { printf("KEY_B \r\n"); status = osSemaphoreAcquire(myBinarySem01Handle, 0); // 非阻塞 if(status == osOK) { printf("acquire ok status = %d\r\n",status); } else if(status == osErrorResource) { printf("acquire fail: sem empty status = %d\r\n",status); } else if(status == osErrorTimeout) { printf("acquire timeout status = %d\r\n",status); } else { printf("acquire fail, status = %d\r\n", status); } } } osDelay(500); } /* USER CODE END StartTask02 */ } /* USER CODE BEGIN Header_StartTask03 */ /** * @brief Function implementing the myTask03 thread. * @param argument: Not used * @retval None */ char task_info[500] = {0}; /* USER CODE END Header_StartTask03 */ void StartTask03(void *argument) { /* USER CODE BEGIN StartTask03 */ /* Infinite loop */ // uint8_t key = 0; uint32_t cnt; // const char *name; for(;;) { cnt = osSemaphoreGetCount(myBinarySem01Handle); printf("Task03 read tokens = %lu\r\n", cnt); // name = osSemaphoreGetName(myBinarySem01Handle); // if (name != NULL) // { // printf("sem name = %s\r\n", name); // } // else // { // printf("no name set\r\n"); // } osDelay(1000); } /* USER CODE END StartTask03 */ } /* Private application code --------------------------------------------------*/ /* USER CODE BEGIN Application */ /* USER CODE END Application */

三. 使用计数信号量示例

3.1 task任务间交互使用计数信号量

执行流程为:

代码为:

/* USER CODE BEGIN Header */ /** ****************************************************************************** * File Name : freertos.c * Description : Code for freertos applications ****************************************************************************** * @attention * * Copyright (c) 2026 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "FreeRTOS.h" #include "task.h" #include "main.h" #include "cmsis_os.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "stdio.h" #include "string.h" #include "queue.h" #include "semphr.h" #include "cmsis_os2.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN Variables */ /* USER CODE END Variables */ /* Definitions for defaultTask */ osThreadId_t defaultTaskHandle; const osThreadAttr_t defaultTask_attributes = { .name = "defaultTask", .stack_size = 128 * 4, .priority = (osPriority_t) osPriorityNormal, }; /* Definitions for myTask02 */ osThreadId_t myTask02Handle; const osThreadAttr_t myTask02_attributes = { .name = "myTask02", .stack_size = 128 * 4, .priority = (osPriority_t) osPriorityLow, }; /* Definitions for myTask03 */ osThreadId_t myTask03Handle; const osThreadAttr_t myTask03_attributes = { .name = "myTask03", .stack_size = 128 * 4, .priority = (osPriority_t) osPriorityLow, }; /* Definitions for myBinarySem01 */ osSemaphoreId_t myBinarySem01Handle; const osSemaphoreAttr_t myBinarySem01_attributes = { .name = "myBinarySem01" }; /* Definitions for myCountingSem01 */ osSemaphoreId_t myCountingSem01Handle; const osSemaphoreAttr_t myCountingSem01_attributes = { .name = "myCountingSem01" }; /* Private function prototypes -----------------------------------------------*/ /* USER CODE BEGIN FunctionPrototypes */ /* USER CODE END FunctionPrototypes */ void StartDefaultTask(void *argument); void StartTask02(void *argument); void StartTask03(void *argument); void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */ /** * @brief FreeRTOS initialization * @param None * @retval None */ void MX_FREERTOS_Init(void) { /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* USER CODE BEGIN RTOS_MUTEX */ /* add mutexes, ... */ /* USER CODE END RTOS_MUTEX */ /* Create the semaphores(s) */ /* creation of myBinarySem01 */ myBinarySem01Handle = osSemaphoreNew(1, 1, &myBinarySem01_attributes); /* creation of myCountingSem01 */ myCountingSem01Handle = osSemaphoreNew(5, 5, &myCountingSem01_attributes); /* USER CODE BEGIN RTOS_SEMAPHORES */ /* add semaphores, ... */ /* USER CODE END RTOS_SEMAPHORES */ /* USER CODE BEGIN RTOS_TIMERS */ /* start timers, add new ones, ... */ /* USER CODE END RTOS_TIMERS */ /* USER CODE BEGIN RTOS_QUEUES */ /* add queues, ... */ /* USER CODE END RTOS_QUEUES */ /* Create the thread(s) */ /* creation of defaultTask */ defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes); /* creation of myTask02 */ myTask02Handle = osThreadNew(StartTask02, NULL, &myTask02_attributes); /* creation of myTask03 */ myTask03Handle = osThreadNew(StartTask03, NULL, &myTask03_attributes); /* USER CODE BEGIN RTOS_THREADS */ /* add threads, ... */ /* USER CODE END RTOS_THREADS */ /* USER CODE BEGIN RTOS_EVENTS */ /* add events, ... */ /* USER CODE END RTOS_EVENTS */ } /* USER CODE BEGIN Header_StartDefaultTask */ /** * @brief Function implementing the defaultTask thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_StartDefaultTask */ void StartDefaultTask(void *argument) { /* USER CODE BEGIN StartDefaultTask */ osStatus_t status; for(;;) { printf("task 1\r\n"); if(!HAL_GPIO_ReadPin(KEY_A_GPIO_Port, KEY_A_Pin)) // 按键A按下 { osDelay(5); //消抖 if(!HAL_GPIO_ReadPin(KEY_A_GPIO_Port, KEY_A_Pin)) { printf("KEY_A pressed: Release sem\r\n"); status = osSemaphoreRelease(myCountingSem01Handle); if(status == osOK) { printf("Release OK, token +1\r\n"); } else if(status == osErrorResource) { // 计数达到最大上限5,不能再加 printf("Release fail: sem reach max count(5)\r\n"); } else { printf("Release fail, status = %d\r\n", status); } } } osDelay(500); } /* USER CODE END StartDefaultTask */ } /* USER CODE BEGIN Header_StartTask02 */ /** * @brief Function implementing the myTask02 thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_StartTask02 */ void StartTask02(void *argument) { /* USER CODE BEGIN StartTask02 */ osStatus_t status; for(;;) { printf("task 2:\r\n"); if(!HAL_GPIO_ReadPin(KEY_B_GPIO_Port, KEY_B_Pin)) // 按键B按下 { osDelay(5); if(!HAL_GPIO_ReadPin(KEY_B_GPIO_Port, KEY_B_Pin)) { printf("KEY_B pressed: Acquire sem\r\n"); status = osSemaphoreAcquire(myCountingSem01Handle, 0); //非阻塞 if(status == osOK) { printf("Acquire OK, token -1\r\n"); } else if(status == osErrorResource) { printf("Acquire fail: sem empty, no token\r\n"); } else { printf("Acquire fail, status = %d\r\n", status); } } } osDelay(500); } /* USER CODE END StartTask02 */ } /* USER CODE BEGIN Header_StartTask03 */ /** * @brief Function implementing the myTask03 thread. * @param argument: Not used * @retval None */ char task_info[500] = {0}; /* USER CODE END Header_StartTask03 */ void StartTask03(void *argument) { /* USER CODE BEGIN StartTask03 */ uint32_t cnt; for(;;) { printf("task 3:\r\n"); cnt = osSemaphoreGetCount(myCountingSem01Handle); printf("===== Semaphore token count = %lu =====\r\n", cnt); osDelay(500); } /* USER CODE END StartTask03 */ } /* Private application code --------------------------------------------------*/ /* USER CODE BEGIN Application */ /* USER CODE END Application */
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 7:58:41

治好了我的电子仓鼠症!偶然挖到一款贼好用的文件夹整理工具

治好了我的电子仓鼠症!偶然挖到一款贼好用的文件夹整理工具(Win/Mac) 不知道大家有没有跟我一样的毛病:下载文件夹和桌面永远像个垃圾场。 平时写代码、下安装包、收各种工作群发来的 PDF、电子发票、截图、解压包……下载的时候…

作者头像 李华
网站建设 2026/9/16 7:57:58

飞秒激光加工双温模型原理与COMSOL仿真实践

1. 飞秒激光加工中的双温现象本质当飞秒激光(10^-15秒量级)作用于金属表面时,其能量传递过程与传统长脉冲激光存在本质差异。我在研究镍合金的激光微加工时,曾用示波器捕捉到一个有趣现象:在800nm飞秒激光照射下&#…

作者头像 李华
网站建设 2026/9/16 7:57:54

PHP Traits:代码复用的高效解决方案

1. 理解 Traits 的核心概念Traits 是面向对象编程中一种强大的代码复用机制,它允许开发者定义一组方法集合,这些方法可以被多个类复用,而无需使用传统的继承方式。与接口(interface)不同,Traits 不仅定义了方法签名,还…

作者头像 李华
网站建设 2026/9/16 7:57:51

基于STM32的远程宠物自动投喂系统

远程宠物自动投喂系统 绪论系统方案设计 需求分析系统总体方案主要模块选型 硬件设计 主控与称重温湿度与液位视频与通信语音与显示系统接线总表 软件设计 主程序流程喂食执行语音与远程交互计划与状态上报 系统实现与测试 调试步骤测试结果 总结 远程宠物自动投喂系统 绪论 …

作者头像 李华
网站建设 2026/9/16 7:57:50

从MQTT到边缘计算:用树莓派和ESP32搭建隐私友好的本地智能家居网关

作为一个把家里智能设备折腾了五六年的人,我越来越觉得市面上的"智能家居"其实笨得要命。每个牌子的传感器都要自己的App、自己的云,晚上想关个灯,语音指令绕一圈云端再回来,少说一两秒,偶尔服务器抽风直接装…

作者头像 李华
网站建设 2026/9/16 7:57:49

小白程序员进阶大模型:超详细学习路线图,收藏必备!

本文提供了一份详尽的AI Agent学习路线图,从模型基础认知到生产工程化,帮助后端工程师系统学习并掌握大模型应用开发,适合希望转向Java AI/Agent应用开发的工程师。 随着 AI Agent 岗位的兴起,以及对 AI 工具的普及,很…

作者头像 李华