Pages: 1 [2] 3   Go Down
Author Topic: Arduino timer scheduler library  (Read 9091 times)
0 Members and 1 Guest are viewing this topic.
London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6250
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Regarding re-entrance, as Martin says, the service routine is only entered by foreground code calling another alarm library function  (typically it will be the library's delay or wait function). It is blocked from processing if its called from an alarm handler. There is flag in the service routine that ensures that a new alarm will never be triggered while processing an existing alarm. This minimizes stack usage and reduces the  likelihood that shared global variables in the users foreground sketch will be abused.

The comments are stimulating my thinking around  a very simple API along the lines mellis suggested with the option to create additional and more sophisticated alarm instances for those that need them. This gives basic scheduling that is highly intuitive out of the box but the richer functionality can be added by people that understand how to instantiate a class.  I don't think it adds any complexity to code I have (although it will be more complex than the code posted above)  but I do want to see if it adds much in the way of resource consumption.  
« Last Edit: August 06, 2008, 03:24:21 pm by mem » Logged

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

Here is slightly modified version of the DateTimeAlarm code that compiles in Arduino version 0012:
 
Code:
/*
  DateTimeAlarms.h - Arduino Date and Time alrms library

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  

*/

#ifndef DateTimeAlarms_h
#define DateTimeAlarms_h

#include <inttypes.h>
//#include <wiring.h> // moved into cpp file for Arduino ver 0012

#include "dateTime.h"

typedef enum {
     dtMillisecond, dtSecond, dtMinute, dtHour, dtDay //,clkMonth,clkYear
 } dtUnits_t;

typedef struct  {
      uint8_t isEnabled              :1 ;
      uint8_t isOneShot              :1 ;
      uint8_t isAlarm                :1 ;    
   }
     AlarmMode_t   ;

typedef uint8_t AlarmID_t;
#define dtINVALID_ALARM_ID 255
#define dtNBR_ALARMS 6

class AlarmClass;  // forward reference
typedef void (*OnTick_t)(uint8_t);  // alarm callback function typedef

// class defining an alarm instance, only used by dtAlarmsClass
class AlarmClass
{      
private:
public:
      AlarmClass();
      OnTick_t onTickHandler;      
      void updateNextTrigger();

      time_t value;
      time_t nextTrigger;
      AlarmID_t ID;           // unique instance id (only used for debugging
      AlarmMode_t Mode;
};


// class containing the collection of alarms
class dtAlarmsClass
{
   friend class TimerClass;
private:
   AlarmClass Alarm[dtNBR_ALARMS];
   void serviceAlarms();
   boolean isServicing;
   AlarmID_t nextID;
   AlarmID_t create( time_t value, OnTick_t onTickHandler,boolean isAlarm, boolean isEnabled );
public:
      dtAlarmsClass();
      void delay(unsigned long ms);
      uint8_t getDigitsNow( dtUnits_t Units);  // returns the current digit value for the given time unit
      void waitForDigits( uint8_t Digits, dtUnits_t Units);
      void waitForRollover(dtUnits_t Units);
      
      // functions for specific alarms identifed by alarm ID
      AlarmID_t createAlarm( time_t value, OnTick_t onTickHandler, boolean isEnabled = true );
    AlarmID_t createTimer( time_t value, OnTick_t onTickHandler, boolean isEnabled = true );
      void setValue(AlarmID_t ID, time_t value);
      void enable(AlarmID_t ID);
      void disable(AlarmID_t ID);

};

extern dtAlarmsClass dtAlarms;  // make an instance for the user

/*==============================================================================
 * MACROS
 *============================================================================*/

/* public */
#define waitUntilThisSecond(_val_) waitForDigits( _val_, dtSecond)
#define waitUntilThisMinute(_val_) waitForDigits( _val_, dtMinute)
#define waitUntilThisHour(_val_)   waitForDigits( _val_, dtHour)
#define waitUntilThisDay(_val_)    waitForDigits( _val_, dtDay)
#define waitMinuteRollover() waitForRollover(dtSecond)
#define waitHourRollover()   waitForRollover(dtMinute)
#define waitDayRollover()    waitForRollover(dtHour)

#define AlarmHMS(_hr_, _min_, _sec_) (_hr_ * SECS_PER_HOUR + _min_ * SECS_PER_MIN + _sec_)

#endif /* Clock_h */
« Last Edit: October 02, 2008, 04:32:24 am by mem » Logged

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

here is the DateTimeAlarms.cpp file moded for 0012
Code:

/*
 DateTimeAlarms.cpp - cooperative scheduler for arduino

 */

extern "C" {
#include <string.h> // for memset
}

#include <wiring.h> // moved from header to here for Arduino 0012
#include "DateTimeAlarms.h"
#include "DateTime.h"

//**************************************************************
//* Alarm Class Constructor

AlarmClass::AlarmClass()
{
  Mode.isAlarm =  Mode.isEnabled = Mode.isOneShot = 0;
  value = nextTrigger = 0;
  onTickHandler = NULL;  // prevent a callback until this pointer is explicitly set
}

//**************************************************************
//* Private Methods

void AlarmClass::updateNextTrigger()
{
  if( (value != 0) && (Mode.isEnabled != 0) )
  {
    time_t time = DateTime.now();
    if(Mode.isAlarm && nextTrigger <= time )   // update alarm if next trigger is not yet in the future
    {
      if( value >= time ) { // is the value a specific data and time in the future
        nextTrigger = value;  // yes, trigger on this value      
      }
      else if ( value <= SECS_PER_DAY) {
        if( value + previousMidnight(DateTime.now()) < time)
          nextTrigger = value + nextMidnight(time); // if time has passed then set for tomorrow
        else
          nextTrigger = value + previousMidnight(time);  // set the date to today and add the time given in value      
      }
      else if ( value <= SECS_PER_WEEK) {
        nextTrigger = value + previousMidnight(time); // set the date to today and add the time given in value
      }
      else {
        Mode.isEnabled = 0; // values more than a year but less than today have expired so the alarm is disabled
      }
    }
    if(Mode.isAlarm == false){
      // its a timer
      nextTrigger = time + value;  // add the value to previous time (this ensures delay always at least Value seconds)
    }
  }
  else {
    Mode.isEnabled = 0;  // Disable if the value is 0
  }
}

//**************************************************************
//* Date Time Alarms Public Methods

dtAlarmsClass::dtAlarmsClass()
{
  nextID = 0;
  isServicing = false;
}

AlarmID_t dtAlarmsClass::createAlarm(time_t value, OnTick_t onTickHandler, boolean isEnabled ){  // returns true if has been registerd ok
  return create( value, onTickHandler, true, isEnabled );
}

AlarmID_t dtAlarmsClass::createTimer( time_t value, OnTick_t onTickHandler, boolean isEnabled ){  // returns true if has been registerd ok
  return create( value, onTickHandler, false, isEnabled );
}

void dtAlarmsClass::enable(AlarmID_t ID)
{
  if(ID < dtNBR_ALARMS){
    Alarm[ID].Mode.isEnabled = (Alarm[ID].value != 0) && (Alarm[ID].onTickHandler != 0) ;  // only enable if value is non zero and a tick handler has been set
    Alarm[ID].updateNextTrigger(); // trigger is updated whenever  this is called, even if already enabled      
  }
}

void dtAlarmsClass::disable(AlarmID_t ID)
{
  if(ID < dtNBR_ALARMS)
    Alarm[ID].Mode.isEnabled = false;
}

void dtAlarmsClass::setValue(AlarmID_t ID, time_t value){
  if(ID < dtNBR_ALARMS){
    Alarm[ID].value = value;
    enable(ID);
  }
}

// following functions are not Alarm ID specific.
void dtAlarmsClass::delay(unsigned long ms)
{
  unsigned long endtime = millis() + ms;
  boolean Overflow = endtime < millis();
  if( Overflow){
    while( millis() > endtime)
      serviceAlarms();
  }
  while( millis() < endtime)
    serviceAlarms();
}

void dtAlarmsClass::waitForDigits( uint8_t Digits, dtUnits_t Units)
{
  while(Digits != getDigitsNow(Units) )
  {
    serviceAlarms();
  }
}

void dtAlarmsClass::waitForRollover( dtUnits_t Units)
{
  while(getDigitsNow(Units) == 0  ) // if its just rolled over than wait for another rollover                                  
    serviceAlarms();
  waitForDigits(0, Units);
}

uint8_t dtAlarmsClass::getDigitsNow( dtUnits_t Units)
{
  time_t time = DateTime.now();
  if(Units == dtSecond) return numberOfSeconds(time);
  if(Units == dtMinute) return numberOfMinutes(time);
  if(Units == dtHour) return numberOfHours(time);
  if(Units == dtDay) return dayOfWeek(time);
  return 255;  // This should never happen
}

//***********************************************************
//* Private Methods

void dtAlarmsClass::serviceAlarms()
{
  if(! isServicing)
  {
    isServicing = true;
    for(uint8_t i = 0; i < dtNBR_ALARMS; i++)
    {
      if( Alarm[i].Mode.isEnabled && (DateTime.now() >= Alarm[i].nextTrigger)  )
      {
        OnTick_t TickHandler = Alarm[i].onTickHandler;
        Alarm[i].updateNextTrigger();
        if( TickHandler != NULL) {                        
          (*TickHandler)(i);     // call the handler
        }
      }
    }
    isServicing = false;
  }
}

AlarmID_t dtAlarmsClass::create( time_t value, OnTick_t onTickHandler,boolean isAlarm, boolean isEnabled ){  // returns true if has been registerd ok
  AlarmID_t id = dtINVALID_ALARM_ID;
  if( nextID < dtNBR_ALARMS){
      id = nextID;
    Alarm[id].onTickHandler = onTickHandler;
    Alarm[id].Mode.isAlarm = isAlarm;
      Alarm[id].value = value;
      isEnabled ?  enable(id) : disable(id);  
    nextID++;  
  }
  return id;
}

// make one instance for the user to use
dtAlarmsClass dtAlarms = dtAlarmsClass() ;
Logged

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

Hi,

I'm trying to use DateTimeAlarms in Arduino 0012.
I need to have a periodic alarm with period PERIOD, but that feature seems to have disappeared... I managed to do it by doing, at the end of the callback function,

Code:
 dtAlarms.setValue(alarmID, DateTime.now() + PERIOD);

Is there any other option?

Moreover, I have a question: is dtAlarms.delay() blocking? If I understand well, I need to include it in the loop() so that alarms get serviced. Because I need sparse alarms (daily or so), this an important thing...

Best regards



Logged

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

If you use the createTimer method instead if createAlarm, the argument to setValue will be the number of seconds from the time now. I think that is what you want.

Timer1 = dtAlarms.createTimer( PERIOD, &OnTimer); // call OnTimer function every PERIOD seconds

//the following is optional if the period needs to be changed on the fly:
dtAlarms.setValue(Timer1, NEW_PERIOD); // change the period for Timer1


dtAlarms.delay() is blocking to the foreground sketch code, it works like delay() – with the difference that while waiting in dtAlarms.delay(), the scheduler is checking and servicing the alarms and timers, so the alarm code will be run even though the foreground sketch is blocked.
« Last Edit: December 23, 2008, 08:22:22 pm by mem » Logged

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

Guys,

I now I am coming in late on this one, but I am trying to compile the TimerAlarms stuff under 016 and am getting the following errors:

In file included from C:\Arduino\arduino-0016\hardware\cores\arduino/WProgram.h:4,


c:/arduino/arduino-0016/hardware/tools/avr/lib/gcc/../../avr/include/stdlib.h:111: error: expected unqualified-id before 'int'


c:/arduino/arduino-0016/hardware/tools/avr/lib/gcc/../../avr/include/stdlib.h:111: error: expected `)' before 'int'


c:/arduino/arduino-0016/hardware/tools/avr/lib/gcc/../../avr/include/stdlib.h:111: error: expected `)' before 'int'


In file included from C:\Arduino\arduino-0016\hardware\cores\arduino/WProgram.h:6,

I am trying to track this down, but wanted to drop a quick note.

Thanks,
Logged

0
Offline Offline
Sr. Member
****
Karma: 0
Posts: 388
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

@techyjt, Check for any code at the beginning of your sketch, like lines you think are comments but aren't really comments.

The problem sounds like a problem with a line just in front of the #INCLUDE for the WProgram.h header file, which the IDE generates.
Logged

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

This is what is in my sketch:

// DateTime.pde
// example sketch for the DateTime library

#include <DateTime.h>
#include <DateTimeStrings.h>
#include "EEMWRITE.h"
#include <EEPROM.h>
#include <TimerAlarms.h>

#define TIME_MSG_LEN  11   // time sync to PC is HEADER followed by unix time_t as ten ascii digits
//#define TIME_HEADER  255   // Header tag for serial time sync message
#define TIME_HEADER  0x03   // Header tag for serial time sync message
//#define TIME_HEADER  0x00// Header tag for serial time sync message
#define SAVE_TIME 5  //time in minutes to save clock data
#define EEPROM_OFFSET 125  //offset to save data to in EEPROM

struct config_t
{
    long timeVal;
    int contrastVal;
} configuration;

int firstTimeFlag = 0;

void setup(){
...

If I comment out #include <TimerAlarms.h> it compiles fine.  If I uncomment I get the errors.  I will keep checking.
Logged

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

use the code in  posts #16 and 17. Note that this library is now called DateTimeAlarms so save the files using that base name and delete the old TimerAlarms directory.
Logged

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

Thanks, that did the trick.  Sorry about that.
Logged

Argentina
Offline Offline
Jr. Member
**
Karma: 0
Posts: 54
EPIC n00b
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

the example on first page is not workign anymore with the updated .12 code ?
Logged

--

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

The libray has been superseded by a newer version that is available as part of the download here: http://www.arduino.cc/playground/Code/Time

Logged

Argentina
Offline Offline
Jr. Member
**
Karma: 0
Posts: 54
EPIC n00b
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

thanks mem, i saw the link but i cant find a way to set alarms like with the code here, am i missing something?
Logged

--

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

The new library has a simpler method for setting the alarms. Did you have a look at the example sketch supplied with the new library and the documentation to see how this is done.
Logged

Argentina
Offline Offline
Jr. Member
**
Karma: 0
Posts: 54
EPIC n00b
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

oh, i saw the page, not the file... thanks!!! smiley-grin
very easy, congratulations smiley
Logged

--

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