FreeRTOS二值信号量详解与实战教程

news2025/4/19 15:59:30

FreeRTOS二值信号量详解与实战教程

📚 作者推荐:想系统学习FreeRTOS嵌入式开发?请访问我的FreeRTOS开源学习库,内含从入门到精通的完整教程和实例代码!

1. 二值信号量核心概念解析

二值信号量(Binary Semaphore)是FreeRTOS提供的一种简单而强大的同步工具,它只有两个可能值:0或1。这种简单特性使它成为嵌入式系统中极其实用的同步原语。

💡 形象理解:二值信号量就像公共卫生间的占用指示灯:

  • 绿灯(值为1):资源可用,任务可以获取
  • 红灯(值为0):资源被占用,需要等待

2. 二值信号量三大应用场景

2.1 资源互斥访问

当多个任务需要访问共享资源(如全局变量、外设)时,二值信号量能确保任一时刻只有一个任务能访问该资源:

// 任务想要访问共享资源时
xSemaphoreTake(xMutexSemaphore, portMAX_DELAY);  // 获取访问权
// 访问共享资源
xSemaphoreGive(xMutexSemaphore);  // 释放访问权

2.2 任务同步控制

实现"任务A必须在任务B之前完成"的先后依赖关系:

// 任务A完成工作后
xSemaphoreGive(xSyncSemaphore);  // 发出"我完成了"的信号

// 任务B开始前
xSemaphoreTake(xSyncSemaphore, portMAX_DELAY);  // 等待任务A完成
// 开始任务B的工作

2.3 任务阻塞与唤醒机制

FreeRTOS使用优先级管理等待同一信号量的多个任务:

  • 不同优先级:高优先级任务优先获得信号量
  • 相同优先级:先等待的任务先获得信号量

3. 二值信号量的底层实现揭秘

🔍 底层原理:二值信号量实质上是一个队列长度为1的特殊队列

在FreeRTOS内核中:

  • 队列为空 → 信号量值为0(不可用)
  • 队列有元素 → 信号量值为1(可用)

这种实现使二值信号量具有队列的所有优势,包括任务阻塞和优先级继承等特性。

4. 二值信号量核心API详解

函数描述使用场景
vSemaphoreCreateBinary()创建二值信号量(创建后自动释放一次)需要初始状态为"可用"的场景
xSemaphoreCreateBinary()创建二值信号量(不会自动释放)需要初始状态为"不可用"的场景
xSemaphoreTake()获取信号量(将信号量由1变为0)任务中获取信号量
xSemaphoreGive()释放信号量(将信号量由0变为1)任务中释放信号量
xSemaphoreTakeFromISR()中断中获取信号量中断服务程序中获取信号量
xSemaphoreGiveFromISR()中断中释放信号量中断服务程序中释放信号量

5. 二值信号量实战示例教程

下面通过一个完整示例展示二值信号量的使用方法。我们创建两个任务:

  • 任务1:定期释放信号量
  • 任务2:等待并获取信号量,成功后打印提示

5.1 代码实现步骤

  1. 准备工程:复制006多任务创建模板,并重命名为010
    在这里插入图片描述

  2. 删除不必要的代码

#include "queue.h"
TaskHandle_t myTaskHandler3;
struct print{
	int cnt;
	char data[20];
};
		struct print data = {
			.data = "myTask1 runnig"
			};
			data.cnt++;
			xQueueSend(myPrintfQueueHandler, &data, 0);
struct print data = {
			.data = "myTask2 runnig"
		};
			data.cnt++;
			xQueueSend(myPrintfQueueHandler, &data, 0);
void myTask3(void *arg)
{
		struct print data;
		BaseType_t xStatus;
		while(1)
		{
			xStatus = xQueueReceive(myPrintfQueueHandler, &data, portMAX_DELAY);
			if(xStatus == pdPASS)
			{
			taskENTER_CRITICAL();
				printf("%s:%d\n", data.data,data.cnt);
			taskEXIT_CRITICAL();
			}
//			vTaskDelay(500);
		}
}
	xTaskCreate(myTask3,"myTask3",128,NULL,2,&myTaskHandler3);
	myPrintfQueueHandler = xQueueCreate(2,sizeof(struct print));
  1. 导入信号量头文件
#include "semphr.h" //信号量相关的头文件
  1. 创建二值信号量
    在这里插入图片描述

深入理解:为什么选择v开头的创建函数?

查看vSemaphoreCreateBinary定义,理解其内部实现:
在这里插入图片描述

#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
    #define vSemaphoreCreateBinary( xSemaphore )                                                                                     \
    {                                                                                                                                \
        ( xSemaphore ) = xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE ); \
        if( ( xSemaphore ) != NULL )                                                                                                 \
        {                                                                                                                            \
            ( void ) xSemaphoreGive( ( xSemaphore ) );                                                                               \
        }                                                                                                                            \
    }
#endif

从源码可以看出:

  • 它首先创建一个长度为1的队列
  • 创建成功后,立即执行xSemaphoreGive释放信号量,使其初始值为1(可用状态)
  • 这正是我们需要的初始状态!

对比另一个创建函数:
在这里插入图片描述

#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
    #define xSemaphoreCreateBinary()    xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )
#endif

xSemaphoreCreateBinary仅创建队列,不自动释放,初始值为0(不可用状态)。

  1. 声明信号量句柄
    在这里插入图片描述

  2. 创建失败检测
    在这里插入图片描述

  3. 编写Task1(释放信号量)
    在这里插入图片描述

void myTask1(void *arg)
{
		BaseType_t res = 0;

		while(1)
		{
			taskENTER_CRITICAL();
			  	printf("myTask1 runnig\n");
		
            /* 释放二值信号量 */
            res = xSemaphoreGive(myPrintfQueueHandler);
            if(res == pdPASS)
            {
                printf("Task1 release successful\r\n");
            }
            else 
            {
                printf("Task1 release failed\r\n");
            }
						taskEXIT_CRITICAL();
        vTaskDelay(500);
		}
}

📝 说明:这里使用临界区保护打印操作,防止多任务打印导致的输出混乱。

查看xSemaphoreGive定义:
在这里插入图片描述

#define xSemaphoreGive( xSemaphore )    xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )

本质上是向队列发送一个空数据!

  1. 编写Task2(获取信号量)
    在这里插入图片描述
void myTask2(void *arg)
{
 BaseType_t res = 0;
    while (1)
    {
        /* 获取二值信号量 */
        res = xSemaphoreTake(myPrintfQueueHandler,portMAX_DELAY);
        if(res == pdPASS)
        {
            printf("Task2 release successful\r\n"); 
        }
        else 
        {
            printf("Task2 release failed\r\n");   
        }

    }
}

查看xSemaphoreTake定义:

#define xSemaphoreTake( xSemaphore, xBlockTime )    xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ) )
  1. 编译、调试、运行:输出如下内容表示成功
    在这里插入图片描述

5.2 实例代码深度解析

  1. 创建信号量:使用vSemaphoreCreateBinary()创建二值信号量,初始值为1(可用)。

  2. Task1工作流程

    • 进入临界区(防止打印混乱)
    • 打印运行状态信息
    • 释放信号量(使值为1)
    • 打印释放结果
    • 退出临界区
    • 延时500ms
  3. Task2工作流程

    • 尝试获取信号量(将值从1变为0)
    • 若成功(返回pdPASS),打印成功信息
    • 若失败,打印失败信息
    • 循环执行(无延时)
  4. portMAX_DELAY参数:表示无限等待,直到获取到信号量才继续执行。

5.3 运行结果分析

执行程序后,我们观察到典型的执行顺序:

  1. 系统启动,Task2立即获取到信号量(因初始值为1)并打印成功信息。
  2. 信号量值变为0,Task2再次尝试获取时进入阻塞状态。
  3. Task1执行,释放信号量,值变为1。
  4. Task2被唤醒,获取信号量,打印成功信息。
  5. 周而复始,形成Task1释放→Task2获取的循环。

🔍 现象解释:为什么有时Task1的释放成功信息会出现在Task2的获取成功信息之后?

这是因为任务调度的时机。当Task1释放信号量后,如果Task2优先级高于Task1,系统会立即切换到Task2执行,导致Task2的打印先于Task1的释放成功信息。

6. 二值信号量高级特性与注意事项

  1. 跨任务操作:信号量的获取和释放可以在不同任务间进行,这是实现任务同步的基础。

  2. 初始状态选择

    • 使用vSemaphoreCreateBinary():初始状态为"可用"(值为1)
    • 使用xSemaphoreCreateBinary():初始状态为"不可用"(值为0)
  3. 超时参数

    • portMAX_DELAY:永久等待
    • 0:不等待,立即返回
    • 其他值:等待指定时间(单位为tick)
  4. 中断中使用:中断服务程序中必须使用FromISR结尾的函数版本。

7. 总结与实践建议

二值信号量是FreeRTOS中极为强大且使用简单的同步工具,适用于互斥访问和任务同步场景。通过本教程的学习,你应该已经掌握了:

✅ 二值信号量的基本概念与原理
✅ 信号量的创建、获取与释放操作
✅ 常见应用场景与实现方法
✅ 底层实现机制与高级特性

应用建议

  1. 选择合适的场景:二值信号量适合简单的同步场景,复杂场景考虑计数信号量或事件组。

  2. 避免优先级反转:在互斥访问场景中,考虑使用互斥量(Mutex)代替二值信号量,因为互斥量支持优先级继承机制。

  3. 防止死锁:确保获取信号量的任务最终会释放它,避免系统陷入死锁。


📚 想深入学习FreeRTOS?
我整理了一套完整的FreeRTOS开发学习资源,从环境搭建到高级特性应用,包含大量实例代码和详细教程。欢迎star和fork!

扩展思考:在什么情况下,你会选择使用二值信号量而非互斥量(Mutex)或计数信号量?欢迎在评论区讨论!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2337199.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

赛灵思 XCVU440-2FLGA2892E XilinxFPGA Virtex UltraScale

XCVU440-2FLGA2892E 属于 Xilinx Virtex UltraScale 系列,是面向高端应用的旗舰 FPGA 器件。该系列产品以出色的高并行处理能力、丰富的逻辑资源和高速互联能力闻名,广泛用于 高性能计算、数字信号处理等对计算能力和带宽要求极高的场景。采用先进的 20n…

Spring Cloud Alibaba微服务-微服务介绍和搭建

1. 课程介绍 单体服务中有订单,用户,库存, 两个缺陷: a. 是以应用的维度进行负载均衡,资源占用大 b. 当其中一个模块宕机,整个应用就不能用了; nacos;ribbon,loadBa…

KALI安装JAVA8和切换JDK版本

一、安装JDK1.8 1、直接使用下面的地址下载java 1.8: https://repo.huaweicloud.com/java/jdk/8u202-b08/jdk-8u202-linux-x64.tar.gz 2、建立目录,将下载的jdk的安装包复制过去并进行解压 sudo mkdir -p /usr/local/java cp jdk-8u202-linux-x64.t…

今日行情明日机会——20250417

指数目前在区间内缩量震荡 2025年4月17日涨停主要行业方向分析 一、核心主线方向 化工(产能优化涨价预期) • 涨停家数:11家(最强方向)。 • 代表标的: ◦ 红宝丽(2连板)&#xff…

一篇讲完自动化测试基础-Python【万字详细讲解】12

✨博客主页: https://blog.csdn.net/m0_63815035?typeblog 💗《博客内容》:.NET、Java.测试开发、Python、Android、Go、Node、Android前端小程序等相关领域知识 📢博客专栏: https://blog.csdn.net/m0_63815035/cat…

极限编程(XP)简介及其价值观与最佳实践

目录 一、什么是极限编程(XP)二、极限编程的核心价值观1. 沟通2. 简单3. 反馈4. 勇气 三、极限编程的12个最佳实践1. 结对编程2. 40小时工作制3. 简单设计4. 代码规范5. 测试驱动开发(TDD)6. 系统隐喻7. 持续集成8. 重构9. 客户在…

四层板的蛇形走线技巧:原理、策略与应用

在四层板的设计过程中,蛇形走线是一种常见且重要的布线方式。它能够满足特定的设计需求,如调整信号线长度、实现等长布线等,但如果使用不当,也可能会带来一些负面影响,如增加信号衰减、引入电磁干扰等。以下将详细探讨…

面向对象—有理数类的设计

目录 1.代码呈现 1.1编写toString、equals方法 1.2测试代码 1.3有理数类的代码 2.论述题 3.有理类设计 1.代码呈现 1.1编写toString、equals方法 (1)toString方法 Overridepublic String toString(){if(this.v20){return "Undefined";}return this.v1 "/…

408数据结构绪论刷题001

答案:D 解析: • A选项:数据元素是组成数据对象的基本单位 ,它只是数据的基本个体,不能完整定义数据结构,所以A选项错误。 • B选项:数据对象是性质相同的数据元素的集合,仅仅描述…

Leetcode 3359. 查找最大元素不超过 K 的有序子矩阵【Plus题】

1.题目基本信息 1.1.题目描述 给定一个大小为 m x n 的二维矩阵 grid。同时给定一个 非负整数 k。 返回满足下列条件的 grid 的子矩阵数量: 子矩阵中最大的元素 小于等于 k。 子矩阵的每一行都以 非递增 顺序排序。 矩阵的子矩阵 (x1, y1, x2, y2) 是通过选择…

文件系统 软硬连接

🌻个人主页:路飞雪吖~ 🌠专栏:Linux 目录 一、理解文件系统 🌠磁盘结构 二、软硬连接 🌟软硬链接 🌠软链接: 🌠硬链接: 🌟理解软硬链接的应…

计算机视觉——JPEG AI 标准发布了图像压缩新突破与数字图像取证的挑战及应对策略

概述 今年2月,经过多年旨在利用机器学习技术开发一种更小、更易于传输和存储且不损失感知质量的图像编解码器的研究后,JPEG AI国际标准正式发布。 来自JPEG AI官方发布流,峰值信噪比(PSNR)与JPEG AI的机器学习增强方法…

Oracle 19c部署之数据库软件安装(二)

在完成了Oracle Linux 9的初始化配置之后,我们准备安装Oracle 19c数据库软件。 Oracle数据库支持两种主要的安装方式:图形化安装和静默安装。这两种方法各有优缺点,选择哪种取决于你的具体需求、环境配置以及个人偏好。 图形化安装 图形化安…

在Vmware15(虚拟机免费) 中安装纯净win10详细过程

一、软件备选 1. VMware15.5.1 网盘下载地址 链接: https://pan.baidu.com/s/1y6GLJ2MG-1tomWblt3otsg?pwdim8e 提取码: im8e 2. windows镜像下载 去官网下载ios包 链接:https://www.microsoft.com/zh-cn/software-download/windows10 二、在VMware15.5.1下安装w…

[Spark]深入解密Spark SQL源码:Catalyst框架如何优雅地解析你的SQL

本文内容组织形式 总结具体例子执行语句解析层优化层物理计划层执行层 猜你喜欢PS 总结 先写个总结,接下来会分别产出各个部分的源码解析,Spark SQL主要分为以下五个执行部分。 具体例子 接下来举个具体的例子来说明 执行语句 SELECT name, age FR…

基于Flask的漏洞挖掘知识库系统设计与实现

基于Flask的漏洞挖掘知识库系统设计与实现 一、系统架构设计 1.1 整体架构 本系统采用经典的三层Web架构,通过Mermaid图展示的组件交互流程清晰呈现了以下核心模块: 前端展示层:基于Bootstrap5构建响应式界面业务逻辑层:Flask…

ECharts散点图-散点图8,附视频讲解与代码下载

引言: ECharts散点图是一种常见的数据可视化图表类型,它通过在二维坐标系或其它坐标系中绘制散乱的点来展示数据之间的关系。本文将详细介绍如何使用ECharts库实现一个散点图,包括图表效果预览、视频讲解及代码下载,让你轻松掌握…

Langchain-构建向量数据库和检索器

向量数据库安装 pip install langchain-chroma 文档》向量存储》向量数据库。 和0416 提示词工程相同。 初始化 import osfrom langchain_chroma import Chroma from langchain_community.chat_message_histories import ChatMessageHistory from langchain_core.documents im…

首席人工智能官(Chief Artificial Intelligence Officer,CAIO)的详细解析

以下是**首席人工智能官(Chief Artificial Intelligence Officer,CAIO)**的详细解析: 1. 职责与核心职能 制定AI战略 制定公司AI技术的长期战略,明确AI在业务中的应用场景和优先级,推动AI与核心业务的深度…

2025华中杯数学建模B题完整分析论文(共42页)(含模型、数据、可运行代码)

2025华中杯大学生数学建模B题完整分析论文 目录 一、问题重述 二、问题分析 三、模型假设 四、 模型建立与求解 4.1问题1 4.1.1问题1解析 4.1.2问题1模型建立 4.1.3问题1样例代码(仅供参考) 4.1.4问题1求解结果(仅供参考&am…