I sometimes need to put in a sketch loop different code parts with different timing requirements. E.g. a sensor that must be read every 20 milliseconds, a PWM that must be updated every 50 milliseconds, a status update that must be sent over the serial every 120 ms, and so on.
AFAIK there's no simple and obvious way to integrate these tasks in the loop() function.
So my proposal is to add to the Arduino standard library a few functions that make easier to integrate different tasks in the main loop without having to worry about how their timings interact with each other.
I've implemented a first version of it, using cooperative multitasking.
Here's some demo code, a fancy version of the "blink" example:
void appendTask(int (*func)());
int ledPin = 13;
int isLedOn = false;
void setup() {
pinMode(ledPin, OUTPUT);
appendTask(blinkDemoTask);
}
void loop() {
runNextTask();
}
int blinkDemoTask() {
if (isLedOn) {
digitalWrite(ledPin, LOW);
isLedOn = false;
}
else {
digitalWrite(ledPin, HIGH);
isLedOn = true;
}
return 1000;
}
struct Task {
unsigned long time;
int (*func)();
};
struct Task tasks[10];
int tasksLen = 0;
void appendTask(int (*func)()) {
tasks[tasksLen] = (struct Task) {
millis(), func
};
tasksLen++;
}
void sortTasks() {
unsigned long now = millis();
for (int i = 0; i < tasksLen; i++) {
if (tasks[i].time < now) {
tasks[i].time = now;
}
}
qsort(tasks, tasksLen, sizeof(tasks[0]), taskCompar);
}
int taskCompar(const void *a, const void *b) {
struct Task *ta = (struct Task *)a;
struct Task *tb = (struct Task *)b;
if (ta->time < tb->time) {
return -1;
}
else if (ta->time == tb->time) {
return 0;
}
return 1;
}
void runNextTask() {
if (tasksLen > 0) {
sortTasks();
struct Task *task = &tasks[0];
while (task->time > millis()) {
// pass
}
task->time = task->func();
task->time += millis();
}
}
The important user code is "appendTask(blinkDemoTask);" in setup() which creates a new task and the function blinkDemoTask() which implements the task. It toggles the led and requests with "return 1000;" to be called again after 1000 ms. It's also a good example of how tasks are usually implemented: a status variable and an "if" or a "switch" that does a bit of work, updates the status variable and returns.
There can be any number of tasks running independently at the same time, as long as they are written to return ASAP. I.e. calls to delay() must be replaced with a "return ;".
I suggest to add to Arduino functions equivalent to appendTask(), runNextTask(), sortTasks() and taskCompar() in this example. Only appendTask() and runNextTask() are part of the public API.
Obviously this is only a first draft to start a discussion, two clear shortcomings are that it uses a static array of 10 tasks (should probably use dynamically allocated memory; each task requires 6 bytes) and there isn't yet a way do cancel a task (shouldn't be hard to add). But I have already used them with success a few times, including a sketch with 6 tasks (PWMs, sensors, web server and misc digital outputs).
Minor detail: I added the function prototype "void appendTask(int (*func)());" at the beginning of this sketch because Arduino didn't automatically generate it. Probably because it doesn't like function pointers used as parameters.
So what do you think? It's a good idea that could be developed into something useful or there's a better way to do this that I don't know?