leOS - a simple taskmanager/scheduler

legacy:
this project may be interesting as an easy demonstration of "IO Callback function (1) scheduling" performed into FIFO way, extremely easy to understand and extremely short in his implementation: it sounds good for arduino =P

Thank you for your appreciation

Hi, nice effort! I've seen the code - and let me kindly ask you following:

  1. why we need to deal with float in:
    //set the initial value of the counter depending on the prescaler
    _starter = 256 - (int)((float)F_CPU * 0.001 / prescaler);
    would not be better to do it as
    #define _starter 256-F_CPU/1000/prescaler
    or something like that?
  2. the clock limitation (1,4,8,16MHz) - that is related to point 1. only, so when I'll find a good _starter (in order to get 1ms timer interrupt) I can use ie 22.11MHz as well, I guess
  3. would it be possible to use it outside the arduino environment as well?
    P.

pito:
Hi, nice effort! I've seen the code - and let me kindly ask you following:

  1. why we need to deal with float in:
    //set the initial value of the counter depending on the prescaler
    _starter = 256 - (int)((float)F_CPU * 0.001 / prescaler);
    would not be better to do it as
    #define _starter 256-F_CPU/1000/prescaler
    or something like that?

The basic core of leOS (the management of Timer 2) derives from another project of mine, swRTC, a library to implement a software RTC in an Atmel MCU using only a timer. That code was inspired by MsTimer library, from which I initially studied and replicated the formulas to computer the timer prescaler and other things. Your notice is interesting, no need to use floats there :wink:

  1. the clock limitation (1,4,8,16MHz) - that is related to point 1. only, so when I'll find a good _starter (in order to get 1ms timer interrupt) I can use ie 22.11MHz as well, I guess

Yeah. I just put several checks to get an initial value for the timer that could give 1 overflow per millisecond. But, due to the fact that usually internal oscillators or external resonators are not as precise as their nominal clock, I should find a formula that could try to reach the nearest value to obtain 1 overflow/ms.

  1. would it be possible to use it outside the arduino environment as well?
    P.

If you want to know if the library can be compiled inside other IDEs the answer is: I don't know.
At the moment I only use the Arduino IDE, so the library has been written using parts from the Arduino core. But I think that you could convert it to be Arduino IDE indipendent: the timer's setting is made using directly the MCU's registers, I think there a just few things to check that could not work

This is really helpful! however the ONETIME keyword does not work. I'm starting task1 to happen every two seconds. Task2 is onetime and I restart is each time task1 happens. However, task2 happens continually.

Jim

Looks cool.
I was looking at your source and wondering:

  • 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.
  • Why don't you allocate the array(s) to dynamic memory so that you can resize it in addTask() (and possibly removeTask()) as necessary. Is there a good reason to be limited to 9 tasks?
  • 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.
  • I see that you declare
const uint8_t ONETIME=2;
const uint8_t SCHEDULED=1;

inside your header, but you never actually bother to use them anywhere in the code. Better than arbitrary constant ints would be to use an enum, that way using the name in place of the raw integer is enforced by the compiler.

alfiesty:
This is really helpful! however the ONETIME keyword does not work. I'm starting task1 to happen every two seconds. Task2 is onetime and I restart is each time task1 happens. However, task2 happens continually.

Jim

I apologize if I answer your question only today but I forgot to follow this thread :sweat_smile:
Can you provide the sketch you're using? Which platoform are you using? Which version of the Arduino IDE?
Thank you for you feedback.

EDIT:
I found a little bug in the management of onetime pads... can you try the new version 0.1.2?
http://www.leonardomiliani.com/?p=516&lang=en

@runnerup:
I appreciate your comments.
This is a kind of pre-stable release. leOS works and is sufficiently stable and usable for using it in everyday's sketches, but it can be improved in a lot of thing. My wills are to review the leOS source to improve it, as soon as possibile.

Hi,

I haven´t still had time to look at the code, but i still have two questions.

  • Is LeOS able to keep up with realtime, does it have any kind of RTC feature?
  • Does LeOS have limitations in terms of number of tasks it can manage?

Regards

LuisSoares:
Is LeOS able to keep up with realtime, does it have any kind of RTC feature?[/li][/list]

At the momento, it doesn't. To make a very good real-time software we have to use the built-in RTC (Real-TimeCounter) module with an external crystal, so that we can set the timer 2 to work in asynchronous mode.
(i.e. in my "Micrologio" project, that is available on my website)

Does LeOS have limitations in terms of number of tasks it can manage?[/li][/list]

The limitations are set by SRAM memory. By default leOS can manage up to 10 tasks: you can change this value in the source of the library.

Hi,

How much memory does a task take?

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

LuisSoares:
Hi,

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 :sweat_smile:).

runnerup:
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:
[x]supports the Atmega344
[x]uses Structs to manage the tasks
[x]has a method to know the status of a task
[x]isn't affected by the overflow of 32-bit counters, so it's not strictly necessary to use 64-bits math for long periods

http://www.leonardomiliani.com/?p=516&lang=en

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:
http://www.leonardomiliani.com/?p=516&lang=en

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:
http://www.leonardomiliani.com/?p=516#leos2

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:

typedef struct { byte led, status } myLed ;
myLed1 = {7, 0} ;
myLed2 = {8, 0} ;

in Setup:

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

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

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:

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++.

Thanks,
<mike

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 :wink:

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.

Thanks,
<mike