A high-performance, ultra-lean, cross-platform cooperative event-loop framework for bare-metal systems.
Unlike the native Arduino Scheduler library, which relies on platform-specific assembly register hacks and only works on select 32-bit ARM boards, CrossScheduler is written in pure, standards-compliant C++. It runs seamlessly on everything from an 8-bit AVR micro (Uno, Nano, Mega) to a 32-bit ARM Cortex, ESP8266, ESP32, Raspberry Pi Pico, or even native desktop environments.
- Universal Compatibility: No platform-specific assembly. Runs anywhere a standard C++ compiler can reach.
-
Dual-Track API: Seamlessly accept classic parameterless callbacks (
void(*)()) or context-aware handlers (void(*)(ScheduleContext)). -
Zero-Overhead Memory Optimization: Uses anonymous C++ unions to pack multi-signature tracks into the space of a single raw pointer. No dynamic heap allocations (
$0$ bytesmalloc/new). -
Zombieless Task Control: Native
ScheduleContextexecution tracking handles allow long-running blocking tasks to poll their own cancellation states internally and exit automatically. -
Zero-Casting Data Passthrough: Attach generic data variables to task instances and retrieve them through an inline, type-erased template abstraction layer (
self.data<T>()). - Precision Interval Rescheduling: Protects repeating loops against execution drift by automatically jumping timelines forward to handle missed intervals.
- Open the Arduino IDE.
- Navigate to Tools -> Manage Libraries...
- Search for CrossScheduler and click Install.
Download this repository as a .zip file, open your Arduino IDE, navigate to Sketch -> Include Library -> Add .ZIP Library..., and select the downloaded file.
Forget nesting recursive scheduling methods inside your functions. Just define your layout once and forget it.
#include <CrossScheduler.h>
void fastBlinker() {
digitalWrite(13, !digitalRead(13));
}
void slowLogger() {
Serial.println("Another second has passed...");
}
void schedule() {
Serial.begin(115200);
pinMode(13, OUTPUT);
// Simple, flat loops
Schedule.loop(250, fastBlinker);
Schedule.loop(1000, slowLogger);
}
Use ScheduleContext to build background logic that can gracefully abort itself when canceled from elsewhere in your program.
#include <CrossScheduler.h>
ScheduleContext monitoringTask;
void constantRunning(ScheduleContext self) {
// self.poll() runs the background event loop and returns false if this task is canceled
while (self.poll()) {
Serial.println("Monitoring sensors...");
self.sleep(100); // Smart, non-blocking sleep that keeps other tasks running
}
Serial.println("Monitoring loop safely exited!");
}
void schedule() {
Serial.begin(115200);
// Spin up a long-running tracking loop
monitoringTask = Schedule.afterMS(1000, constantRunning);
// Schedule a one-shot shutdown routine 4 seconds later
Schedule.afterMS(4000, []() {
Serial.println("An emergency occurred! Canceling monitoring...");
monitoringTask.cancel(); // Instantly tears down the constantRunning loop cleanly
});
}
Pass persistent states or hardware profiles straight into localized tracking loops with absolutely zero pointer prose or global macros.
#include <CrossScheduler.h>
struct Motor {
int pin;
int speed;
};
void runMotor(ScheduleContext self) {
// Automatically resolves the void* data back into your type safely!
Motor* m = self.data<Motor>();
analogWrite(m->pin, m->speed);
}
void schedule() {
// Allocate static state data tracking instances safely
static Motor driveMotor = { 5, 255 };
static Motor intakeMotor = { 6, 128 };
// Dispatch identical logic structures bound to distinct contexts
Schedule.loop(500, runMotor, &driveMotor);
Schedule.loop(500, runMotor, &intakeMotor);
}
You can easily override maximum structural boundaries or change logging channels prior to loading the library by defining compiler parameters:
// Expand or compress static pool limits before inclusion to dial in RAM usage
#define SCHEDULER_TASK_MAX 20
#define SCHEDULER_LISTENER_MAX 10
#include <CrossScheduler.h>
ScheduleContext doTask(Callback cb): Schedules a task to run immediately on the next update tick.ScheduleContext afterMS(unsigned long ms, Callback cb): Runs a task once after a specified millisecond duration.ScheduleContext loop(unsigned long ms, Callback cb, void* userData = nullptr): Schedules a persistent repeating interval loop.ScheduleContext listen(ConditionFn cond, Callback cb, unsigned long interval = 0, WatchMode mode = ALWAYS): Attaches a conditional checker function to fire a callback on states.void cancel(ScheduleContext ctx / ScheduleHandle handle): Safe explicit dynamic loop/listener tearing-down utility.void abortTasks() / abortListeners() / abortAll(): Blanket flushes to immediately wipe pool elements during global state changes.
bool poll(): Advances the tracking loop; returnsfalseif the parent execution handle has been terminated.void sleep(unsigned long ms): High-performance, non-blocking sleep delay wrapper that lets sibling tasks execute.void cancel(): Self-destruct sequence to pull the running task out of rotation.template <typename T> T* data(): Instantly casts associated contextual data tracking points without manual syntax casting.
This library is open-source software licensed under the MIT License.