STM32Cube-FreeRTOS任务调度与任务管理-笔记
STM32Cube-FreeRTOS任务调度与任务管理-笔记
- 一、任务调度机制
- 1.1 调度算法类型
- 二、抢占式调度实现与分析
- 2.1 时间片轮转机制
- 2.2 调度触发条件
- 2.3 抢占式调度例子
- 三、合作式调度实现
- 3.1 核心逻辑
- 3.1 合作式调度例子
- 四、任务管理函数详解
- 4.1 任务创建
- 4.1.1 动态创建任务
- 4.1.2 静态创建任务(手动分配资源)
- 4.2 任务删除
- 4.3 挂起与恢复任务
- 4.3.1 挂起任务
- 4.3.2 恢复任务
- 4.4 调度器控制
- 4.4.1 开启调度器
- 4.4.2 暂停调度器
- 4.4.3 恢复调度器
- 4.5 延时与阻塞
- 4.5.1 基础延时
- 4.5.2 周期性精确延时
- 4.5.3 终止延时
- 4.6 时间相关函数
- 4.6.1 获取当前Tick值
- 4.6.2 终止任务延时
- 五、配置与优化建议
- 5.1 调度算法选择
- 5.2 优先级与时间片配置
- 5.3 低功耗优化
- 六、常见问题与注意事项
一、任务调度机制
1.1 调度算法类型
FreeRTOS支持两种调度算法:
-
抢占式调度(Preemptive Scheduling)
- 特点:高优先级任务可抢占低优先级任务的CPU使用权。
- 时间片轮转:
- 启用时间片:相同优先级任务间按时间片(默认1ms)轮转。
- 禁用时间片:仅在更高优先级任务就绪或当前任务主动让出CPU时切换。
- 配置方式:在STM32CubeMX中设置
USE_PREEMPTION
为Enable
。
-
合作式调度(Cooperative Scheduling)
- 特点:任务需主动让出CPU(如调用
taskYIELD()
),否则不会被抢占。 - 配置方式:在STM32CubeMX中设置
USE_PREEMPTION
为Disable
。
- 特点:任务需主动让出CPU(如调用
二、抢占式调度实现与分析
2.1 时间片轮转机制
-
基础时钟(Tick):
- FreeRTOS基础时钟的一个定时周期称为一个时间片(timeslice),默认值为1ms。当使用时间片时,在基础时钟的每次中断里会要求进行一次上下文切换(contextswitching),函数xPortSysTickHandler()就是SysTick定时中断的处理函数。
- 时间片长度可通过
configTICK_RATE_HZ
宏调整(如设置为100Hz即10ms/次)。
-
中断优先级配置:
- 优先级分组:默认将全部4位用于抢占优先级(如
configPRIO_BITS=4
),系统中断优先级设为最低(15),确保RTOS任务优先级高于中断。
- 优先级分组:默认将全部4位用于抢占优先级(如
2.2 调度触发条件
- 使用时间片时:
- 每个时间片(默认1ms)触发一次任务切换。
- 不使用时间片时:
- 仅在以下情况切换任务:
- 更高优先级任务进入就绪态。
- 当前任务进入阻塞态或挂起态。
- 优点:减少上下文切换频率,降低CPU负担。
- 缺点:同优先级任务可能因时间分配不均导致不公平。
- 仅在以下情况切换任务:
2.3 抢占式调度例子
这张图可以说明带时间片的抢占式任务优先级的特点。
假设task2具有高优先级,task1具有正常优先级,且这两个任务的优先级都高于空闲任务的优先级。那么首先T1时刻是空闲任务在运行,在这一段时间里面系统里面没有其他任务处于就绪状态。在T2时刻进行了调度,task一抢占CPU开始运行,这是因为task1的优先级高于空闲任务。我们在task 3时刻,task1就进入了阻塞态,就让出了CPU的使用权。空闲任务又进入了运行状态。
在T4时刻task1又进入了运行态,在T5时刻更高优先级的task2抢占了CPU的运行。那么这个时候,task1就进入了就绪态,在T6时刻task2进入了阻塞状态,让出了CPU使用权。那么task1就可以从就绪态变为运行态。在T7时刻task一进入了阻塞状态,主动让出了CPU使用权,空闲任务就又进入了运行状态。
三、合作式调度实现
3.1 核心逻辑
- 特点:
- 使用合作式任务调度方法时,FreeRTOS不主动进行上下文切换,而是运行状态的任务进入阻塞状态,或显式地调用taskYIELD()函数让出CPU使用权时才进行上下文切换。
- 任务不会发生抢占,所以也不使用时间片,函数taskYIELD()的作用就是主动申请进行一次上下文切换
- 典型场景:
- 需精确控制任务执行顺序(如调试阶段)。
- 任务间无优先级差异,需按需轮询。
3.1 合作式调度例子
四、任务管理函数详解
4.1 任务创建
4.1.1 动态创建任务
BaseType_t xTaskCreate(TaskFunction_t pxTaskCode, // 任务函数指针const char * const pcName, // 任务名称(调试用)configSTACK_DEPTH_TYPE usStackDepth, // 栈大小(单位:字)void * const pvParameters, // 任务参数UBaseType_t uxPriority, // 优先级(数值越小优先级越低)TaskHandle_t * const pxCreatedTask // 返回任务句柄
);
- 特点:RTOS自动分配栈和TCB空间。
4.1.2 静态创建任务(手动分配资源)
TaskHandle_t xTaskCreateStatic(TaskFunction_t pxTaskCode, // 任务函数指针const char * const pcName, // 任务名称const uint32_t ulStackDepth, // 栈大小(单位:字)void * const pvParameters, // 任务参数UBaseType_t uxPriority, // 优先级StackType_t * const puxStackBuffer, // 静态分配的栈空间StaticTask_t * const pxTaskBuffer // 静态分配的任务控制块
);
- 特点:适用于内存有限的场景,需手动分配栈和TCB空间。
4.2 任务删除
void vTaskDelete(TaskHandle_t xTaskToDelete);
- 功能:
- 删除指定任务(传入
NULL
表示删除自身)。 - 自动释放RTOS分配的栈和TCB,但需手动释放任务内动态分配的内存。
- 需要说明该函数它需要传入的参数是需要删除的任务的句柄。但需要注意,如果要删除的是任务,自己必须在跳出任务死循环之后,在退出任务函数之前执行vTaskDelete删除任务时,自动释放系统自动分配的内存,如动态分配的占空间和任务控制块。但是在任务内,用户自己分配的内存需要在删除任务之前手工释放。
- 删除指定任务(传入
4.3 挂起与恢复任务
4.3.1 挂起任务
void vTaskSuspend(TaskHandle_t xTaskToSuspend);
- 功能:
- 挂起指定任务(传入
NULL
表示挂起自身)。 - 挂起的任务不参与调度,需其他任务调用
vTaskResume()
恢复。
- 挂起指定任务(传入
4.3.2 恢复任务
void vTaskResume(TaskHandle_t xTaskToResume);
- 功能:
- 恢复被挂起的任务,使其进入就绪态。
- 注意:只能在其他任务中调用(不可恢复自身)。
4.4 调度器控制
4.4.1 开启调度器
void vTaskStartScheduler();
- 功能:启动RTOS调度器,开始任务调度。
4.4.2 暂停调度器
void vTaskSuspendAll();
- 功能:暂停所有任务调度,进入临界区。
4.4.3 恢复调度器
BaseType_t xTaskResumeAll();
- 功能:恢复调度器并返回暂停前的就绪任务状态。
4.5 延时与阻塞
4.5.1 基础延时
void vTaskDelay(TickType_t xTicksToDelay);
- 功能:将任务阻塞指定Tick数(如
vTaskDelay(100)
阻塞100ms)。
4.5.2 周期性精确延时
BaseType_t xTaskDelayUntil(TickType_t * const pxPreviousWakeTime, // 上次唤醒时间const TickType_t xTimeIncrement // 周期间隔(单位:Tick)
);
- 功能:用于周期性任务,确保任务以固定间隔执行。
4.5.3 终止延时
void vTaskDelayUntil( /* ... */ );
- 功能:可配合
xTaskDelayUntil()
实现精确周期任务。
4.6 时间相关函数
4.6.1 获取当前Tick值
TickType_t xTaskGetTickCount();
- 功能:返回自系统启动以来的Tick总数。
4.6.2 终止任务延时
BaseType_t xTaskCheckForTimeOut( /* ... */ );
- 功能:检查任务是否超时,用于手动处理延时逻辑。
五、配置与优化建议
5.1 调度算法选择
- 抢占式调度:
- 适用场景:实时性要求高的系统(如工业控制)。
- 配置:STM32CubeMX中启用
USE_PREEMPTION
。
- 合作式调度:
- 适用场景:资源受限或需严格控制任务切换的场景。
5.2 优先级与时间片配置
- 优先级分组:
- 通过
configPRIO_BITS
宏配置抢占优先级与子优先级分配(如configPRIO_BITS=4
表示4位抢占优先级)。
- 通过
- 时间片调整:
- 通过
configTICK_RATE_HZ
修改Tick频率(如设置为100Hz降低中断频率)。
- 通过
5.3 低功耗优化
- Tickless模式:
- 配置
configUSE_TICKLESS_IDLE=1
,在空闲时关闭SysTick,降低功耗。
- 配置
六、常见问题与注意事项
- 任务栈大小:
- 动态任务需合理设置栈大小,避免栈溢出。
- 静态任务需手动分配足够内存。
- 内存管理:
- 删除任务前需手动释放动态分配的内存。
- 中断优先级:
- 确保RTOS任务优先级低于系统中断(如
configMAX_SYSCALL_INTERRUPT_PRIORITY
)。
- 确保RTOS任务优先级低于系统中断(如