Please shoot holes in my thinking and code (warning, interrupts)

OK, I do not have a RTC so decided to try to simulate one. The below is the very basics for me to start with.

I decided to use an interrupt so the RTC code can stay transparent (no ‘user’ code required to regularly update the seconds variable). The alternative could have been a construction with millis() in the ::get() but I foresee problems when this method is only called every 50plus days :wink:

I use a timer0 compare interrupt (based on code from here) to increment a variable that represents seconds since 1/1/1970.

This is basically my first C++ class (on any platform); I’ve always managed to stay away from it (coming from C). I understand just enough to be dangerous with it :smiley:

h file

#ifndef FAKERTC_H
#define FAKERTC_H
#endif

typedef unsigned long time_t;

class FakeRtc
{
  private:
    // seconds1970 used to be here but I could not get it working with the ISR

  public:
    // standard constructor
    FakeRtc();
    // constructor with time (seconds since 1/1/1970)
    FakeRtc(time_t seconds);
    // destructor
    ~FakeRtc();

    // set time (seconds since 1/1/1970)
    void set(time_t seconds);
    // get time (seconds since 1/1/1970)
    time_t get(void);
    
};  // NOTE: the semicolon is important

cpp file

/*
  see https://learn.adafruit.com/multi-tasking-the-arduino-part-2/timers
  for the interrupt stuff
*/

#include "fake_rtc.h"

// include arduino specific stuff
#include <arduino.h>

// fine tuning if timer is a bit inaccurate
#define MILLISECONDSPERSECOND 976

// our time since 1/1/1970
volatile time_t seconds1970;

/* constructors and destructors */
FakeRtc::FakeRtc()
{
  // time
  seconds1970 = millis() / 1000;
  // configure interrupt
  OCR0A = 0xAF;
  TIMSK0 |= _BV(OCIE0A);
}

FakeRtc::FakeRtc(time_t seconds)
{
  // time
  seconds1970 = seconds;
  // configure interrupt
  OCR0A = 0xAF;
  TIMSK0 |= _BV(OCIE0A);
}

FakeRtc::~FakeRtc()
{
}

/* methods */

/*
  set the current time (seconds since 1/1/1970)
*/
void FakeRtc::set(time_t seconds)
{
  seconds1970 = seconds;
}

/*
   get the current time (seconds sicne 1/1/1970)
*/
time_t FakeRtc::get()
{
  return seconds1970;
}

/* interrupt */

/*
   interrupt service routine
*/
SIGNAL(TIMER0_COMPA_vect)
{
  // counter to count 'interrupts'
  static int counter = 0;

  // increment and check if we reached one second
  if (++counter == MILLISECONDSPERSECOND)
  {
    // increment seconds
    seconds1970++;
    // reset counter
    counter = 0;
  }
}

What I don’t like is the ‘global’ variable seconds1970; I would have loved to have it private, but could not figure out a way. Is it possible to have this protected in some way and still accessible from the ISR?

Thanks for reading and comments.

The Time library already does all of this. There is no need to rewrite it. Or is this just an exercise for learning purposes?

sterretje:
The alternative could have been a construction with millis() in the ::get() but I foresee problems when this method is only called every 50plus days :wink:

I'm not going to review complex or "advanced" C++ code - I hope I know some of my limitations.

But the piece I have quoted caught my eye. You could use millis() to update a seconds variable that would not repeat for 1000 times 50 days.

...R

It's very much an exercise. It's also supposed to (eventually) be a drop in for a RTC library for times that I don't have one available; forgot to mention that I ordered my first one this morning :wink:

I will have a look at the time library; I doubt that it works like a RTC (it's part of the overall project so I can use stuff like mktime). I'm aware that Paul Stoffregen's DS1307 library heavily relies on the / his time library.

It should be possible to make the ISR and the volatile variable static members.
That way you could make the variable private.

Robin2:
But the piece I have quoted caught my eye. You could use millis() to update a seconds variable that would not repeat for 1000 times 50 days.

Note that I want it to be transparent (so the same as an RTC); one does not call a RTC method to regularly update the seconds-since-1970 so I also don't want to do that :wink:

sterretje:
It's very much an exercise. It's also supposed to (eventually) be a drop in for a RTC library for times that I don't have one available;

It can never fully replace an RTC anymore than Time does. The reason for an RTC is that Arduino is terribly inaccurate at timekeeping.

Even when you do use an RTC you normally set it as a sync provider for Time library and still use Time library functions to handle time.

2016-04-29_12-02-31.png

.

You could use a mechanism like this:

class classIrq {
  public:
    classIrq(byte irqPin) {
      anchor = this;
      sensePin = irqPin;
    }
    void begin() {
      char irqNum = digitalPinToInterrupt(sensePin);
      if (irqNum != NOT_AN_INTERRUPT) {
        EIFR = _BV(irqNum);         // cancel evt. pending interrupt
        attachInterrupt(irqNum, classIrq::marshall, CHANGE);
      }
    }
  private:
    void isr() {
      isrFlag = true;
    }
    static void marshall() {
      anchor->isr();
    }
    static classIrq* anchor;
    volatile byte isrFlag;
    byte sensePin;
};
classIrq* classIrq::anchor = NULL;

I suspect this may be of some interest...
https://www.google.com/search?q=Arduino+swRTC

RTClib.h which now comes as an included library with the IDE has a Class called RTC_Millis() which uses all the library RTC functions but using the internal millis() clock. The time has to be initialized before use and fails with rollover.

Delta_G:
It can never fully replace an RTC anymore than Time does. The reason for an RTC is that Arduino is terribly inaccurate at timekeeping.

Even when you do use an RTC you normally set it as a sync provider for Time library and still use Time library functions to handle time.

For the OP's stated purpose, it is a replacement. As a learning tool, it is great because you can be quickly using mature, intelligent time functions with experimental code without adding any additional hardware. In fact, a bare RTC is a step down from the Time library because it doesn't handle epoch times. I think the OP is aware of the hardware limitations.

It is true that you normally assign a sync provider, but millis() time is the default. Is the OP's project a duplication of the library functionality? Well, in a way, yes.

To introduce another concept, you can use the Time library as a support package for whatever RTC you are using, without allowing it to actually keep time. I do this, because my app depends on a sync provider being able to push a time sync, rather than waiting for the library to ask for one. In this situation, the defined constants, macros and functions from the library have proved to be an invaluable tool for the job.

I'm a huge fan of the library, if you haven't guessed. It really needs more attention, but I guess it should first get more appreciation. What it needs is a good tutorial, but I don't have the time to take on something like that right now. It is an interesting idea to make it OO. If I were doing that, I would begin by just writing an OO wrapper for it.

Thanks everybody.

@Delta_G, I'm aware that it will never replace a real RTC (unless I'm missing something, except for one with a crappy battery :slight_smile: )

@LarryD, I'm glad you did not shoot holes in my thinking :wink:

@Whandall, I will have a look at the 'static'; and the code snippet might get me there to make the seconds1970 invisible; just had not figured out how to make the ISR part of the class.

@aarg, as stated the time library will be used as part of the overall project (read: application code that uses the fake_rtc). For my own purposes, I think there will be no need; one should separate the functionalities in my opinion. I will happily use a time library to convert a date/time to unix timestamp and pass that to the ::set() method instead of adding a function that takes the individual components or the complete string, converts to unix timestamp and writes that. If I however want to simulate full functionality of an existing library, it might be required.

In general, I will look at all provided information (I will have to try to understand 'sync provider) and might borrow ideas.

Thanks again, all.