Newbie - DLS time set and use via DS3231

I’m trying to setup a clock, timer and temperature display for a scoreboard using an RTC. I’m using a DS3231 and wanted to put a condition in for day light savings time (Melb, Aust).

I’ve written code with other libraries to extract “hour”, “min”, “sec” and have them display on 7 seg displays via shift registers so now I’m experimenting with DLS.

The past week I"ve been experimenting with the “Clock” example that comes with the “Timezone” library (see below). When I upload the code and check the serial monitor, I get a display for example:

08:12:07 Fri 01 May 2015 Current Melbourne Time
08:12:07 Fri 01 May 2015 AEST

All good. If I then add - Serial.print (hour()); then I also get 8. Sort of ok for now. However what I can’t understand is when I close the serial monitor and open it again, the time starts back to the compile time each time. I “assume” with the battery in the clock it increments the time. Why is this increment not displaying. Am I calling it up incorrectly? Thanks in advance.

#include <Time.h>        //http://www.arduino.cc/playground/Code/Time
#include <Timezone.h>    //https://github.com/JChristensen/Timezone

//Australia Eastern Time Zone (Sydney, Melbourne)
TimeChangeRule myDST = {"AEDT", First, Sun, Oct, 2, 60};    //Daylight time = +1 hours
TimeChangeRule mySTD = {"AEST", First, Sun, Apr, 3, 0};    //Standard time = +0 hours
Timezone myTZ(myDST, mySTD);

//If TimeChangeRules are already stored in EEPROM, comment out the three
//lines above and uncomment the line below.
//Timezone myTZ(100);       //assumes rules stored at EEPROM address 100

TimeChangeRule *tcr;        //pointer to the time change rule, use to get TZ abbrev
time_t utc, local;

void setup(void) {
  Serial.begin(9600);
  setTime(myTZ.toUTC(compileTime()));
  //setTime(01, 55, 00, 11, 3, 2012);        //another way to set the time (hr,min,sec,day,mnth,yr)
}

void loop(void) {
  Serial.println();
  utc = now();
  printTime(utc, "Current Melbourne Time");
  local = myTZ.toLocal(utc, &tcr);
  printTime(local, tcr -> abbrev);
  delay(10000);
}

//Function to return the compile date and time as a time_t value
time_t compileTime(void) {
#define FUDGE 25        //fudge factor to allow for compile time (seconds, YMMV)

  char *compDate = __DATE__, *compTime = __TIME__, *months = "JanFebMarAprMayJunJulAugSepOctNovDec";
  char chMon[3], *m;
  int d, y;
  tmElements_t tm;
  time_t t;

  strncpy(chMon, compDate, 3);
  chMon[3] = '\0';
  m = strstr(months, chMon);
  tm.Month = ((m - months) / 3 + 1);

  tm.Day = atoi(compDate + 4);
  tm.Year = atoi(compDate + 7) - 1970;
  tm.Hour = atoi(compTime);
  tm.Minute = atoi(compTime + 3);
  tm.Second = atoi(compTime + 6);
  t = makeTime(tm);
  return t + FUDGE;        //add fudge factor to allow for compile time
}

//Function to print time with time zone
void printTime(time_t t, char *tz) {
  sPrintI00(hour(t));
  sPrintDigits(minute(t));
  sPrintDigits(second(t));
  Serial.print(' ');
  Serial.print(dayShortStr(weekday(t)));
  Serial.print(' ');
  sPrintI00(day(t));
  Serial.print(' ');
  Serial.print(monthShortStr(month(t)));
  Serial.print(' ');
  Serial.print(year(t));
  Serial.print(' ');
  Serial.print(tz);
  Serial.println();
}

//Print an integer in "00" format (with leading zero).
//Input value assumed to be between 0 and 99.
void sPrintI00(int val) {
  if (val < 10) Serial.print('0');
  Serial.print(val, DEC);
  return;
}

//Print an integer in ":00" format (with leading zero).
//Input value assumed to be between 0 and 99.
void sPrintDigits(int val) {
  Serial.print(':');
  if (val < 10) Serial.print('0');
  Serial.print(val, DEC);
}

You're not using any RTC library! So the program doesn't even know what an RTC is. JChristensen also wrote a good one, so go back there and get it.

the time starts back to the compile time each time

Perhaps this has something to do with it:

 setTime(myTZ.toUTC(compileTime()));

Closing and reopening the serial monitor reboots the Arduino.

Pete

It does, but without that, the time goes back to 1970. So a DS3231 library is definitely still needed.

@aarg nailed it. The intent of the Clock example from the Timezone library was just to demonstrate the library and I didn't want to presuppose additional hardware, e.g. an external hardware RTC.

The Arduino Time library is a software RTC; as such it has no way to persist the correct time between resets. The Clock example uses the Time library and initializes its time to the compile time. "Compile time" gets hardcoded into the sketch, so if the MCU is reset a week after compiling, it will then show that time from a week ago.

I would humbly submit my DS3232RTC library, which also works with DS3231. Just #include the library and add code to setup() similar to that in the SetSerial example. The function setSyncProvider() is part of the Time library and causes the Time library to initialize its time from the RTC and then periodically sync its time with that from the RTC.

By the way, I'm really glad this subject came up because I was not aware of the Timezone library, and I need something like that now. Looks perfect! How hard would it be to store time zone settings in EEPROM using it? I already do, but I'm only storing a signed char value (i.e. no DST).

aarg: You're not using any RTC library! So the program doesn't even know what an RTC is. JChristensen also wrote a good one, so go back there and get it.

Yes, I see. It's only calling up the timezone and time libraries.

Also thanks Jack, I didn't realize the time library was simply a software RTC. But it makes sense now. You use it if you don't have an DS3231 or equivalent. I'll try add the #DS3232RTC line and some code from the SetSerial example (already have them downloaded). Fingers crossed...

I added #DS3232RTC and then found it also wanted #Wire.h, so I added that too. I then only added the following line (if that's correct) in the void loop:

setSyncProvider(RTC.get);

However, from the following lines, I'm still getting wrong times.I know it's wrong, but not sure how to fix it:

void loop(void) {
  setSyncProvider(RTC.get);
  Serial.println();
  utc = now();
  printTime(utc, "Current Melbourne Time");
  local = myTZ.toLocal(utc, &tcr);
  printTime(local, tcr -> abbrev);
  delay(10000);
}

What it's returning is:

16:21:49 Fri 01 May 2015 Current Melbourne Time 16:21:49 Fri 01 May 2015 AEST

(however the current time is now 17:54:00). What piece of code actually gets the time? I'd like if possible to:

  • Read the compile time
  • Write that time to the RTC
  • Add the day light saving time rule
  • Then be able to call up hour, minutes and seconds in my program (and later temperature). I assume to call up time I'd write something like "Serial.print(hour()); etc, etc???

Your goals:

  1. It’s terrible to use the compile time to set the RTC. There’s a problem with delays. Far better to load a program that has a manual time set capability, the setserial sketch that comes with the library will do that.

  2. see 1.

  3. I think you’re already on track for that. Unsure, I haven’t tried it myself yet.

  4. Yes.

By the way, nothing personal but can I persuade you to avoid using the term “void loop” which is a really incorrect name for the loop() function?

Hi aarg, sorry abou the incorrection reference to the loop() function. I did try to run Jack's SetSerial.ino code on it's own and found I could set the time via the serial monitor - it also does my temperature. I can then easily call hour, minute, second and temperature.

But I'm getting in knots trying to put them together:

  • SetSerial.ino allows me to set the time via the serial monitor. Works well, however I'd prefer to use something like Tssmmhhddmmyyyy that I've seen elsewhere) - not sure how to include
  • TimeRTC.ino allows me to call up the time and temperature easily
  • WriteRules.pde allows me to adjust for day light savings I just can't work out how to join all these together.

cjcj: I'd like if possible to:

  • Read the compile time
  • Write that time to the RTC
  • Add the day light saving time rule
  • Then be able to call up hour, minutes and seconds in my program (and later temperature). I assume to call up time I'd write something like "Serial.print(hour()); etc, etc???

Also in the German forum there were discussions from time to time about clocks that automatically correct summertime (Daylight Saving Time) and there was no real solution, while there were libraries that did part of the job. So a couple of weeks ago I created a poor mans library that easily can be included in a "Tab" of the current project and which will provide the following functions with DS1307 or DS3231 RTCs: - RTC clock is always runing in "zone time", accordingly to local time zone - library provides "readZoneTime()" to read the RTC time, accordingly to current time zone setting - library provides "readLocalTime" which corrects DST by one hour (in summer) or not (in winter) - library provides "handleSerialCommand()" to provide a possibility to set time using Serial - example sketch also provides "timer switch" functions for on/off switching at certain times The code is available in the German forum and all comments in the code are in English.

There is just one problem that prohibits to use the code outside Europe: Currently there is just one daylight saving rule included in the library, and it is based on summertime in the European Union.

But I think I could easily add different DST rules. So if somebody needs a RTC library which is able to provide always local time and does all the daylight saving / summertime switching automatically, just let me know the exact DST rule for your area: date and time(!) for actually switching over to DST and back to non DST.

The current "European DST" version of the example sketch and library is available as a file attachment to this posting in the German forum: http://forum.arduino.cc/index.php?topic=313047.msg2193148#msg2193148 Needs no library installation, just unpack the folder in the ZIP file to your Arduino sketch folder. Comments in the example sketch and library are all English.

So just let me know about your local DST rule and I can possibly include it.

Hi Jurs. I saw that thread last week (among about 50 others! whilst searching). Google automatically converted the text to english for me, but still was over my head.

Using Jack Christensen’s TimeZone library, the conversion I need for Melbourne is:

//Australia Eastern Time Zone (Sydney, Melbourne)
TimeChangeRule myDST = {“AEDT”, First, Sun, Oct, 2, 60}; //Daylight time = +1 hours
TimeChangeRule mySTD = {“AEST”, First, Sun, Apr, 3, 0}; //Standard time = +0 hours
Timezone myTZ(myDST, mySTD);

Following my previous post, I did end up merging all the libraries I mentioned earlier. The DLS time did calculate, but I can’t seem to extract this time. What I get is:

RTC Sync
05May2015 21:35:52
21:35:52 Tue 05 May 2015 Current Melbourne Time
21:35:52 Tue 05 May 2015 AEST

If I swap the AEDT and AEST dates, then I get:

RTC Sync
05May2015 21:37:44
21:37:44 Tue 05 May 2015 Current Melbourne Time
22:37:44 Tue 05 May 2015 AEDT

So it works, but I can’t seem to extract the time 22:37:44 with “serial.print(hour();” etc. It’s being calculated, but I don’t know hot to call it. Code below (sorry it’s so long!):

#include <DS3232RTC.h>
#include <Streaming.h>
#include <Time.h>
#include <Wire.h>
#include <Timezone.h>

//Australia Eastern Time Zone (Sydney, Melbourne)
TimeChangeRule myDST = {"AEDT", First, Sun, Apr, 2, 60};    //Daylight time = +1 hours
TimeChangeRule mySTD = {"AEST", First, Sun, Oct, 3, 0};    //Standard time = +0 hours
Timezone myTZ(myDST, mySTD);

//If TimeChangeRules are already stored in EEPROM, comment out the three
//lines above and uncomment the line below.
//Timezone myTZ(100);       //assumes rules stored at EEPROM address 100

TimeChangeRule *tcr;        //pointer to the time change rule, use to get TZ abbrev
time_t utc, local;


void setup(void)
{
  Serial.begin(9600);
  setTime(myTZ.toUTC(compileTime()));
  //setTime(18, 31, 00, 01, 05, 2013);        //another way to set the time (hr,min,sec,day,mnth,yr)

  //setSyncProvider() causes the Time library to synchronize with the
  //external RTC by calling RTC.get() every five minutes by default.
  setSyncProvider(RTC.get);
  Serial << F("RTC Sync");
  if (timeStatus() != timeSet) Serial << F(" FAIL!");
  Serial << endl;
}

void loop(void) {
  static time_t tLast;
  time_t t;
  tmElements_t tm;

  //check for input to set the RTC, minimum length is 12, i.e. yy,m,d,h,m,s
  if (Serial.available() >= 12) {
    int y = Serial.parseInt();
    if (y >= 100 && y < 1000)
      Serial << F("Error: Year must be two digits or four digits!") << endl;
    else {
      if (y >= 1000)
        tm.Year = CalendarYrToTm(y);
      else    //(y < 100)
        tm.Year = y2kYearToTm(y);
      tm.Month = Serial.parseInt();
      tm.Day = Serial.parseInt();
      tm.Hour = Serial.parseInt();
      tm.Minute = Serial.parseInt();
      tm.Second = Serial.parseInt();
      t = makeTime(tm);
      RTC.set(t);        //use the time_t value to ensure correct weekday is set
      setTime(t);
      Serial << F("RTC set to: ");
      printDateTime(t);
      Serial << endl;
      while (Serial.available() > 0) Serial.read();
    }
  }

  t = now();
  if (t != tLast) {
    tLast = t;
    printDateTime(t);
    if (second(t) == 0) {
      float c = RTC.temperature() / 4.;
      float f = c * 9. / 5. + 32.;
      Serial << F("  ") << c << F(" C  ") << f << F(" F");
    }
    Serial << endl;
  }
  setSyncProvider(RTC.get);
  Serial.println();
  utc = now();
  printTime(utc, "Current Melbourne Time");
  local = myTZ.toLocal(utc, &tcr);
  printTime(local, tcr -> abbrev);
  delay(10000);

}

//print date and time to Serial
void printDateTime(time_t t)
{
  printDate(t);
  Serial << ' ';
  printTime(t);
}

//print time to Serial
void printTime(time_t t)
{
  printI00(hour(t), ':');
  printI00(minute(t), ':');
  printI00(second(t), ' ');
}

//print date to Serial
void printDate(time_t t)
{
  printI00(day(t), 0);
  Serial << monthShortStr(month(t)) << _DEC(year(t));
}

//Print an integer in "00" format (with leading zero),
//followed by a delimiter character to Serial.
//Input value assumed to be between 0 and 99.
void printI00(int val, char delim)
{
  if (val < 10) Serial << '0';
  Serial << _DEC(val);
  if (delim > 0) Serial << delim;
  return;
}


//Function to return the compile date and time as a time_t value
time_t compileTime(void)
{
#define FUDGE 25        //fudge factor to allow for compile time (seconds, YMMV)

  char *compDate = __DATE__, *compTime = __TIME__, *months = "JanFebMarAprMayJunJulAugSepOctNovDec";
  char chMon[3], *m;
  int d, y;
  tmElements_t tm;
  time_t t;

  strncpy(chMon, compDate, 3);
  chMon[3] = '\0';
  m = strstr(months, chMon);
  tm.Month = ((m - months) / 3 + 1);

  tm.Day = atoi(compDate + 4);
  tm.Year = atoi(compDate + 7) - 1970;
  tm.Hour = atoi(compTime);
  tm.Minute = atoi(compTime + 3);
  tm.Second = atoi(compTime + 6);
  t = makeTime(tm);
  return t + FUDGE;        //add fudge factor to allow for compile time
}

//Function to print time with time zone
void printTime(time_t t, char *tz)
{
  sPrintI00(hour(t));
  sPrintDigits(minute(t));
  sPrintDigits(second(t));
  Serial.print(' ');
  Serial.print(dayShortStr(weekday(t)));
  Serial.print(' ');
  sPrintI00(day(t));
  Serial.print(' ');
  Serial.print(monthShortStr(month(t)));
  Serial.print(' ');
  Serial.print(year(t));
  Serial.print(' ');
  Serial.print(tz);
  Serial.println();
}

//Print an integer in "00" format (with leading zero).
//Input value assumed to be between 0 and 99.
void sPrintI00(int val)
{
  if (val < 10) Serial.print('0');
  Serial.print(val, DEC);
  return;
}

//Print an integer in ":00" format (with leading zero).
//Input value assumed to be between 0 and 99.
void sPrintDigits(int val)
{
  Serial.print(':');
  if (val < 10) Serial.print('0');
  Serial.print(val, DEC);
}

You can easily construct a time in the Time library and use it to set the RTC using RTC.set(). The time library allows you considerable versatility in how you want to put it together.

I'm lost in reading about the time zone stuff, but I'll try to catch up with it later.

cjcj: Using Jack Christensen's TimeZone library, the conversion I need for Melbourne is:

//Australia Eastern Time Zone (Sydney, Melbourne) TimeChangeRule myDST = {"AEDT", First, Sun, Oct, 2, 60}; //Daylight time = +1 hours TimeChangeRule mySTD = {"AEST", First, Sun, Apr, 3, 0}; //Standard time = +0 hours Timezone myTZ(myDST, mySTD);

That sounds easy: The dates are "first Sunday in October" and "first Sunday in April". The time difference during summer months is "plus one hour", same as in Europe. The time difference to UTC is plus, same as in Europe (Europe UTC+1, Australia UTC+10).

So your standard time zone is UTC+10 (zone time, AEST, local time during winter months in Australia). And your DST time zone is UTC+11 (zone time +1, AEDT, local time during summer months in Australia).

I'll think a bit over it, how to hassle-free include different DST dates in my library and example sketch, then do some testing and then I'll post an updated version in this thread.

So here comes the library with an “timer switch” example sketch.

The library is preconfigured for Australian timezone and DST rule with these settings:

#define TIMEZONE 10  // timezone for Germany and most countries in Europe is UTC + 1 hour
#define DST_RULE 1  // 0=European Union, 1=Australia (summertime/DST rules)

The library needs no installation, I’ve put it on a “Tab” in the sketch editor window.
So simply unpack the folder from the ZIP file into your Arduino sketch folder.

The example sketch is somewhat more than minimal, and includes:

  • reading time from RTC (DS1307 or DS3231)
  • using local ‘standard’ or ‘daylight saving’ time automatically
  • setting time by using the serial monitor (make sure that line ending NL and CR is enabled)
  • daily timer switch for one channel and multiple on-times per day
  • date, time and status output on LCD text display (preconfigured: LCD Keypad Shield)
  • date, time and status output on serial

Inner working:

  • RTC always runs in timezone time, which would be UTC+10 for Australia
  • you can either use “RTCreadZoneTime()” to read that time from RTC
  • or you can have local time by using “RTCreadLocalTime()”, with standard/DST time automatically

Some words to setting date/time using the serial monitor:
When the DST period ends, one hour occurs twice: Once in DST, and after setting time back to standard time, the same hour happens to run again. If you like to set the time during this last DST hour within the DST period, the clock will always be set to standard time. In all other cases the time is unambiguously: You always enter time in ‘local time’. If ‘local time’ is standard time, the RTC will be set to that time. But if ‘local time’ is daylight saving time, the RTC will be set one hour early.

RTC is always running at ‘zone time’, so time within the RTC will never change whether you are in standard time or daylight saving time.

If you need further expanations, please ask.

As the original example sketch has grown rather big I have made a more simple sketch that will just provide:

  • reading local time (standard or DST automatically) from RTC
  • setting RTC to new date/time using the serial monitor
  • formatted output on serial once per second
#include <Wire.h>  // include Wire library for I2C devices
#include "jursRTCtimeEasy.h" // include RTC time library

sTime localTime; // holds current time in local time (with regard to DST/summertime)

void serialTask()
{ // Check Serial input for time setting command
  char* command=readSerialCommand();
  if (command!=NULL) // we have found a command from Serial
  {
    Serial.print(command);Serial.print('\t');
    if (strncmp_P(command,PSTR("setc "),5)==0 || strncmp_P(command,PSTR("setd "),5)==0) // compare for command
    {
      if (handleRtcCommand(command)) Serial.println(F("OK"));
      else Serial.println(F("ERROR: invalid time not set"));
    }
    else Serial.println(F("Unknown command")); // handle other commands here
  }
}


void output()
// format localTime and send output to serial monitor
{
  char buf[16];
  // format date to 'buf' array
  snprintf(buf,sizeof(buf),"%02d.%02d.%04d  ", localTime.bDay, localTime.bMonth, localTime.iYear);
  Serial.print(buf); 

  // format time to 'buf' array
  char dstFlag; // DST/summertime flag
  if (localTime.sTimezone==TIMEZONE+1) dstFlag='*'; else dstFlag=' ';
  snprintf(buf,sizeof(buf),"%02d:%02d:%02d%c", localTime.bHour, localTime.bMinute, localTime.bSecond, dstFlag);
  Serial.println(buf); 
}


void usage()
{ // show some information about program usage
  Serial.println(F("\nRTC date and time library demo by 'jurs' for Arduino forum\n"));
  Serial.println(F("Usage for setting RTC date/time from Serial monitor in format"));
  Serial.println(F("setc YYYY MM DD hh mm ss"));
  Serial.println(F("examples:"));
  Serial.println(F("setc 2015 4 6 13 30         (13:30h on 6th of April, 2015)"));
  Serial.println(F("setc 2016-12-24 9:30        (09:30h on 24th of December, 2016)"));
}

void setup() 
{
  Serial.begin(9600); // enable Serial
  usage();
  Serial.print(F("\nInitialize RTC ..."));
  if ( RTCinit()) Serial.println(F("OK")); else Serial.println(F("ERROR"));
  RTCreadLocalTime(localTime); // initial time reading
}

#define UPDATEINTERVAL 1000
void loop() 
{
  serialTask(); // handle serial commands for setting date/time
  static unsigned long lastUpdateTime;
  if (millis()-lastUpdateTime>=UPDATEINTERVAL)
  {
    lastUpdateTime+=UPDATEINTERVAL;
    RTCreadLocalTime(localTime); // read localTime so that 'time' is a local time
    output();
  }
}

dateTimeLibEasyDemo.zip (5.45 KB)

jurs: - RTC clock is always runing in "zone time", accordingly to local time zone

I'm unclear on the meaning of "zone time". Is it local time, or is it UTC? Or is it something else?

Just to throw in a thought. I reasoned that running the RTC on UTC time would be the most logical because it is constant. So that is how I would always want to do it. My RTC time set routines accept input in local time and convert it to UTC. Then changes in time zone won't force me to change the RTC time.

aarg: By the way, I'm really glad this subject came up because I was not aware of the Timezone library, and I need something like that now. Looks perfect! How hard would it be to store time zone settings in EEPROM using it? I already do, but I'm only storing a signed char value (i.e. no DST).

The Clock example sketch demonstrates using time change rules stored in EEPROM or coded in the sketch.

The WriteRules example can be used to write the time change rules to EEPROM.

Edit: Also possibly of interest, here is a project that implements runtime selection from several time zones; also see related blog post.

aarg: Just to throw in a thought. I reasoned that running the RTC on UTC time would be the most logical because it is constant.

Absolutely. Local time is not continuous. When changing from standard to summer time, there is an hour of time that does not exist in local time. When changing back to standard time, there is an hour of time that is duplicated. Therefore it is not possible to properly convert local time to UTC in all cases, but UTC can always be converted to local time (and to any local time, given the time change rules).

jurs:
So here comes the library with an “timer switch” example sketch.

Thanks Jurgen. The code’s a bit complex for me but I’m getting around it. I’ve spend all day now trying to extract what I need and also call temperature. I have a few more questions if that’s ok?

  • “Time setting is in local time during summertime”. Does this mean that when uploading the time, I need to type non daylight savings time regardless of what month I upload it in?
  • To extract the hour, minutes and seconds to shift registers, do I call them up as such:
    localTime.bHour
    localTime.bMinute
    localTime.bSecond
    (I’m currently printing them to serial monitor with:
    Serial.print(localTime.bHour);
  • To print the time in 00 format (say for 1pm), I’ve had to write the following, but is there a better way to call it directly:
    void sPrintDigits(int val) {
    if(val < 10) Serial.print(‘0’);
    Serial.print(val, DEC);
    }

Then I replace Serial.print with sPrintDigits

  • What I absolutely can’t work out is how to get 12 hour time. In the jursTimeLibEasy.h file it has the following line but doesn’t say what to change it to or how:
    time.bHour = bcdToDec(Wire.read() & 0x3f); // Need to change this if 12 hour am/pm