Arduino 上实现无 RTOS 的协作式调度器:基于状态机的任务切换技巧
👁 1 阅读 · 2026-08-27 · 嵌入式
在资源受限的 Arduino 平台上,RTOS 往往显得笨重。本文深入探讨如何利用状态机和协作式调度的思想,在不引入 RTOS 的情况下实现多任务并发。通过手写一个轻量级调度器,你将掌握任务切换的核心原理、状态机建模技巧以及实际代码实现,从而在嵌入式项目中优雅地管理多个任务,提升系统响应性和代码可维护性。
# 引言
在嵌入式开发中,多任务处理是常见需求。然而,对于 Arduino 这类资源有限的平台,完整 RTOS(如 FreeRTOS)可能占用过多 RAM/Flash,且引入额外复杂度。协作式调度器(Cooperative Scheduler)提供了一种轻量级替代方案:任务主动让出 CPU,由调度器按策略切换。结合状态机(State Machine),我们可以实现非阻塞的任务切换,让系统看起来“同时”运行多个任务。
# 原理讲解
## 协作式调度 vs 抢占式调度
- **抢占式调度**:由定时器中断强制切换任务,需要保存/恢复上下文,对硬件要求高。
- **协作式调度**:任务自行决定何时让出 CPU(如调用 `yield()`),无需硬件上下文切换,实现简单,适合事件驱动或周期性任务。
## 状态机任务模型
每个任务被建模为一个有限状态机(FSM)。任务在每次被调度时执行一个“步骤”,根据当前状态执行相应操作,然后转移到下一个状态。通过状态转移,任务可以分阶段完成长操作,避免阻塞。
## 调度策略
最简单的协作式调度器采用**轮询(Round-Robin)**:按顺序依次调用每个任务的 `update()` 函数。任务内部通过状态机控制执行节奏,若某任务需等待外部事件,则直接返回,不占用 CPU。
# 实现步骤
## 1. 定义任务结构
我们定义一个 `Task` 结构体,包含状态变量、周期、上次执行时间等。
```c
typedef struct {
void (*update)(void); // 任务函数指针
uint32_t period; // 执行周期(ms)
uint32_t last_run; // 上次执行时间
uint8_t state; // 当前状态(用户自定义)
} Task;
```
## 2. 实现调度器核心
调度器维护一个任务数组,并在 `loop()` 中遍历调用。
```c
#define MAX_TASKS 5
Task tasks[MAX_TASKS];
uint8_t task_count = 0;
void scheduler_init() {
task_count = 0;
}
bool add_task(void (*update)(void), uint32_t period) {
if (task_count >= MAX_TASKS) return false;
tasks[task_count].update = update;
tasks[task_count].period = period;
tasks[task_count].last_run = 0;
tasks[task_count].state = 0;
task_count++;
return true;
}
void scheduler_run() {
uint32_t now = millis();
for (uint8_t i = 0; i < task_count; i++) {
if (now - tasks[i].last_run >= tasks[i].period) {
tasks[i].last_run = now;
tasks[i].update(); // 调用任务函数
}
}
}
```
## 3. 编写状态机任务示例
以 LED 闪烁和串口打印为例,展示状态机写法。
```c
// 任务1:LED 闪烁(状态机)
#define LED_PIN 13
#define STATE_LED_ON 0
#define STATE_LED_OFF 1
void task_led_update() {
switch (tasks[0].state) { // 假设任务索引为0
case STATE_LED_ON:
digitalWrite(LED_PIN, HIGH);
tasks[0].state = STATE_LED_OFF;
break;
case STATE_LED_OFF:
digitalWrite(LED_PIN, LOW);
tasks[0].state = STATE_LED_ON;
break;
}
}
// 任务2:串口打印计数(状态机)
#define STATE_PRINT 0
void task_print_update() {
static int count = 0;
switch (tasks[1].state) {
case STATE_PRINT:
Serial.print("Count: ");
Serial.println(count++);
tasks[1].state = STATE_PRINT; // 保持状态
break;
}
}
```
## 4. 在 Arduino 中集成
```c
void setup() {
Serial.begin(9600);
pinMode(LED_PIN, OUTPUT);
scheduler_init();
add_task(task_led_update, 500); // 每500ms切换LED
add_task(task_print_update, 1000); // 每1000ms打印
}
void loop() {
scheduler_run();
}
```
# 注意事项
- **任务函数必须非阻塞**:避免使用 `delay()`,改用状态机或记录时间戳。
- **状态管理**:每个任务的状态变量应独立,建议使用 `task[i].state` 而非全局变量,以便调度器管理。
- **周期精度**:`millis()` 基于定时器,若任务执行时间过长,可能影响其他任务,需确保每个任务执行时间远小于周期。
- **任务数量限制**:`MAX_TASKS` 根据 RAM 调整,避免浪费。
- **可扩展性**:可增加优先级或时间片,但保持简单是协作式调度的优势。
# 总结
通过状态机和协作式调度,我们在 Arduino 上实现了轻量级多任务,无需 RTOS 即可满足大部分场景。这种方法不仅节省资源,还让代码结构清晰,易于调试。掌握这一技巧,你将能更高效地开发复杂的嵌入式应用。