2025最新超详细FreeRTOS入门教程:第五章 FreeRTOS信号量

摘要

在前一章中,我们学习了 消息队列(Queue),它主要用于任务之间传递数据。但在某些场景中,我们并不需要传递数据,只需要实现一种 同步机制(比如“有资源时允许访问”,“某事件发生后允许继续执行”),这时使用 信号量(Semaphore) 更加合适。

FreeRTOS 的信号量基于 队列机制 实现,是实现 任务同步、事件触发、共享资源访问保护 的重要工具。本章将带你全面理解 FreeRTOS 信号量的概念、类型、API 使用、常见应用场景以及和消息队列的区别。

2025最新超详细FreeRTOS入门教程


一、信号量的基本概念

📌 定义:信号量(Semaphore)是一种用于 多任务同步与互斥 的机制,它本质上是一个特殊的消息队列,容量为 1 或更大,但存储的不是数据,而是 一个计数值

功能

  • 任务间同步:一个任务通知另一个任务继续执行
  • 中断与任务同步:ISR 中发信号,任务收到后继续执行
  • 资源管理:实现类似操作系统的 P/V 操作,保证共享资源不会被同时访问

二、信号量的类型

FreeRTOS 提供了三种主要信号量:

类型 特点 典型应用
二值信号量 值为 0 或 1,类似事件触发 中断事件通知
计数型信号量 可累加,允许多个事件存储 事件计数、资源池
互斥信号量 特殊的二值信号量,带优先级继承机制 防止共享资源竞争
Give
Take
Give
Take
Take/Give
任务A
二值信号量
任务B
计数型信号量
任务C
任务D
互斥信号量

三、API 使用

1. 创建信号量

SemaphoreHandle_t xSemaphoreCreateBinary(void);   // 二值信号量
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount); // 计数信号量
SemaphoreHandle_t xSemaphoreCreateMutex(void);    // 互斥信号量

2. 获取信号量(Take)

BaseType_t xSemaphoreTake(
   SemaphoreHandle_t xSemaphore,
   TickType_t xTicksToWait
);
  • xTicksToWait:阻塞等待时间
  • 返回值 pdPASS 表示获取成功

3. 释放信号量(Give)

BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);

ISR 中使用:

BaseType_t xSemaphoreGiveFromISR(
   SemaphoreHandle_t xSemaphore,
   BaseType_t *pxHigherPriorityTaskWoken
);

四、二值信号量的应用

示例:中断通知任务

#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

SemaphoreHandle_t xBinarySemaphore;

void vTaskHandler(void *pvParameters)
{
    for(;;)
    {
        if(xSemaphoreTake(xBinarySemaphore, portMAX_DELAY) == pdPASS)
        {
            printf("任务被中断唤醒!\n");
        }
    }
}

void EXTI0_IRQHandler(void)  // 按键中断
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    xSemaphoreGiveFromISR(xBinarySemaphore, &xHigherPriorityTaskWoken);
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();

    xBinarySemaphore = xSemaphoreCreateBinary();

    xTaskCreate(vTaskHandler, "Handler", 128, NULL, 2, NULL);

    vTaskStartScheduler();
    while(1) {}
}

运行结果

  • 当按键触发中断时,任务 Handler 被唤醒,执行相应逻辑

五、计数型信号量的应用

示例:任务统计事件次数

SemaphoreHandle_t xCountingSemaphore;

void vTaskConsumer(void *pvParameters)
{
    for(;;)
    {
        if(xSemaphoreTake(xCountingSemaphore, portMAX_DELAY) == pdPASS)
        {
            printf("处理一个事件\n");
        }
    }
}

void EXTI0_IRQHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    xSemaphoreGiveFromISR(xCountingSemaphore, &xHigherPriorityTaskWoken);
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();

    xCountingSemaphore = xSemaphoreCreateCounting(10, 0);

    xTaskCreate(vTaskConsumer, "Consumer", 128, NULL, 2, NULL);

    vTaskStartScheduler();
}
  • 每次按键触发中断,信号量计数值 +1
  • 消费任务依次取出,处理事件

六、互斥信号量的应用

互斥信号量(Mutex)用于 资源保护,保证同一时刻只有一个任务访问共享资源。
其区别在于 支持优先级继承:当低优先级任务占有资源时,高优先级任务会暂时提升低优先级任务的优先级,避免“优先级反转”。

示例:串口输出保护

SemaphoreHandle_t xMutex;

void vTaskA(void *pvParameters)
{
    for(;;)
    {
        xSemaphoreTake(xMutex, portMAX_DELAY);
        printf("Task A 输出...\n");
        vTaskDelay(500);
        xSemaphoreGive(xMutex);
        vTaskDelay(1000);
    }
}

void vTaskB(void *pvParameters)
{
    for(;;)
    {
        xSemaphoreTake(xMutex, portMAX_DELAY);
        printf("Task B 输出...\n");
        vTaskDelay(500);
        xSemaphoreGive(xMutex);
        vTaskDelay(1000);
    }
}

七、信号量与队列的区别

特性 队列 信号量
传递数据
同步机制 次要
ISR 使用 支持 支持
内部实现 FIFO 缓冲区 基于队列封装
典型应用 生产者-消费者 事件触发、资源互斥

八、信号量状态监控

FreeRTOS 提供 uxSemaphoreGetCount() 来查询信号量计数值:

UBaseType_t uxSemaphoreGetCount(SemaphoreHandle_t xSemaphore);

示例:

printf("当前信号量数量: %d\n", uxSemaphoreGetCount(xCountingSemaphore));

九、常见问题与解决方法

问题 原因 解决方法
任务无法被唤醒 信号量未 Give 确认 ISR 或任务正确释放信号量
死锁 任务未释放信号量 确保 xSemaphoreGive 在所有逻辑路径中调用
优先级反转 使用普通二值信号量 使用 Mutex
中断中报错 使用了非 ISR API 替换为 xSemaphoreGiveFromISR

十、经验总结

📌 开发建议

  1. 二值信号量 适合任务同步,特别是中断与任务之间的触发关系
  2. 计数信号量 适合事件计数,避免事件丢失
  3. 互斥信号量 适合资源保护,优先级继承机制能有效防止优先级反转
  4. 尽量避免在高频中断中频繁使用信号量,可能导致系统负担过重

十一、总结

通过本章学习,你已经掌握:

  • FreeRTOS 提供的三类信号量(Binary、Counting、Mutex)
  • 信号量的 API 使用与应用场景
  • 任务同步与共享资源保护的方法
  • 信号量与队列的区别

信号量是 RTOS 最常用的同步机制,后续我们会进一步学习 互斥量、事件组 等更复杂的同步方法。


🔗 FreeRTOS专栏👉 下一章:2025最新超详细FreeRTOS入门教程:第六章 FreeRTOS互斥量 ——进一步探讨多任务环境下的资源保护与并发访问。


Logo

欢迎加入西安开发者社区!我们致力于为西安地区的开发者提供学习、合作和成长的机会。参与我们的活动,与专家分享最新技术趋势,解决挑战,探索创新。加入我们,共同打造技术社区!

更多推荐