Home ▸ Arduino ▸ Modules Interfacing ▸ DS3231 RTC

DS3231 Arduino Tutorial: RTC Wiring, Set Time, LCD1602, Alarm and Temperature

The DS3231 is a real-time clock (RTC) module that keeps accurate time even when the Arduino is switched off. It uses a temperature-compensated crystal oscillator, so it stays accurate to roughly one minute per year, and a small backup battery keeps the clock running during power loss. The module also contains a temperature sensor, two programmable alarms and a square wave output, which makes it useful for clocks, data loggers and timed automation.

This tutorial shows how to wire the DS3231 to an Arduino Uno and how to install the RTClib library. You will set the time, read the date and time, display a live clock on an LCD1602 I2C screen, read the onboard temperature, trigger Alarm 1 with an LED on the SQW pin and enable the square wave output. Each section includes a complete sketch, and the sketches avoid a common mistake that resets the clock to an old time every time the Arduino restarts.

Both the DS3231 and the I2C LCD use the same two wires, so the LCD1602 I2C tutorial is a useful companion. If you prefer a graphical display, the SSD1306 OLED tutorial works the same way. Other modules in the series include the DS1307 RTC, the AT25C256 EEPROM, the SD card module and the W25Q flash memory, and the Arduino modules collection lists the rest.

DS3231 RTC Module Overview

The DS3231 is a real-time clock that tracks the seconds, minutes, hours, day, date, month and year, and it compensates for leap years automatically. Its crystal oscillator is temperature compensated, so the time stays accurate across the normal operating range. A backup battery, usually a CR2032 coin cell on the module, keeps the clock running when the main power is off. The chip communicates over I2C and answers at the address 0x68.

DS3231 RTC breakout board with the VCC, GND, SDA, SCL, SQW and 32K pins and a CR2032 battery holder

DS3231 Features

  • The temperature-compensated oscillator keeps the time accurate to about ±2 ppm between 0 °C and 40 °C, which is roughly one minute per year.
  • The I2C interface needs only two wires, SDA and SCL.
  • The battery backup input keeps the time during power loss.
  • Two programmable alarms can trigger timers, reminders and scheduled actions.
  • The built-in temperature sensor measures the chip temperature with an accuracy of ±3 °C.
  • The SQW pin outputs a square wave at 1 Hz, 1.024 kHz, 4.096 kHz or 8.192 kHz, and the separate 32K pin outputs 32.768 kHz.
  • The supply range of the chip is 2.3 V to 5.5 V, so the module works with both 3.3 V and 5 V boards.

Common DS3231 Applications

The module appears in many Arduino projects that need reliable time.

  • Digital clocks on LCD or OLED displays
  • Data loggers that store sensor readings with time stamps, for example with the SD card module
  • Home automation tasks that must run at an exact time
  • Alarm systems and reminders that drive a buzzer or a relay
  • Attendance logs and periodic tasks such as watering schedules

DS3231 vs DS1307

The DS1307 is an older RTC that many beginners also meet. Both chips use the same I2C address, and the same RTClib library supports them, but the DS3231 is the better choice for most projects.

FeatureDS3231DS1307
OscillatorBuilt-in temperature-compensated crystalExternal 32.768 kHz crystal without compensation
Typical accuracyAbout ±2 ppm from 0 °C to 40 °CDepends on the crystal and drifts with temperature
Supply voltage2.3 V to 5.5 V4.5 V to 5.5 V
Temperature sensorYesNo
AlarmsTwoNone
Square wave output1 Hz, 1.024 kHz, 4.096 kHz, 8.192 kHz1 Hz, 4.096 kHz, 8.192 kHz, 32.768 kHz

The DS1307 tutorial covers the older chip if you already own one.


DS3231 Pinout

The typical module has six main pins, and the diagram and table below describe them.

DS3231 pinout diagram showing the 32K, SQW, SCL, SDA, VCC and GND pins of the module
PinFunctionDescription
VCCPower3.3 V or 5 V supply for the module
GNDGroundConnects to the Arduino ground
SDAI2C dataCarries data between the DS3231 and the Arduino
SCLI2C clockCarries the I2C clock signal
SQWAlarm or square waveOpen-drain output for alarm signals or the square wave, optional
32K32.768 kHz outputOpen-drain output of a 32.768 kHz clock, optional

The SQW and 32K outputs are open-drain, which means that they can only pull the line LOW. A pull-up resistor is needed to read them as a logic signal, and many modules already include one.


DS3231 Arduino Wiring

The module connects to the I2C pins of the Arduino. On the Uno and the Nano, SDA is on A4 and SCL is on A5, and the Arduino UNO pinout guide and the Arduino Nano pinout guide show these pins in detail. On the Mega, SDA is on pin 20 and SCL is on pin 21.

Wiring diagram of a DS3231 module and an LCD1602 I2C display sharing the A4 (SDA) and A5 (SCL) pins of an Arduino Uno
Arduino UnoDS3231 moduleLCD1602 I2C
5VVCCVCC
GNDGNDGND
A4 (SDA)SDASDA
A5 (SCL)SCLSCL

Both devices share the same two lines, because each one answers at its own I2C address. The DS3231 uses 0x68, and the LCD backpack usually uses 0x27 or 0x3F. Keep the wires short for stable communication. The Arduino I2C tutorial explains how to scan the bus if you are unsure of an address.

The MPU6050 sensor also answers at 0x68 by default, so the two devices cannot share a bus unless the ADO pin of the MPU6050 is pulled HIGH to move it to 0x69.


DS3231 Library Setup

Two libraries make the project simple. RTClib from Adafruit handles the DS3231, and LiquidCrystal_I2C handles the display.

Install RTClib and LiquidCrystal_I2C

  1. Open the Arduino IDE and go to Sketch, then Include Library, then Manage Libraries.
  2. Search for “RTClib” and install the library from Adafruit. It supports the DS3231, the DS1307 and several other RTC chips.
  3. Search for “LiquidCrystal_I2C” and install the version by Frank de Brabander.
Arduino IDE Library Manager with the RTClib library by Adafruit selected for installation
 Arduino IDE Library Manager with the LiquidCrystal_I2C library by Frank de Brabander selected for installation

Test the Library Examples

After the installation, a quick test confirms that the wiring works.

  1. Go to File, then Examples, then RTClib, and upload the ds3231 example. The Serial Monitor should print the date and time.
  2. Go to File, then Examples, then LiquidCrystal_I2C, and upload the HelloWorld example. The LCD should show the sample text.

If the LCD stays blank, adjust the contrast potentiometer on the backpack and try the address 0x3F in place of 0x27.


DS3231 Set Time

The DS3231 holds the time in its own registers and keeps it while the battery lasts, so you need to set the time only once. The rtc.adjust() function writes a new time to the chip, and it accepts a DateTime object in two forms.

Set the Time Manually

The manual method writes a date and time that you type into the sketch. It suits projects that need a specific starting time, and the arguments are the year, month, day, hour, minute and second.

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

RTC_DS3231 rtc;

void setup() {
  Serial.begin(9600);

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

  // Year, month, day, hour, minute, second
  rtc.adjust(DateTime(2026, 10, 18, 14, 30, 0));

  Serial.println("RTC time set manually.");
}

void loop() {
}

The rtc.begin() call checks that the DS3231 answers on the I2C bus, and rtc.adjust() stores the new time in the chip. The time stays in the chip until you change it again.


Set the Time from the Compile Time

The compile-time method uses the date and time of your computer at the moment that the sketch is compiled. The macros __DATE__ and __TIME__ hold these values, so no numbers need to be typed.

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

RTC_DS3231 rtc;

void setup() {
  Serial.begin(9600);

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

  // Set the RTC to the time at which this sketch was compiled
  rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));

  Serial.println("RTC time set from the compile time.");
}

void loop() {
}

The time is a few seconds old when the upload finishes, so upload right after compiling for the best result.


Avoid Resetting the Time at Every Start

A sketch that calls rtc.adjust() in setup() sets the clock back to the compile time every time the Arduino restarts, and the battery backup becomes useless. The lostPower() function reports whether the DS3231 lost its time, for example after a battery change, so the sketch can set the time only when that is needed.

if (rtc.lostPower()) {
  Serial.println("RTC lost power, setting the time.");
  rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}

The remaining sketches in this tutorial use this check, so they keep the correct time across restarts.


DS3231 Read Time and Date

The rtc.now() function reads the current time and returns a DateTime object. You can read the individual values from that object with the functions in the table.

FunctionReturns
now.year()Full year, for example 2026
now.month()Month from 1 to 12
now.day()Day of the month
now.hour()Hour from 0 to 23
now.minute()Minute from 0 to 59
now.second()Second from 0 to 59
now.dayOfTheWeek()Day index, where 0 is Sunday
now.unixtime()Unix time stamp in seconds

Print the Time and Date

The following sketch prints the date and time once per second. The snprintf() function formats the values with leading zeros, so 9:05:03 prints as 09:05:03 and the output stays aligned.

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

RTC_DS3231 rtc;

void setup() {
  Serial.begin(9600);

  if (!rtc.begin()) {
    Serial.println("RTC not found.");
    while (1);
  }

  if (rtc.lostPower()) {
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }
}

void loop() {
  DateTime now = rtc.now();

  char buffer[24];
  snprintf(buffer, sizeof(buffer), "%02d/%02d/%04d  %02d:%02d:%02d",
           now.day(), now.month(), now.year(),
           now.hour(), now.minute(), now.second());

  Serial.println(buffer);
  delay(1000);
}

Convert to 12-Hour Format

The DS3231 reports the hour in 24-hour format, and a small calculation converts it to 12-hour format with AM and PM.

int hour12 = now.hour() % 12;
if (hour12 == 0) hour12 = 12;
const char *period = (now.hour() >= 12) ? "PM" : "AM";

The remainder of the division by 12 gives the hour from 0 to 11, and a result of 0 is shown as 12 for noon and midnight. The condition on the original hour selects AM or PM.


DS3231 LCD1602 Clock

A live clock on a 16×2 I2C LCD is the most common use of the DS3231. The sketch below shows the date on the first line and the time on the second line. The helper function printTwoDigits() adds the leading zero, so the text does not shift when a value drops below 10.

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

RTC_DS3231 rtc;
LiquidCrystal_I2C lcd(0x27, 16, 2);   // Use 0x3F if the screen stays blank

void printTwoDigits(int value) {
  if (value < 10) lcd.print('0');
  lcd.print(value);
}

void setup() {
  Wire.begin();
  lcd.init();
  lcd.backlight();

  if (!rtc.begin()) {
    lcd.print("RTC not found");
    while (1);
  }

  if (rtc.lostPower()) {
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }
}

void loop() {
  DateTime now = rtc.now();

  // First line: DD/MM/YYYY
  lcd.setCursor(0, 0);
  printTwoDigits(now.day());
  lcd.print('/');
  printTwoDigits(now.month());
  lcd.print('/');
  lcd.print(now.year());

  // Second line: HH:MM:SS
  lcd.setCursor(0, 1);
  printTwoDigits(now.hour());
  lcd.print(':');
  printTwoDigits(now.minute());
  lcd.print(':');
  printTwoDigits(now.second());

  delay(250);
}

The loop redraws the same screen positions every 250 ms, so the seconds change without flicker and without a call to lcd.clear(). The LCD1602 I2C tutorial explains the display functions in more detail.

Animated capture of an LCD1602 I2C display showing the DS3231 date on the first row and the time on the second row

DS3231 Temperature Sensor

The DS3231 measures the temperature of the chip to compensate its oscillator, and the getTemperature() function returns this value in degrees Celsius. The accuracy is about ±3 °C, and the sensor updates every 64 seconds, so the reading is suitable for a rough room temperature but not for precise measurements. The chip is also slightly warmer than the room because of the module around it. For accurate temperature data, use a sensor such as the DS18B20 or the BME280.

The sketch below shows the time on the first line and the temperature on the second line.

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

RTC_DS3231 rtc;
LiquidCrystal_I2C lcd(0x27, 16, 2);

void printTwoDigits(int value) {
  if (value < 10) lcd.print('0');
  lcd.print(value);
}

void setup() {
  Wire.begin();
  lcd.init();
  lcd.backlight();

  if (!rtc.begin()) {
    lcd.print("RTC not found");
    while (1);
  }

  if (rtc.lostPower()) {
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }
}

void loop() {
  DateTime now = rtc.now();
  float tempC = rtc.getTemperature();

  lcd.setCursor(0, 0);
  printTwoDigits(now.hour());
  lcd.print(':');
  printTwoDigits(now.minute());
  lcd.print(':');
  printTwoDigits(now.second());

  lcd.setCursor(0, 1);
  lcd.print("Temp: ");
  lcd.print(tempC, 1);       // One decimal place
  lcd.print((char)223);      // Degree symbol
  lcd.print("C ");

  delay(500);
}
 Animated capture of an LCD1602 showing the DS3231 time on the first row and the temperature in degrees Celsius on the second row

DS3231 Alarm

The DS3231 has two alarms. Alarm 1 has a resolution of one second, and Alarm 2 has a resolution of one minute. When an alarm condition is met, the chip sets an alarm flag and, if the SQW pin is in alarm mode, pulls that pin LOW. The pin stays LOW until the sketch clears the flag. This works even when the Arduino is busy with other work, and it can also wake a sleeping microcontroller.

Alarm Modes

The alarm mode decides which parts of the time must match. The RTClib modes are values, and you must pick one of them. They cannot be combined with the | operator, because the result of such a combination is a different mode.

ModeThe alarm fires when
DS3231_A1_PerSecondEvery second
DS3231_A1_SecondThe seconds match, so once per minute
DS3231_A1_MinuteThe minutes and seconds match, so once per hour
DS3231_A1_HourThe hours, minutes and seconds match, so once per day
DS3231_A1_DateThe date, hours, minutes and seconds match
DS3231_A1_DayThe weekday, hours, minutes and seconds match

Alarm 2 has the matching modes DS3231_A2_PerMinute, DS3231_A2_Minute, DS3231_A2_Hour, DS3231_A2_Date and DS3231_A2_Day, and it works without seconds.


Wire an LED to the SQW Pin

The SQW pin is an open-drain output, so it can sink current but cannot source it. Connect the LED anode to the 3.3V pin and the cathode through a resistor to the SQW pin. The LED is off while the pin is idle, and it turns on when the alarm pulls the pin LOW. The output is rated to sink about 3 mA, so use a resistor of 1 kΩ to protect it, and expect a modest brightness.

DS3231 alarm wiring with an LED anode on the 3.3V pin and the cathode through a 1 kΩ resistor to the SQW pin

Alarm 1 Sketch

The sketch sets Alarm 1 to fire 30 seconds after the start so that you can test it quickly. It then shows the clock and the alarm time on the LCD. When the alarm fires, the message stays on the screen for five seconds, and the LED stays on for the same time, because the sketch clears the flag only after that delay.

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

RTC_DS3231 rtc;
LiquidCrystal_I2C lcd(0x27, 16, 2);

void printTwoDigits(int value) {
  if (value < 10) lcd.print('0');
  lcd.print(value);
}

void showAlarmTime() {
  DateTime alarm = rtc.getAlarm1();
  lcd.setCursor(0, 0);
  lcd.print("Alarm ");
  printTwoDigits(alarm.hour());
  lcd.print(':');
  printTwoDigits(alarm.minute());
  lcd.print(':');
  printTwoDigits(alarm.second());
}

void setup() {
  Wire.begin();
  lcd.init();
  lcd.backlight();

  if (!rtc.begin()) {
    lcd.print("RTC not found");
    while (1);
  }

  if (rtc.lostPower()) {
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }

  // Reset the alarm flags and turn Alarm 2 off
  rtc.clearAlarm(1);
  rtc.clearAlarm(2);
  rtc.disableAlarm(2);

  // Use the SQW pin as the alarm output (no square wave)
  rtc.writeSqwPinMode(DS3231_OFF);

  // Fire in 30 seconds, then every day at the same time
  rtc.setAlarm1(rtc.now() + TimeSpan(30), DS3231_A1_Hour);

  showAlarmTime();
}

void loop() {
  DateTime now = rtc.now();

  lcd.setCursor(0, 1);
  printTwoDigits(now.hour());
  lcd.print(':');
  printTwoDigits(now.minute());
  lcd.print(':');
  printTwoDigits(now.second());

  if (rtc.alarmFired(1)) {
    lcd.setCursor(0, 0);
    lcd.print("ALARM TRIGGERED ");

    delay(5000);          // The SQW pin stays LOW, so the LED stays on

    rtc.clearAlarm(1);    // Releases the SQW pin and turns the LED off
    showAlarmTime();
  }

  delay(200);
}

The alarm functions work as follows.

  1. rtc.clearAlarm() removes an alarm flag that is already set, so the sketch starts without a false alarm.
  2. rtc.writeSqwPinMode(DS3231_OFF) switches the SQW pin from square wave output to alarm output.
  3. rtc.setAlarm1() stores the alarm time and the mode. With DS3231_A1_Hour, the alarm fires at that time every day.
  4. rtc.alarmFired(1) returns true while the alarm flag is set.
  5. rtc.clearAlarm(1) clears the flag, and the SQW pin returns to HIGH.

To use a fixed daily time, replace the alarm line with a fixed time, for example rtc.setAlarm1(DateTime(2026, 1, 1, 7, 30, 0), DS3231_A1_Hour);. The date is ignored in this mode. The alarm settings stay in the chip while the battery lasts, so you can set the alarm once and remove the line afterwards.

Animated capture of the DS3231 Alarm 1 firing with ALARM TRIGGERED on the LCD1602 and the SQW LED turning on

For an audible alarm, connect a buzzer to an Arduino pin and play a tone when alarmFired(1) becomes true. The passive buzzer tutorial provides ready alarm and siren sketches for this purpose.


DS3231 Square Wave Output

The SQW pin can also output a steady square wave, which is useful as a precise clock source, a timer tick or a wake-up signal. The writeSqwPinMode() function selects the frequency, and the available options are listed below.

RTClib optionFrequency
DS3231_SquareWave1Hz1 Hz
DS3231_SquareWave1kHz1.024 kHz
DS3231_SquareWave4kHz4.096 kHz
DS3231_SquareWave8kHz8.192 kHz
DS3231_OFFSquare wave off, alarm output active

The sketch below enables the 1 Hz output. An LED on the SQW pin, wired as in the alarm section, then blinks once per second.

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

RTC_DS3231 rtc;

void setup() {
  Serial.begin(9600);

  if (!rtc.begin()) {
    Serial.println("RTC not found.");
    while (1);
  }

  // Options: DS3231_SquareWave1Hz, 1kHz, 4kHz, 8kHz
  rtc.writeSqwPinMode(DS3231_SquareWave1Hz);

  Serial.println("1 Hz square wave enabled on the SQW pin.");
}

void loop() {
}

An LED shows the 1 Hz signal clearly, but at 1.024 kHz and above it looks constantly lit, so use an oscilloscope or a logic analyzer to check the faster frequencies.

Animated capture of an LED on the DS3231 SQW pin blinking once per second with the 1 Hz square wave enabled

Use the SQW Pin as an Interrupt

The 1 Hz output can drive an Arduino interrupt, which lets the sketch update the display exactly once per second without polling. Connect SQW to digital pin 2, enable the internal pull-up and attach a falling-edge interrupt.

const int sqwPin = 2;
volatile bool tick = false;

void onTick() {
  tick = true;
}

void setup() {
  pinMode(sqwPin, INPUT_PULLUP);
  rtc.writeSqwPinMode(DS3231_SquareWave1Hz);
  attachInterrupt(digitalPinToInterrupt(sqwPin), onTick, FALLING);
}

void loop() {
  if (tick) {
    tick = false;
    // Update the clock once per second here
  }
}

The Arduino external interrupts tutorial explains the interrupt functions in detail.


DS3231 Troubleshooting

Most DS3231 problems come from the wiring, the battery or the way that the time is set.

The RTC Is Not Found

Check that SDA is on A4 and SCL is on A5, that the module has power and that the wires are firm. Run the I2C scanner from the Arduino I2C tutorial, which should report the address 0x68. Many modules also show a second address, usually 0x57, which belongs to the small EEPROM chip on the board.


The Time Resets After a Restart

This usually means that the sketch calls rtc.adjust() at every start, which sets the clock back to the compile time. Use the lostPower() check shown earlier. If the time is lost even with that check, the backup battery is empty or missing.


The Time Is Wrong by a Few Seconds After Upload

The compile-time method uses the moment of compilation, so the time is behind by the duration of the upload. Upload right after compiling, or set the time manually.


Check the Battery Circuit on Cheap Modules

Many inexpensive DS3231 modules include a small charging circuit that was designed for rechargeable LIR2032 cells. This circuit can heat or damage a standard, non-rechargeable CR2032 cell over time. If your module has this circuit, use an LIR2032 cell or remove the charging resistor, and do not leave a primary cell in a module that charges it.


Conclusion

This tutorial showed how to wire the DS3231 to an Arduino, set and read the time, show a live clock on an LCD1602, read the onboard temperature, trigger Alarm 1 with an LED and enable the square wave output. The key points are to set the time only when the chip has lost power, to pick one alarm mode and not combine several, and to remember that the SQW pin is open-drain. With these basics you can build clocks, data loggers and scheduled controllers that keep correct time through power loss.




About the Author
Arun Rawat
Arun Rawat
Embedded Systems Engineer · Founder, ControllersTech

Arun is an embedded systems engineer with 10+ years of experience in STM32, ESP32, and AVR microcontrollers. He created ControllersTech to share practical tutorials on embedded software, HAL drivers, RTOS, and hardware design — grounded in real industrial automation experience.

Subscribe
Notify of

0 Comments
Newest
Oldest Most Voted
×

Don’t Miss Future STM32 Tutorials

Join thousands of developers getting free guides, code examples, and updates.