引言

在嵌入式开发领域,任务同步与通信是系统稳定运行的核心。STM32配合FreeRTOS操作系统,为开发者提供了强大的工具支持。其中,二值信号量和计数信号量作为FreeRTOS的关键同步机制,分别用于任务间的简单同步和资源计数控制。二值信号量如同一个开关,用于控制任务的进入与退出;而计数信号量则用于管理有限资源的访问,确保资源的合理分配。本文将深入剖析这两种信号量的工作原理、使用方法及在STM32平台上的应用实例,助力开发者精准掌握其精髓,提升系统开发效率与稳定性。

什么是二值信号量?

二值信号量是一种同步机制,用于在多任务环境中协调任务的执行。它本质上是一个只能取两个值(通常是0和1)的变量,类似于一个开关。当信号量的值为1时,表示资源可用;当值为0时,表示资源已被占用。在FreeRTOS中,二值信号量主要用于任务间的同步,而不是用于互斥。它的工作原理如下:当一个任务需要资源时,它会尝试获取信号量。如果信号量的值为1,任务会将其值减1并继续执行;如果信号量的值为0,任务会进入阻塞状态,等待信号量变为1。当另一个任务释放资源时,它会将信号量的值加1,从而唤醒等待的任务。

什么是计数信号量?

计数信号量是一种用于同步和资源管理的机制,它维护一个非负整数值,表示可用资源的数量。在FreeRTOS中,计数信号量通常用于管理有限数量的资源,例如缓冲区、硬件设备等。计数信号量的工作原理如下:当一个任务需要使用资源时,它会尝试获取信号量。如果信号量的值大于0,任务会将其值减1并继续执行,表示占用了一个资源;如果信号量的值为0,任务会进入阻塞状态,等待资源变为可用。当任务释放资源时,它会将信号量的值加1,从而表示资源数量增加,可能会唤醒等待的任务。

二值信号量

创建二值信号量句柄

步骤都跟前面的创建任务差不多,只不过在创建任务的基础上添加了二值信号量的相关函数,从本文章开始就不再细说怎么创建任务,直接开始讲二值信号量的创建及使用。

首先定义一下二值信号量的句柄并为其赋初始值为NULL:

SemaphoreHandle_t BinarySem_Handle =NULL; //二值信号量句柄

然后是xSemaphoreCreateBinary,它是FreeRTOS操作系统提供的一个函数,用于创建一个二值信号量,前面也说了,二值信号量是一个特殊的信号量,它的值只能是0或1,通常用于任务间的同步。其函数原型为:

SemaphoreHandle_t xSemaphoreCreateBinary(void);

这个函数没有参数,但有一个返回值,通常返回的是一个二值信号量的句柄,如果创建成功就返回相应的值,失败就返回NULL。

发送二值信号量

也叫释放二值信号量,其函数为xSemaphoreGive(),该函数原型为:

BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore)

参数xSemaphore为需要发送或释放的信号量句柄,这里我们填入上面定义好的二值信号量句柄。同时,该函数还有一个返回值,返回的类型是BaseType_t,通常是一个整数类型。如果返回值为pdTRUE(在freertos中通常定义为1),则表示信号量发送或释放成功。如果返回值为pdFALSH(在freertos中通常定义为0),则表示发送或释放失败。

获取二值信号量

有发送就有获取,其函数为xSemaphoreTake(),该函数原型为:

BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);

参数xSemaphore跟上面的一样,填入定义好的句柄即可。参数xTicksToWait为任务等待信号量的最大时间,可根据需要选择,通常为portMAX_DELAY。该函数还有一个返回值,跟上面一样,这里就跳过了。

二值信号量示例代码

#include "myfreertos.h"
#include "FreeRTOS.h"
#include "semphr.h"
#include "queue.h"
#include "Usart.h"
#include "oled.h"
#include "Task.h"
#include "led.h"
#include "key.h"TaskHandle_t MyTaskHandler;//任务句柄TaskHandle_t MyTask1Handler;//任务1句柄TaskHandle_t SendTask_Handler;  //发送消息句柄TaskHandle_t ReceiveTask_Handler;//接收消息句柄void MyTask(void *pvParameters);    //声明启动函数void MyTask1(void *pvParameters);   //声明任务1函数void Send_task(void *pvParameters);  //声明发送消息函数void Receive_task(void *pvParameters);  //声明接收消息函数SemaphoreHandle_t BinarySem_Handle =NULL; //二值信号量句柄void Start_Task(void)
{xTaskCreate(MyTask,"MyTask",128,NULL,1,&MyTaskHandler);//动态方法创建任务vTaskStartScheduler();//启动任务调动		
}void MyTask(void *arg)            //开始创建任务函数
{taskENTER_CRITICAL();           //进入临界区	/* 创建二值信号量 BinarySem */BinarySem_Handle = xSemaphoreCreateBinary();	xTaskCreate(MyTask1,"MyTask1",50,NULL,2,&MyTask1Handler);//动态方法创建任务1xTaskCreate(Receive_task,"Receive_task",50,NULL,3,&ReceiveTask_Handler);//创建接收消息任务xTaskCreate(Send_task,"Send_task",50,NULL,4,&SendTask_Handler);  //创建发送消息任务vTaskDelete(MyTaskHandler);    //删除开始任务taskEXIT_CRITICAL();           //退出临界区
}void MyTask1(void *arg)     //任务1函数体
{while(1){OLED_ShowString(1,1,"Runing Task Led");GPIO_ResetBits(GPIOC,GPIO_Pin_13);vTaskDelay(300);GPIO_SetBits(GPIOC,GPIO_Pin_13);vTaskDelay(900);}	
}//接收任务函数
void Receive_task(void *pvParameters)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */while(1){		      //获取二值信号量 xSemaphore,没获取到则一直等待xReturn = xSemaphoreTake(BinarySem_Handle,100); /* 等待时间 */if(pdTRUE == xReturn)OLED_ShowString(3,1,"Receive_OK");elseOLED_ShowString(3,1,"Receive_NO");vTaskDelay(200);}
}//发送任务函数
void Send_task(void *pvParameters)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */while(1){if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==1){xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量if( xReturn == pdTRUE )OLED_ShowString(2,1,"Send_OK");}else{OLED_ShowString(2,1,"Send_NO");}vTaskDelay(200);}
}

计数信号量

创建计数信号量

跟二值信号量一样,先定义一个计数信号量句柄并给其赋初值为NULL:

SemaphoreHandle_t CountSem_Handle =NULL; //计数信号量句柄

xSemaphoreCreateCounting()是FreeRTOS中用于创建计数信号量的函数,其函数原型为:

SemaphoreHandle_t xSemaphoreCreateCounting(
UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount
)

参数uxMaxCount是计数信号量的最大计数值,当信号量值达此值时,不能再通过xSemaphoreGive增加其值。参数uxInitialCount为信号量的初始计数值。该函数还有一个返回值,如果成功创建信号量,就返回信号量的句柄,失败则返回NULL。

发送计数信号量与获取计数信号量

步骤都跟二值信号量大致相同,这里就不过多论述。

计数信号量示例代码

#include "myfreertos.h"
#include "FreeRTOS.h"
#include "semphr.h"
#include "queue.h"
#include "Usart.h"
#include "oled.h"
#include "Task.h"
#include "led.h"
#include "key.h"TaskHandle_t MyTaskHandler;//任务句柄TaskHandle_t MyTask1Handler;//任务1句柄TaskHandle_t SendTask_Handler;  //发送消息句柄TaskHandle_t ReceiveTask_Handler;//接收消息句柄void MyTask(void *pvParameters);    //声明启动函数void MyTask1(void *pvParameters);   //声明任务1函数void Send_task(void *pvParameters);  //声明发送消息函数void Receive_task(void *pvParameters);  //声明接收消息函数SemaphoreHandle_t CountSem_Handle =NULL; //计数信号量句柄void Start_Task(void)
{xTaskCreate(MyTask,"MyTask",128,NULL,1,&MyTaskHandler);//动态方法创建任务vTaskStartScheduler();//启动任务调动		
}void MyTask(void *arg)            //开始创建任务函数
{taskENTER_CRITICAL();           //进入临界区	/* 创建 CountSem */CountSem_Handle = xSemaphoreCreateCounting(5,5); 	xTaskCreate(MyTask1,"MyTask1",50,NULL,2,&MyTask1Handler);//动态方法创建任务1xTaskCreate(Receive_task,"Receive_task",50,NULL,3,&ReceiveTask_Handler);//创建接收消息任务xTaskCreate(Send_task,"Send_task",50,NULL,4,&SendTask_Handler);  //创建发送消息任务vTaskDelete(MyTaskHandler);    //删除开始任务taskEXIT_CRITICAL();           //退出临界区
}void MyTask1(void *arg)     //任务1函数体
{while(1){OLED_ShowString(1,1,"Runing Task Led");GPIO_ResetBits(GPIOC,GPIO_Pin_13);vTaskDelay(300);GPIO_SetBits(GPIOC,GPIO_Pin_13);vTaskDelay(900);}	
}//接收任务函数
void Receive_task(void *pvParameters)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */while(1){		      //获取计数信号量 xSemaphore,没获取到则一直等待xReturn = xSemaphoreTake(CountSem_Handle,100); /* 等待时间 */if(pdTRUE == xReturn)OLED_ShowString(3,1,"Receive_OK");//elseOLED_ShowString(3,1,"Receive_NO");vTaskDelay(200);}
}//发送任务函数
void Send_task(void *pvParameters)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */while(1){if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==1){xReturn = xSemaphoreGive( CountSem_Handle );//给出计数信号量if( xReturn == pdTRUE )OLED_ShowString(2,1,"Send_OK");}else{OLED_ShowString(2,1,"Send_NO");}vTaskDelay(200);}
}

二值信号量与计数信号量的区别

二值信号量只有两种状态(0和1),通常用于控制互斥访问,一次只允许一个进程进入临界区。而计数信号量可以有多个值,用于表示资源的数量,允许多个进程同时访问有限数量的资源。

讲解一下上面两个代码的思路

二值信号量

这个我主要是通过按下按键然后去释放二值信号量,按下按键释放二值信号量,然后被接收二值信号量任务接收,该任务就会执行里面的程序,执行完以后退出,二值信号量的信息返回值有由1变0,等待下一次信号的到来。值得注意的是,二值信号量是单次事件,如果需要执行多个事件,执行事件的顺序就从高优先级向低优先级执行,不能同时执行。

计数信号量

跟二值信号量的思路逻辑是一样的,但对于计数信号量而言,它能同时执行多个任务,提高了资源的利用率。

总结

以上是我的个人看法,如有不足,欢迎指出!

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MarTech营销技术全景解析:概念、图谱与最新实践案例

一、引言:为什么企业越来越依赖MarTech?在数字化浪潮下,企业营销环境正发生深刻变化:客户触点增加:从官网、社交媒体到短视频、展会,信息渠道呈指数级增长。决策链条复杂:B2B客户通常需要多轮调…

服务器 - 从一台服务器切换至另一台服务器(损失数十条访客记录)

服务器 - 从一台服务器切换至另一台服务器(损失数十条PV记录为代价) 看着四年的服务器正式到期,没什么轰轰烈烈的告别,就像目送老朋友转身走远,只默默记下:哦,原来它陪了我这么久啊。 前言 一台陪伴了我4年的服务器昨…

《云原生边缘与AI训练场景:2类高频隐蔽Bug的深度排查与架构修复》

在云原生技术向边缘计算与AI训练场景渗透的过程中,基础设施层的问题往往会被场景特性放大——边缘环境的弱网络、异构硬件,AI训练的高资源依赖、分布式协作,都可能让原本隐藏的Bug以“业务故障”的形式爆发。这些问题大多不具备直观的报错信息…

【51单片机】【protues仿真】基于51单片机数控直流稳压电源系统

目录 一、主要功能 二、使用步骤 三、硬件资源 四、软件设计 五、实验现象 一、主要功能 1、数码管显示输出电压值 2、滑动电阻调节输出电压 3、电压输出范围0-15V,步进值1 二、使用步骤 基于51单片机的数控直流稳压电源是一种通过数字控制实现电压调节的智…

xtuoj Rectangle

题目思路将矩形间的相交情况通过投影转化为x、y两个方向下的线段是否相交,即前面的题目,判断两个区间是否相交,x投影的每个区间的左端点是每个矩形x的min,右端点是每个矩形的x的max,y投影情况同理,只要x轴的…

【深度学习踩坑实录】从 Checkpoint 报错到 TrainingArguments 精通:QNLI 任务微调全流程复盘

作为一名深度学习初学者,最近在基于 Hugging Face Transformers 微调 BERT 模型做 QNLI 任务时,被Checkpoint 保存和TrainingArguments 配置这两个知识点卡了整整两天。从磁盘爆满、权重文件加载报错,到不知道如何控制 Checkpoint 数量&#…

Java面试小册(3)

21【Q】: 什么是Java的SPI机制?【A】:SPI 是一种插件机制,用于在运行时动态加载服务的实现。它通过定义接口(服务接口)并提供一种可扩展的方式来让服务的提供着(实现类)在运行时注入&#xff0c…

P1150 Peter 的烟

记录20#include <bits/stdc.h> using namespace std; int main(){int n,k;cin>>n>>k;int cnt0;while(n>k){cntk;nn-k1;}cntn;cout<<cnt;return 0; }突破口每吸完一根烟就把烟蒂保存起来&#xff0c;k&#xff08;k>1&#xff09;个烟蒂可以换一个…

Cursor和Hbuilder用5分钟开发微信小程序

分享一个5分钟搞定微信小程序开发的技能&#xff0c;需要用到两个工具&#xff1a;Cursor和Hbuilder。 第1步、下载HBuilder。Hbuilder可以实现一套代码直接生成安卓、苹果、鸿蒙各个平台APP。访问Hbuilder的官方网站&#xff0c;HBuilderX-高效极客技巧&#xff0c;选择适合…

k8s的dashboard

找一个装有docker的机器&#xff0c;在一个rocky linux的虚拟机里弄拉取一个rancher镜像建立一个目录&#xff0c;目的&#xff1a;和里面数据做持久化关联后台运行&#xff0c;让他有权限&#xff0c;8080端口和容器80端口映射&#xff0c;443和443做映射查看一下删掉&#xf…

桥接模式,打造灵活可扩展的日志系统C++

一、为什么用桥接模式在企业开发中&#xff0c;日志系统几乎是标配。常见需求&#xff1a;日志有多种类型&#xff08;Info、Warning、Error 等&#xff09;&#xff1b;日志需要支持多种输出方式&#xff08;控制台输出、写文件、远程上传、数据库存储等&#xff09;。如果把这…

kafka--基础知识点--5.3--producer事务

1 事务简介 Kafka事务是Apache Kafka在流处理场景中实现Exactly-Once语义的核心机制。它允许生产者在跨多个分区和主题的操作中&#xff0c;以原子性&#xff08;Atomicity&#xff09;的方式提交或回滚消息&#xff0c;确保数据处理的最终一致性。例如&#xff0c;在流处理中…

利用DeepSeek实现服务器客户端模式的DuckDB原型

在网上看到韩国公司开发的一款GooseDB&#xff0c;DuckDB™ 的功能扩展分支&#xff0c;具有服务器/客户端、多会话和并发写入支持&#xff0c;使用 PostgreSQL 有线协议&#xff0c;但它是Freeware而不是开源&#xff0c;所以让DeepSeek实现之。 首先把readme页面发给他翻译&a…

麦当劳APP逆向

版本 V 7.0.17.0反调试 梆梆企业加固 frida反调试部分代码 headers {"biz_scenario": "500","biz_from": "1004","User-Agent": "mcdonald_Android/7.0.17.0 (Android)","ct": "102","…

大数据毕业设计选题推荐-基于大数据的结核病数据可视化分析系统-Hadoop-Spark-数据可视化-BigData

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、PHP、.NET、Node.js、GO、微信小程序、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇…

Vue3 视频播放器完整指南 – @videojs-player/vue 从入门到精通

前言 在 Vue 3 生态中&#xff0c;视频播放功能是许多应用的核心需求。videojs-player/vue 是一个专门为 Vue 3 设计的视频播放器组件&#xff0c;基于成熟的 Video.js 库构建&#xff0c;提供了简单而强大的视频播放解决方案。 主要特性 Vue 3 组件化&#xff1a;原生 Vue …

【靶场练习】--DVWA第一关Brute Force(暴力破解)全难度分析

注意&#xff0c;这一关必须要使用Burpsuite来抓包 目录Low1.抓包2.发送到爆破模块3.选择爆破模式爆破模式介绍4.添加载荷5.添加字典6.爆破查看查看源码Medium查看源码High1.抓包2.在bp的extensions中找到CSRF Token Tracker&#xff0c;并安装3.构造字典4.成功爆破查看源码Imp…

Java语言——排序算法

一、基本概念排序&#xff1a;将n个数字按一定顺序排列&#xff08;比如&#xff1a;升序&#xff0c;或者降序&#xff09; ^内部排序 &#xff1a;若整个排序过程不需要访问外存便能完成&#xff0c;则称此类排序问题为内部排序 ^外部排序&#xff1a;若参加排序的记录数量很…

【Linux】人事档案——用户及组管理

目录 1 用户及组管理 2 用户及用户组管理命令 2.1 useradd&#xff1a;建立用户 useradd命令用于建立用户&#xff0c;该 2.2 passwd&#xff1a;更改用户密码 2.3 usermod&#xff1a;更改用户信息 2.4 groupadd&#xff1a;建立用户组 2.5 finger&#xff1a;查找并显…

给定一个有序的正数数组arr和一个正数range,如果可以自由选择arr中的数字,想累加得 到 1~range 范围上所有的数,返回arr最少还缺几个数。

给定一个有序的正数数组arr和一个正数range&#xff0c;如果可以自由选择arr中的数字&#xff0c;想累加得 到 1~range 范围上所有的数&#xff0c;返回arr最少还缺几个数。 #include <iostream> #include <vector>using namespace std;void func1(std::vector<…