# 引言 在嵌入式开发中,当需要同时处理多个任务(如按键扫描、LED闪烁、传感器读取)时,初学者常陷入`delay()`阻塞的困境。协作式调度器(Cooperative Scheduler)提供了一种轻量级解决方案,无需操作系统即可实现任务并发。本文将基于Arduino平台,剖析其设计核心,助你掌握这一关键技术。 # 协作式调度器原理 协作式调度器基于**非抢占式**多任务模型:任务主动让出CPU(通过`yield()`或`delay()`),调度器才切换至下一任务。与抢占式RTOS不同,协作式调度器无需复杂上下文切换,内存占用极低,适合资源受限的MCU(如ATmega328P)。 核心思想: - 任务以函数形式注册,每个任务具有独立状态(就绪、运行、等待)。 - 调度器维护任务列表,按优先级或时间片轮转执行。 - 任务通过`delay()`或`yield()`释放CPU,实现协作。 # 设计要点 ## 1. 任务控制块(TCB) 每个任务需一个控制块,存储函数指针、周期、上次执行时间、状态等。 ```c typedef struct { void (*func)(void); // 任务函数 unsigned long period; // 执行周期(ms) unsigned long lastRun; // 上次执行时间戳 uint8_t enabled; // 使能标志 } Task; ``` ## 2. 调度策略 - **时间片轮转**:所有任务按顺序执行,每个任务运行至主动让出。 - **周期调度**:基于`millis()`实现定时触发,适合周期性任务(如每10ms扫描按键)。 - **优先级调度**:高优先级任务先执行,但需防止低优先级饿死,可结合时间片。 推荐采用**周期调度+时间片轮转**结合,兼顾实时性与公平性。 ## 3. 定时管理 使用`millis()`而非`delay()`,避免阻塞。核心是记录上次执行时间,判断是否到达周期。 ```c unsigned long now = millis(); if (now - task.lastRun >= task.period) { task.func(); task.lastRun = now; } ``` 注意`millis()`溢出问题(约49天),需使用无符号减法处理。 ## 4. 任务状态与协作 任务内部需主动让出CPU,常用方式: - `delay(0)`:仅让出当前时间片,不等待。 - `delay(ms)`:让出并等待指定时间,期间调度器可运行其他任务。 - 自定义`yield()`:调用调度器切换。 示例: ```c void taskBlink() { digitalWrite(LED, !digitalRead(LED)); delay(100); // 让出CPU 100ms } ``` ## 5. 内存与栈管理 Arduino Uno仅有2KB SRAM,任务函数需避免大数组和递归。每个任务独立栈?协作式调度器通常共享栈,因此任务内局部变量需谨慎,避免深度调用。建议: - 使用`static`或全局变量保存状态。 - 避免在任务内定义大数组。 - 可设置栈检查(如`StackHighMark`)监控使用量。 # 完整代码示例 以下实现一个简单的协作式调度器,包含两个任务:LED闪烁和串口打印。 ```c // 协作式调度器示例 #include #define MAX_TASKS 5 typedef struct { void (*func)(void); unsigned long period; unsigned long lastRun; uint8_t enabled; } Task; Task tasks[MAX_TASKS]; uint8_t taskCount = 0; // 注册任务 bool addTask(void (*f)(void), unsigned long period) { if (taskCount >= MAX_TASKS) return false; tasks[taskCount].func = f; tasks[taskCount].period = period; tasks[taskCount].lastRun = 0; tasks[taskCount].enabled = 1; taskCount++; return true; } // 调度器主循环 void schedulerRun() { unsigned long now = millis(); for (uint8_t i = 0; i < taskCount; i++) { if (tasks[i].enabled && (now - tasks[i].lastRun >= tasks[i].period)) { tasks[i].func(); tasks[i].lastRun = now; } } } // 任务1:LED闪烁 void taskLED() { static uint8_t state = 0; digitalWrite(13, state); state = !state; // 无需delay,调度器自动按周期调用 } // 任务2:串口打印 void taskPrint() { Serial.println("Task Print Running"); } void setup() { pinMode(13, OUTPUT); Serial.begin(9600); addTask(taskLED, 500); // 每500ms切换 addTask(taskPrint, 1000); // 每1s打印 } void loop() { schedulerRun(); } ``` # 注意事项 - **任务执行时间**:任务函数应快速返回,避免长时间占用CPU,否则影响其他任务实时性。 - **共享资源保护**:协作式调度器无互斥锁,若任务间共享变量,需在任务内关闭中断或使用原子操作。 - **优先级反转**:若采用优先级调度,需注意低优先级任务持锁导致高优先级等待。 - **调试技巧**:使用`Serial`打印任务执行时间,或利用`micros()`测量任务耗时。 - **扩展性**:可增加任务删除、暂停功能,或支持动态优先级。 # 总结 协作式调度器是嵌入式多任务编程的基石,其设计简单、高效,适合大多数Arduino应用。通过合理设计任务结构、调度策略和内存管理,可显著提升系统响应性。掌握其要点,为后续学习RTOS打下坚实基础。