Task Scheduler

Hi all.

I was looking for a simple task scheduler, and as I was not able to find anything that pleased my needs I decided to reinvent the wheel and make my own library. In case it is of any help to anyone else out there, here is my project. Feel free to contribute with your comments and/or code.

a timer queue is a fairly basic approach to scheduling many activities either periodically or one-time without needing a pre-emptive scheduler. (see chapter 2 of the XINU book)

a timer queue is a linked list of entries describing an actions (function ptr) and number of timer tics from the previously list entry. New entries can be inserted or removed from the list by simply updating the # of tics the new entry precedes. Multiple entries for the same time can of course exist with zero tics for the 2nd and subsequent entries

each timer event decrements the # tics in the entry at the head of the list and executes it when the # tics becomes zero.

You should add at least one example sketch to your library.

aarg:
You should add at least one example sketch to your library.

You mean like the TaskScheduler.ino example that is included in the library?

#include "TaskScheduler.h"

#define PRECISION micros
#define LOOP_PREVENTION

#if PRECISION == micros
# define SECOND 1000000 // 1 s = 1000000 µs.
# define UNIT "µs"
#elif PRECISION == millis
# define SECOND 1000 // 1 s = 1000 ms.
# define UNIT "ms"
#endif

Scheduler task;

volatile int second = 0;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(9600);
  while (!Serial) {};
  task.add(secondCounter, SECOND);
  task.add(start, SECOND/2);
}

void loop() {
  task.run();
}

void start() {
  task.remove(start);
  task.add(dot, SECOND/4);
  Serial.print(" " UNIT);
  printTab(2);
  Serial.print("Sec");
  printTab(1);
  Serial.print("Task");
  timestamp();
  Serial.print("Start of program");
}

void dot() {
  static int dots = 0;
  Serial.print(".");
  dots++;
  if (dots == 3) {
    task.remove(dot);
    task.add(ledOn, SECOND/2);
    task.add(taskA);
  }
}

void end() {
  task.remove();
  task.remove(timestamp);
  task.remove(ledOn);
  task.remove(ledOff);
  timestamp();
  Serial.print("End of program.");
}

void taskA() {
  task.remove();
  timestamp();
  Serial.print("Task A: run once. Task B will run in a second.");
  task.add(taskB);
}

void taskB() {
  static int counter = 0;
  const int repetitions = 5;
  if (counter < repetitions) {
    timestamp();
    Serial.print("Task B: run ");
    int times = repetitions - 1 - counter;
    if (times > 0) {
      Serial.print(repetitions - 1 - counter);
      Serial.print(" more");
    }
    else Serial.print("one last");
    Serial.print(" time");
    if (times > 0) {
      if (counter < repetitions - 2) Serial.print("s");
      Serial.print(", next in ");
      Serial.print(repetitions - counter);
      Serial.print(" second");
      if (counter < repetitions - 1) Serial.print("s");
    }
    Serial.print(".");
    task.setPeriod(taskB, (repetitions - counter) * SECOND);
    task.add(taskC, SECOND);
  }
  else {
    task.remove();
    timestamp();
    Serial.print("Call task D.");
    task.add(taskD);
  }
  counter++;
}

void taskC() {
  timestamp();
  Serial.print("Task C: run 1 second after task B.");
  task.remove();
}

void taskD() {
  timestamp();
  Serial.print("Task D: Run once at the end.");
  task.remove(taskD);
  task.add(end);
}

void timestamp() {
  Serial.println();
  Serial.print(" [");
  Serial.print(PRECISION());
  Serial.print(" ] ");
  printTab(1);
  Serial.print("(");
  Serial.print(second);
  Serial.print(")");
  printTab(1);
}

void secondCounter() {
  second++;
}

void ledOn() {
  task.remove();
  digitalWrite(LED_BUILTIN, HIGH);
  timestamp();
  Serial.print("Led on.");
  task.add(ledOff, SECOND/2);
}

void ledOff() {
  task.remove();
  digitalWrite(LED_BUILTIN, LOW);
  timestamp();
  Serial.print("Led off.");
  task.add(ledOn, SECOND/2);
}

void printTab(int number) {
  const char TAB = '\t';
  while (number > 0) {
    Serial.print(TAB);
    number--;
  }
}

Regards,
Ray L.

You can also comb through this thread here for examples of Arduino task schedulers. Some interesting examples emerged, some generously described as “operating systems”, but were actually little more than task schedulers:

6v6gt:
You can also comb through this thread here for examples of Arduino task schedulers. Some interesting examples emerged, some generously described as “operating systems”, but were actually little more than task schedulers:
https://forum.arduino.cc/index.php?topic=467927.0

Had a look at that thread about Arduino OS's people have written. People go on about how they can't see how these could be any benefit... I so totally disagree! For example; a handheld that has different screens running different sketches. How can you do that without some sort of overall OS to manage them?

-jim lee

jimLee:
a handheld that has different screens running different sketches. How can you do that without some sort of overall OS to manage them?

-jim lee

And if you're doing something like that, why on earth do it on an Arduino? Use a $10 RPi ZeroW, and you have Linux, WiFi, HDMI, 512K RAM, and a real operating system. You can run Apache, SQL, Javascript, PHP, and have an app like that running in a couple of hours.

Regards,
Ray L

gcjr:
a timer queue is a fairly basic approach to scheduling many activities either periodically or one-time without needing a pre-emptive scheduler. (see chapter 2 of the XINU book)

a timer queue is a linked list of entries describing an actions (function ptr) and number of timer tics from the previously list entry. New entries can be inserted or removed from the list by simply updating the # of tics the new entry precedes. Multiple entries for the same time can of course exist with zero tics for the 2nd and subsequent entries

each timer event decrements the # tics in the entry at the head of the list and executes it when the # tics becomes zero.

Very interesting! That would be a fairly accurate description of what I have implemented. Your comment helped me realize that what I have implemented would be called a 'cooperative multitask'. I added that detail to the description of my project in it's readme file, so thanks for your contribution!

aarg:
You should add at least one example sketch to your library.

Thanks for the comment. As @RayLivingston pointed out, there is an example file named 'TaskScheduler.ino'. I see the confusion, as it is not labeled as such, so I added a clarification in the readme file. Perhaps I should have divided the files in a 'source' and an 'example' folder, but I thought it would be more comfortable and simple to have everything at the same level.

6v6gt:
You can also comb through this thread here for examples of Arduino task schedulers. Some interesting examples emerged, some generously described as “operating systems”, but were actually little more than task schedulers:
https://forum.arduino.cc/index.php?topic=467927.0

All I wanted to do is be able to orchestrate the call of functions without using interrupts nor delays. Perhaps my project is over-simplistic, as I was looking for something board-independent and with a high-level of abstraction. The concept of an OS for Arduino sounds interesting, but I guess it would mean a level of complexity I do not want to dig into. Anyways, that thread is a good read and I did learn a couple of new things, so thanks!

The ESP32 has a built in OS called freeRTOS, a task scheduler; works great

I used uMT on a DUE, a RTOS, works well.

jimLee:
Had a look at that thread about Arduino OS's people have written. People go on about how they can't see how these could be any benefit... I so totally disagree! For example; a handheld that has different screens running different sketches. How can you do that without some sort of overall OS to manage them?

-jim lee

Hi Jim. Your example is similar to my case. I wanted to control a bunch of sensors and a display in a orderly manner, making use of idle cycles to access the sensors or refresh the display, minimizing the amount of time the processor is idle. However, I am looking to make a quick proof of concept, so learning how to use an OS would mean a whole lot of effort. That is what motivated me to make this project. However, I am pretty sure there is plenty of room for improvement on what I have done.

Idahowalker:
The ESP32 has a built in OS called freeRTOS, a task scheduler; works great

I used uMT on a DUE, a RTOS, works well.

Those are great references. I will post links to them in case someone else wants to investigate more about them.

uMT: GitHub - GO01260/uMT: uMT - a preemptive, soft real-time (not deterministic) multitasker for the ARDUINO AVR and SAM boards

freeRTOS: Using FreeRTOS multi-tasking in Arduino - Arduino Project Hub

RayLivingston:
And if you're doing something like that, why on earth do it on an Arduino? Use a $10 RPi ZeroW, and you have Linux, WiFi, HDMI, 512K RAM, and a real operating system. You can run Apache, SQL, Javascript, PHP, and have an app like that running in a couple of hours.

Regards,
Ray L

Because the Arduino is the easiest thing in the world to code, there are Arduino compatibles that are WAY more powerful I can grab off the shelf (IE Teensys) There is an unending array of hardware that I can use. And above this all, I don't need to learn Linux or anything else to get started. Setup() an loop() and away we go!

-jim lee

joebrave:
Hi Jim. Your example is similar to my case. I wanted to control a bunch of sensors and a display in a orderly manner, making use of idle cycles to access the sensors or refresh the display, minimizing the amount of time the processor is idle. However, I am looking to make a quick proof of concept, so learning how to use an OS would mean a whole lot of effort. That is what motivated me to make this project. However, I am pretty sure there is plenty of room for improvement on what I have done.

Actually I followed the same path awhile ago. I wrote a class idler that has a method void idle(void); Anything that is derived from this class has an idle() method you can inherit and fill in to do whatever you want. Along with all this, comes a function idle() I put in my loop() function. This autamatically calls al my myriad of "ilders" each time through.

If you want to deal with time, as in "lets only look every 100ms" I wrote a class timeObj that works like an egg timer. Set it for a time and it has a method ding() that returns true if the time has expired. Between the two, all of the worries about doing multiple things at once, just evaporated.

I tried to share this stuff, but was basically told I was stupid for writing such drivel.

Since then I've noticed, its common for people to write stuff that really helps them, but just confuses the heck out of everyone else. So I guess begin called dumb, is a somewhat common response.

-jim lee

i have lots of experience writing and debugging application code that relies on OS features to synchronize and communicate with other processes and processors. Early in my careers i read the OS books, Unix, XINU and Minix which gave be a good understanding of those OS concepts i needed later.

But I believe almost all simple applications running on small Atmel or esp32 processor don't have the real-time or social (yea!) needs for a multi-tasking OS. real-time is relative and social just avoid arguments.

But even sudo multitasking makes those little jobs SO much easier!

-jim lee

can you give me an example?

Well, this runs 99% on sudo multitasking. Also uses sketch swapping. (To keep RAM usage low.)

My example

Here's the main loop() function from above.

void loop() {     // During loop..
  idle();         // Idlers get their time.
  ourOS.loop();   // ourOS gets a kick to pass on to the current panel.
}

-jim lee

Actually, now that I think about it. If I could wipe a Raspberry Pi and make it Arduino compatible; IE setup() loop(), I'd do it! This way the ongoing project is al I'd have to think about. No wondering how all the stuff it comes with works, or why I can't get it to work now.

In fact, that would be kinda' cool.

-jim lee