news 2026/5/7 9:43:31

计数信号量 | FreeRTOS 学习Day9

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
计数信号量 | FreeRTOS 学习Day9

计数信号量


1. 今天必须记住的 3 句话

  1. 计数信号量 = N 个二值信号量,计数值可以累加(0~N)
  2. 核心作用:事件计数(按键按了几次)、资源管理(有几个车位可用)
  3. 和二值信号量的最大区别:二值信号量连续 Give 多次,值还是 1,会丢事件;计数信号量连续 Give 多次,值会累加,不会丢事件!

2. 最直观的类比:停车场车位模型

把计数信号量想象成有 N 个车位的停车场

表格

停车场概念对应计数信号量 API含义
停车场总共有 10 个车位xSemaphoreCreateCounting(10, 0)最大计数值 10,初始值 0(初始时 0 个车位可用,或者反过来理解)
一辆车开走,释放 1 个车位xSemaphoreGive()计数值 + 1(可用车位 + 1)
一辆车开进来,占用 1 个车位xSemaphoreTake()计数值 - 1(可用车位 - 1)
车位全满了,Take 会阻塞xSemaphoreTake(..., 阻塞时间)计数值为 0 时,Take 会等待,直到有车位释放

3. 今天的核心:2 个 API(比二值信号量多 1 个创建参数)

(1)创建计数信号量

SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount, // 【参数1】最大计数值(比如100,对应100个车位) UBaseType_t uxInitialCount // 【参数2】初始计数值(一般设为0) );
  • 返回值:成功返回句柄,失败返回NULL(堆内存不足)。
  • 对比二值信号量:二值信号量相当于xSemaphoreCreateCounting(1, 0),最大计数值只能是 1。

(2)获取 / 释放信号量(和二值信号量完全一样)

  • 任务中获取xSemaphoreTake(xSem, xBlockTime)(计数值 - 1)
  • 任务中释放xSemaphoreGive(xSem)(计数值 + 1)
  • 中断中释放xSemaphoreGiveFromISR(xSem, &xHigherPriorityTaskWoken)(计数值 + 1,必须配合portYIELD_FROM_ISR()

4. 今天的练习:按键计数(不会丢事件)

需求

  • 按键快速按下多次(比如 1 秒按 5 次)
  • 中断里只发计数信号量(Give),计数值累加
  • 任务里慢慢处理(Take),每次处理间隔 1 秒,不会丢任何一次按键事件

完整可复制代码

第一步:全局变量定义

/* USER CODE BEGIN PV */ SemaphoreHandle_t xKeyCountSem; // 按键计数用的计数信号量 SemaphoreHandle_t xUARTMutex; // 串口打印互斥锁(解决乱码) /* USER CODE END PV */

第二步:任务函数(慢慢处理,不会丢事件)

/* USER CODE BEGIN 0 */ void Key_Count_Task(void *pvParameters) { uint32_t process_count = 0; printf("=== 按键任务启动 ===\r\n"); while(1) { // 调试打印:查看信号量累积情况 printf(">>> 当前信号量计数值:%d\r\n", (int)uxSemaphoreGetCount(xKeyCountSem)); // 等待按键触发的信号量 if(xSemaphoreTake(xKeyCountSem, portMAX_DELAY) == pdPASS) { // 2. 有效按键:计数+打印 process_count++; printf("任务处理第 %d 次按键事件\r\n", process_count); // 3. 消抖完成,解锁,响应下一次按键 key_lock = 0; // 4. 你的需求:打印后间隔1秒 vTaskDelay(3000); } } } /* USER CODE END 0 */

第三步:main 函数里创建(顺序不能乱)

int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ xUARTMutex = xSemaphoreCreateMutex(); // 创建互斥锁 xKeyCountSem = xSemaphoreCreateCounting(20, 0); xDataQueue = xQueueCreate(5, sizeof(uint32_t)); if(xTaskCreate(Key_Count_Task, "Key_Count_Task", 128 , NULL, 2, NULL) == pdPASS) { HAL_UART_Transmit(&huart1, (uint8_t *)"--- KEY_Task 创建成功 ---\r\n", strlen("--- KEY_Task 创建成功 ---\r\n"), 100); } else HAL_UART_Transmit(&huart1, (uint8_t *)"--- KEY_Task 创建失败 ---\r\n", strlen("--- KEY_Task 创建失败 ---\r\n"), 100); if(xTaskCreate(Key_Scan_Task, "Key_Scan_Task", 128 , NULL, 3, NULL) == pdPASS) { HAL_UART_Transmit(&huart1, (uint8_t *)"--- Key_Scan_Task 创建成功 ---\r\n", strlen("--- Key_Scan_Task 创建成功 ---\r\n"), 100); } else HAL_UART_Transmit(&huart1, (uint8_t *)"--- Key_Scan_Task 创建失败 ---\r\n", strlen("--- Key_Scan_Task 创建失败 ---\r\n"), 100); if(xKeyCountSem == NULL) { HAL_UART_Transmit(&huart1, (uint8_t *)" 计数信号量创建失败\r\n", strlen(" 计数信号量创建失败\r\n"), 100); } else HAL_UART_Transmit(&huart1, (uint8_t *)" 计数信号量创建成功\r\n", strlen(" 计数信号量创建成功\r\n"), 100); /* USER CODE END 2 */ /* Call init function for freertos objects (in cmsis_os2.c) */ MX_FREERTOS_Init(); /* Start scheduler */ osKernelStart(); /* We should never get here as control is now taken by the scheduler */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }

第四步:按键扫描函数

void Key_Scan_Task(void *pvParameters) { uint8_t key_last_state = 1; // 上一次状态:默认高电平(上拉) uint8_t key_current_state; TickType_t key_press_tick = 0; // 记录按键按下的起始时间 const TickType_t long_press_time = pdMS_TO_TICKS(2000); // 长按阈值:2秒 printf("=== 按键扫描任务启动(短按<2s生效) ===\r\n"); while(1) { // 1. 读取当前按键电平 key_current_state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0); // ============================================== // 【检测按下:下降沿】记录按下的时间点 // ============================================== if(key_last_state == 1 && key_current_state == 0) { key_press_tick = xTaskGetTickCount(); // 保存按下时刻 } // ============================================== // 【检测松手:上升沿】判断按下时长,短按才生效 // ============================================== if(key_last_state == 0 && key_current_state == 1) { vTaskDelay(20); // 20ms消抖 // 再次确认松手 if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == 1) { // 计算按下总时长 TickType_t press_time = xTaskGetTickCount() - key_press_tick; //核心判断:按下时间 < 2秒 → 触发;≥2秒 → 忽略 if(press_time < long_press_time) { xSemaphoreGive(xKeyCountSem); // 短按有效,释放信号量 } else { printf("长按超过2秒,忽略触发\r\n"); // 可选调试打印 } } } // 更新状态 key_last_state = key_current_state; // 扫描周期10ms vTaskDelay(10); } }

5. 今天必须搞懂的核心:为什么计数信号量不会丢事件?

对比二值信号量和计数信号量

场景:1 秒内快速按下 5 次按键

表格

类型连续 Give 5 次后的计数值任务 Take 的结果结论
二值信号量只能是 1(因为最大计数值是 1,连续 Give 多次值不会累加)任务只能 Take 1 次,剩下 4 次事件直接丢失❌ 会丢事件
计数信号量变成 5(连续 Give 5 次,值累加 5 次)任务可以 Take 5 次,每次处理 1 次,不会丢任何事件✅ 不会丢事件

6. 运行效果(你会看到)

哪怕你按得很快,任务处理得很慢,计数信号量会把所有按键事件都记下来,任务会一个一个慢慢处理,绝对不会丢!


7. 今日总结(背下来)

  • 计数信号量 = N 个二值信号量,计数值可以累加(0~N)
  • 核心作用:事件计数(按键按了几次)、资源管理(有几个车位可用)
  • 和二值信号量的最大区别:二值信号量连续 Give 多次会丢事件,计数信号量不会丢
  • 固定模板必须背:中断里用xSemaphoreGiveFromISR+portYIELD_FROM_ISR
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/7 9:42:20

3步掌握MTK设备救砖:从黑屏到正常启动的完整指南

3步掌握MTK设备救砖&#xff1a;从黑屏到正常启动的完整指南 【免费下载链接】mtkclient MTK reverse engineering and flash tool 项目地址: https://gitcode.com/gh_mirrors/mt/mtkclient 你是否遇到过联发科手机黑屏无法开机&#xff1f;或者刷机失败后设备变砖&…

作者头像 李华
网站建设 2026/5/7 9:40:34

GESP5级C++考试语法知识(十三、贪心算法(三))

&#x1f31f;第三课&#xff1a;《贪心王国大冒险》第三章——贪心的极限与陷阱&#x1f3f0; 一、故事开场&#xff1a;勇士的危机1、同学们已经掌握了&#xff1a;海盗船&#xff08;选小&#xff09;排队接水&#xff08;选快&#xff09;活动选择&#xff08;选结束早&…

作者头像 李华
网站建设 2026/5/7 9:39:39

UCNPs-OA/PAA/Fe3O4,上转换纳米颗粒表面修饰与复合体系差异分析

中英文名称&#xff1a; UCNPs-OA&#xff0c;油酸稳定化上转换纳米颗粒 UCNPs-PAA&#xff0c;聚丙烯酸修饰上转换纳米颗粒 UCNPs-Fe3O4&#xff0c;四氧化三铁复合上转换纳米颗粒 一、UCNPs-OA&#xff0c;油酸稳定化上转换纳米颗粒 材料特点 UCNPs-OA通常是以油酸&#xff0…

作者头像 李华
网站建设 2026/5/7 9:38:33

别再只会setStyleSheet了!Qt实现背景透明的5种方法实测与避坑指南

别再只会setStyleSheet了&#xff01;Qt实现背景透明的5种方法实测与避坑指南 在开发现代桌面应用时&#xff0c;透明效果已经成为提升用户体验的重要设计元素。无论是悬浮工具窗口、HUD界面还是需要融入系统环境的特殊应用&#xff0c;背景透明都是实现这些效果的关键技术。作…

作者头像 李华
网站建设 2026/5/7 9:37:28

TensorFlow模型快速部署:基于Gradio的AI演示界面构建指南

1. 项目概述&#xff1a;当TensorFlow遇上Gradio&#xff0c;一个快速构建AI演示界面的利器如果你正在用TensorFlow捣鼓机器学习模型&#xff0c;并且已经厌倦了在Jupyter Notebook里反复运行单元格&#xff0c;或者想给非技术背景的同事、朋友直观地展示你的模型效果&#xff…

作者头像 李华
网站建设 2026/5/7 9:36:43

ComfyUI Manager终极指南:AI绘画插件的智能管家

ComfyUI Manager终极指南&#xff1a;AI绘画插件的智能管家 【免费下载链接】ComfyUI-Manager ComfyUI-Manager is an extension designed to enhance the usability of ComfyUI. It offers management functions to install, remove, disable, and enable various custom node…

作者头像 李华