Skip to Content

Real-Time-Clock DS3231 with ESP32

Real-Time-Clock DS3231 with ESP32

In this tutorial you will learn how to use a DS3231 Real Time Clock (RTC) module with an ESP32.

An RTC module is an external clock 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.

I will show you how to use an RTC in combination with the ESP32 in deep-sleep, how to adjust the RTC for daylight savings time and how to synchronize the RTC with an internet time server.

Required Parts

You will need an DS3231 RTC Module and an ESP32 for this project. I am using the ES32 lite as a microprocessor, since it is has a battery charging interface, which allows you to run the ESP32 and the RTC on a LiPo battery. However, any other ESP32 or ESP8266 will work as well. To display the time, I chose an OLED but you could also go with an LCD display.

ESP32 lite Lolin32

ESP32 lite

USB data cable

USB Data Cable

DS3231 RTC Module

Dupont wire set

Dupont Wire Set

Half_breadboard56a

Breadboard

OLED display

OLED Display

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 holder, allowing it to run 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 ESP32 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 shows 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 ESP32 lite

Connecting the DS3231 RTC Module to an ES32 is simple. First connect SCL of the DS3231 to pin 23 of the ESP32. Then connect SDA to pin 19 of the DS3231. Finally, connect ground to GND, and 3.3V to VCC as show below:

Connecting DS3231 RTC Module to ESP32 lite
Connecting DS3231 RTC Module to ESP32 lite

Since we are running DS3231 RTC Module on 3.3V, you could have a CR2032 battery inserted, while connected to the power supply.

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 sketch 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.printf("%4d-%02d-%02d %02d:%02d:%02d\n",
                dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second);
  delay(1000);
}

The code starts by including the Wire.h library for I2C communication and the DS3231.h library for communicating with the DS3231 RTC.

Next, we create an instance of the DS3231 class. In the setup() function, we initialize RTC and set the current date and time using rtc.setDateTime(). The macros __DATE__ and __TIME__ automatically insert the date and time when the sketch was compiled. Note that the time will not be current, when the ESP32 is restarted after the code was compiled and uploaded, but we will handle that later.

In the loop function, we retrieve the current date and time from the RTC using RTCDateTime dt = rtc.getDateTime() and use printf to print date and time to the Serial Monitor.

Output example

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

RTC date and time printed in Serial Monitor

Daylight Savings Time with DS3231 RTC Module

While the DS3231 keeps accurate track of date and time, it does not adjust for Daylight Savings Time (DST). Which means the DS3231 will report the wrong time (and date) when your country switches to DST or back to Standard Time (ST).

There are essentially two ways to handle this. 1) you could add a button that manually switches from DST to ST and vice versa. This is obviously not optimal. 2) you add code that performs the switch automatically for your country’s time zone.

In this section, I show you how to automatically handle daylight savings time, while using the DS3231 RTC Module. We are going to use the ezTime Library for this. You can install it via the Library Manager as usual:

Install ezTime Library via Library Manager
Install ezTime Library via Library Manager

Once installed you can use it to adjust the DS3231 RTC to daylight savings time using the following code:

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

const char* TIMEZONE = "AEST-10AEDT,M10.1.0,M4.1.0/3";  // Melbourne

Timezone loc;
DS3231 rtc;

void setup() {
  Serial.begin(9600);
  rtc.begin();
  rtc.setDateTime(2024,12,4,3,16,30);  // UTC
  loc.setPosix(TIMEZONE);
}

void loop() {
  RTCDateTime dt = rtc.getDateTime();
  UTC.setTime(dt.hour, dt.minute, dt.second, dt.day, dt.month, dt.year);

  Serial.printf("RTC: %4d-%02d-%02d %02d:%02d:%02d\n",
                dt.year, dt.month, dt.day, 
                dt.hour, dt.minute, dt.second);

  Serial.printf("LOC: %4d-%02d-%02d %02d:%02d:%02d\n",
                loc.year(), loc.month(), loc.day(), 
                loc.hour(), loc.minute(), loc.second());                
               
  Serial.println();

  delay(5000);
}

In the code above, we interface an ESP32 with a DS3231 Real-Time Clock (RTC) module to keep track of the current time, including adjustments for daylight savings time. The program initializes the RTC, sets a specific date and time, and then continuously retrieves and displays the current UTC and local time every 5 seconds.

Let’s break down the code into its components for a clearer understanding.

Libraries Included

We start by including the necessary libraries for our project. The Wire.h library is used for I2C communication, which is how the ESP32 communicates with the DS3231 RTC. The DS3231.h library provides functions specifically for interacting with the DS3231 module, and ezTime.h is used for handling time zones and daylight savings adjustments.

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

Constants and Variables

Next, we define a constant for the timezone. In this case, we are using AEST-10AEDT,M10.1.0,M4.1.0/3, which corresponds to Melbourne, Australia. This string indicates the standard time offset and the rules for daylight savings time.

const char* TIMEZONE = "AEST-10AEDT,M10.1.0,M4.1.0/3";  // Melbourne

The parts of this time zone definition are as follows

  • AEST: Australian Eastern Standard Time
  • -10: UTC offset of 10 hours ahead of Coordinated Universal Time (UTC)
  • AEDT: Australian Eastern Daylight Time
  • M10.1.0: Transition to daylight saving time occurs on the 1st Sunday of October
  • M4.1.0/3: Transition back to standard time occurs on the 1st Sunday of April, with a 3-hour difference from UTC.

For other time zone definitions have a look at the Posix Timezones Database. Just copy the string you find there and change the TIMEZONE constant accordingly.

We also create instances of the Timezone and DS3231 classes. The loc variable will hold our local time information, while rtc will manage the RTC module.

Timezone loc;
DS3231 rtc;

Setup Function

In the setup() function, we initialize the serial communication at a baud rate of 9600 for debugging purposes. We then initialize the RTC and set it to a specific date and time (December 4, 2024, at 03:16:30 UTC). Finally, we set the timezone using the previously defined constant.

void setup() {
  Serial.begin(9600);
  rtc.begin();
  rtc.setDateTime(2024,12,4,3,16,30);  // UTC
  loc.setPosix(TIMEZONE);
}

Loop Function

The loop() function runs continuously. First, we retrieve the current date and time from the RTC using rtc.getDateTime(). We then set the UTC time using the retrieved values.

  RTCDateTime dt = rtc.getDateTime();
  UTC.setTime(dt.hour, dt.minute, dt.second, dt.day, dt.month, dt.year);

Next, we print the current UTC time to the serial monitor in a formatted string. This includes the year, month, day, hour, minute, and second.

  Serial.printf("RTC: %4d-%02d-%02d %02d:%02d:%02d\n",
                dt.year, dt.month, dt.day, 
                dt.hour, dt.minute, dt.second);

We also print the local time using the loc variable, which has been adjusted for the timezone and daylight savings time.

  Serial.printf("LOC: %4d-%02d-%02d %02d:%02d:%02d\n",
                loc.year(), loc.month(), loc.day(), 
                loc.hour(), loc.minute(), loc.second());                

Finally, we add a newline for better readability in the serial output and introduce a delay of 5000 milliseconds (5 seconds) before the loop repeats.

  Serial.println();
  delay(5000);

Output example

If you upload the code and open the Serial Monitor, you should see the UTC time of the RTC and the local time (LOC) printed:

UTC and local time of RTC on Serial Monitor
UTC and local time of RTC on Serial Monitor

Synchronize DS3231 RTC with SNTP Server

While the above code now handles the adjustment for daylight savings time automatically, it still requires you to manually set the time of the RTC when starting the ESP32. Even worse, it requires you to connect the ESP32 to a computer, change the code and reprogram the ESP32. This is annoying!

You could add buttons, to edit the time and date while the ESP32 is running. If you want to do that, have a look at the Arduino and RTC Module DS3231 tutorial, where we use two buttons to set the time.

The better option, however, is to automatically synchronize the RTC with an Internet Time Provider (SNTP). For more background information, have a look at the How to synchronize ESP32 clock with SNTP server tutorial.

If you have consistent internet access and don’t care about power consumption, you wouldn’t need an RTC, since you can regularly synchronize the internal clock of the ESP32. This would automatically set the clock, and would handle daylight savings time as well. See the Automatic Daylight Savings Time Clock and the Digital Clock on e-Paper Display tutorials.

However, for a battery powered project you want to avoid using WiFi too frequently, since it consumes a lot of power. A common use case is a data logger, e.g. for temperature, that you want to run on battery power as long as possible but also needs accurate time stamps.

The following code shows you how to do this. It uses WiFi only when the ESP32 is reset to synchronize the RTC. Otherwise, the ESP32 is in deep-sleep mode to preserve power and only wakes up occasionally, to get the time from the RTC and print it:

#include "WiFi.h"
#include "esp_sntp.h"
#include "Wire.h"
#include "DS3231.h"
#include "ezTime.h"

const char* TIMEZONE = "AEST-10AEDT,M10.1.0,M4.1.0/3";  // Melbourne
const char* SSID = "SSID";
const char* PWD = "PASSWORD";
const int SLEEP = 10; // sec
 
DS3231 rtc; 
Timezone loc;

void syncTime() {
  WiFi.begin(SSID, PWD);
  while (WiFi.status() != WL_CONNECTED)
    ;
  configTzTime("UTC", "pool.ntp.org");
  setRtcTime();
}

void setRtcTime() {
  struct tm t;
  getLocalTime(&t);
  rtc.setDateTime(t.tm_year+1900, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
}

void printTime() {
  RTCDateTime dt = rtc.getDateTime();
  UTC.setTime(dt.hour, dt.minute, dt.second, dt.day, dt.month, dt.year);

  Serial.printf("RTC: %4d-%02d-%02d %02d:%02d:%02d\n",
                dt.year, dt.month, dt.day, 
                dt.hour, dt.minute, dt.second);

  Serial.printf("LOC: %4d-%02d-%02d %02d:%02d:%02d\n",
                loc.year(), loc.month(), loc.day(), 
                loc.hour(), loc.minute(), loc.second());                
               
  Serial.println();

}

bool isReset() {
  return esp_sleep_get_wakeup_cause() != ESP_SLEEP_WAKEUP_TIMER;
}

void setup() {
  Serial.begin(9600);
  rtc.begin();
  loc.setPosix(TIMEZONE);

  if (isReset()) {
    syncTime();
  }   
  printTime();

  esp_sleep_enable_timer_wakeup(SLEEP * 1000000);                
  esp_deep_sleep_start();
}

void loop() {
}

Let’s break down the code into its components for a better understanding.

Libraries and Constants

We start by including the necessary libraries for Wi-Fi, SNTP (Simple Network Time Protocol), I2C communication, and the DS3231 RTC. We also define some constants for the timezone, Wi-Ficredentials, and sleep duration.

#include "WiFi.h"
#include "esp_sntp.h"
#include "Wire.h"
#include "DS3231.h"
#include "ezTime.h"

const char* TIMEZONE = "AEST-10AEDT,M10.1.0,M4.1.0/3";  // Melbourne
const char* SSID = "SSID";
const char* PWD = "PASSWORD";
const int SLEEP = 10; // sec

Obviously, you will have to replace the Wi-Ficredentials by your own Wi-Fi credentials.

RTC and Timezone Objects

We create instances of the DS3231 class for the RTC and the Timezone class to handle local time calculations.

DS3231 rtc; 
Timezone loc;

Sync Time Function

The syncTime() function connects to the WiFi network and configures the timezone for the NTP server. It then calls setRtcTime() to update the RTC with the current time.

void syncTime() {
  WiFi.begin(SSID, PWD);
  while (WiFi.status() != WL_CONNECTED)
    ;
  configTzTime("UTC", "pool.ntp.org");
  setRtcTime();
}

Set RTC Time Function

In the setRtcTime() function, we retrieve the local time and set the RTC’s date and time accordingly. The year is adjusted by adding 1900 because the tm_year field returns the number of years since 1900.

void setRtcTime() {
  struct tm t;
  getLocalTime(&t);
  rtc.setDateTime(t.tm_year+1900, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
}

Note that we also have to add 1 to tm_mon, to get the correct month, since the struct tm data structure uses inconsistent ranges. For instance, the day of the month starts at 1 but the month of the year starts at 0:

  Member    Type  Meaning                   Range
  tm_sec    int   seconds after the minute  0-61*
  tm_min    int   minutes after the hour    0-59
  tm_hour   int   hours since midnight      0-23
  tm_mday   int   day of the month          1-31
  tm_mon    int   months since January      0-11
  tm_year   int   years since 1900
  tm_wday   int   days since Sunday         0-6
  tm_yday   int   days since January 1      0-365
  tm_isdst  int   Daylight Saving Time flag

Print Time Function

The printTime() function retrieves the current date and time from the RTC and prints it to the Serial Monitor. It also prints the local time using the timezone object.

void printTime() {
  RTCDateTime dt = rtc.getDateTime();
  UTC.setTime(dt.hour, dt.minute, dt.second, dt.day, dt.month, dt.year);

  Serial.printf("RTC: %4d-%02d-%02d %02d:%02d:%02d\n",
                dt.year, dt.month, dt.day, 
                dt.hour, dt.minute, dt.second);

  Serial.printf("LOC: %4d-%02d-%02d %02d:%02d:%02d\n",
                loc.year(), loc.month(), loc.day(), 
                loc.hour(), loc.minute(), loc.second());                

  Serial.println();
}

Check Reset Function

The isReset() function checks if the ESP32 woke up from deep sleep due to a timer or another cause. This helps determine whether to synchronize the time or not.

bool isReset() {
  return esp_sleep_get_wakeup_cause() != ESP_SLEEP_WAKEUP_TIMER;
}

Setup Function

In the setup() function, we initialize the Serial communication, the RTC, and set the time zone. If the ESP32 is starting fresh (not waking up from deep sleep), we synchronize the time. Finally, we print the time and put the ESP32 into deep sleep mode for the specified duration.

void setup() {
  Serial.begin(9600);
  rtc.begin();
  loc.setPosix(TIMEZONE);

  if (isReset()) {
    syncTime();
  }   
  printTime();

  esp_sleep_enable_timer_wakeup(SLEEP * 1000000);                
  esp_deep_sleep_start();
}

Loop Function

The loop() function is empty because the ESP32 will not run any code while in deep sleep. It will only wake up to execute the setup() function again after the sleep duration.

void loop() { }

Displaying RTC time on OLED

As a final example, I want to show you how to add an OLED to display the time and date of the RTC on a screen. This is just a simple extension of the code above.

Connecting the OLED is easy, since it is also an I2C device. Simply connect SDA, SCL, VCC, and GND of the OLED in parallel to the DS3231 as shown below

Connecting OLED and DS3231 to ESP32 lite
Connecting OLED and DS3231 to ESP32 lite

Below is the code that displays the time and date on the OLED. Note that it uses the Adafruit_SSD1306 library, which you can install it via the Library Manager as usual.

#include "WiFi.h"
#include "esp_sntp.h"
#include "Wire.h"
#include "DS3231.h"
#include "ezTime.h"
#include "Adafruit_SSD1306.h"

const char* TIMEZONE = "AEST-10AEDT,M10.1.0,M4.1.0/3";
const char* SSID = "SSID";
const char* PWD = "PASSWORD";
const int SLEEP = 10; // sec
 
DS3231 rtc; 
Timezone loc;
Adafruit_SSD1306 oled(128, 64, &Wire, -1);

void oled_init() {
  oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  oled.setTextSize(2);
  oled.setTextColor(WHITE);
}

void syncTime() {
  WiFi.begin(SSID, PWD);
  while (WiFi.status() != WL_CONNECTED)
    ;
  configTime(0, 0, "pool.ntp.org");
  setRtcTime();
}

void setRtcTime() {
  struct tm t;
  getLocalTime(&t);
  rtc.setDateTime(t.tm_year+1900, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
}

void displayTime() {
  RTCDateTime dt = rtc.getDateTime();
  UTC.setTime(dt.hour, dt.minute, dt.second, dt.day, dt.month, dt.year);
  oled.clearDisplay();
  oled.setCursor(10, 15);
  oled.println(loc.dateTime("H:i:s"));  
  oled.setCursor(2, 45);
  oled.println(loc.dateTime("d/m/Y"));  
  oled.display();
}

bool isReset() {
  return esp_sleep_get_wakeup_cause() != ESP_SLEEP_WAKEUP_TIMER;
}

void setup() {
  rtc.begin();
  loc.setPosix(TIMEZONE);
  oled_init();

  if (isReset()) {
    syncTime();
  }   
  displayTime();

  esp_sleep_enable_timer_wakeup(SLEEP * 1000000);                
  esp_deep_sleep_start();
}

void loop() {
}

The only additions and changes to the code are the function oled_init(), which initializes the OLED, and the function displayTime(), which displays time and date on the OLED. If you upload the code to your ESP32 you should see the following on the OLED:

Time and date displayed on OLED
Time and date displayed on OLED

And that’s it! You should now be able to use the DS3231 RTC together with an ESP32.

Conclusions

In this tutorial you learned how to use a DS3231 Real Time Clock (RTC) module with an ESP32.

The ESP32 lite with a Real Time Clock is especially suited for battery-powered project that need to keep accurate time without consuming much power. Common use cases are data loggers or electronic clocks. For the latter, I recommend e-Paper displays, since the consume almost no power. See our tutorials, Analog Clock on e-Paper Display and Digital Clock on e-Paper Display.

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,

If you have any questions feel free to leave them in the comment section.

Happy Tinkering ; )