Real-Time Clock TinyShield Tutorial

The Real-Time Clock TinyShield is great for time-based projects where events occur at a specific time of the day, week, or month.
This tutorial teaches the basics of using this TinyShield for various applications.
To learn more about the TinyDuino Platform, click here
Description
The Real-Time Clock TinyShield lets you keep track of time in seconds, minutes, hours, days, date, month and year with leap-year compensation (valid up to 2100). This TinyShield is based around the Maxim DS1339 Real-Time clock.
To see what other TinyShields are compatible with this TinyShield, see the TinyShield Compatibility Matrix
Technical Details
Maxim DS1339 Real-Time Clock (RTC) Specs- Real-Time Clock (RTC) Counts Seconds, Minutes, Hours, Day, Date, Month, and Year with Leap-Year Compensation Valid Up to 2100
- Two Time-of-Day Alarms
- Oscillator Stop Flag
- Automatic Power-Fail Detect and Switch Circuitry
- Backup battery trickle charge (software controlled)
- Voltage: 3.0V - 5.5V
- Current: 450uA (Active Mode). Due to the low current, this board can be run using the TinyDuino coin cell option
- Built in Backup Battery
- 11mAh rechargeable Lithium 3.0V backup battery
- Provides 2.5 years of timekeeping backup
- Backup battery trickle charging under software control
- Backup battery current: 600 nA
- A5/SCL - I2C Serial Clock line
- A4/SDA - I2C Serial Data line
- 20mm x 20mm (.787 inches x .787 inches)
- Max Height (from lower bottom TinyShield Connector to upper top TinyShield Connector): 5.11mm (0.201 inches)
- Weight: 1.58 grams (.06 ounces)
Notes
- The backup battery can be recharged from the main TinyDuino power supply via a software command, see the example code or the datasheet for more information.
Materials

Hardware
- A TinyDuino Processor Board
- Real-Time Clock TinyShield
- Micro USB Cable
Software
Hardware Assembly
On top of your processor board of choice, place the Real-Time Clock TinyShield. Plug a MicroUSB cable into the micro USB port (or USB shield) and then plug the cable into an available USB port on your computer. Make sure the processor is switched on.

Software Setup
First, open the Arduino IDE. If you don't have it installed or are unfamiliar with how to upload the code, check out the TinyDuino Setup Tutorial or TinyZero Setup Tutorial depending on which processor you're using.
The DSRTCLib library is required for this project along with a couple of AVR headers which can all be downloaded from our GitHub or the links above.
The code for this TinyShield is rather lengthy because it is capable of demonstrating most functions of the board. If your application is more specific, feel free to just grab the parts you need!
Upload Program
Code
/*
TinyCircuits Real-Time Clock TinyShield Example Sketch
This example code shows basic usage of the DS1339 Real-Time Clock TinyShield.
Be sure to set the Serial Monitor to "No line ending"
Written
By Ben Rose
Modified 21 May 2019
By Hunter Hykes
https://TinyCircuits.com
*/
#include "DSRTCLib.h"
#include <Wire.h>
#include <avr/power.h>
#include <avr/sleep.h>
int ledPin = 13; // LED connected to digital pin 13
int INT_PIN = 3; // INTerrupt pin from the RTC. On Arduino Uno, this should be mapped to digital pin 2 or pin 3, which support external interrupts
int int_number = 1; // On Arduino Uno, INT0 corresponds to pin 2, and INT1 to pin 3
volatile int alarmTriggered = 0; //flags when the alarm (interrupt) is triggered
unsigned long printInterval = 1000; //for displaying alarm countdown info
unsigned long lastPrint = 0;
//use this initialization to disable interrupts
//DS1339 RTC = DS1339(); //starts I2C communication (INT_PIN = 2, int_number = 0)
//use this initialization to enable interrupts
DS1339 RTC = DS1339(INT_PIN, int_number);
//INT_PIN is INPUT for Interrupt
//int_number enables a software pullup on RTC Interrupt pin
void setup() {
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);
//INTERRUPTS
digitalWrite(INT_PIN, HIGH);
attachInterrupt(1, alarm, FALLING);
//enable deep sleeping
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
Serial.flush(); //clear serial line
Serial.begin(9600); //start serial comms
Serial.println ("DSRTCLib Tests");
RTC.start(); // ensure RTC oscillator is running, if not already
if(!RTC.time_is_set()) { // set a time, if none set already...
Serial.print("Clock not set. ");
set_time();
}
// If the oscillator is borked (or not really talking to the RTC), try to warn about it
if(!RTC.time_is_set()) {
Serial.println("Clock did not set! Check that its oscillator is working.");
}
RTC.enable_interrupt();
}
void loop() {
Serial.flush();
Serial.println ("\nRTC Library Tests \n 1) Basic (read and write time) \n 2) Alarm interrupts/wakeup \n 3) date <--> epoch seconds validation \n 4) Read time \n 5) Set time \n 6) Set alarm \n");
Serial.flush();
while(!Serial.available()){ //while there is no input
; //do nothing
}
switch(Serial.read())
{
case '1':
test_basic();
break;
case '2':
test_interrupts();
break;
case '3':
test_epoch_seconds();
break;
case '4':
read_time();
break;
case '5':
set_time();
case '6':
set_alarm() ;
default:
break;
}
}
//CASE 6
int set_alarm() {
//setup
Serial.print("Alarm set to: ");
RTC.readAlarm();
Serial.print(int(RTC.getHours()));
Serial.print(":");
Serial.print(int(RTC.getMinutes()));
Serial.print(":");
Serial.println(int(RTC.getSeconds()));
Serial.println("Enter time for alarm (HH:MM:SS)");
while(1) {
if(millis() > printInterval + lastPrint) {
lastPrint = millis();
read_time();
}
if(Serial.available() > 5){
delay(100);
char in[10]; //array for storing alarm time
int i = 0;
while(Serial.available() && i < 10){ //read 10 bytes of data
in[i] = Serial.read();
if(isdigit(in[i]))
i++;
}
if(i > 5){
int hour=((in[0]-'0') * 10) + (in[1] - '0');
int minute=((in[2]-'0') * 10) + (in[3] - '0');
int second=((in[4]-'0') * 10) + (in[5] - '0');
RTC.setHours(hour);
RTC.setMinutes(minute);
RTC.setSeconds(second);
RTC.setAlarmRepeat(EVERY_WEEK);
RTC.writeAlarm();
Serial.print("Alarm set to ");
Serial.print(hour);
Serial.print(":");
Serial.print(minute);
Serial.print(":");
Serial.println(second);
}else{
Serial.println("Input error!");
}
}
if(alarmTriggered) {
RTC.clear_interrupt();
attachInterrupt(1, alarm, FALLING);
alarmTriggered = 0;
if(millis() > 3000) { //ignore interrupts at startup
Serial.println("Alarm!");
//Do stuff! We'll just turn on the pin 13 LED for a second
digitalWrite(ledPin, HIGH);
delay(1000);
digitalWrite(ledPin, LOW);
return 0;
}
}
}
}
void alarm() {
detachInterrupt(1);
alarmTriggered = 1;
}
//case 2 helper
void nap() {
// Dummy function. We don't actually want to do anything here, just use an interrupt to wake up.
//RTC.clear_interrupt();
// For some reason, sending commands to clear the interrupt on the RTC here does not work. Maybe Wire uses interrupts itself?
Serial.print(".");
}
//case 5 helper
int read_int(int numbytes) {
static byte c; //character read in
static int i; //return variable
static int d; //used to calculate current digit
int num = 0; //used to count iterations
i = 0;
while(1) {
while(!Serial.available()) { //while no serial data received
; //do nothing
}
c = Serial.read(); //read one byte (character) of data
num++; //tracks number of bytes read
if(isdigit(c)) { //if the byte read is a digit
d = c - '0'; //converts ASCII value of digit to decimal value
for (int k = 0; k < numbytes - num; k++) {
d *= 10; //multiplies digit by proper power of 10
} //would have used pow(), but type conversion made this difficult
i += d; //add the new value of c to the variable to be returned
} else {
Serial.print("\r\nERROR: \"");
Serial.print(c);
Serial.println("\" is not a digit.\r\n");
return -1;
}
if(num == numbytes) { //if we have read all desired bytes
//Serial.print("Return value is: ");
//Serial.println(i);
return i; //return requested data
}
}
}
//case 5 helper
int read_int(char sep) {
static byte c;
static int i;
i = 0;
while (1)
{
while(!Serial.available()) { //if no serial data is being received
; //do nothing
}
c = Serial.read(); //read one byte (character) of data
//Serial.write(c);
if (c == sep) //if the byte read matches the byte (char) passed to the function
{
Serial.print("Return value is: ");
Serial.println(i);
return i;
}
if (isdigit(c))
{
i = i * 10 + c - '0';
}
else
{
Serial.print("\r\nERROR: \"");
Serial.print(c);
Serial.println("\" is not a digit\r\n");
return -1;
}
}
}
//case 5 helper
int read_date(int* year, int* month, int *day, int* hour, int* minute, int* second) {
*year = read_int(4);
*month = read_int(2);
*day = read_int(' ');
*hour = read_int(':');
*minute = read_int(':');
*second = read_int(2);
return 0;
}
//CASE 5
void set_time() {
Serial.println("Enter date and time (YYYYMMDD HH:MM:SS)");
int year, month, day, hour, minute, second;
int result = read_date(&year, &month, &day, &hour, &minute, &second);
if (result != 0) { //if all goes well, result should be 0
Serial.println("Date not in correct format!");
return;
}
//set initially to epoch
RTC.setSeconds(second);
RTC.setMinutes(minute);
RTC.setHours(hour);
RTC.setDays(day);
RTC.setMonths(month);
RTC.setYears(year);
RTC.writeTime();
read_time();
}
//CASE 4
void read_time() {
Serial.print ("The current time is ");
RTC.readTime(); // update RTC library's buffers from chip
printTime(0);
Serial.println();
}
//CASE 1
void test_basic() {
// Test basic functions (time read and write)
Serial.print ("The current time is ");
RTC.readTime(); // update RTC library's buffers from chip
printTime(0);
Serial.println("\nSetting times using direct method: 1/31/07 12:34:56");
RTC.setSeconds(56);
RTC.setMinutes(34);
RTC.setHours(12);
RTC.setDays(31);
RTC.setMonths(1);
RTC.setYears(2007); // 2-digit or 4-digit years are supported
RTC.writeTime();
delay(500); // This is not needed; just making it more clear that we are reading a new result
RTC.readTime();
Serial.print("Read back: ");
printTime(0);
Serial.println(" (we'll never forget)");
Serial.println("Setting time using epoch seconds: 2971468800 (midnight on 2/29/2064)");
//RTC.writeTime(2971468800u);
RTC.writeTime(2971468800);
delay(500);
RTC.readTime();
Serial.print("Read back: ");
printTime(0);
Serial.println(" (Happy 21st birthday Carlotta) ");
Serial.println("Writing alarm: 8:00am on the 15th of the month.");
RTC.setSeconds(0);
RTC.setMinutes(0);
RTC.setHours(8);
RTC.setDays(15);
RTC.setAlarmRepeat(EVERY_MONTH); // There is no DS1339 setting for 'alarm once' - user must shut off the alarm after it goes off.
RTC.writeAlarm();
delay(500);
RTC.readAlarm();
Serial.print("Read back: ");
printTime(1);
Serial.println("\nWriting alarm: 2:31:05 pm on the 3rd day of the week.");
RTC.setSeconds(5);
RTC.setMinutes(31);
RTC.setHours(14);
RTC.setDayOfWeek(3);
RTC.setAlarmRepeat(EVERY_WEEK); // to alarm on matching day-of-week instead of date
RTC.writeAlarm();
delay(500);
RTC.readAlarm();
Serial.print("Read back: ");
printTime(1);
Serial.println("\n");
}
//CASE 2
void test_interrupts() {
Serial.println("Setting a 1Hz periodic alarm interrupt to sleep in between. Watchen das blinkenlights...");
Serial.flush();
// Steps to use an alarm interrupt:
// 1) attach an interrupt handler (it can be blank if you just want to wake)
// 2) enable alarm interrupt from RTC using RTC.enable_interrupt();
// 3) set and write the alarm time
// 4) sleep! ...zzz...
// 5) clear the interrupt from RTC using RTC.clear_interrupt();
// ...
// 6) If no further alarms desired, disable the RTC alarm interrupt using RTC.disable_interrupt();
attachInterrupt(int_number, nap, FALLING);
RTC.enable_interrupt();
RTC.setAlarmRepeat(EVERY_SECOND); // if alarming every second, time registers larger than 1 second (hour, etc.) are don't-care
RTC.writeAlarm();
for(byte i = 0; i<3; i++)
{
digitalWrite(ledPin, HIGH);
delay(3); // wait >2 byte times for any pending Tx bytes to finish writing
sleep_cpu(); // sleep. Will we waked by next alarm interrupt
RTC.clear_interrupt();
digitalWrite(ledPin, LOW);
delay(3); // wait >2 byte times for any pending Tx bytes to finish writing
sleep_cpu(); // sleep. Will we waked by next alarm interrupt
RTC.clear_interrupt();
}
RTC.disable_interrupt(); // ensure we stop receiving interrupts
detachInterrupt(int_number);
Serial.println("Going to snooze for 10 seconds...");
Serial.flush();
read_time();
Serial.flush();
RTC.snooze(10);
read_time();
Serial.flush();
Serial.println("...and wake up again.");
}
//CASE 3
void test_epoch_seconds() {
// Output the time calculated in epoch seconds at midnight for every day between 1/1/2000 and 12/31/2099.
// Also, convert the result back to a date/time and make sure it matches the original value.
// To ensure we are getting clean values to start with, no calculation results are written back to
// the RTC. Instead, we use the alarm to wake up when it rolls over midnight, then advance the clock
// to 23:59:59 of that same day. This way one 'day' passes per second, and a century's worth of tests
// will complete in ~10 hours.
unsigned char second;
unsigned char minute;
unsigned char hour;
unsigned char month;
unsigned char day;
unsigned int year;
unsigned long old_epoch_seconds = 946684800;
unsigned long new_epoch_seconds = 946684800 + 1;
Serial.println("Going to output and check epoch seconds at midnight on every day \n from 1/1/2000 to 12/31/2099. This will take a long time! (overnight)\n You probably want to capture the output to a file (e.g. hyperterminal). \n Press SPACE to continue or any other key to skip.\n");
Serial.flush();
while(!Serial.available()){}
if(Serial.read() == ' ')
{
Serial.println("Date, Seconds Since Epoch, Consistency Check Date, Consistency Check Result");
RTC.writeTime(old_epoch_seconds); // reset time to epoch
RTC.setAlarmRepeat(EVERY_DAY);
RTC.writeAlarm(old_epoch_seconds); // ensure alarm starts at a valid value too
RTC.enable_interrupt(); // make RTC generate a pulse every time one 'day' passes
while(new_epoch_seconds > old_epoch_seconds) // keep going until date rolls over to 1/1/2000 again
{
// fastforward to the end of the day. The math for converting hours/min to seconds is trivial;
// this test is mainly concerned with ensuring stuff like days-in-a-month and leap years are handled correctly.
RTC.readTime(); // restore known-good copy of date/time from chip to library's buffer
RTC.setHours(23);
RTC.setMinutes(59);
RTC.setSeconds(59);
RTC.writeTime(); // note: writing a new time resets the RTC's oscillator count ("milliseconds") to 0, so we have a full second until the next interrupt happens.
while(digitalRead(INT_PIN) != 0) {} // wait for 'day' to rollover. Pin 24 = INT2
RTC.readTime();
//store a copy of the original values
second = RTC.getSeconds();
minute = RTC.getMinutes();
hour = RTC.getHours();
day = RTC.getDays();
month = RTC.getMonths();
year = RTC.getYears();
printTime(0);
old_epoch_seconds = new_epoch_seconds;
new_epoch_seconds = RTC.date_to_epoch_seconds();
Serial.print(" , ");
Serial.print(new_epoch_seconds);
Serial.print(" , ");
// ensure that the result converted back to date matches the original value.
// Remember that this function will update the contents of the RTC library's buffer, NOT on the chip.
RTC.epoch_seconds_to_date(new_epoch_seconds);
printTime(0);
if( second == RTC.getSeconds() && minute == RTC.getMinutes() && hour == RTC.getHours() && day == RTC.getDays() && month == RTC.getMonths() && year == RTC.getYears() )
{
Serial.println(", Pass");
}
else
{
Serial.println(", FAIL!");
}
}
Serial.println("\n\nDone!");
RTC.disable_interrupt();
}
}
void printTime(byte type) {
// Print a formatted string of the current date and time.
// If 'type' is non-zero, print as an alarm value (seconds thru DOW/month only)
// This function assumes the desired time values are already present in the RTC library buffer (e.g. readTime() has been called recently)
if(!type)
{
Serial.print(int(RTC.getMonths()));
Serial.print("/");
Serial.print(int(RTC.getDays()));
Serial.print("/");
Serial.print(RTC.getYears());
}
else
{
//if(RTC.getDays() == 0) // Day-Of-Week repeating alarm will have DayOfWeek *instead* of date, so print that.
{
Serial.print(int(RTC.getDayOfWeek()));
Serial.print("th day of week, ");
}
//else
{
Serial.print(int(RTC.getDays()));
Serial.print("th day of month, ");
}
}
Serial.print(" ");
Serial.print(int(RTC.getHours()));
Serial.print(":");
Serial.print(int(RTC.getMinutes()));
Serial.print(":");
Serial.print(int(RTC.getSeconds()));
}
Once the upload is complete, open the Serial Monitor at 9600 Baud, and be sure to select "No line ending" to avoid the program reading newline characters instead of relevant data! From here, you are able to input an integer to perform its respective function.

Common Issues
- Be sure not to include parenthesis when setting the date or an alarm
- Select the "No line ending" option within the Serial Monitor
Downloads
Contact Us
If you have any questions or feedback, feel free to email us or make a post on our forum. Show us what you make by tagging @TinyCircuits on Instagram, Twitter, or Facebook so we can feature it.
Thanks for making with us!