Skip to Content

Arduino and RTC Module DS3231

Arduino and RTC Module DS3231

In this tutorial you will learn how to use a DS3231 Real Time Clock (RTC) module with an Arduino. We will build a simple clock application with a display and buttons to set the time and date.

An RTC module is a device that keeps track of the current time and date. It operates independently of the main system power, allowing it to maintain accurate time even when the power is turned off. RTC modules typically use a small battery to keep running.

If you want maintain accurate time on your Arduino, even after a power loss, an RTC module is the way to go. Common applications for RTC modules include clocks, alarms, timers, and data logging systems.

Required Parts

You will need an DS3231 RTC Module and an Arduino for this project. I used an Arduino Uno but any other Arduino or even a ESP32/ESP8266 board will work as well. You will also need some kind of display to show the time. I am using a LCD1602 Display Module. Finally, you will need a few push buttons to set the time.

DS3231 RTC Module

Arduino

Arduino Uno

LCD1602 Display Module

Push buttons

USB Data Sync cable Arduino

USB Cable for Arduino UNO

Dupont wire set

Dupont Wire Set

Half_breadboard56a

Breadboard

Makerguides.com is a participant in the Amazon Services LLC Associates Program, an affiliate advertising program designed to provide a means for sites to earn advertising fees by advertising and linking to products on Amazon.com. As an Amazon Associate we earn from qualifying purchases.

Basics of the DS3231 Chip

The DS3231, is a small chip (38 x 22 x 14mm) with a high-precision real-time clock (RTC) and an integrated temperature-compensated crystal oscillator and crystal. This chip includes a battery input, allowing it to maintain accurate timekeeping even when the main power supply is disconnected.

DS3231 Chip
DS3231 Chip

The RTC keeps track of seconds, minutes, hours, day of the week, date, month, and year information. For months with fewer than 31 days, the date is automatically adjusted, including corrections for leap years. The clock operates in either 24-hour format or 12-hour format. It provides two programmable calendar alarms and a programmable square-wave output. Communication is via a I2C bus.

A precision, temperature-compensated voltage reference and comparator circuit monitor the VCC status, detect power failures, provide a reset output, and automatically switch to the backup power supply when necessary. The device integrates a digital temperature sensor, which can be accessed via the I2C interface.

The following section lists the main features of the DS3231 (source):

Technical specification

  • Operating Voltage: 3.3–5.5V
  • Clock Chip: High-precision clock chip DS3231
  • Clock Accuracy: 2ppm within the range of 0-40°C, annual error approximately 1 minute
  • Two calendar alarms
  • Programmable square-wave output
  • Real-time clock generates seconds, minutes, hours, day of the week, date, month, and year timekeeping, with leap year compensation valid until 2100
  • Built-in temperature sensor with an accuracy of ±3°C
  • Storage Chip: AT24C32 (32K storage capacity)
  • IIC bus interface, maximum transmission speed 400KHz (when operating voltage is 5V)
  • Can be cascaded with other IIC devices, 24C32 address can be modified by shorting A0/A1/A2, default address is 0x57
  • Uses CR2032 battery to ensure the clock continues to run normally after a power outage

Block diagram of DS3231

The following block diagram shows the internal components of the DS3231. You can see the crystal oscillator, the temperature sensor, the power control and the I2C interface.

Block Diagram of DS3231 Chip
Block Diagram of DS3231 Chip

The pins of the DS3231 are as follows: VCC and GND for general power supply and VBAT for the battery backup. SCL and SDA are for the I2C interface.

The pin labelled “33kHz” outputs the 32kHz clock signal. INT/SQW is an interrupt pin that can be used to signal alarms or serves as a programmable square-wave output.

RST is the reset pin that can be used to signal to the connected microprocessor that power was lost or restored. The “33kHz”, INT/SQW and RST pins have other functions as well. For more details have a look at the datasheet linked below:

Basics of the DS3231 RTC Module

The DS3231 Chip itself is too small to connect to an Arduino and is also missing a few required part. You therefore typically use a DS3231 RTC Module. The module has the missing components and also has a holder for a CR2032 battery that provides the backup power. The picture below shows the front and back of a typical DS3231 RTC Module:


Front and back of DS3231 RTC Module
Front and back of DS3231 RTC Module

Instead of a CR2032 battery you can also use a rechargeable LIR2032 battery but you need to be careful with the power supply voltage. More about this later.

Pinout of DS3231 RTC Module

The pinout of a DS3231 RTC Module is essentially the same as described for the DS3231 chip.

Pinout of DS3231 RTC Module
Pinout of DS3231 RTC Module

VCC and GND are for power supply, with a supply voltage of 3.3–5.5V. SCL and SDA are the pins for the I2C interface (with integrated 4.7k pullup-resistors). The 32K and SQW are outputs for the 32kHZ clock signal and the programmable square wave signal but you won’t need those here.

The I2C address of the DS3231 RTC Module is configurable via three solder pads (A0, A1, A2) that can be bridged, See the highlighted section in the lower right corner of the module in the image above. The default I2C address is 0x57, when no bridges are set. The picture below shows all possible configurations and the corresponding I2C addresses.

Setting I2C address of the DS3231 RTC Module
Configuring I2C address of DS3231 RTC Module (source)

Battery charging with DS3231 RTC Module

The DS3231 RTC Module allows you to charge the backup battery from the main power supply (VCC). The picture below shows the circuit diagram of the DS3231 RTC Module. In the yellow marked section, you see a 200Ω resistor and a 1N4148 diode that act as a very simple charging circuit.

Schematic  of DS3231 RTC Module
Schematic of DS3231 RTC Module

However, you need to disable this charging circuit when an non-rechargeable CR2032 battery is used with a supply voltage VCC of 5V. And if you use a re-chargeable LIR2032 battery, VCC should never get higher than 4.7 volts for safe charging.

The Battery charging circuit of DS3231 module article describes the problems of the charging circuit in more detail and also describes how to disable it. The following table from the article lists the different scenarios with different supply voltages and battery types, and what needs to be done:

Battery type3.3 V Supply5 V Supply
CR2032Battery not affectedDisable charging circuit
LIR2032Battery not affected
Charging does not work
Disable charging circuit, or
Make sure 5 V is actually 4.7 V

Connecting the DS3231 RTC Module to Arduino

Thanks to the I2C interface of the DS3231 RTC Module, connecting it to an Arduino is simple. First, connect the SCL and SDA pins of the DS3231 RTC Module to the corresponding pins on the Arduino board as shown below. Next, connect ground to GND and 3.3V (or 5V) to VCC of the DS3231 RTC Module and that’s it.

Connecting DS3231 RTC Module to Arduino
Connecting DS3231 RTC Module to Arduino

Note that running the DS3231 RTC Module on 3.3V allows us to have a CR2032 inserted, while connected to the power supply. As mentioned above, if you connect the RTC Module to 5V, you will need to disable the charging circuit. Otherwise it could damage an inserted CR2032 battery and there are reports of exploding batteries in this case!

Simple test code for DS3231 RTC Module

You will need a software library to communicate with the DS3231 RTC Module. There are several ones but I like the Arduino-DS3231 library by Korneliusz Jarzębski best. To install it go to the github repo and click the green “Code” button. Then select “Download Zip” from the menu as shown below:

Download ZIP file for Arduino-DS3231library
Download ZIP file for Arduino-DS3231library

This will download a file named “Arduino-DS3231-dev.zip” to your computer. To install this ZIP library follow the usual steps. Click on Sketch -> Add .ZIP Library, and then select the file Arduino-DS3231-dev.zip, you just downloaded.

Installing a .ZIP Arduino library
Installing a .ZIP Arduino library

With the library installed, we can test the function of the DS3231 RTC Module. The following simple code sets the time and date in RTC Module to the compilation time of the code and then prints time and date in a loop:

#include "Wire.h"
#include "DS3231.h"

DS3231 rtc;

void setup() {
  Serial.begin(9600);
  rtc.begin();
  rtc.setDateTime(__DATE__, __TIME__);
}

void loop() {
  RTCDateTime dt = rtc.getDateTime();
  Serial.println(rtc.dateFormat("d-m-Y H:i:s", dt));
  delay(500);
}

At the beginning of the code, we include the necessary libraries for I2C communication and the DS3231 RTC module.

#include "Wire.h"
#include "DS3231.h"

The Wire.h library is used for I2C communication, which is how the Arduino communicates with the DS3231 module. The DS3231.h library provides functions specific to the DS3231 RTC.

Next, we create an instance of the DS3231 class, which allows us to interact with the RTC module.

DS3231 rtc;

Setup Function

In the setup() function, we perform the initial configuration for the Arduino and the RTC module.

void setup() {
  Serial.begin(9600);
  rtc.begin();
  rtc.setDateTime(__DATE__, __TIME__);
}

First, we initialize the Serial communication at a baud rate of 9600 using Serial.begin(9600). This allows us to send data to the Serial Monitor.

Next, we call rtc.begin() to initialize the RTC module. This step is crucial as it prepares the module for communication.

Finally, we set the current date and time using rtc.setDateTime(__DATE__, __TIME__). The macros __DATE__ and __TIME__ automatically insert the date and time when the code was compiled, ensuring that the RTC starts with the current time, when compiling and uploading the sketch. Note that the time will not be current, when the Arduino is restarted, but we will deal with that later.

Loop Function

The loop() function is where the main functionality of the program occurs. This function runs continuously after the setup() function has completed.

void loop() {
  RTCDateTime dt = rtc.getDateTime();
  Serial.println(rtc.dateFormat("d-m-Y H:i:s", dt));
  delay(500);
}

In the first line of the loop, we retrieve the current date and time from the RTC using RTCDateTime dt = rtc.getDateTime(). This function returns a RTCDateTime object containing the current date and time.

Next, we format the date and time as a string and print it to the Serial Monitor with Serial.println(rtc.dateFormat("d-m-Y H:i:s", dt)). The format specified here is day-month-year hours:minutes:seconds. See the DS3231_dateformat.ino file of the Arduino-DS3231 library for more formatting examples.

Finally, we introduce a delay of 500 milliseconds using delay(500), preventing the Serial Monitor from being flooded with messages.

Output example

If you upload the code and open the Serial Monitor, you should see the date and time being printed as follows:

RTC date and time printed in Serial Monitor
RTC date and time printed in Serial Monitor

Showing time and date of RTC Module on LCD

In this section, we are adding and LCD to our circuit. This will allow us to display the date and time on an LCD instead of the Serial Monitor.

Since the LCD1602 Display Module is also an I2C device, connecting it is easy. Simple connect the SCL and SDA pins of the LCD Module in parallel to the DS3231 to the corresponding Arduino pins. For the power supply, connect VCC of the LCD to 5V and GND to ground, as shown below:

Connecting LCD and RTC Module to Arduino
Connecting LCD and RTC Module to Arduino

Some LCD modules work with 3.3V as well but mine needed 5V. Next we write some code.

Example code to show DS3231 RTC time on LCD

Before we can display anything on the LCD, we need to install a suitable library. My favourite is the “LiquidCrystal_I2C.h“ library by Frank de Brabander. To install it just open the Library Manager, search for “LiquidCrystal_I2C”, pick the one by Frank de Brabander, and press “INSTALL”:

Installing LiquidCrystal_I2C library by Frank de Brabander
Installing LiquidCrystal_I2C library by Frank de Brabander

Below is the code that displays date and time from the RTC on the LCD. It is similar to the code above and the only real other change is the formatting of the date and time information and the control of the LCD.

#include "Wire.h"
#include "DS3231.h"
#include "LiquidCrystal_I2C.h"

LiquidCrystal_I2C lcd(0x3F, 16, 2);
DS3231 rtc;

void setup() {
  Serial.begin(9600);
  rtc.begin();
  rtc.setDateTime(__DATE__, __TIME__);
  lcd.init();
  lcd.backlight();
  lcd.clear();
}

void loop() {
  static char buf[32];

  RTCDateTime dt = rtc.getDateTime();

  lcd.setCursor(4, 0);
  sprintf(buf, "%2d:%02d:%02d", dt.hour, dt.minute, dt.second);
  lcd.print(buf);

  lcd.setCursor(3, 1);
  sprintf(buf, "%4d-%02d-%02d", dt.year, dt.month, dt.day);
  lcd.print(buf);

  delay(500);
}

First we include the library and create the lcd object:

#include "LiquidCrystal_I2C.h"

LiquidCrystal_I2C lcd(0x3F, 16, 2);

The parameters passed to the LiquidCrystal_I2C constructor specify the I2C address of the display (0x3F) and the number of columns and rows (16 and 2, respectively). If you use the a 20×4 LCD display instead you need to change this. Also note that some displays have the I2C address 0x27 instead of 0x3F.

For more detailed information on LCD displays have a look at our tutorials on How to use a 16×2 character LCD with Arduino and Character I2C LCD with Arduino Tutorial (8 Examples).

Setup function

In the setup function, we now also initialize the display, activate the backlight and clear the display:

void setup() {
  ...
  lcd.init();
  lcd.backlight();
  lcd.clear();
}

Loop function

In the loop function, we create a static buffer buf that stores the formatted date and time strings. For the formatting we will use the sprintf function.

void loop() {
  static char buf[32];

  RTCDateTime dt = rtc.getDateTime();

  sprintf(buf, "%2d:%02d:%02d", dt.hour, dt.minute, dt.second);
  lcd.setCursor(4, 0);
  lcd.print(buf);

  sprintf(buf, "%4d-%02d-%02d", dt.year, dt.month, dt.day);
  lcd.setCursor(4, 1);
  lcd.print(buf);

  delay(500);
}

After having received the current time from the RTC via getDateTime(), we first print hour, minute and second information to the buffer, using the format “%2d:%02d:%02d“. This will produce a string that looks like 5:21:31 for the time, for instance. We then set the cursor to the first row (0) and fifths column (4) and print the time string to the LCD.

For the display of the date we are using the format “%4d-%02d-%02d“, which will result in a string “2024-09-14“, for instance. The date string is written to cursor position (3,1), so one line below time and one character to the left.

Output example

If you upload and run this code, you should see time and data displayed on the LCD similar to the picture below:

Time and date displayed on LCD
Time and date displayed on LCD

Setting time and date of RTC Module using Buttons

So far, we have set the time and date and of the RTC Module based on the compile time of the sketch. You can also call the setDateTime() function to set an arbitrary time and date, for instance:

// YYYY, MM, DD, HH, II, SS
rtc.setDateTime(2024, 11, 28, 19, 21, 00);

However, in both cases the time is set once when the Arduino starts and you cannot change it afterwards without changing the code and uploading the changed sketch.

That is very limiting and we therefore going to add two buttons that allow us to change time and date while the Arduino is running (and not connected to a computer).

Below is the wiring diagram. The wiring of the RTC Module and LCD is the same as before. New are two buttons. The red button is for toggling the time setting mode and is connected to pin 5 of the Arduino. The yellow button is for incrementing the time unit that is currently selected and is connected to pin 4 of the Arduino. Both buttons are also connected to ground.

Connecting Buttons, LCD and RTC Module to Arduino
Connecting Buttons, LCD and RTC Module to Arduino

If you need to learn more about push buttons, have a look at our How To Use A Push Button With Arduino tutorial. In the next section we will write the code to actually set the date and time with the two buttons.

Code to set DS3231 RTC time and date with Buttons

We want implement the following functionality: When the red button is pressed once we enter the setting mode and show a blinking cursor at the time unit we want to edit. If the red button is pressed again we switch to the next time unit. The yellow button increments the time unit currently selected.

The short video clip shows how this works in practice. The blinking cursor is a bit hard to see, but hopefully you see how the hours are incremented from 00 to 03:

Setting of hour value of RTC on LCD
Setting of hour value of RTC on LCD

Below is the complete code. It is a bit long and complex, so have a quick look first and then we will explain the details.

#include "Wire.h"
#include "DS3231.h"
#include "LiquidCrystal_I2C.h"

const int setPin = 5;
const int incPin = 4;

LiquidCrystal_I2C lcd(0x3F, 16, 2);
DS3231 rtc;
RTCDateTime dt;
int index = 0;

void init_buttons() {
  pinMode(setPin, INPUT_PULLUP);
  pinMode(incPin, INPUT_PULLUP);
}

void init_lcd() {
  lcd.init();
  lcd.backlight();
  lcd.clear();
}

void init_rtc() {
  rtc.begin();
  rtc.setDateTime(__DATE__, __TIME__);
}

void display_time() {
  static char buf[32];
  lcd.setCursor(4, 0);
  sprintf(buf, "%02d:%02d:%02d", dt.hour, dt.minute, dt.second);
  lcd.print(buf);

  lcd.setCursor(4, 1);
  sprintf(buf, "%02d/%02d/%02d", dt.day, dt.month, dt.year);
  lcd.print(buf);
}

bool is_editmode() {
  return index > 0;
}

void display_cursor() {
  if (is_editmode()) {
    int c = 5 + ((index - 1) % 3) * 3;
    int r = index < 4 ? 0 : 1;
    lcd.setCursor(c, r);
    lcd.blink_on();
  } else {
    lcd.blink_off();
  }
}

void inc_time() {
  switch (index) {
    case 1: dt.hour = (dt.hour + 1) % 24; break;
    case 2: dt.minute = (dt.minute + 1) % 60; break;
    case 3: dt.second = (dt.second + 1) % 60; break;
    case 4: dt.day = (dt.day % 31) + 1; break;
    case 5: dt.month = (dt.month % 12) + 1; break;
    case 6: dt.year = ((dt.year + 1) % 50); break;
  }
}

void update_time() {
  if (!is_editmode()) {
    dt = rtc.getDateTime();
    dt.year -= 2000;
  }
}

void check_editmode() {
  if (digitalRead(setPin) == LOW) {
    index = (index + 1) % 7;
    if (!is_editmode()) {
      rtc.setDateTime(dt.year + 2000, dt.month, dt.day,
                      dt.hour, dt.minute, dt.second);
    }
  }
}

void check_incmode() {
  if (is_editmode() && (digitalRead(incPin) == LOW)) {
    inc_time();
  }
}

void setup() {
  init_rtc();
  init_buttons();
  init_lcd();
}

void loop() {
  update_time();
  display_time();
  display_cursor();
  check_editmode();
  check_incmode();
  delay(200);
}

The code above allows you to set the time and date of an RTC (Real-Time Clock) using two buttons. The red button is used to select which part of the date/time to edit, while the yellow button increments the selected value. The current time and date are displayed on a 16×2 LCD screen.

Libraries and Constants

We start by including the necessary libraries for I2C communication, the DS3231 RTC module, and the LiquidCrystal display.

#include "Wire.h"
#include "DS3231.h"
#include "LiquidCrystal_I2C.h"

Next, we define the pins for the buttons used to set and increment the time.

const int setPin = 5;
const int incPin = 4;

Global Variables

We create instances of the LCD and RTC classes, as well as a variable to hold the date and time. We also initialize an index variable to keep track of which part of the date/time is being edited.

LiquidCrystal_I2C lcd(0x3F, 16, 2);
DS3231 rtc;
RTCDateTime dt;
int index = 0;

Button Initialization

The init_buttons() function sets the button pins as input with internal pull-up resistors, ensuring they read HIGH when not pressed.

void init_buttons() {
  pinMode(setPin, INPUT_PULLUP);
  pinMode(incPin, INPUT_PULLUP);
}

LCD Initialization

In the init_lcd() function, we initialize the LCD, turn on the backlight, and clear any previous data on the display.

void init_lcd() {
  lcd.init();
  lcd.backlight();
  lcd.clear();
}

RTC Initialization

The init_rtc() function initializes the RTC and sets the current date and time using the compilation date and time. You could also set any other starting time and date you like, since we will be able to change it, using the buttons.

void init_rtc() {
  rtc.begin();
  rtc.setDateTime(__DATE__, __TIME__);
}

Display Time Function

The display_time() function formats and displays the current time and date on the LCD. It uses a static buffer to hold the formatted strings.

void display_time() {
  static char buf[32];
  lcd.setCursor(4, 0);
  sprintf(buf, "%02d:%02d:%02d", dt.hour, dt.minute, dt.second);
  lcd.print(buf);

  lcd.setCursor(4, 1);
  sprintf(buf, "%02d/%02d/%02d", dt.day, dt.month, dt.year);
  lcd.print(buf);
}

The output on the LCD should look like this:

Time and Date format on LCD
Time and Date format on LCD

Note that we display only the last two digits of the current year. This will make the positioning of the cursor and the editing of the year simpler.

Edit Mode Check

The is_editmode() function checks if the index is greater than zero, indicating that we are in edit mode.

bool is_editmode() {
  return index > 0;
}

Display Cursor Function

The display_cursor() function manages the cursor’s position and blinking on the LCD based on whether we are in edit mode or not.

void display_cursor() {
  if (is_editmode()) {
    int c = 5 + ((index - 1) % 3) * 3;
    int r = index < 4 ? 0 : 1;
    lcd.setCursor(c, r);
    lcd.blink_on();
  } else {
    lcd.blink_off();
  }
}

Based on the index, we calculate the row r and column c of the cursor position and switch it on.

Increment Time Function

The inc_time() function increments the selected time or date component based on the current index. It wraps around when reaching the maximum value for each component.

void inc_time() {
  switch (index) {
    case 1: dt.hour = (dt.hour + 1) % 24; break;
    case 2: dt.minute = (dt.minute + 1) % 60; break;
    case 3: dt.second = (dt.second + 1) % 60; break;
    case 4: dt.day = (dt.day % 31) + 1; break;
    case 5: dt.month = (dt.month % 12) + 1; break;
    case 6: dt.year = ((dt.year + 1) % 50); break;
  }
}

Update Time Function

The update_time() function retrieves the current date and time from the RTC unless we are in edit mode. It also adjusts the year to a two-digit format.

void update_time() {
  if (!is_editmode()) {
    dt = rtc.getDateTime();
    dt.year -= 2000;
  }
}

Check Edit Mode Function

The check_editmode() function checks if the red set button is pressed. In edit mode it increments the index, meaning we are cycling through the time units (hour, minute, second, day, months, year) at each press of the red button. Once we cycled through all time units, edit mode is disabled and the RTC is updated with the new values.

void check_editmode() {
  if (digitalRead(setPin) == LOW) {
    index = (index + 1) % 7;
    if (!is_editmode()) {
      rtc.setDateTime(dt.year + 2000, dt.month, dt.day,
                      dt.hour, dt.minute, dt.second);
    }
  }
}

Check Increment Mode Function

The check_incmode() function checks if the increment button is pressed while in edit mode. If it is, it calls the inc_time() function to increment the selected value.

void check_incmode() {
  if (is_editmode() && (digitalRead(incPin) == LOW)) {
    inc_time();
  }
}

Setup Function

In the setup() function, we initialize the RTC, buttons, and LCD.

void setup() {
  init_rtc();
  init_buttons();
  init_lcd();
}

Loop Function

Finally, the loop() function continuously updates the time, displays it, manages the cursor, and checks for button presses to enter edit mode or increment values. It includes a short delay to debounce the button presses and reduces the display updates.

void loop() {
  update_time();
  display_time();
  display_cursor();
  check_editmode();
  check_incmode();
  delay(200);
}

And that is the complete code for a Real Time Clock on an LCD with buttons to set time and date.

Conclusions

In this tutorial you learned how to use a DS3231 Real Time Clock (RTC) module with an Arduino. We built a simple clock application with a display and buttons to set the time and date.

There are a few obvious extensions. You could add third button that allows you to decrement time units, in addition to the yellow button that can only increment. An even nicer solution would be to use a Rotary Encoder. A push on the Rotary Encoder Button would toggle the setting mode and turning the Encoder Knob increments or decrements the time unit.

Furthermore, it would be nice to have an alarm for the clock. The DS3231 RTC and the Arduino-DS3231 library support alarms. You just would have to connect a buzzer to the Arduino and add the functionality to set the alarm time.

Finally, while the DS3231 RTC keeps accurate track of time and takes into account leap years, it knows nothing about daylight saving time (DST). You could either write code to adjust for the switch from daylight saving time (DST) to standard time (ST) or you could synchronize the clock with an internet time provider. See the Automatic Daylight Savings Time Clock tutorial for more details. However, you better use an ESP32 in this case, because you will need an internet connection (WiFi).

If you want to learn a bit more about clocks with different display types and time synchronization have a look at the Digital Clock on e-Paper Display, the Digital Clock with CrowPanel 3.5″ ESP32 Display and the How to synchronize ESP32 clock with SNTP server tutorials.

Have fun and happy tinkering ; )

Derek

Wednesday 29th of March 2023

found solution. line 19 should read LCD.BEGIN();

Derek

Wednesday 29th of March 2023

Line 19 LCD.INIT(); error message exit status 1

Compilation error: 'class LiquidCrystal_I2C' has no member named 'init'

Can you give me a fix for this please