Here is a short demo: running 5 tasks (the last one isn't actually executed, just to show as an example):
//demo for multi-tasking
#define MAX_TASKS 5 //maximum number of tasks
//task 0 - lcd1602
#include "LiquidCrystal.h"
// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(12, 13, 8, 9, 10, 11);
void task0(void) {
static unsigned char i=0; //count
lcd.setCursor(0, 1); //column 0, line 1
lcd.print(i++); //print count, then increment it
}
//task 1
#define LED_1 1 //led1 on pin1
void task1(void) {
static unsigned char count=0;
count+=1; //increment count
if (count==100) {
count -= 100; //reset count
digitalWrite(LED_1, !digitalRead(LED_1)); //blink led1 every 100 turns
}
}
//task 2
#define LED_2 2 //led1 on pin1
void task2(void) {
static unsigned char count=0;
count+=1; //increment count
if (count==200) {
count -= 200; //reset count
digitalWrite(LED_2, random(0,2)); //randomly turn on / off led_2
}
}
//task 3
#define LED_3 3
void task3(void) {
digitalWrite(LED_3, digitalRead(LED_1)); //follow led_1
}
//task 4
#define LED_4 4
void task4(void) {
digitalWrite(LED_4, !digitalRead(LED_2)); //follow the opposite of LED_2
}
//task 5
#define LED_5 5
void task5(void) {
digitalWrite(LED_5, !digitalRead(LED_5)); //flip led5 - never executed
}
void setup(void) {
//initialize all tasks
//initialize for task0
lcd.begin(16,2); //display is 16.2
lcd.print("Hello, world!");
//initialize for task1
pinMode(LED_1, OUTPUT);
//initialize for task2
pinMode(LED_2, OUTPUT);
//initialize for task3
pinMode(LED_3, OUTPUT);
//initialize for task4
pinMode(LED_4, OUTPUT);
//initialize for task2
pinMode(LED_5, OUTPUT);
}
void loop(void) {
static unsigned char current_task = 0; //task indexer
switch (current_task) {
case 0: task0(); break;
case 1: task1(); break;
case 2: task2(); break;
case 3: task3(); break;
case 4: task4(); break;
case 5: task5(); break; //never executed
default: break;
}
current_task = (current_task==MAX_TASKS - 1)?0:(current_task+1); //increment task indexer
}
task0 is to run an lcd and count up a value; task1..4 are simple blinking of leds. task5 is excluded from execution, by the definition of MAX_TASKS.
The scheduler doesn't assign a fixed time slot to individual tasks: the next task is run as soon as the previous one finishes. You can fix that by using a systick, like millies() at the end of the task and define a minimum time slot for individual tasks.
You can introduce functional pointers and use them to insert user tasks into a scheduler shell, like the one above, to make it easier to install user tasks into the scheduler - and potentially keep the scheduler entirely out of the user code base.
That's basically the gist of pretty much any RTOS.
dc42:
If I have to write a system that does more than 3 or 4 things at a time on an Arduino, I use a cooperative multi tasking scheduler. It still requires discipline because you have to split each task into chunks that don't block and always complete within a few hundred microseconds. For example, when updating an LCD, you have to avoid using the clear and home functions, and only write a few characters at a time (I also modified the LiquidCrystal library to make it faster). The big advantage of using a scheduler is that it lets you keep the code for the different tasks separate.
For simpler systems, I have used blink-without-delay style timer polling for most tasks, and a tick interrupt for time-critical regular tasks such as polling rotary encoders and multiplexing displays.
dhenry:
Here is a short demo: running 5 tasks (the last one isn't actually executed, just to show as an example):
Yes, but why would you add that complexity rather than just executing each of the 'tasks' in turn within loop()? What advantage are you trying to obtain by making loop() call one function at each pass, rather than call all the functions? In this example, there is no advantage that I can see.
There are a lot of good responses here on this subject and as you might notice, in some cases its a matter of personal preference. Hobby coding is kind of cool that way, once you figure out how to make it go, and it works, its right for you; even if someone else thinks its a total hack. This is not necessarily the case for a professional coding project where maintenance, upgrades, or even personal injury issues are at stake. Anyhow, your solution could be polling, interrupts, RTOS etc. I guess what I'm saying is there is no one universal good answer to the asynchronous problem for a hobbyist. Creative programming can overcome most situations.
For me, I started out looking for an asynchronous platform to build my application upon even though I don't have a concrete project yet, but that's just me. I didn't really find anything completely suitable, so I'm building my own personal asynchronous device framework. Funny thing is, right now I have no idea if it will hold up to a real project. Fortunately for me, building my own framework is just part of the fun of using a micro-controller; even if I throw the code away, I'll have gained good experience and had fun in the process. I encourage you to explore and have fun.
Last night I completed some tests on my framework and blogged about it this morning if you are interested - the code will be available for further hacking by anyone. I do not plan on making a library out of this code, its just a personal project for me.