什么是信号量?信号量的种类、使用方法与 STM32 案例

复制代码
一、什么是信号量?

信号量可以理解为 RTOS 里的一种"受保护的计数器":任务通过它申请资源、等待事件、协调执行顺序。

在 STM32 + FreeRTOS 里,最常用的是 二值信号量 和 计数信号量 ;如果只是为了保护共享资源不被多个任务同时访问,通常更推荐用 互斥信号量。

二、信号量的种类

类型 取值/特点 典型用途
二值信号量 0 / 1 任务同步、中断通知任务
计数信号量 0 ~ 最大值 管理多个同类资源、事件计数
互斥信号量 特殊二值信号量,带优先级继承 保护临界资源,避免优先级反转
递归信号量 同一任务可多次获取 递归调用或嵌套加锁场景

三、基本使用方法

FreeRTOS 中信号量常用 API:

复制代码
xSemaphoreCreateBinary();          // 创建二值信号量
xSemaphoreCreateCounting(max, init);// 创建计数信号量
xSemaphoreTake(sem, timeout);      // 获取信号量
xSemaphoreGive(sem);               // 释放信号量
xSemaphoreGiveFromISR(sem, &hp);   // 中断中释放信号量

使用流程一般是:

  1. 创建信号量:在调度器启动前创建。
  2. 任务获取 :调用 xSemaphoreTake(),获取不到就阻塞等待。
  3. 事件/资源释放 :调用 xSemaphoreGive() 或 xSemaphoreGiveFromISR()。
  4. 配对使用:Take 和 Give 必须成对出现,否则容易造成任务永久阻塞。

四、STM32 案例一:二值信号量做任务同步

场景: 传感器采集任务采集完数据后,通知数据处理任务开始处理。

复制代码
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

SemaphoreHandle_t xDataReadySem;

void vSensorTask(void *pvParameters)
{
    for (;;)
    {
        /* 采集传感器数据 */
        // Sensor_Read();

        /* 数据准备好,释放信号量 */
        xSemaphoreGive(xDataReadySem);

        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

void vProcessTask(void *pvParameters)
{
    for (;;)
    {
        /* 等待数据就绪 */
        if (xSemaphoreTake(xDataReadySem, portMAX_DELAY) == pdTRUE)
        {
            /* 处理数据 */
            // Data_Process();
        }
    }
}

int main(void)
{
    /* 硬件初始化省略 */

    xDataReadySem = xSemaphoreCreateBinary();

    if (xDataReadySem != NULL)
    {
        xTaskCreate(vSensorTask,  "Sensor",  128, NULL, 1, NULL);
        xTaskCreate(vProcessTask, "Process", 128, NULL, 2, NULL);
        vTaskStartScheduler();
    }

    for (;;);
}

这里 vSensorTask 负责释放信号量,vProcessTask 负责获取信号量。二值信号量创建后初始为空,所以处理任务会先阻塞,直到采集任务给出信号量。

五、STM32 案例二:中断通知任务

场景: 按键中断发生后,唤醒一个任务处理按键事件。

复制代码
SemaphoreHandle_t xKeySem;

void vKeyTask(void *pvParameters)
{
    for (;;)
    {
        if (xSemaphoreTake(xKeySem, portMAX_DELAY) == pdTRUE)
        {
            /* 处理按键 */
            // Key_Handler();
        }
    }
}

void EXTI0_IRQHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    /* 清除中断标志省略 */

    xSemaphoreGiveFromISR(xKeySem, &xHigherPriorityTaskWoken);
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

int main(void)
{
    /* 硬件初始化省略 */

    xKeySem = xSemaphoreCreateBinary();
    xTaskCreate(vKeyTask, "KeyTask", 128, NULL, 2, NULL);
    vTaskStartScheduler();

    for (;;);
}

注意:中断里不能用 xSemaphoreGive(),必须用 xSemaphoreGiveFromISR()。

六、STM32 案例三:计数信号量管理资源

场景: 系统有 3 个串口缓冲区,最多允许 3 个任务同时使用。

复制代码
SemaphoreHandle_t xUartResSem;

void vUartUserTask(void *pvParameters)
{
    for (;;)
    {
        /* 申请一个串口缓冲区资源 */
        if (xSemaphoreTake(xUartResSem, portMAX_DELAY) == pdTRUE)
        {
            /* 使用串口缓冲区 */
            // UART_Send();

            vTaskDelay(pdMS_TO_TICKS(50));

            /* 使用完毕,归还资源 */
            xSemaphoreGive(xUartResSem);
        }

        vTaskDelay(pdMS_TO_TICKS(200));
    }
}

int main(void)
{
    /* 硬件初始化省略 */

    xUartResSem = xSemaphoreCreateCounting(3, 3);

    if (xUartResSem != NULL)
    {
        xTaskCreate(vUartUserTask, "UartUser1", 128, NULL, 1, NULL);
        xTaskCreate(vUartUserTask, "UartUser2", 128, NULL, 1, NULL);
        xTaskCreate(vUartUserTask, "UartUser3", 128, NULL, 1, NULL);
        vTaskStartScheduler();
    }

    for (;;);
}

这里最大计数值是 3,初始值也是 3,表示一开始有 3 个可用资源。每调用一次 xSemaphoreTake(),计数值减 1;调用 xSemaphoreGive(),计数值加 1。

七、选型建议

  • 任务/中断同步:优先用二值信号量。
  • 管理多个同类资源:用计数信号量。
  • 保护共享资源互斥访问:优先用互斥信号量,因为它支持优先级继承,能降低优先级反转风险。
  • 中断里释放信号量 :必须使用 xSemaphoreGiveFromISR()。
  • 简单事件通知:在 STM32 + FreeRTOS 里也可以考虑任务通知,开销通常更小。
相关推荐
t13970181 小时前
MPPT 和 PWM 有什么区别?小功率太阳能供电到底该选哪个
嵌入式硬件·物联网
en.en..2 小时前
STM32入门最佳学习顺序(寄存器→标准库→HAL库)
单片机·嵌入式硬件
Good白10193 小时前
嵌入式:1.嵌入式入门|初识嵌入式、处理器、ARM与STM32
arm开发·stm32·嵌入式硬件
小李不想当小白3 小时前
HAL库之UART(STM32 HAL库学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习·嵌入式
Dev_XH4 小时前
【成品设计】基于物联网的旅游生态农业互动系统
stm32·单片机·嵌入式硬件·物联网·旅游
Industio_触觉智能17 小时前
RK3506 Buildroot系统目录地图-上|7个关键符号链接与构建产物全解析
嵌入式硬件·sdk·智能硬件·固件·瑞芯微·系统编译·rk3506
lzj_pxxw20 小时前
STM32移植FreeRTOS(Cmake+CubeMX)
vscode·stm32·嵌入式硬件
물の韜21 小时前
ZYNQ/FMQL系列Linux之I2C开发实战
linux·arm开发·嵌入式硬件·设备树·iic·zynq
无敌贵点大王1 天前
从寄存器层面重学STM32(7)——定时器
stm32·单片机·嵌入式硬件
碧海银沙音频科技研究院1 天前
粉色笔记本电脑每次要等把电池电耗尽才能开机问题解决方法
嵌入式硬件