Arduino Projects

Interface DS1307 RTC Module with Arduino

We all know that most MCUs we use for our projects are time-agnostic; simply put they are unaware of the time around them. It’s OK for most of our projects but once in a while when you come across an idea where keeping time is a prime concern, DS1307 RTC module is a savior. It’s perfect for projects containing data-logging, clock-building, time stamping, timers and alarms.

DS1307 RTC chip

At the heart of the module is a low-cost, quite accurate RTC chip from Maxim – DS1307. It manages all timekeeping functions and features a simple two-wire I2C interface which can be easily interfaced with any microcontroller of your choice.

The chip maintains seconds, minutes, hours, day, date, month, and year information. The date at the end of the month is automatically adjusted for months with fewer than 31 days, including corrections for leap year (valid up to 2100). The clock operates in either the 24-hour or 12-hour format with an AM/PM indicator.

The other cool feature of this board comes with SQW pin, which outputs one of four square-wave frequencies 1Hz, 4kHz, 8kHz or 32kHz and can be enabled programmatically.

NOTE

DS1307 come with an external 32kHz crystal for time-keeping. The problem with these crystals is that external temperature can affect their oscillation frequency. This change in frequency is negligible but it surely adds up.

This may sound like a problem, but it’s not. It actually results with the clock being off by around five or so minutes per month.

Battery Backup

The DS1307 incorporates a battery input, and maintains accurate timekeeping when main power to the device is interrupted.

The built-in power-sense circuit continuously monitors the status of VCC to detect power failures and automatically switches to the backup supply. So, you need not worry about power outages, your MCU can still keep track of time.

CR2032 Battery Holder on DS1307 Module

The bottom side of the board holds a battery holder for 20mm 3V lithium coincells. Any CR2032 battery can fit well.

Assuming a fully charged CR2032 battery with capacity 47mAh is used and chip consumes its minimum 300nA, the battey can keep the RTC running for a minimum of 17.87 years without an external 5V power supply.

47mAh/300nA = 156666.67 hours = 6527.78 days = 17.87 years

Onboard 24C32 EEPROM

DS1307 RTC module also comes with a 32 bytes 24C32 EEPROM chip from Atmel having limited read-write cycles. It can be used to save settings or really anything.

24C32 EEPROM Chip on DS1307 Module

The 24C32 EEPROM uses I2C interface for communication and shares the same I2C bus as DS1307.

TIP

The onboard 24C32 EEPROM has a hardwired I2C address and is set to 0x50HEX

Code for reading/writing onboard 24C32 EEPROM is given at the end of the tutorial.

Module’s Hidden Feature – DS18B20

There’s a provision on our DS1307 RTC module that often goes unnoticed. It allows us to install DS18B20 temperature sensor.

The 3 holes in the top corner right next to the battery holder(labled as U1) is where the DS18B20 is installed.

Provision for DS18B20 on DS1307 Module

Once you install the DS18B20, you will be able to get temperature readings from DS pin. These readings can further be used to compensate for temperature based time drift in code.

Follow the silk-screen to solder DS18B20. You might also need a 4.7K resistor between VCC and DS.

DS1307 RTC Module Pinout

The DS1307 RTC module has total 7 pins that interface it to the outside world. The connections are as follows:

DS1307 RTC Module Pinout

SQW pin outputs one of four square-wave frequencies 1Hz, 4kHz, 8kHz or 32kHz and can be enabled programmatically.

DS pin is supposed output temperature readings if your module has a DS18B20 temperature sensor installed right next to the battery holder(labled as U1).

SCL is the clock input for the I2C interface and is used to synchronize data movement on the serial interface.

SDA is the data input/output for the I2C serial interface.

VCC pin supplies power for the module. It can be anywhere between 3.3V to 5.5V.

GND is a ground pin.

BAT is a backup supply input for any standard 3V lithium cell or other energy source to maintain accurate timekeeping when main power to the device is interrupted.

Wiring DS1307 RTC module to Arduino UNO

Let’s hook the RTC up to the Arduino.

Connections are fairly simple. Start by connecting VCC pin to the 5V output on the Arduino and connect GND to ground.

Now we are remaining with the pins that are used for I2C communication. Note that each Arduino Board has different I2C pins which should be connected accordingly. On the Arduino boards with the R3 layout, the SDA (data line) and SCL (clock line) are on the pin headers close to the AREF pin. They are also known as A5 (SCL) and A4 (SDA).

If you have a Mega, the pins are different! You’ll want to use digital 21 (SCL) and 20 (SDA). Refer below table for quick understanding.

I2C Pin Configration of Arduino Uno,Arduino Nano and Arduino Mega
SCL SDA
Arduino Uno A5 A4
Arduino Nano A5 A4
Arduino Mega 21 20
Leonardo/Micro 3 2

The following diagram shows you how to wire everything.

Wiring DS1307 RTC module with Arduino
Wiring DS1307 RTC module with Arduino

Installing RTClib library

Communicating with a RTC module is a bunch of work. Fortunately, RTClib library was written to hide away all the complexities so that we can issue simple commands to read the RTC data.

To install the library navigate to the Sketch > Include Library > Manage Libraries…Wait for Library Manager to download libraries index and update list of installed libraries.

Arduino Library Installation - Selecting Manage Libraries in Arduino IDE

Filter your search by typing ‘rtclib’. There should be a couple entries. Look for RTClib by Adafruit. Click on that entry, and then select Install.

Installing RTClib Arduino library

Arduino Code – Reading Date & Time

The following sketch will give you complete understanding on how to set/read date & time on DS1307 RTC module and can serve as the basis for more practical experiments and projects.

#include <Wire.h>
#include "RTClib.h"

RTC_DS1307 rtc;

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

void setup () 
{
  Serial.begin(9600);
  delay(3000); // wait for console opening

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

  if (!rtc.isrunning()) {
    Serial.println("RTC lost power, lets set the time!");
	
	// Comment out below lines once you set the date & time.
    // Following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
	
    // Following line sets the RTC with an explicit date & time
    // for example to set January 27 2017 at 12:56 you would call:
    // rtc.adjust(DateTime(2017, 1, 27, 12, 56, 0));
  }
}

void loop () 
{
    DateTime now = rtc.now();
    
    Serial.println("Current Date & Time: ");
    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.println("Unix Time: ");
    Serial.print("elapsed ");
    Serial.print(now.unixtime());
    Serial.print(" seconds/");
    Serial.print(now.unixtime() / 86400L);
    Serial.println(" days since 1/1/1970");
    
    // calculate a date which is 7 days & 30 seconds into the future
    DateTime future (now + TimeSpan(7,0,0,30));
    
    Serial.println("Future Date & Time (Now + 7days & 30s): ");
    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.println();
    delay(1000);
}

Here’s how the output looks like in the serial monitor.

DS1307 Output On Serial Monitor
DS1307 Output On Serial Monitor

Code Explanation:

The sketch starts with including wire.h & RTClib.h libraries for communicating with the module. We then create an object of RTClib library and define daysOfTheWeek 2D character array to store days information.

In setup and loop sections of the code, we use following functions to interact with the RTC module.

begin() function ensures that the RTC module is connected.

isrunning() function reads the DS1307’s internal I2C registers to check if the chip has lost track of time. If the function returns false, we can then set the date & time.

adjust() function sets the date & time.  This is an overloaded function.

  • One overloaded method DateTime(F(__DATE__), F(__TIME__)) sets the date & time at which the sketch was compiled.
  • Second overloaded method DateTime(YYYY, M, D, H, M, s) sets the RTC with an explicit date & time. For example to set January 27 2017 at 12:56 you would call: rtc.adjust(DateTime(2017, 1, 27, 12, 56, 0));

now() function returns current date & time. Its return value is usually stored in the variable of datatype DateTime.

year() function returns current year.

month() function returns current month.

day() function returns current day.

dayOfTheWeek() function returns current day of the week. This function is usually used as an index of a 2D character array that stores days information like the one defined in above program daysOfTheWeek

hour() function returns current hour.

minute() function returns current minute.

second() function returns current seconds.

unixtime() function returns unix time in seconds. Unix time is a system for describing a point in time. It is the number of seconds that have elapsed since 00:00:00(known as Coordinated Universal Time – Thursday, 1 January 1970).

TimeSpan() function is used to add/subtract time to/from current time. You can add/subtract days, hours, minutes & seconds. It’s also an overloaded function.

  • now() + TimeSpan(seconds) returns the future time with seconds added into current time.
  • now() - TimeSpan(days,hours, minutes, seconds) returns the past time.

Arduino Code – Reading/Writing in 24C32 EEPROM

With DS1307 RTC module, as a bonus, you get 32 bytes of Electrically Erasable ROM. Its contents will not be erased even if main power to the device is interrupted.

The following program writes and then reads a message from the 24C32 EEPROM. You can use this program to save settings or passwords or really anything.

#include <Wire.h>

void setup()
{
    char somedata[] = "lastminuteengineers.com"; // data to write
    Wire.begin(); // initialise the connection
    Serial.begin(9600);
    Serial.println("Writing into memory...");
	
	// write to EEPROM
    i2c_eeprom_write_page(0x57, 0, (byte *)somedata, sizeof(somedata));

    delay(100); //add a small delay
    Serial.println("Memory written");
}

void loop()
{
    Serial.print("Reading memory: ");
    int addr=0; //first address
	
	// access the first address from the memory
    byte b = i2c_eeprom_read_byte(0x57, 0);

    while (b!=0)
    {
        Serial.print((char)b); //print content to serial port
        addr++; //increase address
        b = i2c_eeprom_read_byte(0x57, addr); //access an address from the memory
    }
    Serial.println(" ");
    delay(2000);
}

void i2c_eeprom_write_byte( int deviceaddress, unsigned int eeaddress, byte data ) {
    int rdata = data;
    Wire.beginTransmission(deviceaddress);
    Wire.write((int)(eeaddress >> 8)); // MSB
    Wire.write((int)(eeaddress & 0xFF)); // LSB
    Wire.write(rdata);
    Wire.endTransmission();
}

// WARNING: address is a page address, 6-bit end will wrap around
// also, data can be maximum of about 30 bytes, because the Wire library has a buffer of 32 bytes
void i2c_eeprom_write_page( int deviceaddress, unsigned int eeaddresspage, byte* data, byte length ) {
    Wire.beginTransmission(deviceaddress);
    Wire.write((int)(eeaddresspage >> 8)); // MSB
    Wire.write((int)(eeaddresspage & 0xFF)); // LSB
    byte c;
    for ( c = 0; c < length; c++)
        Wire.write(data[c]);
    Wire.endTransmission();
}

byte i2c_eeprom_read_byte( int deviceaddress, unsigned int eeaddress ) {
    byte rdata = 0xFF;
    Wire.beginTransmission(deviceaddress);
    Wire.write((int)(eeaddress >> 8)); // MSB
    Wire.write((int)(eeaddress & 0xFF)); // LSB
    Wire.endTransmission();
    Wire.requestFrom(deviceaddress,1);
    if (Wire.available()) rdata = Wire.read();
    return rdata;
}

// maybe let's not read more than 30 or 32 bytes at a time!
void i2c_eeprom_read_buffer( int deviceaddress, unsigned int eeaddress, byte *buffer, int length ) {
    Wire.beginTransmission(deviceaddress);
    Wire.write((int)(eeaddress >> 8)); // MSB
    Wire.write((int)(eeaddress & 0xFF)); // LSB
    Wire.endTransmission();
    Wire.requestFrom(deviceaddress,length);
    int c = 0;
    for ( c = 0; c < length; c++ )
        if (Wire.available()) buffer[c] = Wire.read();
}

Here’s how the output looks like in the serial monitor.

24C32 EEPROM Output On Serial Monitor