当前位置: 首页 > news >正文

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)

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

相关文章:

  • 计算机操作系统概要
  • 电子电路:什么是色环电阻器,怎么识别和计算阻值?
  • Windows系统永久暂停更新操作步骤
  • c++从入门到精通(五)--异常处理,命名空间,多继承与虚继承
  • vscode vue 项目 css 颜色调色版有两个
  • java中的包机制
  • MongoDB聚合查询:从入门到精通
  • 实例化异常(InstantiationException)详解
  • (面试)View相关知识
  • STM32F103定时器1每毫秒中断一次
  • 如何 naive UI n-data-table 改变行移动光标背景色
  • Web3开发工具与框架全解析:从入门到实战
  • 角点特征:从传统算法到深度学习算法演进
  • 深度学习中独热编码(One-Hot Encoding)
  • 国内AWS CloudFront与S3私有桶集成指南:安全访问静态内容
  • MUSE Pi Pro 开发板 Imagination GPU 利用 OpenCL 测试
  • Python异常模块和包
  • 【Ragflow】22.RagflowPlus(v0.3.0):用户会话管理/文件类型拓展/诸多优化更新
  • python四则运算计算器
  • HarmonyOS NEXT~鸿蒙应用上架指南:HarmonyOS应用发布全流程解析
  • 台湾关闭最后的核电,岛内担忧“非核家园”缺电、涨电价困局难解
  • 广州医药集团有限公司原党委书记、董事长李楚源被“双开”
  • 中国新闻发言人论坛在京举行,郭嘉昆:让中国声音抢占第一落点
  • “GoFun出行”订单时隔7年扣费后续:平台将退费,双方已和解
  • 老字号“逆生长”,上海制造的出海“蜜”钥
  • 乌克兰谈判代表团由12人组成,乌防长率领