Arduino 协作式状态机框架:无操作系统的多任务调度实战指南
👁 2 阅读 · 2026-08-27 · 嵌入式
在资源受限的 Arduino 平台上,多任务处理常被误认为需要 RTOS。本文深入探讨一种轻量级协作式状态机框架,通过非阻塞延时与状态切换实现高效多任务调度。你将掌握核心原理、配置步骤及完整代码示例,并了解如何规避常见陷阱,让单片机在无操作系统环境下流畅运行多个并发任务。
# 引言
嵌入式开发中,多任务需求无处不在:同时处理按键扫描、LED 闪烁、传感器读取和串口通信。然而,Arduino Uno 仅有 2KB RAM,运行 RTOS 往往力不从心。协作式状态机(Cooperative State Machine)提供了一种优雅的解决方案:通过非阻塞延时和状态切换,在单线程中模拟并发,实现高效、可预测的多任务调度。
# 核心原理
## 协作式 vs 抢占式
- **抢占式(RTOS)**:由内核强制切换任务,需要上下文保存、优先级管理等,开销大。
- **协作式**:任务主动让出 CPU(通过 `delay()` 或 `yield()`),代码简单,内存占用极低。
## 状态机模型
每个任务是一个有限状态机(FSM),包含:
- **状态**:用枚举或常量表示当前阶段。
- **事件**:如时间到达、外部信号。
- **转移**:根据事件切换状态。
核心思想:在 `loop()` 中轮询所有任务,每个任务检查自身状态和条件,若满足则执行动作并更新状态,否则立即返回。
## 非阻塞延时
使用 `millis()` 代替 `delay()`,记录上次执行时间,通过时间差判断是否到达执行时刻。
```c
unsigned long previousMillis = 0;
const long interval = 1000;
void task() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
// 执行任务
}
}
```
# 框架设计
## 任务结构体
定义统一的任务接口,便于管理。
```c
typedef struct {
void (*init)(void); // 初始化函数
void (*update)(void); // 状态更新函数
unsigned long period; // 执行周期(ms)
unsigned long lastRun; // 上次执行时间
} Task;
```
## 调度器
维护任务数组,在 `loop()` 中遍历并执行到期任务。
```c
#define MAX_TASKS 5
Task tasks[MAX_TASKS];
int taskCount = 0;
void scheduler_init() {
taskCount = 0;
}
bool scheduler_add(Task t) {
if (taskCount >= MAX_TASKS) return false;
t.lastRun = 0;
tasks[taskCount++] = t;
return true;
}
void scheduler_run() {
unsigned long now = millis();
for (int i = 0; i < taskCount; i++) {
if (now - tasks[i].lastRun >= tasks[i].period) {
tasks[i].lastRun = now;
tasks[i].update();
}
}
}
```
## 状态机实现示例
以 LED 闪烁和按键扫描为例,展示如何将任务转化为状态机。
```c
// LED 闪烁任务
enum LEDState { LED_OFF, LED_ON };
LEDState ledState = LED_OFF;
unsigned long ledTimer = 0;
void led_init() {
pinMode(13, OUTPUT);
}
void led_update() {
unsigned long now = millis();
switch (ledState) {
case LED_OFF:
if (now - ledTimer >= 500) {
digitalWrite(13, HIGH);
ledState = LED_ON;
ledTimer = now;
}
break;
case LED_ON:
if (now - ledTimer >= 300) {
digitalWrite(13, LOW);
ledState = LED_OFF;
ledTimer = now;
}
break;
}
}
// 按键扫描任务
enum KeyState { KEY_IDLE, KEY_DEBOUNCE, KEY_PRESSED };
KeyState keyState = KEY_IDLE;
unsigned long keyTimer = 0;
const int keyPin = 2;
void key_init() {
pinMode(keyPin, INPUT_PULLUP);
}
void key_update() {
unsigned long now = millis();
bool pressed = (digitalRead(keyPin) == LOW);
switch (keyState) {
case KEY_IDLE:
if (pressed) {
keyState = KEY_DEBOUNCE;
keyTimer = now;
}
break;
case KEY_DEBOUNCE:
if (now - keyTimer >= 20) {
if (pressed) {
keyState = KEY_PRESSED;
Serial.println("Key pressed!");
} else {
keyState = KEY_IDLE;
}
}
break;
case KEY_PRESSED:
if (!pressed) {
keyState = KEY_IDLE;
}
break;
}
}
```
# 配置步骤
1. **定义任务**:为每个任务编写 `init` 和 `update` 函数,内部使用状态机。
2. **创建任务结构体**:设置周期(如 LED 任务 10ms,按键 5ms)。
3. **初始化调度器**:在 `setup()` 中调用 `scheduler_init()`。
4. **注册任务**:使用 `scheduler_add()` 添加任务。
5. **运行调度器**:在 `loop()` 中调用 `scheduler_run()`。
```c
void setup() {
Serial.begin(9600);
scheduler_init();
Task ledTask = { led_init, led_update, 10, 0 };
Task keyTask = { key_init, key_update, 5, 0 };
scheduler_add(ledTask);
scheduler_add(keyTask);
// 初始化所有任务
for (int i = 0; i < taskCount; i++) {
tasks[i].init();
}
}
void loop() {
scheduler_run();
}
```
# 完整代码示例
将上述片段整合,形成可直接运行的完整程序。
```c
// 协作式状态机框架示例
#include
// 任务结构体定义...(如前)
// LED 任务实现...(如前)
// 按键任务实现...(如前)
void setup() {
Serial.begin(9600);
scheduler_init();
Task ledTask = { led_init, led_update, 10, 0 };
Task keyTask = { key_init, key_update, 5, 0 };
scheduler_add(ledTask);
scheduler_add(keyTask);
for (int i = 0; i < taskCount; i++) {
tasks[i].init();
}
}
void loop() {
scheduler_run();
}
```
# 注意事项
- **避免阻塞**:任务内严禁使用 `delay()` 或长循环,否则会阻塞其他任务。
- **时间精度**:`millis()` 在 50 天后溢出,需使用无符号长整型差值比较(如 `(now - lastRun) >= period`),可自动处理溢出。
- **任务周期**:周期不宜过短,否则调度器频繁轮询,浪费 CPU;建议最小 1ms。
- **共享资源**:多任务访问全局变量时,需考虑原子性,必要时使用 `noInterrupts()` 保护。
- **状态机复杂度**:状态过多时,可考虑使用函数指针表优化,但保持简单优先。
- **调试**:利用串口打印状态切换,便于定位逻辑错误。
# 总结
协作式状态机框架在 Arduino 上实现了轻量级多任务调度,无需 RTOS 即可满足大多数应用需求。通过非阻塞延时和状态切换,我们获得了确定性的时序和极低的内存开销。此框架可轻松扩展至更多任务,并适用于其他单片机平台。掌握这一技术,你将能更高效地构建复杂嵌入式系统。