Go Down

Topic: leOS - a simple taskmanager/scheduler (Read 18209 times) previous topic - next topic



How much memory does a task take?

leOS isn't a real OS so it doesn't reserve stack space or RAM space for data. The "task" will consume only the resources that it will use. So, every "task" (or sub-routine) can be as little as it can.


The LeOS in oriented to all the range of MCUs or only for 328?

At the moment it can compile for a wide range of MCUs:
From the readme.txt:

- Attiny2313/4313
- Attiny24/44/84 & Attiny25/45/85
- Atmega644/1284
- Atmega8
- Atmega88/168/328
- Atmega640/1280/1281/2560/2561
- Atmega32U4 (only at 16 MHz)

Supported clocks are 1, 4, 8, and 16 MHz.


I apprecited the post of runnerup so I decided to work on his suggestions. The result is leOS 0.1.4 (look at the first post for the package).
It uses a Struct to manage the tasks but it doesn't use other suggestions runnerup told to me, i.e. dynamic memory or other fixes, because of the memory consumption. So the actual library is (I think) the best tradeoff between simplicity/clearness/readability of the code and firmware size (and my C/C++ skills  :smiley-sweat:).

Why don't you make a 'task' struct. That would be a more intuitive approach in your library because you would only need one array instead of four or five distinct arrays for each task.

Done. With the leOS_2_tasks example, the code has grown from 2390 to 2424 bytes.


Why don't you allocate the array(s) to dynamic memory so that you can resize it in addTask() (and possibly removeTask()) as necessary.

Not done. Too much over my programming skills. And, I think, too much Flash-eater. Remember that leOS can run over little Attiny MCUs.


In your source there are two places where you slide all of the array elements over to fill in a gap (in removeTask() and ISR() interrupt handler when cleaning out one time functions). I recommend you either break the common code out into a common function or just call removeTask() from ISR(). It will make your code easier to read and it will do magical things to your code size.

Not done. The ISR routine is hard to manage. To call external functions you have to work a lot (i.e. using "this" and pointers to it) and the final size of the sketch grows of about a hunder bytes more. So I dropped it.

Thanks a lot to runnerup for his interest in my little library.


News about the leOS.
Now at version 1.0.1a, leOS:
  • supports the Atmega344
  • uses Structs to manage the tasks
  • has a method to know the status of a task
  • isn't affected by the overflow of 32-bit counters, so it's not strictly necessary to use 64-bits math for long periods



A couples of days ago I wrote leOS2.
One of the most frequent appointment that I've found reading the messages of the users that tried leOS is about the incompatibility between the scheduler and other libraries that use timer 2: this is because a timer can only be used by one application.

So I've read about the architecture of the microcontrollers I usually use and I've realized to use the WatchDog Timer, WDT. This circuit is usually used to reset the microcontroller if the user doesn't reset its counter before this one has expired. This is good in critical applications where the sketch must never freeze, both for logical errors or for infinite loops caused by external factors (i.e. datas that should arrive through an ethernet or serial connection). The WatchDog is particular because it uses the internal 128 kHz oscillator as clock source, so it can operate asynchronously respect to the system clock; additionally, it can not only reset the microcontroller but it can also raise an interrupt. We can use the latter and intercept the corresponding ISR, putting in it the leOS's scheduler: in this manner the scheduler won't use any timer of the MCU. This is leOS2.

To use leOS2 you just have to copy it into the folder /libraries that is included in your sketchs' folder, and then add your sketch the inclusion of the library and the creation of a new instance of leOS2.
You are now ready to use leOS2 like its predecessor. Only 2 methods are changed, let's see them in detail. Due to the fact that leOS2 is based on WDT and that this only has a fixed clock and a few prescalers, there are some limitations on the suitable intervals for a task: the minimum available interval is 16 ms, and the intervals must also be multiplies of this value. So, 16 ms represents a "tick" and the intervals must be passed to addTask() & modifyTask() in ticks and not in milliseconds as in leOS. So, addTask(function, 100) will not add a task scheduled to be run every 100 ms but every 100 ticks, or 1,600 ms. To help converting from milliseconds to ticks (if you don't want to add a simple bit shift, i.e.: interval>>4) I'vew added a new function called convertMs() that accepts a time in milliseconds and returns the corresponding number of ticks.

As usual, more details can be found on my website:


I wrote a beta version of leOS2.
The release 2.0.9x introduces the ability to pass to leOS2 a param during initialization that instructs the class to set the WDT to work in interrupt mode only or in interrupt+system reset mode.
The first mode is the mode actually used by leOS2: when the WatchDog timer expires, an interrupt is raised, then it's intercepted by the corresponding ISR and the scheduler is run.
Using the second me we can use a particular way of working of the WDT: the first time that its timer expires it raises an interrupt, then it clears the WDIE flag so that the next timeout it will reset the MCU. But if in the ISR routine we set again the WDIE flag to "1", at the next WDT timeout we will get another interrupt, not a system reset. And so on.

Starting from this, I thinked how to use this trick. So, using a suggestion of the user lesto, I created the ISR routine interruptable using the param "ISR_NOBLOCK" so that it can be halted during its execution by another call made by the WDT. Then I put a flag to check if a task is running: if it is, a decrement a counter. This counter contains the param passed by the user, that I use as a timeout befor the reset. While I wait that the task has completed its execution, I decrement the counter and set to "1" the WDIE bit: if the counter reaches zero before the task has completed its work the scheduler assumes that it freezed during execution, so the scheduler didn't set the WDIE to 1 anymore and the WDT will reset the microcontroller.

In this manner I can create non-freezable sketches using only ony peripheral.


I would like to update the situation of leOS2, my little scheduler for Arduino and other Atmel MCUs.
The release 2.1.0 is out. A fixed a bug inside the code that affected the algorithm that checked the timeout, now leOS2 works perfectly.

For whose that don't know nothing about leOS2: leOS2 is a little scheduler that is based on the Watchdog timer (WDT). Using the WDT, leOS2 doesn't make use of other timers so it is compatible with other libraries for Arduino.
Moreover, the use of WDT can permit to write a system of tasks that can not freeze the MCU because if a task crashes for some reason, the scheduler will reset the MCU after the preconfigured timeout, starting the device again.

More info on leOS and leOS2 (and the downloadable libraries) can be found here:

Now on GitHub too:


As mentioned elsewhere, leOS doesn't meet my requirements because it doesn't have a way to pass data to the callback. The C idiom I'm used to is to pass a void * to the scheduler with the function, then pass that on to the function. Given that this is an OOPL, one would expect to be able to pass a bound method, but C++ doesn't have those.

To see why, consider your 3-led example if you had this interface. You'd have the following changes:

Code: [Select]
typedef struct { byte led, status } myLed ;
myLed1 = {7, 0} ;
myLed2 = {8, 0} ;

in Setup:

Code: [Select]

   myOS.addTask(flashLed, (void *) &myLed1, 600) ;
   myOs.addtask(flashLed, (void *) &myLed2,  350) ;

And then the one flashLed function replacing the two you have now:

Code: [Select]

void flashLed(void* data) {
    myLed* led = (myLed *) status ;

   led->status ^= 1 ;
   digitalWrite(led->led, led->status) ;

While this case doesn't really save much code, note that you can add an LED by adding two lines of code instead of a new function. My use case was a bit more complicated, involving a task to bump the active row in an LED matrix using shift controllers.

One final note: a more OOP alternative would be to replace the function with an leOS_callBack object:

Code: [Select]

class leOS_callBack {
    public: void callBack(void) ;
} ;

Then, I'd use it by adding a callBack function to my method, and the library would invoke that method, giving me access to all the class variables that are currently causing me to curse C++.



Actually, leOS and leOS2 have been written with resource consumption and simplicity in mind because I wanted to do something that was: easy to use for beginners (the real RTOSs are powerful and rich in features but miss the target of simplicity); usable on little MCUs too, like Attiny84/85, where there are not big quantities of SRAM to deal with (many RTOSs reserve fixed quantities of SRAM for stack's tasks, leaving a little room for the variables of the program).

I'll try to see if I can improve my schedulers using your hints  ;)


Different goals. I habitually look for ways to minimize code, and making that pointer available helps you avoid repeating code that just uses a different global(s). But I can see if you're really tight on ram, that might be the wrong trade off. Unfortunately, if (like me) you want to use it in a library where you don't know how many tasks the user is going to require, using globals to pass the data doesn't really work.

I would argue that it doesn't make the simple case noticeably more complicated. One extra parameter that you set to NULL in the call, and then ignore in the task handler until you need this facility.


Go Up