对于上一张讲过的优先级反转的的问题的时候我们引入了互斥量(也叫互斥锁)的概念,其实互斥量就是特俗的信号量。

那互斥量怎么去使用呢?

很简单,和信号量一样创建用 xSemaphoreCreateMutex(),获取用 xSemaphoreTake(),释放用 xSemaphoreGive(),保持保护区域尽量短,FreeRTOS会自动处理优先级反转。

互斥量是一种特殊的二进制信号量但是特殊的点是二进制信号量和计数信号量获取者和释放者可以不是同一个任务,但是互斥信号量获取者和释放者必须是同一个人。

函数

创建互斥量的函数有2种:动态分配内存,静态分配内存,函数原型如下:

/* 创建一个互斥量,返回它的句柄。
 * 此函数内部会分配互斥量结构体 
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateMutex( void );

/* 创建一个互斥量,返回它的句柄。
 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer );

要想使用互斥量,需要在配置文件FreeRTOSConfig.h中定义:

#define configUSE_MUTEXES 1

要注意的是,互斥量不能在ISR中使用。

各类操作函数,比如删除、give/take,跟一般是信号量是一样的。

/*
 * xSemaphore: 信号量句柄,你要删除哪个信号量, 互斥量也是一种信号量
 */
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );

/* 释放 */
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );


/* 获得 */
BaseType_t xSemaphoreTake(
                   SemaphoreHandle_t xSemaphore,
                   TickType_t xTicksToWait
               );

但是在使用时也会遇到别的问题比如在嵌套或者递归函数中遇到了信号量冲突

比如任务A拿也互斥量M,但是任务A内部的嵌套函数需要信号量M但是拿不到,这样就会阻塞卡死

这是就需要用到递归锁

递归锁允许同一任务多次获取同一锁,通过内部计数机制避免自我死锁,必须使用TakeRecursive/GiveRecursive配套函数,获取N次就必须释放N次!

其他使用方式就和互斥锁一样,最重要的就是,获取N次就必须释放N次!获取N次就必须释放N次!获取N次就必须释放N次!

递归锁 一般互斥量
创建 xSemaphoreCreateRecursiveMutex xSemaphoreCreateMutex
获得 xSemaphoreTakeRecursive xSemaphoreTake
释放 xSemaphoreGiveRecursive xSemaphoreGive

/* 创建一个递归锁,返回它的句柄。*

 * 此函数内部会分配互斥量结构体* 

 * 返回值: 返回句柄,非NULL表示成功*

 */

SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void );

*/ 释放 */

BaseType_t xSemaphoreGiveRecursive( SemaphoreHandle_t xSemaphore );

*/ 获得 */

BaseType_t xSemaphoreTakeRecursive(

         SemaphoreHandle_t xSemaphore,

         TickType_t xTicksToWait

        );

  

我们每次建立一个任务只能触发一个任务函数,那有没有什么方法可以一个任务,触发多个函数话或者信号量呢。

有的兄弟有的

这个就是事件组

使用事件组必须要引用的头文件

#include "event_groups.h"

主要特点:

  • 每个事件由事件位(bit)表示,最多32个事件(32位)

  • 支持"或"(任意事件发生)和"与"(所有事件发生)两种等待模式

  • 事件标志被设置后不会自动清除,需要手动清除

  • 线程安全,可在任务和中断中使用

事件组的使用主要分为4个主要的部分就是:创建,设置,等待和是释放

首先是创建事件组

// 创建事件组
EventGroupHandle_t xEventGroupCreate(void);

// 示例
EventGroupHandle_t xEventGroup = xEventGroupCreate();
if (xEventGroup == NULL) {
    // 创建失败处理
}

其次设置事件组

c
// 在任务中设置事件位
EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup, 
                               const EventBits_t uxBitsToSet);

// 在中断中设置事件位(带中断安全版本)
BaseType_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup,
                                     const EventBits_t uxBitsToSet,
                                     BaseType_t *pxHigherPriorityTaskWoken);

然后是等待事件组

EventBits_t xEventGroupWaitBits(
    const EventGroupHandle_t xEventGroup,
    const EventBits_t uxBitsToWaitFor,
    const BaseType_t xClearOnExit,  // pdTRUE: 等待成功后清除事件位
    const BaseType_t xWaitForAllBits, // pdTRUE: 等待所有位; pdFALSE: 等待任意位
    TickType_t xTicksToWait);

最后是释放事件组也叫清除事件组

// 清除指定事件位
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup,
                                 const EventBits_t uxBitsToClear);

// 在中断中清除事件位
BaseType_t xEventGroupClearBitsFromISR(EventGroupHandle_t xEventGroup,
                                       const EventBits_t uxBitsToClear);

// 获取当前事件位值(不修改)
EventBits_t xEventGroupGetBits(EventGroupHandle_t xEventGroup);
EventBits_t xEventGroupGetBitsFromISR(EventGroupHandle_t xEventGroup);

就这四部使用起来也比较简单给大家一个一般的使用格式

// 定义事件位(建议使用1左移方式)
#define BIT_0 (1 << 0)
#define BIT_1 (1 << 1)
#define BIT_2 (1 << 2)

// 创建事件组
EventGroupHandle_t xEventGroup;

void vTask1(void *pvParameters) {
    EventBits_t uxBits;
    
    for (;;) {
        // 等待BIT_0或BIT_1任意一个事件发生
        uxBits = xEventGroupWaitBits(
            xEventGroup,      // 事件组句柄
            BIT_0 | BIT_1,    // 等待的事件位
            pdTRUE,           // 成功等待后清除这些位
            pdFALSE,          // 任意一个事件即可
            portMAX_DELAY);   // 无限等待
        
        if ((uxBits & BIT_0) != 0) {
            // BIT_0被设置
        }
        if ((uxBits & BIT_1) != 0) {
            // BIT_1被设置
        }
    }
}

void vTask2(void *pvParameters) {
    for (;;) {
        // 执行某些操作后设置事件
        xEventGroupSetBits(xEventGroup, BIT_0);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

第一步宏定义的操作可有可无但是这样会大大提高代码的可读性

对于这个串代码进行变种就能变成任务的与操作

// 必需的头文件
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"

// 可选:用于调试输出
#include <stdio.h>

// 定义事件位
#define BIT_0 (1 << 0)
#define BIT_1 (1 << 1)
#define BIT_2 (1 << 2)

// 事件组句柄
EventGroupHandle_t xEventGroup;

void vSenderTask(void *pvParameters) {
    for (;;) {
        // 设置事件位
        xEventGroupSetBits(xEventGroup, BIT_0);
        
        // 任务延迟
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void vReceiverTask(void *pvParameters) {
    EventBits_t uxBits;
    
    for (;;) {
        // 等待事件位
        uxBits = xEventGroupWaitBits(
            xEventGroup,
            BIT_0 | BIT_1,
            pdTRUE,        // 清除事件位
            pdFALSE,       // 等待任意位
            portMAX_DELAY  // 无限等待
        );
        
        if (uxBits & BIT_0) {
            printf("BIT_0 received\n");
        }
    }
}

int main(void) {
    // 创建事件组
    xEventGroup = xEventGroupCreate();
    
    // 创建任务
    xTaskCreate(vSenderTask, "Sender", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
    xTaskCreate(vReceiverTask, "Receiver", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
    
    // 启动调度器
    vTaskStartScheduler();
    
    return 0;
}

然后也可以改成在中断中使用的样子

// 必需的头文件
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"

// 可选:硬件相关头文件
#include "stm32f4xx.h"  // 示例:STM32 MCU

EventGroupHandle_t xEventGroup;

// 中断服务程序
void TIM2_IRQHandler(void) {
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
        
        // 在中断中设置事件位
        xEventGroupSetBitsFromISR(
            xEventGroup,
            BIT_0,
            &xHigherPriorityTaskWoken
        );
        
        // 如果需要,执行上下文切换
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
}

我在配置的过程中遇到了一些错误,在这分享给大家

七、常见错误排查

错误1:未找到头文件

错误信息:fatal error: event_groups.h: No such file or directory
解决方案:确保include路径正确

在MX或IDE中正确设置include路径
例如:-I../FreeRTOS/Source/include

错误2:未启用事件组功能

//确保在FreeRTOSConfig.h中启用
#define configUSE_EVENT_GROUPS 1  // 必须为1

错误3:链接错误

错误:undefined reference to `xEventGroupCreate'

解决方案:确保链接了FreeRTOS库文件

在Makefile中添加:freertos.c 或相应的库文件

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐