什么是信号量?信号量的种类、使用方法与 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 里也可以考虑任务通知,开销通常更小。
相关推荐
雨田言炎1 小时前
STM32之ADC--电压采集
stm32·单片机·嵌入式硬件
远翔调光芯片^138287988721 小时前
大型洗地机刷盘电机怎么驱动?轴向磁通电机+无感FOC方案解析
科技·单片机·嵌入式硬件·智能家居·能源
服务器硬件项目经理1 小时前
第3章GPU vs CPU:为什么AI训练要用GPU
服务器·人工智能·嵌入式硬件·gru·硬件工程·硬件
碧海银沙音频科技研究院3 小时前
基于ATS308X的无线语音交互对话功能实现
人工智能·嵌入式硬件·深度学习·算法
inrichtt3 小时前
第十课时:中断优先级揭秘:抢占与嵌套的底层逻辑:
c语言·stm32·单片机·嵌入式硬件·github
国科安芯3 小时前
星载微控制器MCU功能安全与信息安全协同机制综述
网络·单片机·嵌入式硬件·安全·抗辐射
深圳市恒锐丰科技杨生3 小时前
SS6209L 30V 集成 MOS 半桥功率芯片|LeadPower 率能半导体
嵌入式硬件·硬件工程
comyccccc3 小时前
STM32最小系统板高效布线与铺铜优化学习笔记
单片机·嵌入式硬件
一直C4 小时前
嵌入式 Linux 驱动开发学习笔记:中断、等待队列与工作队列
linux·驱动开发·嵌入式硬件·学习·imx6ull