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,等待下一次信号的到来。值得注意的是,二值信号量是单次事件,如果需要执行多个事件,执行事件的顺序就从高优先级向低优先级执行,不能同时执行。

计数信号量

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

总结

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

相关推荐
如愿小李2 小时前
STM32之水质浑浊度传感器模块
stm32·单片机·嵌入式硬件
GilgameshJSS4 小时前
【学习K230-例程21】GT6700-UDP-Client
网络·python·单片机·网络协议·学习·udp
BYSJMG5 小时前
计算机毕设推荐:基于Hadoop+Spark物联网网络安全数据分析系统 物联网威胁分析系统【源码+文档+调试】
大数据·hadoop·python·物联网·spark·django·课程设计
深圳市尚想信息技术有限公司7 小时前
APT32F0042F6P6 32位微控制器(MCU)单片机 APT爱普特微电子 芯片核心解析
单片机·集成电路·电子元器件·爱普特
沐欣工作室_lvyiyi7 小时前
基于K210和STM32的小区门禁系统(论文+源码)
stm32·单片机·嵌入式硬件·毕业设计·人脸识别·k210·计算机毕业设计
殷忆枫9 小时前
基于STM32的智能语音识别饮水机系统设计
stm32·嵌入式硬件·语音识别
月阳羊10 小时前
【硬件-笔试面试题-93】硬件/电子工程师,笔试面试题(知识点:波特图)
java·经验分享·单片机·嵌入式硬件·面试
Wave84519 小时前
STM32--时间戳,BKB,RTC
stm32·单片机·嵌入式硬件