开发环境
- 底层库:STM32Cube MCU Package (HAL 库)
- 硬件平台:STM32G4xx/F1xx/F4xx (通用)
- 核心资源:SysTick 滴答定时器(HAL_GetTick)
在嵌入式开发初期,我们习惯在 while(1) 里通过 HAL_Delay() 控制节奏。但随着任务增多(既要扫按键,又要刷屏幕,还要读传感器),传统的阻塞式延时会导致系统卡顿。
为了解决这个问题,在本方案中采用了轻量级任务调度器(Task Scheduler)架构。通过将系统拆分为多个非阻塞的任务(Task),并按照固定的时间片(如 10ms、50ms)轮询执行,从而确保了多任务环境下的实时性与流畅度。在无需 RTOS 的前提下,使用裸机开发最大限度提升了系统的实时响应能力与执行流畅度。
为什么选择 HAL 库实现?
很多从标准库转 HAL 库的朋友可能会问:标准库也能做调度器,为什么要强调 HAL 库?
- 零配置成本:在标准库中,你需要手动配置 SysTick_Config,并在 stm32f10x_it.c 的中断服务函数中手动累加全局变量。而 HAL 库在初始化时默认就开启了 SysTick,并提供了全局可调用的 HAL_GetTick() 函数,真正实现了'开箱即用'。
- 跨平台兼容:本套调度器代码在 HAL 库下具有极强的移植性。无论你从 F1 系列换到 G4 甚至 H7 系列,代码逻辑无需改动,只需要更换一下头文件即可。
- 更安全的时间戳:HAL 库内置了对 Systick 的管理,避免了开发者重复定义计时器导致的逻辑冲突。
核心原理
传统的 HAL_Delay() 就像是原地睡觉,睡醒前什么都干不了。而调度器逻辑就像是定闹钟打卡:
- CPU 不断巡逻:通过 while(1) 极速轮询。
- 时间戳比对:利用 HAL 库自带的 HAL_GetTick()(每 1ms 加 1)获取当前时间。
- 到点执行:如果'当前时间 - 上次执行时间 >= 设定周期',则触发任务函数。
代码封装
为了提高复用性,我们将调度器逻辑拆分为 scheduler.h 和 scheduler.c。
首先是结构体定义 scheduler.h,我们定义一个任务'身份证',包含它要做什么、多久做一次、上次什么时候做的。
typedef struct {
void (*pTaskFunc)(void); // 函数指针:要执行的任务
uint32_t interval; // 任务周期 (ms)
uint32_t lastTick; // 上次执行的时间戳
} Task_t;
其次是调度引擎的实现 scheduler.c,核心算法在于无符号数减法。即便 HAL_GetTick() 在运行 49.7 天后溢出归零,current - last 的结果依然能保持正确。
void scheduler_run(void) {
uint32_t currentTick = HAL_GetTick();
for (uint16_t i = 0; i < taskCount; i++) {
// 时间到!执行并更新时间戳
if (currentTick - pTaskList[i].lastTick >= pTaskList[i].interval) {
pTaskList[i].pTaskFunc();
pTaskList[i].lastTick = currentTick;
}
}
}


