mini RTOS [sic]

Off in the "millis broken" discussion, there has been talk of a mini-RTOS for scheduling time-based functions. I think that an actual multi-processing OS is overkill for most such applications, and very difficult to get working well on a system with as little ram as an ATmega168.
However, a simpler scheme might be pretty easy to implement. Consider something like the following:

setup() {
  sched_once(timesup, 3*ONEMINUTE);  // Run "timesup" in three minutes
  sched_periodic(blinker, ONESEC);       // Run "blinker" every second
}
loop() {
  sched_check();  // check and maybe run scheduled tasks
  // other code, hopefully not taking too long.

It's certainly not "real time." It's not preemptive. It's not a real operating system at all.
It's dependent on well-behaved co-operative tasks and code ("cooperative mutitasking")
It doesn't allow tasks to "block" on arbitrary events.
OTOH, it'd be really simple to implement, not use very much memory (flash OR ram), and some very large systems indeed run pretty well with similar limitations.

As long as the compiler supports function pointers, something like this could work splendidly, in my opinion.

(I'm at the office now, or I'd check it out myself. I really need to get a job where tinkering with Arduino is acceptable....)

I implemented something along these lines a while ago but have not had the time to write it up.

It's a library called dateTimeAlarm that works alongside the dateTime library I posted here: Arduino Playground - DateTime

The posted code adds timekeeping without requiring a real time clock.

The (as yet) unposted Alarm library adds the ability to schedule tasks at specific times or after specific delays from seconds to days. The library has a delay function that can be a number of milliseconds or a specific second, minute, hour or day. But the key functionality is a set of Alarms that are handled by user callback functions that are serviced while any of the library delay functions are executing. The scheduling is co-operative and handled on a first come first serve basis. These alarm callbacks can be set for a specific time of day or after a specific interval has elapsed.

Here is a fragment of a sketch by way of example

#include <DateTime.h>
#include <DateTimeAlarms.h>

AlarmID_t Alarm9,Alarm10,Timer11;  // id to identify what triggered alarm if callbacks are shared

void OnAlarm(AlarmID_t Sender){
  // callback for time of day alarms
  if( Sender ==  Alarm9) {
    Serial.print("Alarm9: ");         
    dtAlarms.setValue(Alarm9, DateTime.now() + 9 ); // reset alarm to trigger at the time that is 9 seconds from now
  }
  else  if( Sender ==  Alarm12) 
  {
    Serial.print("Alarm12: ");    
    dtAlarms.setValue(Alarm12(AlarmHMS(12,0,0),); // reset alarm to trigger at noon
  }     
}

void OnTimer(AlarmID_t Sender){
  // callback for time delay alarm
  Serial.print("Timer11: 11 sec timer: ");    
  dtAlarms.setValue(Timer11, 11 ); // delay another 11 seconds
}

void setup(){
  Serial.begin(19200);
  Alarm9 = dtAlarms.createAlarm( DateTime.now() + 9, OnAlarm); // trigger 9 seconds from now
  Alarm12 = dtAlarms.createAlarm(AlarmHMS(12,0,0), OnAlarm); // trigger at mid day
  Timer11 = dtAlarms.createTimer( 11, OnTimer); // trigger in 11 seconds
}

void  loop(){  
   
  dtAlarms.waitUntilThisSecond(0); //  this code blocks waiting for start of the next minute, background alarms are still serviced 
  digitalWrite(13, HIGH);
  dtAlarms.delay(2000);  // note we call the alarm delay to service the background alarms 
  digitalWrite(13,LOW);  
  dtAlarms.delay(2000); // delay is in milliseconds , all other alarm values are seconds
}

If there is interest in this I will knuckle under and post the code. I would very much welcome volunteers to help me document the usage.

mem: that looks very cool.

I think even a simplified version would be useful. That is, something that allows scheduling an event in N seconds (or milliseconds) and something that allows scheduling an event every N seconds (or milliseconds). You don't necessarily need to allow the user to create Alarm instances: there could just be a single global one that allows registration of multiple callbacks.

mem: that looks very cool.

I think even a simplified version would be useful. That is, something that allows scheduling an event in N seconds (or milliseconds) and something that allows scheduling an event every N seconds (or milliseconds). You don't necessarily need to allow the user to create Alarm instances: there could just be a single global one that allows registration of multiple callbacks.

That functionality would certainly simplify the usage (and the effort to write it up), particularly if the library could ignore time of day and just handle the duration of the alarm from the time its set.

How many alarms would you suggest. I would like to avoid the overhead of malloc if I can , and the number could be easily changed by the user in a re-compile where desired. Do you think I can get away with say six different alarms by default? (a repeating alarm uses the same resource as a one-shot alarm, so its just a question of how many different alarm values)

Just a thought, but why not use a port of the freeRTOS project (and eventually rewrite some parts of it so that it works with the off-the-shelf-16Mhz-crystal Arduino) ?

http://www.kieltech.de/uweswiki/FreeRTOS

Hi melka,

I agree with westfw's first post, its overkill for any arduino applications I can think of. I would be interested to hear about an application that would fit on an arduino board with room to spare for a RTOS that that couldn't be done with a simple cooperative scheduler.

I agree that what is being looked for is a simple scheduler and a os that keeps reasonable time.

Support for resource contention and typical inter task communications is well beyond the requirements to implement a simple data logger or simple control system with time stamped event logging.

It is true freeRTOS could form the basis for processing/wiring; however: you would most likely see increased memory, CPU and complexity. I would hold off until you need an integrated IP stack and web support.

There may be a need for a fork - simplicity versus web support.

I would agree that some type of hopefully ISR driven core scheduling mechanism would be nice, especially since I can't get FrequencyTimer2 to work properly with delay() (see me other thread on this). But, I also have a simple suggestion to perhaps make programming delays without an ISR a little better in some cases than using the delay() function. My suggestion is to add two new function that together would perform similarly to the way delay() works, but they would split the delay job into two jobs so that more accurate scheduling can be achieved. The two calls would look something like this:

unsigned long millisAdd(int ms)

and

delayUntil(unsigned long timems)

This would allow one to schedule more regular delays by taking into account the time it takes to execute code between the delays. For example imagine wanting to do something every 10ms coded like this with delay():

void loop() {

  // execute some variable execution length code that may take anywhere from 1 to 5 ms

  delay(10);
}

This will lead to very inconsistent timing results. However, with these two new functions, one could easily code up the following:

void loop() {
  unsigned long timems = millisAdd(10);

  // execute some variable execution length code that may take anywhere from 1 to 5 ms

  delayUntil(timems);
}

and get much more accurate results. These two functions should be extremely easy to implement since it basically just involves splitting the code to delay() in two. Instead of adding the millisAdd() function, it would probably be easy enough to simply overload the current millis() call to take an optional second argument which would take the current millis() value and add ms to it.

While the utility of this solution over using an ISR may not be quite so obvious for continuously repeating patterns, if one imagines doing something for a limited amount of iterations (or varying consecutive delays) this becomes much more useful. Example of code which is not very ISR timing friendly:

  // Changing delay interval
  unsigned long timems;
  for(int i=0; i <8 ; i++) {
     timems = millisAdd(10 * i);
    // execute some variable execution length code that may take anywhere from 1 to 5 ms
    delayUntil(timems);
  }

 // Or changing tasks and delays.
  timems = millisAdd(5 * i);
   // execute some variable execution length code that may take anywhere from 1 to 3 ms
  delayUntil(timems);
  ...
  timems = millisAdd(20 * i);
   // execute some variable execution length code that may take anywhere from 1 to 5 ms
  delayUntil(timems);
  timems = millisAdd(15 * i);
   // execute some variable execution length code that may take anywhere from 1 to 10 ms
  delayUntil(timems);

I have posted the first cut of the simple scheduler library.
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1217881285

I would be interest in comments regarding is suitability for performing the kinds of tasks that people posting on the topic of scheduling have in mind.

mellis, are you looking to potentially include a solution for this in the Arduino environment? Because, if so, I think that it would heavily influence the design, especially with respect to whether a static or dynamic approach is used. If it is something aimed at the core I would suspect that limiting resources when NOT using the feature would become a priority over limiting resources when using the feature. But, however if this were an addon library, it would be reasonable to make the opposite assumption and to focus on limiting resources when using the library since when not using it, it would simply not be compiled in. These two priorities could lead to drastically different designs. What do you think?

I would think that such an implementation would go through a period of time available as an external library before it would "qualify" to be added to the core. For that matter, I don't see any big reason to add it to the core at all, other than making things simpler for users...
(but it might be better in general to add some sort of improved library handling to the arduino environment, so that the issue of whether things should be added to "the core" comes up less often...)

mellis, are you looking to potentially include a solution for this in the Arduino environment? Because, if so, I think that it would heavily influence the design, especially with respect to whether a static or dynamic approach is used. If it is something aimed at the core I would suspect that limiting resources when NOT using the feature would become a priority over limiting resources when using the feature. But, however if this were an addon library, it would be reasonable to make the opposite assumption and to focus on limiting resources when using the library since when not using it, it would simply not be compiled in. These two priorities could lead to drastically different designs. What do you think?

My personal view is that the design requirements are influenced far more by usability issues than resource usage. However frugal a piece of code, I don't think it would be appropriate for the arduino unless it could be easily understood and used by people with little or no prior experience with programming.

My understanding of the core requirement is to provide arduino users the ability to easily schedule a few different things to happen in different periods of time ranging from milliseconds to days or longer. To me, easy means using expressions consistent with the existing arduino abstractions.

That's pretty vague and leaves lots of decisions unresolved but if there is consensus on this point I think it will not be difficult to address thing like:

  • should the user explicitly instantiate the scheduled objects or should a fixed number be created for him, if the latter how many ?
  • should there be a single callback or can the user explicitly declare his callbacks
  • What is the most intuitive naming for the various objects that provide this functionality
  • Is there a requirement for both periodic scheduling (do something after this amount of time has elapsed ) and date/time scheduling (do something at this specific date and time)
  • (How ) should the co-operative scheduler handle slip (something should happen now but another task is still executing). Should the amount of slip be subtracted from the next scheduled trigger (minimizing slip accumulation) or ignored (guaranteeing that at least the scheduled time will elapse before the next trigger)]

I would be very interested in hearing other views on what the simple scheduler should do and how it should look..

Being able to write/use your own callback functions would be nice, I think. So +1 from me on that idea :slight_smile:

I would suggest a slightly different view of the sample code. The term Alarm would be replaced by Task. Rather than one loop with testing there would be separate loops.

The setup should allow for delta time, absolute time as well as one time and repeating schedules.

I would default the maximum number of tasks at 6 and wait for user feedback.

#include <DateTime.h>
#include <DateTimeStrings.h>
#include <TaskScheduler.h>


void onTask1(){
      Serial.println("15 seconds Alarm");         
  }

void onTask2(){
      Serial.println("90 second Alarm");    
  }

void onTask3(){
      Serial.print("One Shot alarm, elapsed period was ");    
      time_t alarmValue  = dtAlarms.getValue(Sender);
      Serial.println(alarmValue,DEC); 
  }

void onTask4(){
      Serial.print("re-trigged One Shot alarm, elapsed period was ");    
      time_t alarmValue  = dtAlarms.getValue(Sender);
      Serial.println(alarmValue,DEC); 
      dtAlarms.setValue(Sender, alarmValue + 2); //re-enable with a new value two seconds longer
    }
}

void setup(){
   pinMode(13,OUTPUT);
   Serial.begin(19200);

   Task1 = dtAlarms.createRepeating( 15 );  // alarm every 15 seconds
   Task2 = dtAlarms.createRepeating( AlarmHMS(0,1,30) );  // alarm every 1 minute 30 seconds
   Task3 = dtAlarms.createOneshot( 10 );  // one shot alarm in 10 seconds
   Task4 = dtAlarms.createOneshot( 12 );  // one shot alarm that will be manually retriggered

   Serial.println("started");
 }

void loop(){
  dtAlarms.waitUntilThisSecond(0); //  this code blocks waiting for start of the next minute, background alarms are still serviced 
  Serial.println("turning LED on");
  digitalWrite(13, HIGH);
  dtAlarms.delay(2000);  // note we call the alarm delay to service the background alarms 
  Serial.println("turning LED off");
  digitalWrite(13,LOW);  

}

Well, I know that you seemed to think that inclusion in the core was not an important question, but a non user-definable method to allocate space for these Alarms/event seems unreasonable if this library is ever going to be a part of the core. Mem has done a good job of keeping resource usage (RAM) very low for users of his suggested library, but it still uses 60 bytes! That seems unacceptable to me for core inclusion considering that some people may never even want to use these Alarms. Choosing a lower default number of Alarms does not seem like a good core solution either, and neither does making things compile time options. So, if this is ever going to be considered for core inclusion I would suggest a different approach for allocation memory. However since Mem did do a good job of minimizing RAM usage, any core implementation would probably take up more RAM and therefor as a standalone library his solution may be better.

If libraries are never going to be core solutions, compile time options may well be good solutions, especially if the library does something very specialized that could greatly benefit from compile time options to either save RAM or to increase speed. For example: I am working on an LED TDM library and have come to the conclusion that speed is paramount. Although I would like to create a library that could be easily used without recompiling, I have determined that this would potentially mean a significant (3x) speed decrease. Since this might very well push the library utility out of the problem space that it is trying to address in the first place, this does not seem worth it. I will probably leave certain things compile time options for this TDM library.

While I certainly agree that the programing interface is important to keep simple, the target user audience is also somewhat dependent on whether a library is meant for core inclusion or not. Obviously we want to keep interfaces simple, but often times a sacrifice in simplicity is warranted for better performance or flexibility. Sometimes the only way to make good decisions about which of these tradeoffs to make is by deciding who the likely target audience is.

Allowing the user to create his own callbacks is easy and probably not much more complicated for a newbie to use (it would look similar to the way attachInterrupt works). Only uses two more bytes per instance so nothing to worry about there.

Creating instances of alarms (or whatever they will be called) at runtime to minimize RAM usage to the minimum needed has also been implemented, I started out with a version of the library that allows the user to create instances of the timers he needs so memory is only consumed when needed, but it seemed a less friendly to newbies then the simpler version posted earlier.

Here is a fragment from one of my test sketches that creates three instances

AlarmClass Timer1;  
AlarmClass Timer2;
AlarmClass Timer3;

void setup(){
  // you can register time of day Alarms at any time but really shouldn't enable them until the internal clock is set

   if( dtAlarms.registerTimer( &Timer1 ) ) {        
     Timer1.value = DateTime.now() + 11;   // fire at the time of day 11 seconds from now
     Timer1.Mode.isTimeOfDay = true; // the value given above is a time of day
     Timer1.onTickHandler = &OnTimer1Tick;
     Timer1.enable();  
  }
   if( dtAlarms.registerTimer( &Timer2 ) ) {        
     Timer2.value = AlarmHMS(12,30,0)    // this is 30 minutes after 12 noon
     Timer2.onTickHandler = &OnTimer2Tick;
     Timer2.Mode.isTimeOfDay = true;
     Timer2.enable();  
  }
   if( dtAlarms.registerTimer( &Timer3 ) ) {        
     Timer3.Mode.isTimeOfDay = false; // the timer value is treated as a delay in seconds, not absolute time 
     Timer3.value =  13;   // delay in seconds from the time this alarm is enabled
     Timer3.onTickHandler = &OnTimer1Tick;
     Timer3.enable();  
  }

}

void OnTimer1Tick(void *Sender){

  if( Sender ==  &Timer1) 
      Serial.print("Timer1 event: ");            
   else  if( Sender ==  &Timer3) 
      Serial.print("Timer3 event: ");      
  timeDisplay();     
}

void OnTimer2Tick(void *Sender){
    Serial.print("Timer2 event: ");   
}

mem,
can you tell me what to modify to get your dateTimeAlarm library to run with 0012? I am seeing the
same kind of library compile errors others have seen with moving to 0012

can you tell me what to modify to get your dateTimeAlarm library to run with 0012? I am seeing the
same kind of library compile errors others have seen with moving to 0012

In the DateTimeAlarm.h file comment out the following line:

//#include <wiring.h> // this line must be commented out or removed for 0012

mem,
Did you mean TimerAlarms.h ? I tried commenting out the include in TimerAlarms.h but it did not work. Same kind of errors at compile time. Any other ideas? I am using the Mac version.