DuinOS: small and simple rtos

We have developed an alpha version of a small preemptive full open source rtos for Arduino, based on open source software, but with a new very simple syntax.

We hope it will be available for download next week.

It has been adapted to fit even in a mega168 (with some limitations), and we are working to have it running on the 328, the 644 and the new 1284. In our (very) initial tests, it's compatible with most standard Arduino code, so it could be added as a library to any application, just taking into account the following:

  • The loop() is now a task, so it's speed may be different.
  • The timer 1 is used by the kernel (so no pwm in pins 9 and 10).
  • The delayMicroseconds() is allowed, but not welcome in DuinOS based sketches.

Here is a small example of use (it's running on a 168), with two LEDs blinking, where the red LED is "modulated" by two separated tasks:

#include <DuinOS.h>

/*
DuinOS TwoLEDsBlinking

Blinks two LEDs with different freqs.

The circuit: ##Pins:

  • LED connected from digital pin 14 to Vcc.
  • LED connected from digital pin 15 to Vcc.

##Pins:

  • Note: On most Comm.ProgUSB boards, there are already two LEDs on the board
    connected to pins 14 and 15, so you don't need any extra components for this example.

Created 2009.10.26 (yyyy.mm.dd)
by Julián da Silva Gillig

Based on the original Blink code by David Cuartielles

*/
int ledPinRed = 14;
int ledPinGreen = 15;
boolean redLED_isOn = false;

taskLoop(redLED)
{
redLED_isOn = false;
delay(500);
redLED_isOn = true;
delay(500);
}

taskLoop(greenLED)
{
static unsigned char counter = 0;

digitalWrite(ledPinGreen, LOW); // set the LED on
delay(200);
digitalWrite(ledPinGreen, HIGH); // set the LED off
delay(200);

if (counter >= 29)
suspend(); //After a while, the tasks suspends itself (forever)
counter++;
}

// The setup() method runs once, when the sketch starts

void setup()
{
// Initialize the digital pins as outputs:
pinMode(ledPinRed, OUTPUT);
pinMode(ledPinGreen, OUTPUT);

createTaskLoop(redLED, LOW_PRIORITY);
createTaskLoop(greenLED, NORMAL_PRIORITY);

//A task can be suspended by it's name
//suspendTask(redLED);
}

// This is the main loop() method, wich runs over and over again,
// as long as the Arduino has power. Is a LOW_PRIORITY loopTask:

void loop()
{
if (redLED_isOn)
{
digitalWrite(ledPinRed, HIGH); // set the LED off
delay(25); // The OS can be tested reducing these delays, and seeing how both LEDs work together...
digitalWrite(ledPinRed, LOW); // set the LED on
delay(25);
}
else
{
digitalWrite(ledPinRed, HIGH); // LED is off
//If nextTask is not called, the application will not hang, because the OS is preemptive. BUT, the current task
//will consume a lot of computational resources (due to it's lack of a delay() in this branch), the application will
//turn slower, and the other tasks may be affected by this, loossing precision in their timing:
nextTask();
}
}

We are new to the Arduino community, so any comments are welcome!

Regards,
Julián

Julián,

DuinOS looks like it will be very useful. I can see where it will make complex programs easier to read, and being able to control task priorities will be helpful.

Will you be able to control the priority of the loop() task? What's the OS footprint in code and RAM?

Thanks,

-Mike

Hi Mike, thanks for your comments.
Yes, you can control everything if you want, (the main loop priority is just a value in a header). More: you can change priorities at runtime (but not in the m168, due it's small RAM).
About the footprint, this is de avrsize output for the mega168 (with the size optimizations flag):

Program: 5260 bytes (32.1% Full)
(.text + .data + .bootloader)
Data: 900 bytes (87.9% Full)
(.data + .bss + .noinit)

I know it lets small RAM to the user, but in a 328, a 1280 or a 644 it's much better.

Regards,
Julián

I can see where it will make complex programs easier to read

That's odd; I looked at this code and thought it was probably making something simple a lot more complex to read.

Hi Andy, you are right about this small example. But it's just an small example. We are making this to be used in robots (among other things), and with a lot of sensors, some DC motors and communications, we think it may help. Specially if using subsumption, or a computer intensive PID, or when some class of arbiter is needed to control behavior.
Another use is when using graphic displays, with drawing primitives.
Regards,
Julián

I am quite interested in the underlying framework and code.

Is there some 'scheduling' taking place, or are you simply calling functions that has a reenterpretation of the delay allowing it to execute other functions instead of nop.
What I ask is this: do the redLed loop completely execute before the greenLed
loop is called? Or have you somehow implemented multitasking?

My guess is that your ISR simply calls [or prepares a call] for the next function in a list, that has been added by createTaskLoop

If this is it, this library offers yet another API / abstraction of the good old BlinkWithoutDelay.
Although this posting sounds negative, I think your approach is a good one as it enables the user to utilize the familiar delay without blocking program flow [of the other loops].

But, your solution also consumes a lot of resources as opposed to the alternatives.
[this only applies if your code is, in fact, another protothreading abstraction. <with the additional reinterpretation of delay]

Hoping to hear some details and I hope for seeing some code :slight_smile:

[edit]Welcome to this wonderful community! :)[/edit]

Hi AlphaBeta, thanks for the welcome!

Well, as I commented in my first post, DuinOS is based in open source tools. To be more specific, it's a layer over the FreeRTOS v5.4.2 kernel (www.freertos.org), which implements a RoundRobin preemptive multitasking subsystem, with only one timer.
That RTOS is robust (it's available since a few years), but there is no much really simple AVR code out there, specially for the smaller mcus (the demo app comes for the old ATMega323, and for the ATMega32).

So, we started to integrate it in our projects, porting it to the m88, m168, m644, m1284 and m1280, and finally made this, which we think has a more "arduino" syntax, simplifying some things. For example: instead of working with taskHandles, you can suspend or resume them by name (thanks to some simple macros).

The other part of our work was to make it fit in the smaller devices.

Finally, we are working now on integrating it to the Arduino IDE, because we want to just add some dirs, without overwriting existing ones, and making the IDE and the OS work together with the 644 and the 1284, wich we use in our robots.

Regarding the BlinkWithoutDelay, that's a typical cooperative application. DuinOS is full preemptive (and FreeRTOS has cooperative tasks too, that we disabled them in this port, but can be reenabled by the user).
In small applications, the cooperative is generally better, due to it's small footprint (both Flash and RAM). But when the apps grows and goes more complex (please think about the 1284, with 128 KB of flash and 16 KB or RAM), the preemptive really simplifies things, and in bigger cpus (328+) this kernel fits well.

Regards,
Julián

Cool stuff!

A brilliant contribution to the community!
I'll definitly have to test this for a project sometime. :slight_smile:

Just read throught the FreeRTOS source. Have you renamed every function to suit arduino naming convention, or just those most frequently used in 'client code'?

Question about implementation:
Do you use heap model 1,2 or 3?

How much overhead does the OS bring into the system?

Great work! :smiley:

Thanks!
No, didn't renamed all the functions. Just the ones we think are more usefull here, in an Arduino Env. But, the other are available with their oiriginal names. And we added the macro taskLoop, whose code is the following:

#define taskLoop(name)\
void name##Function();\
xTaskHandle name;\
void name##_Task(void *pvParameters)\
{\
      for(;;)\
            name##Function();\
}\
void name##Function()

So, then you can use the macros like:

#define suspendTask(name) vTaskSuspend(name)
#define resumeTask(name) vTaskResume(name)

We also rewrote the delay(), wich is quite simple as well:

#define delay(ticks) vTaskDelay(ticks)

Regarding the heap, we are using model 1, because, to cut the RAM and Flash usage we don't allow tasks deletes. If you enable this functionality (easy, just a #define), you must use model 2, but we didn't test it yet. Model 3 does not make sense here, I think.

About the execution time, in our initial tests it seems to be quiet acceptable. By now, I can tell you that we compiled a few "standard" arduino examples (Comms, etc.) but with this kernel (so the main loop is a task), and they seems to be running without problems. But have to make more tests with a digital osciloscope about the timming.
That's what we are going to do this weekend!

:wink:

Wonderful idea - i relaly love that already now :0)
What's the maximum number of tasks that can run properly semi-simultanously?
Could this be used to call in one task a TWI device and in the other do some data - output?

Hi, thanks for your kindly comments!
The maximun tasks that can be running simultanously depends on what they are doing. We are testing these kind of things now, but I think in a few days will be uploaded the full IDE so you could run your own tests, and tell us (thanks in advance! :)). By now, I can tell you that the system interrupt tick is at 1 KHz.

In addition, as it's based on FreeRTOS, in the bigger CPUs (like the 644, 1280, etc.), you can enable the mutexes, semaphores, and even the lighter coroutines. We didn't encapsulate these, because think that in the more advanced applications that will use them, perhaps the programmer will prefer to access the FreeRTOS API directly.

Regards,
Julián

Hi Julián, I wonder how much time a typical context switch takes. If it is many microseconds than the 1 ms interrupts could affect the servo library. Have you tested with that library?

mmm, good point. I will run a test with a servo these days.
Thanks!
Julián

its not just servos that may be affected by a context switch. pulseIn readings may also be affected. I would be interested to know how long the context switch takes.

I was looking the servo and pulsein, although I could not make tests today. The servo library will have a conflict with the kernel, but I just added to the list of things to modify (as done with the delay() function). The problem is not a big problem: the current library uses the timer 1.

Regarding the pulseIn, I have two comments:

  1. The kernel has CRITICAL_SECTIONS, so computation-intensive routines could be used. I don't think this aproach is the better, when using a preemptive kernel for many reasons, but it is still out there.

  2. We plan in our group to continue improving this, and one of the main things to do is to get a better integration with the Arduino environment as a whole. This will imply to modify or even rewrite some things. There are examples, of small boards with nice libraries which can manage servos, pwms, pulses and has multitasking. One of them has more than 10 years old an runs on a 1 MIPS old HC11 (@4 MHz in some versions). It's the HandyBoard, wich has multitasking running over a virtual stack machine (IC). So we think this is possible in a near 16 MIPS AVR, but will surely demand some work.

Regards,
Julián

Julian,

Welcome to the community!

Looks like great work! This will help a lot!

Cheers,
pracas

Thank you! we hope this will be online today or tomorrow (very first Alpha version).
Regards,
Julián

DuinOS v0.1 Alpha is here now:

Regards,
Julián

Downloaded DuinOS and can't use it out of the box without modifying the DuinOS.h file. The path specified in there is a Windows path so you'll get a compile error on any unix variant OS.

Many thanks! We fixed it. We did not test it on Linux, so any comment is welcome.

Regards,
Julián