# 引言 在 Arduino 开发中,我们常面临多任务需求:同时处理按键扫描、LED 闪烁、传感器读取和串口通信。若使用 `delay()` 阻塞,系统将失去实时性;若引入 RTOS,又可能超出硬件资源。协作式状态机(Cooperative State Machine)提供了一种折中方案:通过任务主动让出 CPU,实现非抢占式调度,无需操作系统,即可高效管理多个任务。 # 原理:协作式多任务与状态机 ## 协作式多任务 协作式调度依赖任务主动让出控制权。每个任务在完成一个“步骤”后,必须返回主循环,让其他任务运行。这避免了抢占式调度带来的竞态条件,但要求任务不能长时间阻塞。 ## 状态机 状态机将任务分解为有限个状态,每个状态对应一段代码,通过事件或条件触发状态转移。将状态机与协作式调度结合,每个任务就是一个状态机,在每次调度时执行当前状态的动作,然后转移到下一个状态。 ## 模板设计 我们使用 C++ 模板实现一个通用的 `StateMachine` 类,通过函数指针或 `std::function` 存储状态处理函数,并维护一个状态变量。调度器则是一个简单的循环,依次调用每个任务的状态机实例。 # 配置步骤 1. **定义状态枚举**:为每个任务定义状态,如 `IDLE`, `RUNNING`, `DONE`。 2. **实现状态处理函数**:每个状态对应一个函数,返回下一个状态。 3. **创建状态机实例**:使用模板实例化,传入初始状态和处理函数表。 4. **注册任务**:在 `setup()` 中创建任务对象,并加入调度器列表。 5. **运行调度器**:在 `loop()` 中调用调度器的 `run()` 方法。 # 完整代码示例 以下示例实现两个任务:LED 闪烁(每 500ms 翻转)和串口打印(每 1s 打印一次)。 ```cpp #include #include // 状态机模板类 template class StateMachine { public: using StateHandler = std::function; StateMachine(StateType initState, const StateHandler* handlers, int numStates) : currentState(initState), handlers(handlers), numStates(numStates) {} void run() { if (currentState >= 0 && currentState < numStates && handlers[currentState]) { currentState = handlers[currentState](); } } private: StateType currentState; const StateHandler* handlers; int numStates; }; // 任务1:LED 闪烁 enum LedState { LED_OFF, LED_ON }; unsigned long lastLedToggle = 0; const int ledPin = 13; LedState ledOffHandler() { if (millis() - lastLedToggle >= 500) { digitalWrite(ledPin, HIGH); lastLedToggle = millis(); return LED_ON; } return LED_OFF; } LedState ledOnHandler() { if (millis() - lastLedToggle >= 500) { digitalWrite(ledPin, LOW); lastLedToggle = millis(); return LED_OFF; } return LED_ON; } StateMachine::StateHandler ledHandlers[] = { ledOffHandler, ledOnHandler }; StateMachine ledSM(LED_OFF, ledHandlers, 2); // 任务2:串口打印 enum PrintState { PRINT_IDLE, PRINT_WAIT }; unsigned long lastPrint = 0; PrintState printIdleHandler() { if (millis() - lastPrint >= 1000) { Serial.println("Hello from state machine!"); lastPrint = millis(); return PRINT_WAIT; } return PRINT_IDLE; } PrintState printWaitHandler() { // 等待1秒后回到IDLE,但这里我们直接返回IDLE,因为millis检查在IDLE中 return PRINT_IDLE; } StateMachine::StateHandler printHandlers[] = { printIdleHandler, printWaitHandler }; StateMachine printSM(PRINT_IDLE, printHandlers, 2); // 调度器:简单数组管理 StateMachineBase* tasks[] = { &ledSM, &printSM }; // 注意:需要基类,这里简化 void setup() { pinMode(ledPin, OUTPUT); Serial.begin(9600); } void loop() { ledSM.run(); printSM.run(); } ``` **注意**:上述代码中 `StateMachineBase` 未定义,实际使用时需定义基类或使用模板函数。为简化,我们直接调用两个实例。 # 注意事项 - **非阻塞原则**:状态处理函数内严禁使用 `delay()` 或长时间循环,否则会阻塞其他任务。 - **时间管理**:使用 `millis()` 而非 `delay()` 实现定时,注意 `millis()` 溢出问题(约49天)。 - **状态转移**:确保每个状态都有明确的转移条件,避免死循环。 - **内存开销**:`std::function` 可能占用较多内存,在 AVR 上建议使用普通函数指针。 - **扩展性**:任务数量增多时,可考虑使用链表或动态数组管理任务,但注意内存碎片。 # 总结 协作式状态机模板为 Arduino 提供了一种轻量级多任务方案,它结构清晰、易于调试,且不依赖外部库。通过合理设计状态,可以应对大多数非实时性任务。对于更复杂的实时需求,可考虑升级为抢占式调度或引入 RTOS。希望本文能帮助你提升嵌入式项目的架构水平。