# 引言:当 Arduino 遇见多任务 在嵌入式开发中,多任务处理常被等同于 RTOS。然而,对于 Arduino Uno 这类仅有 2KB RAM 的 MCU,引入 RTOS 往往得不偿失。协作式状态机(Cooperative State Machine)提供了一种轻量级替代方案:它通过任务主动让出 CPU 来实现并发,无需抢占式调度,内存开销极小。本文将带你从原理到实践,构建一个通用的状态机模板。 ## 原理:协作式调度的核心思想 协作式多任务的核心是“非阻塞”与“主动让权”。每个任务(Task)被拆分为多个状态,每个状态执行一段短小的代码,然后返回。调度器(Scheduler)按顺序轮询所有任务,每个任务检查自身状态并执行相应动作。关键在于:任务内部绝不能使用 `delay()` 等阻塞函数,而是通过记录时间戳来判断是否该执行下一步。 状态机通常包含: - **状态变量**:表示当前所处状态(如 `LED_ON`、`LED_OFF`)。 - **时间戳**:记录上次状态切换的时间,用于非阻塞延时。 - **事件触发**:如按键按下、串口数据到达,可驱动状态转移。 ## 模板设计:可复用的 C++ 类 我们设计一个 `StateMachine` 基类,提供 `update()` 虚函数供子类实现状态逻辑,并内置 `delay()` 非阻塞延时方法。 ```cpp // StateMachine.h #pragma once class StateMachine { public: virtual void update() = 0; // 每帧调用 bool isReady() { return _ready; } protected: bool _ready = true; unsigned long _lastTime = 0; bool delay(unsigned long ms) { if (millis() - _lastTime >= ms) { _lastTime = millis(); return true; } return false; } }; ``` ## 实战:LED 闪烁与按键扫描任务 ### 1. LED 闪烁任务 ```cpp // LedBlink.h #include "StateMachine.h" class LedBlink : public StateMachine { public: LedBlink(int pin) : _pin(pin) { pinMode(_pin, OUTPUT); } void update() override { switch (_state) { case 0: // 点亮 digitalWrite(_pin, HIGH); _state = 1; _lastTime = millis(); break; case 1: // 等待 500ms if (delay(500)) _state = 2; break; case 2: // 熄灭 digitalWrite(_pin, LOW); _state = 3; _lastTime = millis(); break; case 3: // 等待 500ms if (delay(500)) _state = 0; break; } } private: int _pin; int _state = 0; }; ``` ### 2. 按键扫描任务(带消抖) ```cpp // ButtonScan.h #include "StateMachine.h" class ButtonScan : public StateMachine { public: ButtonScan(int pin) : _pin(pin) { pinMode(_pin, INPUT_PULLUP); } void update() override { switch (_state) { case 0: // 等待按键按下 if (digitalRead(_pin) == LOW) { _state = 1; _lastTime = millis(); } break; case 1: // 消抖延时 20ms if (delay(20)) { if (digitalRead(_pin) == LOW) { _state = 2; } else { _state = 0; } } break; case 2: // 确认按下,触发事件 onPress(); _state = 3; _lastTime = millis(); break; case 3: // 等待释放 if (digitalRead(_pin) == HIGH) { _state = 0; } break; } } void onPress() { Serial.println("Button pressed!"); } private: int _pin; int _state = 0; }; ``` ### 3. 主程序:调度器 ```cpp // main.ino #include "LedBlink.h" #include "ButtonScan.h" LedBlink led(13); ButtonScan btn(2); void setup() { Serial.begin(9600); } void loop() { led.update(); btn.update(); } ``` ## 深入:状态机的扩展与优化 - **事件队列**:当任务需要与其他任务通信时,可添加一个简单的环形队列,在 `update()` 中处理事件。 - **优先级**:在调度器中,可给紧急任务分配更高的调用频率(如每 2 次循环调用一次)。 - **状态枚举**:使用 `enum` 代替魔法数字,提高可读性。 ## 注意事项 - **避免阻塞**:任何 `delay()`、`while` 循环都会破坏协作调度,务必使用非阻塞延时。 - **时间戳溢出**:`millis()` 约 49 天后回绕,但基于无符号长整型的减法在回绕时仍正确,无需处理。 - **任务数量**:每个任务占用少量 RAM,但状态机数量过多时,注意栈空间(Arduino Uno 默认 2KB)。 - **调试**:在状态切换处添加 `Serial.print` 可帮助追踪逻辑,但调试代码会占用时间,正式发布时移除。 ## 总结 协作式状态机模板为 Arduino 提供了一种轻量、高效的多任务方案。它避免了 RTOS 的复杂性和资源开销,同时保持了代码的模块化和可维护性。通过本文的模板,你可以轻松扩展多个任务,实现 LED 控制、按键扫描、传感器读取等并发操作。这种模式不仅适用于 Arduino,也适用于任何裸机嵌入式环境,是嵌入式开发者必备的技能之一。