Arduino & Real-Time Clock

Mar 26, 2022 · 1044 words · 5 minute read

There are a lot of applications where knowing the right time is critical; logging sensor data, actions based on a time of day or making clocks or calendars. If you don't want to use a micro controller or computer that connects to the internet, then a real-time clock (RTC) is a must.

The DS3231 is a low cost real-time clock that you can get for less than 4€ on a breakout board with it's own back up battery,1 so that it will remember the time even if the main power supply is turned off. It also features two alarms that can be set on the RTC, which can save you some programming effort as you don't have to bother coding in extra checks on the time to act as alarms, and the alarms can be used as interrupts (more on why that's useful later).

There are a few real-time clock libraries for the Arduino, but I ended up using the DS3232RTC library, which also supports the DS3231 RTC. One reason for choosing this library is because it in turn uses the Time library, which integrates well with various time sources, such as an RTC, but also has a pleasant time object and functions for dealing with time in your code.

If, like me, you're using the Arduino IDE, you can install both these libraries through the library manager (under Tools > Manage Libraries…).

Wiring

The DS3231 uses I2C to connect to the Arduino, so it needs power (5V) and Ground, plus the SDA and SCL pins for I2C. There are dedicated pins A4 and A5 on the Arduino for this.

DS3231Arduino
SCLA5
SDAA4
VCC5V
GNDGND
SQWFor interrupt alarm, set with wakeUpPin(), e.g. Pin D2

Demos & Setting the Time

The most basic example is the TimeRTC example (File > examples > DS3232RTC > TimeRTC) that comes with the DS3232RTC library. This, when wired as above, it displays the current time from the RTC every second on the serial console (remember to set the Baud rate in the console to match that set in the code, otherwise you'll see gibberish).

One important thing to do on the RTC is to set the time, this can be done easily with the SetSerial sketch in the same example folder. You can enter the time live into the Arduino IDE's serial console, and if the RTC doesn't loose power (this is where the back-up button battery is useful), it'll remember the time until the next time you want to use it.

Alarms and Waking the Arduino

Another great feature of the DS3231 is that it features two built in alarms, either of which can trigger the square wave pin. If this is connected to an Arduino pin that is waiting for an interrupt, it can be used to, well, interrupt the Arduino from its current activity and respond to the alarm. This includes while it's asleep.

Using the LowPower library it's possible to power down the Arduino so it uses a tiny amount of power, until it's woken again to do a task by an interrupt, in this case triggered by our RTC.

The alarms are not completely self explanatory, but the library has a good overview of which alarms are available, and how to set them, along with various example sketches.

Example Sketch, Printing the Time and Sleeping In Between

To test the above I wrote a simple sketch that powers down the Arduino until it's woken by an interrupt, triggered every minute by an alarm. Then it writes the time to the serial console. It might seem overkill to power down the Arduino if it's only going to wake up again in less than a minute, but it can still be a big power saving. It only needs to run for a second or two, instead of continuously, so if you're running it on battery power, cutting it's power on time by 30 is huge.

To write the text to the serial console I wanted to create a formatted text string, as it will be useful if I want to display the text somewhere else, like perhaps an external display (ooh forboding). There was only one slight issue…

Oddity with snprintf() and Time Library

If you load the linked sketch 'sleep_wake_with_rtc.ino' above, and look at how the date string variable "dateTime" that is printed to the serial console is formed, it might seem a bit weird:

setSyncProvider(myRTC.get);
time_t t = myRTC.get(); //get the time from the RTC
char dateTime[40];
char currentWeekday[10]; //long enough for Wednesday plus terminator.
strcpy(currentWeekday, dayStr(weekday(t)));
snprintf(dateTime,
	 sizeof(dateTime),
	 "%s %i %s %i %02i:%02i:%02i",
	 currentWeekday,
	 day(t),
	 monthStr(month(t)),
	 year(t),
	 hour(t),
	 minute(t),
	 second(t));

Serial.println(dateTime);

Why do we need to copy the day of the week to a separate array before using snprintf()? Well, if you try, with the following:

char dateTime[40];
snprintf(dateTime,
           sizeof(dateTime),
           "%s %i %s %i %02i:%02i:%02i",
           dayStr(weekday(t)),
           day(t),
           monthStr(month(t)),
           year(t),
           hour(t),
           minute(t),
           second(t));

You would expect to get a string like "Monday 21 March 2022 20:31", but you don't, you get "March 21 March 2022 20:31" instead.

It took a bit of hunting to discover why, but the clue came on a comment on line 92 of rtc_interrupt.ino in the DS3232RTC examples, which has a function:

// format and print a time_t value
void printTime(time_t t)
{
    char buf[25];
    char m[4];    // temporary storage for month string (DateStrings.cpp uses shared buffer)
    strcpy(m, monthShortStr(month(t)));
    sprintf(buf, "%.2d:%.2d:%.2d %s %.2d %s %d",
        hour(t), minute(t), second(t), dayShortStr(weekday(t)), day(t), m, year(t));
    Serial.println(buf);
}

Which implies that in the underlying library the weekday and month (plus possibly other strings) share the same space in memory. So if you call dayStr(weekday(t))), which puts the day of the week into the buffer, but then call monthStr(month(t)), that buffer is overwritten with the month, so when snprintf() runs, it sees the month string twice, as it's really looking in the same place twice.

I got around this in the same way the above function does, and that is copy one string into a separate array first, and then using this intermediate array.


1

This is where I bought mine, but you can get them lots of other places, so it's not really an endorsement, and do I get any benefit if you do buy yours there.