RTC: in Search of Lost Time ( w/ apologies to Proust)

My hardware:

  • Dué
  • RTC DS1307 shield with coin-cell backup (with new battery!)
  • RTC 8523T shield, with coin-cell backup (also, with new battery!)

Problem:

After uploading an appropriate RTC sketch to set the RTC clock in motion (the correct time being demonstrated by the serial output...everything is OK there!), if I unplug the USB cable for (say...) 10 minutes and then plug the USB cable back in.....the time being reported by the serial-output is (in this case....) 10 minutes slow.

Question:

  1. What am I missing here? Shouldn't the RTC battery-backup keep the clock running such that, when the Arduino is re-powered from the USB and continues the loop section...
void loop () {
    DateTime now = rtc.now();

    Serial.print(now.year(), DEC);
    Serial.print('/');
    Serial.print(now.month(), DEC);
    Serial.print('/');
    Serial.print(now.day(), DEC);
    Serial.print(" (");
    Serial.print(daysOfTheWeek[now.dayOfTheWeek()]);
    Serial.print(") ");
    Serial.print(now.hour(), DEC);
    Serial.print(':');
    Serial.print(now.minute(), DEC);
    Serial.print(':');
    Serial.print(now.second(), DEC);
    Serial.println();

it receives the true RTC time (i.e., without the 10-minute gap) ?

  1. Could it be that the 3.3v coin-cell battery is actually keeping the Dué microcontroller chip awake during what should be a power-off condition, such that the Dué just "picks up where it left off" when the USB cable is reconnected to my PC?
  2. If I leave the USB connected to the Dué, but disconnect the I2C comm wires for 10 minutes, the Dué jumps from 2025 back to 2008 (and merrily continues to report time via Serial Print); when I then reconnect the I2C comm wires the time reported via Serial Print jumps back to 2025, but with a 10-minute gap !

[Truly, this is far less like Proust than it is Philomena Cunk's Moments of Wonder, Episode 1, "Time"]

The time you could have spent posting a complete sketch would maybe not have been lost.

1 Like

Touché, ZX80.
It is the lowest level of Example sketch from the RTClib library, DS3231 (or the equivalent for the 8523T). Here are the 'inconvenienced electrons', in all their glory:

// Date and time functions using a DS3231 RTC connected via I2C and Wire lib
#include "RTClib.h"

RTC_DS3231 rtc;

char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

void setup () {
  Serial.begin(57600);

#ifndef ESP8266
  while (!Serial); // wait for serial port to connect. Needed for native USB
#endif

  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    Serial.flush();
    while (1) delay(10);
  }

  if (rtc.lostPower()) {
    Serial.println("RTC lost power, let's set the time!");
    // When time needs to be set on a new device, or after a power loss, the
    // following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
  }

  // When time needs to be re-set on a previously configured device, the
  // following line sets the RTC to the date & time this sketch was compiled
   rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  // This line sets the RTC with an explicit date & time, for example to set
  // January 21, 2014 at 3am you would call:
  // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
}

void loop () {
    DateTime now = rtc.now();

    Serial.print(now.year(), DEC);
    Serial.print('/');
    Serial.print(now.month(), DEC);
    Serial.print('/');
    Serial.print(now.day(), DEC);
    Serial.print(" (");
    Serial.print(daysOfTheWeek[now.dayOfTheWeek()]);
    Serial.print(") ");
    Serial.print(now.hour(), DEC);
    Serial.print(':');
    Serial.print(now.minute(), DEC);
    Serial.print(':');
    Serial.print(now.second(), DEC);
    Serial.println();

    Serial.print(" since midnight 1/1/1970 = ");
    Serial.print(now.unixtime());
    Serial.print("s = ");
    Serial.print(now.unixtime() / 86400L);
    Serial.println("d");

    // calculate a date which is 7 days, 12 hours, 30 minutes, 6 seconds into the future
    DateTime future (now + TimeSpan(7,12,30,6));

    Serial.print(" now + 7d + 12h + 30m + 6s: ");
    Serial.print(future.year(), DEC);
    Serial.print('/');
    Serial.print(future.month(), DEC);
    Serial.print('/');
    Serial.print(future.day(), DEC);
    Serial.print(' ');
    Serial.print(future.hour(), DEC);
    Serial.print(':');
    Serial.print(future.minute(), DEC);
    Serial.print(':');
    Serial.print(future.second(), DEC);
    Serial.println();

    Serial.print("Temperature: ");
    Serial.print(rtc.getTemperature());
    Serial.println(" C");

    Serial.println();
    delay(3000);
}

You should send (home-made) madeleines to whomever solves the problem :wink:

Is this happening with both RTC modules or just one? Does the time ever change?

[edit]
After seeing the whole code.

  1. Set the time
  2. Comment out the code that sets the time so it doesn't run again.
  3. Reprogram the arduino with the updated code
  4. Power off for 10 minutes
  5. Power back on
  6. What time is reported now?

Also double check that the ESP8266 is voltage compatible with the RTC. I think 8266 is 3.3V. Make sure your RTCs don't expect 5V.

I agree: madeleines would be an excellent reward. Hard to extrude them through the Internet connection, though...

Re: your suggested test....

  • The Arduino being used is a Dué, not an ESP8266 (I assume that the latter is mentioned in that "if" statement, simply to make the Example widely compatible). So YES, the Arduino being used is a 3.3vdc system. But, the Shield has circuitry to make it automatically adjust to either 5v or 3.3v, and of course the coin-cell battery is just 3.3v
  • I had performed the same test as you suggested last night (prior to writing this Post). But, I re-ran the test just as you had suggested (to confirm the results) and I still get Lost Time

[Perhaps I am in need of the Time Shield for Arduino, by the Wells Electronics Co., UK, ... to go back and collect my Lost Time.]

Until moments ago, I was blissfully unaware that the Dué has an RTC built into the ATMEL SAM microcontroller core ! See, for example:

  1. Dué Built-in RTC -- New Library
  2. Dué RTC: "Anyone Know about the clock()?"

I downloaded the Library (first URL, above), and ran the Example, RTCDue_Unixtime_Compilertime.ino, which compiles smoothly, uploads, and seems to work "as advertised".

Here is the code for that Example sketch, in full:

/*
  Simple Unixtime for Arduino Due

  Demonstrates the use of the RTC library for the Arduino Due

  This example code is in the public domain

  created by Markus Lange
  10 Feb 2016
*/

#include "RTCDue.h"

/* Create an rtc object and select Slowclock source */
//RTCDue rtc(RC);
RTCDue rtc(XTAL);

const char* daynames[]={"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};

void setup() {
  Serial.begin(9600);
  rtc.begin(); // initialize RTC

  // Set the time
  rtc.setTime(__TIME__);

  // Set the date
  rtc.setDate(__DATE__);

  // you can use also
  //rtc.setClock(__TIME__, __DATE__);
}

void loop() {
  // Print unixtime...
  Serial.print("Unixtime: ");
  Serial.println(rtc.unixtime());

  // Print time...
  Serial.println("And in plain for everyone");
  Serial.print("Time: ");
  digitprint(rtc.getHours(), 2);
  Serial.print(":");
  digitprint(rtc.getMinutes(), 2);
  Serial.print(":");
  digitprint(rtc.getSeconds(), 2);
  Serial.println("");

  // Print date...
  Serial.print("Date: ");
  Serial.print(daynames[rtc.getDayofWeek()]);
  Serial.print(" ");
  digitprint(rtc.getDay(), 2);
  Serial.print(".");
  digitprint(rtc.getMonth(), 2);
  Serial.print(".");
  Serial.println(rtc.getYear());
  Serial.println("");
  delay (5000);
}

void digitprint(int value, int lenght){
  for (int i = 0; i < (lenght - numdigits(value)); i++){
    Serial.print("0");
  }
  Serial.print(value);
}

int numdigits(int i){
  int digits;
  if (i < 10)
    digits = 1;
  else
    digits = (int)(log10((double)i)) + 1;
  return digits;
}

Speculation:

  1. I am out-of-my-depth on this, so I can only speculate....Is it possible that the Dué internal RTC (within the microcontroller architecture) is competing with the external RTC (i.e., RTC on the Shield)?
  2. Suggestions for troubleshooting this possibility?

I can't imagine a scenario where the internal RTC would conflict with the external unless someone had written a library that was aware of both. That possibility's remote enough to exclude it. What you're seeing is just... weird. It's as if the module is saving the current time but not updating while the power is off. Memory's being retained, but its clock isn't running on battery.

If it's really a DS3231 as indicated in the code, and not a DS1307 as you initially reported, there's a control register bit that could account for the frozen time when on battery. The datasheet says:

Control Register (0Eh)
Bit 7: Enable Oscillator (EOSC). When set to logic 0,
the oscillator is started. When set to logic 1, the oscillator
is stopped when the DS3231 switches to VBAT. This bit
is clear (logic 0) when power is first applied. When the
DS3231 is powered by VCC, the oscillator is always on
regardless of the status of the EOSC bit. When EOSC is
disabled, all register data is static.

I don't know about the other RTC.

1 Like

Does the DS1307 have a similar control bit in a different location? If so, using the DS3231 lib with a DS1307 could explain the behavior.

mea culpe, I made a typographical error. The chip is (as noted in Post #1), a DS1307. I do not think that I ran the sketch, DS3231.ino by mistake, but ⸺ out of curiosity ⸺ I ran both versions of the Example sketches, and they both compiled and worked [exhibiting the same sort of behaviour that I reported initially]

Thanks to cedarlakeinstruments for the comment that the internal clock (i.e., of the Dué SAM microcontroller) was almost certainly not conflicting with the RTC mounted on the external shield.

Now...

It seems that the peculiar Lost Time behaviour I noted is rooted in my confused notion about how uploaded Arduino code behaves, generally, when power to the Arduino microcontroller is interrupted. Specifically, I mean that:

  • I mistakenly believed that, whenever Arduino power is interrupted and for whatever reason, the code would only execute the previously active loop routine. But it does not do that: it resumes by executing the entire sketch "from the top, down" [d'Oh. You learn something new every day, even when you are not trying to...].

The appearance of "lost time" resulted because:

  1. Not only did I not appreciate the fact that the Arduino would re-execute the entire sketch from-the-top-down; but, because of my ignorance,
  2. I also blissfully ignored the lines of code written above loop, with the result that I did not comprehend how the if statement (see lines of code immediately below...) would behave, were that section of the code to be re-executed ; and so I now know that,
  3. To avoid the appearance of "Lost Time" it was necessary to comment-out some lines of code to constrain the behaviour of the Arduino microcontroller (not the RTC itself) after the microcontroller was disconnected from power, even if the RTC remained under power via the coin-cell battery. Once the sketch has been modified, that code must of course be uploaded to the microcontroller to replace the code that set the RTC in the first place!
if (rtc.lostPower()) {
    Serial.println("RTC lost power, let's set the time!");
    // When time needs to be set on a new device, or after a power loss, the
    // following line sets the RTC to the date & time this sketch was compiled
    //rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
  }

  // When time needs to be re-set on a previously configured device, the
  // following line sets the RTC to the date & time this sketch was compiled
  //rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  // This line sets the RTC with an explicit date & time, for example to set
  // January 21, 2014 at 3am you would call:
  // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
}

In short, as I now understand the system:

  1. First, you must know whether your RTC is actually keeping time, right now: If it is an RTC-on-a-shield, and iff a time-stamp was previously pushed to the RTC chip, and iff it has a battery installed as backup and iff the battery is still good, you have "a previously configured device" as referred to in Line 31 of the sketch DS1307.ino
  2. If the RTC is not "previously configured" as defined above, the RTC has defaulted to it's factory time-stamp. In the case of the chips I have, the date & time is 2000/01/01 00:00:00
  3. If you have not sent a command of the form, rtc.adjust(DateTime......))), the clock will not start running. In my case, the RTC constantly reports the static time-stamp ''2000/01/01 00:00:00'' for each and every instance that the loop is executed;
  4. iff the line of the code uploaded to and stored within the microcontroller rtc.adjust(dateTime(F(__DATE__), F(__TIME__))) is active, and iff you turn-off the power to the microcontroller and then restore that power....the sketch re-executes from-the-top-down such that the RTC is re-set to the time "stored" in the sketch (i.e., the static, numerical value of the time that the sketch was compiled; which was subsequently uploaded & stored in the microcontroller-chip's memory)

Summary

  1. Once you have configured a lifeless RTC; or
  2. Once you have re-set the time on an RTC that was "previously configured" and up-and-running; and,
  3. iff your RTC has a backup coin-cell battery (i.e., making it independent of the Arduino microcontroller's power state); then
  4. You must comment-out all the rtc.adjust(dateTime(.........) lines of code, followed by the recompile-and-upload the Example sketch, DS1307.ino (or its equivalent).
  5. Only under these conditions will the RTC keep time independently ⸺ regardless of how many times the Arduino microcontroller is switched on-and-off, and regardless of how long the microcontroller remains powered-down

{This has been a lengthy reply, to be sure, but it was written in the hopes that it will wrap-up the discussion and perhaps offer some insight. Comments are very welcome, of course}

I'm confused. I thought that you said you did this:

and you saw the same behavior. Or did you misunderstand what I was saying?

The compiler isn't that smart and it's not its job. All it's supposed to care about is the code / sketch.

You misunderstood.
In Post #10, I meant that

  1. I tried both the DS1307.ino and the DS3231.ino sketches with my Shield that, in fact, has the DS1307 chip. Both of these software sketches worked with the DS1307 chip (a bit of a surprise). And that,
  2. when I executed the tests of each of those sketches, I did them in precisely the same manner as I had done when I experienced the Lost Time problem

In other words: the point of running both the DS1307.ino and the DS3231.ino sketches was simply to discover whether I had goofed from the outset by running DS3231 software on DS1307 hardware ⸺ and I had not. Even if I had chosen the wrong sketch for my hardware, that alone would not have caused the Lost Time experience.

The commenting-out of the rtc.adjust(...) lines of code in the if {.....................} sections, and of course the subsequent re-upload of the trimmed sketches, was something I did after the "as-is" testing of DS1307.ino / DS3231.ino , as I just described above.

I was merely stating the fact that both of these compiled-and-uploaded, and both achieved some initial success with the DS1307 chip (i.e., the RTC did have its time set, and the sketch started to merrily report the time to the Serial Monitor at the required intervals). This was a surprise because I did not expect to have positive results with the DS321.ino code being delivered to the DS1307 chip!
Does my further explanation make sense? It may also help to read Post #13 (made in response to cedarlakeinstruments, Post #11)

1 Like

I'd say that the DS3231 is based partly upon or at least related to DS1307, and usually there's backwards compability. There's one library btw, RTC, that covers both chips and others.

But yes, you make sense. :+1: I've encountered similar things in my short EE journey.

Well, of course you did, but it just didn't matter in this particular case. It might matter if you tried to do other things with the 1307.

Hmm. Not really sure what ShermanP meant by "of course you did". If "did" refers to making a mistake, I am almost certain that I did not make an error by running DS3231.ino from the outset, thereby creating the Lost Time error reported in Post #1.

I confirmed (by testing both the DS1307.ino and DS3231.ino sketches, as reported in Post #10) that even if I HAD run DS3231.ino by mistake, that flub alone would not have been the source of the Lost Time.

In fact, both DS1307.ino and DS3231.ino behave the same ⸺ my ignorance (see Post #10) was the overarching problem.

The "error" was a typographical one: while writing Post #3, glanced at my Library file tree to remind myself of the numbers in DSnnnn.ino; I hastily focussed on the wrong number and opened the wrong file (in my text editor, Notepadqq--not even in the IDE!), from which to copy-and-paste the lines of code that ZX80 had requested.

And, yes: I can appreciate that if I had tried to perform some other RTC functions by incorrectly defining the RTC (i.e., by using the line, RTC_DS3231 rtc;) the results might've been unpredictable and possibly quite wrong. But I have not gotten anywhere near having any of those more-sophisticated problems!

Additional ignorance on my part (due to an assumption):

It was my assumption that the act of installing the coin-cell provided a hardware lock on the RTC, as well as simply keeping it running while the RTC sat on the shelf. My intuition was that the RTC should behave as a set-it-and-forget-it device. In other words: I should be able to program the RTC time, lock the time by installing the coin cell, and by so locking the RTC not need to worry about programming errors, glitches (etc etc) causing the RTC to be corrupted. That would make the RTC shield entirely portable and robust when moving it from one Arduino to another, or uploading different Sketches to the Arduino (which Sketches might inadvertently contain lines of code related to RTC-setting)

If this was the case, then it would be impossible to ever change the time without opening up whatever enclosure the system was in and removing the coin cell.

The fact that the difference in parts didn't cause your lost time doesn't mean that it's ok to specify in your code a different part than you're actually using.

If you use the DS3231 sketch on a DS1307, the rtc.lostPower() function will not find its DS3231_STATUSREG 0x0F .
From https://github.com/adafruit/RTClib/blob/master/src/RTC_DS3231.cpp

#include "RTClib.h"

#define DS3231_ADDRESS 0x68   ///< I2C address for DS3231
#define DS3231_TIME 0x00      ///< Time register
#define DS3231_ALARM1 0x07    ///< Alarm 1 register
#define DS3231_ALARM2 0x0B    ///< Alarm 2 register
#define DS3231_CONTROL 0x0E   ///< Control register
#define DS3231_STATUSREG 0x0F ///< Status register
#define DS3231_TEMPERATUREREG                                                  \
  0x11 ///< Temperature register (high byte - low byte is at 0x12), 10-bit
       ///< temperature value

/**************************************************************************/
/*!
    @brief  Start I2C for the DS3231 and test succesful connection
    @param  wireInstance pointer to the I2C bus
    @return True if Wire can find DS3231 or false otherwise.
*/
/**************************************************************************/
bool RTC_DS3231::begin(TwoWire *wireInstance) {
  if (i2c_dev)
    delete i2c_dev;
  i2c_dev = new Adafruit_I2CDevice(DS3231_ADDRESS, wireInstance);
  if (!i2c_dev->begin())
    return false;
  return true;
}

/**************************************************************************/
/*!
    @brief  Check the status register Oscillator Stop Flag to see if the DS3231
   stopped due to power loss
    @return True if the bit is set (oscillator stopped) or false if it is
   running
*/
/**************************************************************************/
bool RTC_DS3231::lostPower(void) {
  return read_register(DS3231_STATUSREG) >> 7;
}