Pages: [1] 2 3   Go Down
Author Topic: mini RTOS [sic]  (Read 4149 times)
0 Members and 1 Guest are viewing this topic.
SF Bay Area (USA)
Offline Offline
Tesla Member
***
Karma: 132
Posts: 6747
Strongly opinionated, but not official!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
Code:
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.

Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 27
Absolute Newbie
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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....)
Logged

London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6255
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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: http://www.arduino.cc/playground/Code/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

Code:
#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.
« Last Edit: August 04, 2008, 09:10:06 am by mem » Logged

Forum Administrator
Cambridge, MA
Offline Offline
Faraday Member
*****
Karma: 12
Posts: 3538
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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

London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6255
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
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)
Logged

Grenoble/Lyon - France
Offline Offline
Sr. Member
****
Karma: 0
Posts: 363
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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.freertos.org
http://www.kieltech.de/uweswiki/FreeRTOS
« Last Edit: August 04, 2008, 05:39:19 pm by melka » Logged


London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6255
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

0
Offline Offline
Newbie
*
Karma: 0
Posts: 47
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Boulder, CO
Offline Offline
Newbie
*
Karma: 0
Posts: 43
I want to be a really useful engine!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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():

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

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

Code:
 // 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);
« Last Edit: August 04, 2008, 03:31:03 pm by MartinFick » Logged

London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6255
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.

Logged

Boulder, CO
Offline Offline
Newbie
*
Karma: 0
Posts: 43
I want to be a really useful engine!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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?

Logged

SF Bay Area (USA)
Offline Offline
Tesla Member
***
Karma: 132
Posts: 6747
Strongly opinionated, but not official!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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...)
Logged

London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6255
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
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..
Logged

0
Offline Offline
Jr. Member
**
Karma: 0
Posts: 59
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

0
Offline Offline
Newbie
*
Karma: 0
Posts: 47
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.

Code:
#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);  

}
Logged

Pages: [1] 2 3   Go Up
Jump to: