In this tutorial, we will discuss the purpose of getting the current date and time on the Arduino, what is a Real-Time Clock, what is a DS3231 RTC module and we will build a project using a DS3231 RTC module, a 16×2 I2C LCD and an Arduino Uno.

Why Keep Track of the Date and Time?

Keeping track of the current date/time for an Arduino has many purposes. One use for it is for recording/log purposes. For example, an Arduino Weather Station needs timestamps in recording weather data. Another example is for an Arduino digital clock or calendar. Arduino-based clocks use the current time as a timer for reminders or to execute a scheduled command via the Arduino’s I/O pins. Depending on the project, having a way to get the current date and time is very useful.

How to Get the Current Date and Time on an Arduino

There are several ways to get the current date and time. We can get it from a Real-Time Clock (RTC), a GPS device, or a time server.

  1. Real-Time Clock (RTC) – A Real-Time Clock, or RTC for short, is an integrated circuit that keeps track of time. It uses a back-up battery to maintain the time in the event that the main power source is removed.
  2. Global Positioning Device (GPS) – A GPS device communicates with satellites to determine its location anywhere in the world. Its GPS data also contains time data.
  3. Time Server– A Time Server is a computer on a network that reads the time from some reference clock and distributes it to the network. The clock source of a time server can be another time server, an atomic clock, or a radio clock.

An RTC is a very popular and accurate source of time and date in an embedded system like an Arduino because it has low power consumption.

If you want to learn how to communicate with an internet time server to get the current time and date, please read How to Keep Track of the Date and Time on an Arduino.

The DS3231 Real Time Clock Module

DS3231-real-time-clock-module
Figure 1: DS3231 Real-Time Clock Module

The DS3231 RTC module is a real-time clock module using the DS3231 IC. The DS3231 IC is a very affordable and extremely accurate RTC with an I2C interface. It is very accurate because it uses an integrated temperature-compensated crystal oscillator (TCXO) along with a crystal. To keep track of time even if the main power source is removed, the DS3231 has a backup battery mounted at the back of the module. The chip automatically switches between main and backup power sources when necessary.

The RTC keeps track of seconds, minutes, hours, day, date, month, and year data. It also automatically adjusts for months with less than 31 days and also for leap years. The clock can operate in either 24H or 12H (with AM/PM) formats. There are also two programmable time-of-day alarms and also a programmable square-wave output. Communication with the RTC is done through an I2C interface with a fixed default address of 0x68.

Aside from the RTC chip, this particular module also has a 24C32 EEPROM chip. An EEPROM is a kind of data storage device wherein you can read/write data. The 24C32 has 32 bytes of available data storage space. It shares the module’s I2C bus with the DS3231 and has the default address of 0x57. We can change the EEPROM’s default address by bridging the solder pads indicated by A0, A1, and A2 as shown in Figure 2.

eeprom-address-solder-pads
Figure 2: EEPROM address solder pad combinations

Module Pinouts

DS3231-real-time-clock-module-pinout
Figure 3: DS3231 Module Pinout Diagram
  • 32K – outputs from the DS3231 chip a very accurate 32KHz oscillator
  • SQW – outputs a square-wave signal from the DS3231 chip. The frequency of the square-wave can be changed between 1Hz, 4kHz, 8kHz, or 32kHz programmatically. this pin can also be used programmed as an interrupt output.
  • SCL – input pin for I2C Serial Clock
  • SDA – input/output pin for I2C Serial Data
  • VCC – power source input pin for the module; can be any voltage from +3.3V to +5.5V DC
  • GND – Ground pin connection
  • The SCL, SDA, VCC, and GND pins at the right side of the module are connected internally at the left side pins with the same label.

Project: Arduino Calendar Clock

After learning about timekeeping and the DS3231 RTC, it is now time to build a project using the DS3231 RTC. For this project, we will make a simple Arduino Calendar Clock using a DS3231 module, a 16×2 I2C LCD, and an Arduino Uno board.

Components Required

Wiring Diagram

The wiring diagram for our project is shown in Figure 4. Because we are using I2C, all devices share a common bus consisting of only 4 wires.

arduino-calendar-clock-wiring-diagram
Figure 4: Wiring Diagram

Arduino Sketch

To make it easy for us to develop the code of our project, we will use libraries and create custom functions to make our code easier to read.

Libraries

Our project will include the following libraries. See Figure 5 to check which libraries to install using the Arduino IDE’s built-in Library Manager.

  • Wire.h library for the I2C interface (included in Arduino IDE)
  • LiquidCrystal_I2C.h library (by Frank de Brabander) for the I2C 16×2 LCD module (GitHub link)
  • RTClib.h library (by Adafruit) for the DS3231 RTC module (GitHub link)
library-manager
Figure 5: Library Manager

The Wire.h library and the I2C protocol were already discussed in previous articles (here and here) and therefore will not be addressed in this tutorial.

To start our sketch, add the abovementioned libraries to our code by using the keyword #include. We will also initialize two objects lcd() and rtc to be used for communicating with the LCD and DS3231 respectively.

#include <Wire.h>                   // for I2C communication
#include <LiquidCrystal_I2C.h>      // for LCD
#include <RTClib.h>                 // for RTC

LiquidCrystal_I2C lcd(0x27, 16, 2); // create LCD with I2C address 0x27, 16 characters per line, 2 lines
RTC_DS3231 rtc;                     // create rtc for the DS3231 RTC module, address is fixed at 0x68

Custom Functions: updateRTC() and updateLCD()

To make our code easier to manage, we will create two custom functions.

The first function we will code is the function updateRTC(). This function will be responsible for asking the user for the date and time and updating the RTC’s internal clock with the user’s input data. After getting the user input, we can update the RTC’s internal clock by using the function rtc.adjust() from the RTCLib.h library. The rtc.adjust() function receives a parameter with type DataTime which it uses to update the rtc’s internal time and date.

/*
   function to update RTC time using user input
*/
void updateRTC()
{
  
  lcd.clear();  // clear LCD display
  lcd.setCursor(0, 0);
  lcd.print("Edit Mode...");

  // ask user to enter new date and time
  const char txt[6][15] = { "year [4-digit]", "month [1~12]", "day [1~31]",
                            "hours [0~23]", "minutes [0~59]", "seconds [0~59]"};
  String str = "";
  long newDate[6];

  while (Serial.available()) {
    Serial.read();  // clear serial buffer
  }

  for (int i = 0; i < 6; i++) {

    Serial.print("Enter ");
    Serial.print(txt[i]);
    Serial.print(": ");

    while (!Serial.available()) {
      ; // wait for user input
    }

    str = Serial.readString();  // read user input
    newDate[i] = str.toInt();   // convert user input to number and save to array

    Serial.println(newDate[i]); // show user input
  }

  // update RTC
  rtc.adjust(DateTime(newDate[0], newDate[1], newDate[2], newDate[3], newDate[4], newDate[5]));
  Serial.println("RTC Updated!");
}

The second custom function we will create is the function updateLCD(). This function will update or refresh the text displayed on the LCD. Inside this function, we will first get the time and date from the RTC. This is done by calling rtc.now() function which is included in the RTCLib.h library.

The function rtc.now() in our code returns a DateTime data type that contains the current date and time of the rtc. We then assign the data to different variables for additional formatting on the LCD. After assigning the variables, we use the functions lcd.setCursor() and lcd.print() from the LiquidCrystal_I2C.h to position the cursor and to display the text respectively on the LCD. The code below shows how these functions come together to get the rtc time, format the text and display it to the LCD.

/*
   function to update LCD text
*/
void updateLCD()
{

  /*
     create array to convert digit days to words:

     0 = Sunday    |   4 = Thursday
     1 = Monday    |   5 = Friday
     2 = Tuesday   |   6 = Saturday
     3 = Wednesday |
  */
  const char dayInWords[7][4] = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"};

  /*
     create array to convert digit months to words:

     0 = [no use]  |
     1 = January   |   6 = June
     2 = February  |   7 = July
     3 = March     |   8 = August
     4 = April     |   9 = September
     5 = May       |   10 = October
     6 = June      |   11 = November
     7 = July      |   12 = December
  */
  const char monthInWords[13][4] = {" ", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", 
                                         "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};

  // get time and date from RTC and save in variables
  DateTime rtcTime = rtc.now();

  int ss = rtcTime.second();
  int mm = rtcTime.minute();
  int hh = rtcTime.twelveHour();
  int DD = rtcTime.dayOfTheWeek();
  int dd = rtcTime.day();
  int MM = rtcTime.month();
  int yyyy = rtcTime.year();

  // move LCD cursor to upper-left position
  lcd.setCursor(0, 0);

  // print date in dd-MMM-yyyy format and day of week
  if (dd < 10) lcd.print("0");  // add preceeding '0' if number is less than 10
  lcd.print(dd);
  lcd.print("-");
  lcd.print(monthInWords[MM]);
  lcd.print("-");
  lcd.print(yyyy);

  lcd.print("  ");
  lcd.print(dayInWords[DD]);

  // move LCD cursor to lower-left position
  lcd.setCursor(0, 1);

  // print time in 12H format
  if (hh < 10) lcd.print("0");
  lcd.print(hh);
  lcd.print(':');

  if (mm < 10) lcd.print("0");
  lcd.print(mm);
  lcd.print(':');

  if (ss < 10) lcd.print("0");
  lcd.print(ss);

  if (rtcTime.isPM()) lcd.print(" PM"); // print AM/PM indication
  else lcd.print(" AM");
}

Standard Functions: setup() and loop()

The last phase in completing our code for an Arduino Calendar Clock is to add the standard Arduino functions setup() and loop().

Inside setup(), we will initialize the serial interface, the lcd and the rtc objects. To initialize the serial with a baud rate of 9600 bps, we will use the code Serial.begin(9600);. For the LCD, we need to initialize the LCD object and switch-on the backlight of the display. This is achieved by the codes lcd.init(); and lcd.backlight();. And finally, we add the code rtc.begin(); to initialize the rtc object.

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

  lcd.init();       // initialize lcd
  lcd.backlight();  // switch-on lcd backlight

  rtc.begin();       // initialize rtc
}

For the loop() function, we will update the text displayed on the LCD by calling updateLCD();. We will also add the capability to accept user input to update the RTC’s internal clock. If the user sends the char ‘u’ via the serial monitor, it means the user wants to modify the set time and date of the rtc. If this is the case, then we call the function updateRTC(); to handle user input and update the RTC internal clock.

void loop()
{
  updateLCD();  // update LCD text

  if (Serial.available()) {
    char input = Serial.read();
    if (input == 'u') updateRTC();  // update RTC time
  }
}

Our sketch is now complete. Save the sketch as arduino-rtc-tutorial.ino and upload it to your Arduino Uno.

Project Test

After uploading the sketch, your Arduino Uno should display the date and time on the LCD as shown in Figure 6.

arduino-calendar-clock
Figure 6: Arduino Calendar Clock

To change the date/time, open your Serial Monitor, and send the letter ‘u’. And then just follow the on-screen prompts to enter the new date and time.

serial-monitor
Figure 7: Serial Monitor

In summary, an RTC module is an easy and inexpensive way to add timekeeping capability to an Arduino based project. This tutorial just showed the basic capability of the DS3231 RTC module. And with more tinkering, you can find lots of uses for this module.