STM32裸机多任务调度:时间片轮询实现与架构解析
👁 5 阅读 · 2026-08-16 · STM32 架构
在无操作系统的STM32开发中,多任务调度常通过裸机时间片轮询实现。该方法借助SysTick定时器产生固定时基,配合任务表与标志位,以极简架构完成并发任务切换。本文从STM32内核架构出发,讲解时间片轮询原理、详细配置步骤,并给出可直接移植的完整代码示例,同时剖析中断处理、任务执行时长与临界区等关键注意事项,帮助开发者高效构建稳定可靠的轻量级调度框架。
## 一、为什么需要时间片轮询?
在资源受限的STM32应用中,直接引入RTOS可能带来额外RAM/ROM开销与移植复杂度。而传统的顺序执行(super-loop)无法应对“多事件并发”需求——例如同时处理按键扫描、LED刷新、传感器读取和串口发送。时间片轮询(Time-Slice Polling)是一种基于周期调度的裸机方案,它利用`SysTick`定时器产生稳定时基,将任务划分为多个时间片,在主循环中按序执行。
## 二、时间片轮询的原理与架构
### 2.1 核心机制
- **时基**:配置`SysTick`每1ms产生一次中断,作为系统心跳。
- **任务表**:定义结构体数组,包含任务函数指针、任务周期(ms)、当前计数值、运行标志。
- **调度流程**:每次SysTick中断中,遍历任务表,将计数值减1;当计数值归零时,重置为周期值并置位运行标志。主循环检测标志,执行相应任务,执行后清除标志。
### 2.2 STM32架构优势
STM32的`SysTick`是内核级定时器,不占用通用外设定时器,且中断响应快(优先级可配置)。利用其嵌套向量中断控制器(NVIC),可确保时基的精准性,同时主循环保持最低优先级,避免打断任务执行中的关键操作。
## 三、实现步骤
### 3.1 配置SysTick时基
使用标准库或HAL库初始化:
```c
void SysTick_Init(uint32_t freq) {
SysTick_Config(SystemCoreClock / freq); // 例如1kHz -> 1ms
}
```
### 3.2 定义任务控制块
```c
#define TASK_MAX 4
typedef struct {
void (*func)(void); // 任务函数指针
uint16_t period; // 任务周期(ms)
uint16_t counter; // 递减计数器
uint8_t runFlag; // 运行标志
} TaskCtrl_t;
TaskCtrl_t taskTable[TASK_MAX];
```
### 3.3 SysTick中断处理
```c
void SysTick_Handler(void) {
for (int i = 0; i < TASK_MAX; i++) {
if (taskTable[i].counter > 0) {
taskTable[i].counter--;
if (taskTable[i].counter == 0) {
taskTable[i].counter = taskTable[i].period;
taskTable[i].runFlag = 1;
}
}
}
}
```
### 3.4 任务注册与主循环
```c
void Task_Init(void) {
taskTable[0].func = Task_KeyScan;
taskTable[0].period = 5; // 5ms
taskTable[0].counter = 5;
taskTable[1].func = Task_LedRefresh;
taskTable[1].period = 20; // 20ms
taskTable[1].counter = 20;
taskTable[2].func = Task_SensorRead;
taskTable[2].period = 100; // 100ms
taskTable[2].counter = 100;
}
int main(void) {
SysTick_Init(1000); // 1ms时基
Task_Init();
while (1) {
for (int i = 0; i < TASK_MAX; i++) {
if (taskTable[i].runFlag) {
taskTable[i].runFlag = 0;
taskTable[i].func();
}
}
}
}
```
### 3.5 任务函数定义
```c
void Task_LedRefresh(void) {
static uint8_t toggle = 0;
toggle = !toggle;
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, toggle);
}
void Task_KeyScan(void) {
// 按键扫描逻辑
}
```
## 四、注意事项
- **中断处理时间**:SysTick中断中的循环尽量简短,仅做计数与标志操作,避免实时性受损。
- **任务执行时长**:任务应保证在时间片内完成,尤其是高优先级任务。若任务耗时超过周期,会占用后续任务执行时机,导致调度偏移。
- **任务间数据共享**:当任务与中断函数(如串口接收)共享变量时,使用`volatile`修饰,并在必要时进入临界区(如关闭中断)保护。
- **优先级平衡**:周期短的任务排在任务表前面,以便更快获得CPU。但需注意,轮询模式下所有任务平等,没有抢占,所以长任务不应阻塞。
- **动态周期调整**:可根据运行模式修改`period`字段,但需在非中断状态下更新,避免竞态条件。
## 五、总结
时间片轮询是一种优雅的裸机调度方案,它利用了STM32内核的SysTick硬件,以极少的资源占用实现多任务并发。尤其适用于设备控制、简单数据采集等场景。相比RTOS,它没有信号量、消息队列等同步机制,但通过合理的任务划分和周期设计,足以胜任大多数中低复杂度产品。理解其架构,也为后续迁移到RTOS打下基础。