引言
在嵌入式开发领域,任务同步与通信是系统稳定运行的核心。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,等待下一次信号的到来。值得注意的是,二值信号量是单次事件,如果需要执行多个事件,执行事件的顺序就从高优先级向低优先级执行,不能同时执行。
计数信号量
跟二值信号量的思路逻辑是一样的,但对于计数信号量而言,它能同时执行多个任务,提高了资源的利用率。
总结
以上是我的个人看法,如有不足,欢迎指出!