STM32-FreeRTOS操作系统-二值信号量与计数信号量

引言

在嵌入式开发领域,任务同步与通信是系统稳定运行的核心。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。该函数还有一个返回值,跟上面一样,这里就跳过了。

二值信号量示例代码

cs 复制代码
#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);//动态方法创建任务1
	
	xTaskCreate(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");
		else
		OLED_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。

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

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

计数信号量示例代码

cs 复制代码
#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);//动态方法创建任务1
	
	xTaskCreate(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");//
		else
		OLED_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,等待下一次信号的到来。值得注意的是,二值信号量是单次事件,如果需要执行多个事件,执行事件的顺序就从高优先级向低优先级执行,不能同时执行。

计数信号量

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

总结

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

相关推荐
国科安芯4 分钟前
火箭传感器控制单元的抗辐照MCU选型与环境适应性验证
单片机·嵌入式硬件·架构·risc-v·安全性测试
-Springer-38 分钟前
STM32 学习 —— 个人学习笔记5(EXTI 外部中断 & 对射式红外传感器及旋转编码器计数)
笔记·stm32·学习
LS_learner1 小时前
树莓派(ARM64 架构)Ubuntu 24.04 (Noble) 系统 `apt update` 报错解决方案
嵌入式硬件
来自晴朗的明天2 小时前
16、电压跟随器(缓冲器)电路
单片机·嵌入式硬件·硬件工程
钰珠AIOT2 小时前
在同一块电路板上同时存在 0805 0603 不同的封装有什么利弊?
嵌入式硬件
代码游侠2 小时前
复习——Linux设备驱动开发笔记
linux·arm开发·驱动开发·笔记·嵌入式硬件·架构
代码游侠13 小时前
学习笔记——设备树基础
linux·运维·开发语言·单片机·算法
xuxg200515 小时前
4G 模组 AT 命令解析框架课程正式发布
stm32·嵌入式·at命令解析框架
CODECOLLECT16 小时前
京元 I62D Windows PDA 技术拆解:Windows 10 IoT 兼容 + 硬解码模块,如何降低工业软件迁移成本?
stm32·单片机·嵌入式硬件