一、任务创建
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)的发生。
-
作用场景
- 当多个任务或中断需要访问共享资源(如全局变量、硬件寄存器、缓冲区等)时,必须通过临界区间保证操作的原子性。
- 例如:修改多个关联变量时(如先更新数据缓冲区,再更新缓冲区指针),若操作被打断,可能导致数据不一致。
-
关键特性
- ++原子性:临界区间内的代码必须一次性执行完毕,不允许被中断或任务切换打断。++
- 时效性:临界区间的代码应尽可能简短,避免长时间阻塞中断或任务调度,影响系统实时性。
使用方式
关闭中断(全局临界区)
-
原理:通过关闭所有中断(包括可屏蔽中断),确保临界区间内的代码不会被任何中断打断,同时隐式禁止任务切换(因为任务切换通常由滴答中断触发)。
关闭中断(全局临界区)
// 进入临界区(关闭中断)
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)
-
提示:中断优先级数值越小优先级越高,任务优先级数值****越大优先级越高