FreeRTOS的学习记录(任务创建,任务挂起)

一、任务创建

1动态创建

1. 配置 FreeRTOS 堆内存

首先需要在**FreeRTOSConfig.h**中配置堆内存大小:

复制代码
#define configTOTAL_HEAP_SIZE (10 * 1024)  // 分配10KB堆内存

FreeRTOS 提供了 5 种堆内存管理方案(heap_1.c~heap_5.c),默认使用heap_4.c,它支持内存块的分配和释放,且能避免内存碎片。

2. 定义任务函数

定义一个符合 FreeRTOS 要求的任务函数:

复制代码
void vTaskFunction(void *pvParameters)
{
    // 任务的执行代码
    for( ;; )
    {
        // 任务主体
        vTaskDelay(1000); // 延时1秒
    }
}

3. 动态创建任务

使用***++xTaskCreate()++***函数创建任务:

复制代码
TaskHandle_t xTaskHandle = NULL;

// 动态创建任务
BaseType_t xReturned = xTaskCreate(
    vTaskFunction,        // 任务函数
    "MyTask",             // 任务名称
    128,                  // 任务栈大小(以字为单位,不是字节)
    NULL,                 // 传递给任务的参数
    tskIDLE_PRIORITY,     // 任务优先级
    &xTaskHandle          // 任务句柄(用于后续操作任务)
);

// 检查任务是否创建成功
if (xReturned == pdPASS) {
    printf("任务创建成功\n");
} else {
    printf("任务创建失败,原因:堆内存不足或参数错误\n");
}

4. 启动调度器

所有任务创建完成后启动调度器:

复制代码
vTaskStartScheduler();

2静态任务创建 (空闲任务内存分配是必须的,定时器是可选的)

1. 配置FreeRTOSConfig.h

在工程中修改FreeRTOSConfig.h文件,启用静态内存分配:

复制代码
#define configSUPPORT_STATIC_ALLOCATION 1 // 启用静态分配 
#define configUSE_TIMERS 0 // 如果不用软件定时器可关闭 
#define configMAX_PRIORITIES 5 // 根据需求调整优先级数量


#define configUSE_TIMERS                1       // 启用软件定时器功能
#define configTIMER_TASK_PRIORITY       (tskIDLE_PRIORITY + 1)  // 定时器任务优先级
#define configTIMER_QUEUE_LENGTH        10      // 定时器命令队列长度
#define configTIMER_TASK_STACK_DEPTH    (configMINIMAL_STACK_SIZE * 2)  // 定时器任务栈大小

2. 实现内存回调函数

main.c中添加以下函数,为FreeRTOS的空闲任务和定时器任务分配静态内存:

复制代码
// 空闲任务静态内存
static StaticTask_t xIdleTaskTCBBuffer;
static StackType_t xIdleStack[configMINIMAL_STACK_SIZE];

// 定时器任务静态内存(如果启用)
#if configUSE_TIMERS == 1
static StaticTask_t xTimerTaskTCBBuffer;
static StackType_t xTimerStack[configTIMER_TASK_STACK_DEPTH];
#endif

// 回调函数实现
void vApplicationGetIdleTaskMemory(
    StaticTask_t **ppxIdleTaskTCBBuffer,
    StackType_t **ppxIdleTaskStackBuffer,
    uint32_t *pulIdleTaskStackSize) {
    *ppxIdleTaskTCBBuffer = &xIdleTaskTCBBuffer;
    *ppxIdleTaskStackBuffer = xIdleStack;
    *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}

#if configUSE_TIMERS == 1
void vApplicationGetTimerTaskMemory(
    StaticTask_t **ppxTimerTaskTCBBuffer,
    StackType_t **ppxTimerTaskStackBuffer,
    uint32_t *pulTimerTaskStackSize) {
    *ppxTimerTaskTCBBuffer = &xTimerTaskTCBBuffer;
    *ppxTimerTaskStackBuffer = xTimerStack;
    *pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}
#endif

3. 定义任务栈和TCB

在全局区域定义任务的栈和任务控制块(TCB):

复制代码
// 任务栈和TCB
#define TASK_STACK_SIZE 128
StaticTask_t xTaskTCB;
StackType_t xTaskStack[TASK_STACK_SIZE];

4. 编写任务函数

实现任务逻辑(例如LED闪烁):

复制代码
void vTaskFunction(void *pvParameters) {
    (void)pvParameters;
    for (;;) {
        HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // 示例:控制PA5引脚
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

5. 创建静态任务

main()函数中创建任务并启动调度器:

复制代码
int main(void) {
    HAL_Init();
    SystemClock_Config(); // 配置系统时钟(例如72MHz)
    MX_GPIO_Init();       // 初始化GPIO

    // 创建静态任务
    TaskHandle_t xTaskHandle = xTaskCreateStatic(
        vTaskFunction,           // 任务函数
        "StaticTask",            // 任务名称
        TASK_STACK_SIZE,         // 栈大小
        NULL,                    // 参数
        tskIDLE_PRIORITY + 1,    // 优先级
        xTaskStack,              // 栈数组
        &xTaskTCB                // TCB结构体
    );

    if (xTaskHandle != NULL) {
        vTaskStartScheduler(); // 启动调度器
    }

    while (1); // 调度器启动后不应执行到这里
}

6.创建静态定时器

复制代码
#include "FreeRTOS.h"
#include "timers.h"

// 为定时器控制块提供内存
StaticTimer_t xTimerBuffer;  // 用于静态创建定时器

// 定时器回调函数(禁止调用会阻塞的API)
void vTimerCallback(TimerHandle_t xTimer)
{
    // 定时器到期时执行的代码
    static uint32_t ulCount = 0;
    ulCount++;
    printf("定时器触发次数: %lu\n", ulCount);
}

// 静态创建并启动定时器
void vSetupStaticTimer(void)
{
    TimerHandle_t xTimer = NULL;
    
    // 静态创建一个周期性定时器(自动重载模式)
    xTimer = xTimerCreateStatic(
        "MyStaticTimer",      // 定时器名称
        pdMS_TO_TICKS(1000),  // 定时器周期(1000ms)
        pdTRUE,               // pdTRUE=自动重载,pdFALSE=单次触发
        (void*)0,             // 定时器ID(用户定义)
        vTimerCallback,       // 回调函数
        &xTimerBuffer         // 静态分配的控制块内存
    );
    
    if (xTimer != NULL) {
        // 启动定时器(0表示不等待)
        if (xTimerStart(xTimer, 0) != pdPASS) {
            printf("启动定时器失败\n");
        }
    }
}

3任务删除

复制代码
vTaskDelete(xTaskHandle);//动态静态任务都用这个,参数为NULL时删除正在运行的任务(运行结束删除)

静态任务与动态任务的删除对比

特性 动态任务(xTaskCreate) 静态任务(xTaskCreateStatic)
内存释放 调用 vTaskDelete() 后自动释放 内存永久保留,需手动管理
适合场景 需动态创建 / 删除的任务 生命周期固定的关键任务
内存碎片化风险 频繁删除可能导致堆碎片化 无碎片化风险
资源释放责任 空闲任务自动回收内存 需用户确保资源手动释放

4、临界区间

在实时操作系统(如 FreeRTOS)中,** ++临界区间(Critical Section)++** 是指一段必须被原子执行的代码区域,即不允许被中断或任务切换打断。临界区间的核心目标是确保共享资源在被访问时的一致性和完整性,防止竞态条件(Race Condition)的发生。

  1. 作用场景

    • 当多个任务或中断需要访问共享资源(如全局变量、硬件寄存器、缓冲区等)时,必须通过临界区间保证操作的原子性。
    • 例如:修改多个关联变量时(如先更新数据缓冲区,再更新缓冲区指针),若操作被打断,可能导致数据不一致。
  2. 关键特性

    • ++原子性:临界区间内的代码必须一次性执行完毕,不允许被中断或任务切换打断。++
    • 时效性:临界区间的代码应尽可能简短,避免长时间阻塞中断或任务调度,影响系统实时性。

使用方式

关闭中断(全局临界区)
  • 原理:通过关闭所有中断(包括可屏蔽中断),确保临界区间内的代码不会被任何中断打断,同时隐式禁止任务切换(因为任务切换通常由滴答中断触发)。

    关闭中断(全局临界区)
    // 进入临界区(关闭中断)
    portDISABLE_INTERRUPTS();

    // 临界区代码(访问共享资源)
    vAccessSharedResource();

    // 退出临界区(恢复中断)
    portENABLE_INTERRUPTS();

特点

  • 完全禁止中断,实时性影响较大,仅适用于极短的临界区。
  • 可在任务或中断服务程序(ISR)中使用
关闭任务切换(局部临界区)
  • 原理:允许中断继续响应,但禁止任务调度器进行任务切换(即不允许上下文切换)。中断服务程序仍可执行,但执行完毕后不会触发任务切换,直到退出临界区。

  • FreeRTOS 接口

    复制代码
    // 进入临界区(禁止任务切换,允许中断)
    //taskENTER_CRITICAL_FROM_ISR(); // 从中断进入时使用
    taskENTER_CRITICAL(); // 从任务进入时使用
    
    // 临界区代码(访问共享资源)
    vModifySharedVariable();
    
    // 退出临界区(允许任务切换)
    taskEXIT_CRITICAL();
    //taskEXIT_CRITICAL_FROM_ISR() ;// 从中断退出时使用
  • 特点

    • 允许中断响应,实时性影响较小,适用于较长的临界区。
    • 若在中断中使用,需通过 taskENTER_CRITICAL_FROM_ISR() 进入,并搭配 taskEXIT_CRITICAL_FROM_ISR() 返回是否需要切换任务。

++注意事项:++ 在实际开发中,应遵循 ++"能用信号量 / 互斥量解决的场景,绝不使用临界区间;必须使用临界区间时,确保其最短化和中断安全"++

机制 临界区间 信号量 / 互斥量
控制范围 代码段(原子性) 资源所有权(阻塞 / 唤醒)
中断影响 可能关闭中断或调度 不影响中断,仅通过阻塞任务实现
适用场景 极短的原子操作(如单变量修改) 较长时间的资源独占(如外设操作)
优先级反转 可能发生(若持有资源的任务被低优先级任务阻塞) 互斥量可通过优先级继承解决

二、任务挂起

++任务挂起:挂起任务相当于暂停,可以恢复++

1、挂起任务

使用++vTaskSuspend()++ 函数暂停指定任务的执行,被挂起的任务不会参与调度,直到被恢复。
函数原型

复制代码
#define INCLUDE_vTaskSuspend     1    //需配置宏

void vTaskSuspend(TaskHandle_t xTaskToSuspend);
  • 参数
    • ++xTaskToSuspend++ 要挂起的任务句柄。若为 NULL,则挂起当前任务
  • 注意事项
    • 挂起持有互斥量的任务可能导致死锁,建议在挂起前释放锁。

2、恢复被挂起任务(任务中恢复)

使用++vTaskResume()++ 函数恢复被挂起的任务,使其重新参与调度。
函数原型

复制代码
void vTaskResume(TaskHandle_t xTaskToResume);
  • 参数
    • ++xTaskToResume++:要恢复的任务句柄,只有挂起和非挂起俩种状态。

      复制代码
      // 任务被多次挂起,但只需一次恢复即可运行
      vTaskSuspend(taskHandle); // 任务挂起
      vTaskSuspend(taskHandle); // 无实际效果(任务已挂起)
      vTaskResume(taskHandle);  // 任务恢复为就绪状态

3、在中断中恢复被挂起的任务

FreeRTOS 要求在中断中调用任务恢复函数时,必须使用专门的中断安全版本:
xTaskResumeFromISR()(而非普通的 vTaskResume())。

这是因为普通任务函数(如 vTaskResume())可能触发上下文切换,而中断上下文需要特殊处理。


2. 函数原型与参数

复制代码
BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume);
  • 参数xTaskToResume 是要恢复的任务句柄。

  • 返回值

    • pdTRUE:恢复任务后需要触发上下文切换(例如,恢复的任务优先级高于当前任务)。

    • pdFALSE:无需立即切换上下文。


3. 上下文切换的注意事项

如果 xTaskResumeFromISR() 返回 pdTRUE,通常需要手动触发一次上下文切换,以确保高优先级任务立即获得 CPU 控制权。

使用 portYIELD_FROM_ISR() 宏实现中断内的上下文切换:

复制代码
#define INCLUDE_vTaskSuspend             1//必须定义
#define INCLUDE_vTaskResumeFromISR       1//必须定义


// 假设任务句柄已定义:TaskHandle_t xTaskToResumeHandle;

void vExampleISR(void) {
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    // 恢复被挂起的任务
    xHigherPriorityTaskWoken = xTaskResumeFromISR(xTaskToResumeHandle);

    // 如果需要,触发上下文切换
    if (xHigherPriorityTaskWoken == pdTRUE) {
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
}

4. 中断服务程序(ISR)的限制

  • 中断优先级 :需确保 FreeRTOS 管理的中断优先级正确配置(通常低于或等于 configMAX_SYSCALL_INTERRUPT_PRIORITY),否则可能导致数据竞争或未定义行为。

  • 保持简短:ISR 应尽量简短,避免在中断中执行复杂逻辑。

  • 中断优先级配置必须符合 FreeRTOS 的要求,避免系统不稳定(一般默认是5~15)

  • 提示:中断优先级数值越小优先级越高任务优先级数值****越大优先级越高

相关推荐
Jerry&Louis5 分钟前
【Ubuntu】Waydroid-Linux安卓模拟器安装
linux·ubuntu
yangpan0116 分钟前
ubuntu 24.04安装ros1 noetic
linux·运维·ubuntu
星卯教育tony7 分钟前
ubuntu 20.04 更改国内镜像源-阿里源 确保可用
linux·运维·ubuntu
小Tomkk13 分钟前
2025年PMP 学习十八 第11章 项目风险管理 (11.5~11.7)
学习·项目管理·pmp
卡戎-caryon22 分钟前
【C++】15.并发支持库
java·linux·开发语言·c++·多线程
hweiyu0042 分钟前
C#学习教程(附电子书资料)
开发语言·学习·c#
爱做ppt的阿伟43 分钟前
2025/517学习
学习
superior tigre1 小时前
C++学习:六个月从基础到就业——C++11/14:列表初始化
c++·学习
weixin_434255611 小时前
命令行快速上传文件到SFTP服务器(附参考示例)
linux·运维·服务器
阿图灵1 小时前
文章记单词 | 第93篇(六级)
学习·学习方法