FreeRTOS——信号量篇

信号量是什么

在FreeRTOS中,信号量(Semaphore)是一种用于任务间同步或保护共享资源的机制。

任务间同步:即确保一个任务在另一个任务执行到某个点后在执行。

资源保护:即控制对共享资源的访问,防止多个任务同时访问资源导致数据不一致。

信号量的类型

  1. 二进制信号量(Binary Semaphore):

    二进制信号量在同一时间只允许一个任务访问资源。它们通常用于实现任务间的同步。例如,一个任务可以等待另一个任务发出信号,然后继续执行。(阻塞式)
    二进制信号量

  2. 计数信号量(Counting Semaphore):

    计数信号量允许多个任务访问相同的资源。它们有一个初始计数值,每次获取信号量时计数减一,每次释放信号量时计数加一。当计数值为零时,表示没有可用资源,任务必须等待
    计数信号量

使用信号量的基本步骤

  1. 创建信号量

    • xSemaphoreCreateBinary(): 创建一个二进制信号量。
    • xSemaphoreCreateCounting(): 创建一个计数信号量。
  2. 获取信号量

    • xSemaphoreTake(): 获取信号量,如果信号量不可用,任务可以选择等待。
  3. 释放信号量

    • xSemaphoreGive(): 释放信号量,使其他等待的任务能够继续执行。

FreeRTOS中信号量的API

xSemaphoreCreateBinary()

SemaphoreHandle_t xSemaphoreCreateBinary( void );
参数解析
无参数:xSemaphoreCreateBinary()函数没有任何参数。
返回值
成功:返回一个SemaphoreHandle_t类型的信号量句柄,该句柄可用于其他信号量操作函数。
失败:返回NULL,表示内存分配失败,无法创建信号量。
使用说明
在创建的初始状态下,二进制信号量是空的(即,初始值为0),因此在创建后需要先通过xSemaphoreGive()将其置为“满”状态(即,初始值为1),才能供任务使用。

xSemaphoreCreateCounting()

SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
参数解析
uxMaxCount:指定信号量的最大计数值,即信号量可以达到的最大值。
uxInitialCount:指定信号量的初始计数值,即信号量在创建时的初始值。
返回值
成功:返回一个SemaphoreHandle_t类型的信号量句柄,该句柄可用于其他信号量操作函数。
失败:返回NULL,表示内存分配失败,无法创建信号量。
使用说明
计数信号量可以用来控制对有限资源的访问,或者用于多个任务之间的事件计数。

xSemaphoreTake()

BaseType_t xSemaphoreTake(
    SemaphoreHandle_t xSemaphore,
    TickType_t xTicksToWait
);
参数解析
xSemaphore:指定要获取的信号量的句柄(handle)。
xTicksToWait:等待获取信号量的最长时间(以时钟节拍(tick)为单位)。可以是以下几种值:
portMAX_DELAY:永久等待,直到获取信号量。
0:立即返回,不等待信号量。
其他正整数值:等待指定时长,如果在指定时间内未能获取到信号量,则函数返回。
返回值
pdTRUE:成功获取到信号量。
pdFALSE:未能获取到信号量,在等待超时后返回。
使用说明
在调用xSemaphoreTake()之前,需要确保已经创建了相应的信号量,并且任务拥有该信号量的访问权限。
如果信号量当前不可用(即已经被其他任务获取),任务会根据xTicksToWait的设置进入阻塞状态,直到满足获取条件。
当任务成功获取信号量时,信号量的计数值会相应减少。

xSemaphoreGive()

BaseType_t xSemaphoreGive(
    SemaphoreHandle_t xSemaphore
);
参数解析
xSemaphore:指定要释放的信号量的句柄(handle)。
返回值
pdTRUE:成功释放信号量。
pdFALSE:释放信号量失败。
使用说明
xSemaphoreGive()函数用于将信号量的计数值增加,表示释放一个资源或通知一个事件。
如果有任务在等待获取该信号量,释放后会有一个任务能够获取到信号量并继续执行。
一般情况下,使用xSemaphoreGive()的任务应当在使用完共享资源后释放信号量,以便其他任务可以使用它。

示例一:利用二进制信号量,当Send任务中Key1按下时释放一个信号量,Receive任务获取信号量。

下述代码中按键Key1相关配置不提供,仅对信号量的使用进行一个距离

#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include <stdio.h>

// 定义信号量句柄
SemaphoreHandle_t BinarySemHandle;

// 发送任务
void vSenderTask(void *pvParameters) 
{
	BinarySemHandle = xSemaphoreCreateBinary();	    // 创建一个二进制信号量

    if (BinarySem != NULL)
	 {
        // 成功创建信号量
        // 立即释放一次信号量,初始化信号量为可用状态
        xSemaphoreGive(BinarySemHandle);
    }

    for(;;) 
	{
        // 检查按键1是否按下
        if (HAL_GPIO_ReadPin(Key1_GPIO_Port, Key1_Pin) == GPIO_PIN_RESET) 
		{
            // 按键1按下,发送信号量
            xSemaphoreGive(BinarySem);
            printf("发送任务:按键1按下,信号量已发送\n");
        }
    }
	vTaskDelay(pdMS_TO_TICKS(200));
}

// 接收任务
void vReceiverTask(void *pvParameters) 
{
    for(;;) 
	{
        // 尝试获取信号量
        if (xSemaphoreTake(BinarySem, portMAX_DELAY) == pdTRUE) 
		{
            // 成功获取信号量,可以继续执行
            printf("接收任务:信号量已接收\n");
            // 模拟处理接收到的信号量
            vTaskDelay(pdMS_TO_TICKS(500));
        } 
		else 
		{
            printf("未接收到信号量\n");
        }
    }
}

// 主函数
int main(void) 
{
    // 创建发送任务
    xTaskCreate(vSenderTask, "Sender Task", configMINIMAL_STACK_SIZE, NULL, 1, NULL);

    // 创建接收任务
    xTaskCreate(vReceiverTask, "Receiver Task", configMINIMAL_STACK_SIZE, NULL, 1, NULL);

    // 启动调度器
    vTaskStartScheduler();

    // 如果程序能运行到这里,说明调度器启动失败
    for(;;);

    return 0;
}

示例2:利用计数信号量,按下Key1申请车位,按下Key2释放车位

#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include <stdio.h>

SemaphoreHandle_t ParkingSem;	// 定义信号量句柄

#define MAX_PARKING_SPACES 5	// 停车位的最大数量

// 申请车位任务
void vRequestParkingTask(void *pvParameters) 
{
    for(;;) 
	{
        // 检查按键Key1是否按下
        if (HAL_GPIO_ReadPin(Key1_GPIO_Port,Key1_Pin) == GPIO_PIN_RESET) 
		{ 
            if (xSemaphoreTake(ParkingSem, 0) == pdTRUE) 	// 按键Key1按下,尝试申请车位
			{
                printf("申请车位任务:成功申请一个车位\n");
            } 
			else 
			{
                printf("申请车位任务:车位已满,无法申请车位\n");
            }

            // 防止按键抖动,延迟一段时间
            vTaskDelay(pdMS_TO_TICKS(200));
        }

        // 延迟一段时间,模拟任务执行
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// 释放车位任务
void vReleaseParkingTask(void *pvParameters) 
{
    for(;;) 
	{
        // 检查按键Key2是否按下
        if (HAL_GPIO_ReadPin(Key2_GPIO_Port, Key2_Pin) == GPIO_PIN_RESET) 
		{ 
            if (uxSemaphoreGetCount(ParkingSem) < MAX_PARKING_SPACES) // 按键Key2按下,尝试释放车位
			{
                xSemaphoreGive(ParkingSem);
                printf("释放车位任务:成功释放一个车位\n");
            } 
			else 
			{
                printf("释放车位任务:车位已满,无需释放车位\n");
            }

            // 防止按键抖动,延迟一段时间
            vTaskDelay(pdMS_TO_TICKS(200));
        }

        // 延迟一段时间,模拟任务执行
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// 主函数
int main(void) 
{
    // 创建一个计数信号量,初始值为MAX_PARKING_SPACES,最大值为MAX_PARKING_SPACES
    ParkingSem = xSemaphoreCreateCounting(MAX_PARKING_SPACES, MAX_PARKING_SPACES);

    if (ParkingSem != NULL) // 成功创建信号量
	{
        // 创建申请车位任务
        xTaskCreate(vRequestParkingTask, "Request Parking Task", configMINIMAL_STACK_SIZE, NULL, 1, NULL);

        // 创建释放车位任务
        xTaskCreate(vReleaseParkingTask, "Release Parking Task", configMINIMAL_STACK_SIZE, NULL, 1, NULL);

        // 启动调度器
        vTaskStartScheduler();
    } else 
	{
        printf("无法创建计数信号量\n");
    }

    // 如果程序能运行到这里,说明调度器启动失败
    for(;;);

    return 0;
}

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

园园顺顺崽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值