# 引言 Arduino凭借其简单易用广受欢迎,但许多开发者习惯在`loop()`中使用`delay()`实现延时,这会导致CPU空转、外设响应迟钝,尤其在多任务场景(如同时处理按键、LED闪烁、传感器读取)中捉襟见肘。本文提出一种基于状态机的非阻塞调度框架,通过时间片轮转和状态迁移,实现轻量级多任务并发,适用于资源受限的嵌入式环境。 # 非阻塞编程基础 ## 阻塞 vs 非阻塞 - **阻塞**:`delay(1000)`暂停整个程序,期间无法处理其他事件。 - **非阻塞**:利用`millis()`记录时间戳,通过比较时间差判断是否执行任务,CPU在等待期间可处理其他任务。 ## 核心原则 - 绝不使用`delay()`,改用时间戳检查。 - 将任务拆分为多个状态,每个状态执行非阻塞操作。 - 使用状态机管理任务生命周期。 # 状态机调度框架设计 ## 架构概览 框架包含三个核心组件: - **任务控制块(TCB)**:描述任务属性(周期、状态、回调函数)。 - **调度器**:管理任务注册、轮询和状态切换。 - **事件标志**:支持外部事件触发任务。 ## 数据结构定义 ```c // 任务状态枚举 typedef enum { TASK_READY, // 就绪,等待执行 TASK_RUNNING, // 运行中 TASK_WAITING, // 等待事件或延时 TASK_SUSPENDED // 挂起 } TaskState; // 任务控制块 typedef struct { void (*func)(void); // 任务函数指针 uint32_t period; // 执行周期(ms) uint32_t lastRun; // 上次执行时间戳 TaskState state; // 当前状态 uint8_t priority; // 优先级(0-255,数值大优先) void *arg; // 可选参数 } TaskCB; ``` ## 调度器实现 ```c #define MAX_TASKS 8 static TaskCB taskList[MAX_TASKS]; static uint8_t taskCount = 0; // 注册任务 int scheduler_add_task(void (*func)(void), uint32_t period, uint8_t priority) { if (taskCount >= MAX_TASKS) return -1; taskList[taskCount].func = func; taskList[taskCount].period = period; taskList[taskCount].lastRun = 0; taskList[taskCount].state = TASK_READY; taskList[taskCount].priority = priority; taskList[taskCount].arg = NULL; return taskCount++; } // 调度主循环(在loop()中调用) void scheduler_run() { uint32_t now = millis(); for (uint8_t i = 0; i < taskCount; i++) { if (taskList[i].state == TASK_READY && (now - taskList[i].lastRun >= taskList[i].period)) { taskList[i].lastRun = now; taskList[i].state = TASK_RUNNING; taskList[i].func(); // 执行任务 taskList[i].state = TASK_READY; } } } ``` ## 状态机任务示例 以LED闪烁为例,使用状态机实现非阻塞闪烁: ```c // LED任务状态 typedef enum { LED_OFF, LED_ON, LED_WAIT } LedState; static LedState ledState = LED_OFF; static uint32_t ledTimer = 0; void led_task() { switch (ledState) { case LED_OFF: digitalWrite(LED_BUILTIN, LOW); ledTimer = millis(); ledState = LED_WAIT; break; case LED_ON: digitalWrite(LED_BUILTIN, HIGH); ledTimer = millis(); ledState = LED_WAIT; break; case LED_WAIT: if (millis() - ledTimer >= 500) { // 切换状态 ledState = (digitalRead(LED_BUILTIN) == HIGH) ? LED_OFF : LED_ON; } break; } } ``` # 事件驱动扩展 ## 事件标志机制 通过位掩码实现事件触发,任务可等待特定事件: ```c volatile uint32_t eventFlags = 0; #define EVENT_BUTTON (1 << 0) #define EVENT_TIMER (1 << 1) // 设置事件 void event_set(uint32_t flag) { eventFlags |= flag; } // 在任务中检查事件 void button_task() { if (eventFlags & EVENT_BUTTON) { eventFlags &= ~EVENT_BUTTON; // 处理按键事件 } } ``` ## 中断与调度器交互 在ISR中仅设置事件标志,避免耗时操作: ```c void buttonISR() { event_set(EVENT_BUTTON); } ``` # 完整示例:多任务系统 ## 需求 - 任务1:LED以1Hz闪烁 - 任务2:每2秒读取传感器并打印 - 任务3:按键按下时响应(事件触发) ## 代码实现 ```c #include // 框架代码(如上)... // 任务函数声明 void led_task(); void sensor_task(); void button_task(); void setup() { Serial.begin(9600); pinMode(LED_BUILTIN, OUTPUT); pinMode(2, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(2), buttonISR, FALLING); scheduler_add_task(led_task, 500, 1); // 周期500ms scheduler_add_task(sensor_task, 2000, 2); // 周期2s scheduler_add_task(button_task, 0, 3); // 事件触发,周期0表示不按周期 } void loop() { scheduler_run(); } // 任务实现... ``` # 注意事项与优化 - **任务函数应快速返回**:避免在任务内进行长时间阻塞操作,如`delay`、`Serial.print`大量数据。 - **共享数据保护**:多任务间共享变量需使用`volatile`或临界区保护。 - **优先级调度**:当前框架简单轮询,未实现抢占,高优先级任务可能被低优先级任务延迟,可增加优先级排序。 - **内存占用**:每个TCB约20字节,8个任务仅160字节,适合Arduino UNO。 - **时间基准**:`millis()`在50天后溢出,需处理溢出情况(使用无符号减法可自动处理)。 - **调试技巧**:在任务中打印状态时,注意串口缓冲区阻塞,可改用非阻塞打印或环形缓冲区。 # 总结 本文设计的基于状态机的非阻塞调度框架,以极小的资源开销实现了多任务并发,避免了`delay()`带来的响应迟钝问题。通过任务注册、周期调度和事件驱动,开发者可以轻松构建复杂的嵌入式应用。该框架可进一步扩展为协作式调度器,甚至移植到其他MCU平台。希望本文能帮助你在嵌入式开发中更上一层楼。