Keeping Time with a DS3231
| Difficulty | Intermediate |
|---|---|
| Category | Inputs |
| Supported boards | Arduino Uno R3 and compatible ATmega328P boards |
| Time needed | 50 minutes |
| Prerequisites | I2C, Two Wires and Many Devices |
| Tested board | Not yet verified |
| Tested on | Not yet verified |
| Arduino IDE version | Not yet verified |
| Library | Not yet verified |
What it does
A real time clock: a chip that knows the date and time and keeps counting through power cuts, on a coin cell, for years.
An Arduino cannot do this on its own. millis() counts milliseconds since the sketch started and resets to zero on every power-up, and the crystal driving it is accurate to perhaps a few hundred parts per million, which is minutes of drift per week.
The DS3231 solves both. It has its own battery, and it contains a temperature-compensated crystal oscillator, which is what makes it unusual at this price: most cheap clock chips drift badly as the room warms and cools, and this one corrects for it. Expect a couple of minutes a year rather than a couple of minutes a month.
Before you begin
Required parts and tools
- An Arduino Uno or compatible board.
- One DS3231 module. This is Siratek product 2522.
- A coin cell for the module, if one is not fitted.
- Four jumper wires, which may need to be male-to-female: see below.
Two shapes of module
DS3231 boards come in two common forms, and the Siratek part is the smaller one.
- The small black board, which is what the product photographs show: a DS3231 chip, a coin cell on the underside and a female header block. It is designed to plug onto a Raspberry Pi’s GPIO header, which is why the connector is female.
- The larger red board, often marked ZS-042, with a six-pin male header and room for an AT24C32 memory chip.
Both are the same chip, both speak I2C, and both work identically with the code below. What differs is the connector: the small board needs male-to-female jumpers, with the male end in the module.
Read the pin labels printed on your own board. On the small module they are marked beside the header and are easy to miss.
The battery, and a genuine hazard
This is the part to read properly.
Some DS3231 boards, the larger red ones especially, include a charging circuit intended for a rechargeable LIR2032 cell. The circuit trickle-charges whatever is fitted whenever the module has power.
Fit a non-rechargeable CR2032 into a board with that circuit and it will be charged anyway. Non-rechargeable lithium cells are not designed to accept a charge: they can swell, leak, vent, or in the worst case rupture.
So:
- Find out which cell your board expects.
LIR2032is rechargeable,CR2032is not. - If the board has a charging circuit and you only have a CR2032, the usual fix is to remove the series resistor in the charging path, often marked
R5or similar, or to lift the diode. That disables charging and the cell runs down normally over several years. - If you are not sure, use the cell the board is marked for, and do not leave an unfamiliar combination powered unattended.
A CR2032 in a non-charging holder is entirely safe and lasts years. The problem only arises with the combination.
It is an I2C device
Four wires, and A4 and A5 again. The DS3231’s address is 0x68, which it does not share with any display in this library, so it will happily sit on the same bus as an LCD or OLED.
If a bus scan finds a device at 0x57 as well, that is the AT24C32 memory chip on the larger boards. Harmless, and occasionally useful.
Setting the time, and the trap in it
The time has to be put into the chip once. Every library offers a way to set it from the compile-time of your sketch, which is convenient and contains a trap:
If that line stays in your sketch, the clock is reset to the compile time every time the board restarts. The project works perfectly on the bench, then loses an hour overnight because it was power-cycled, and the clock quietly jumps back to whenever you last compiled.
The sketch below sets the time only if the chip reports that it has lost power, which is the behaviour you actually want and costs one if.
Pin reference
| Module pin | Connect to | Purpose | Notes |
|---|---|---|---|
GND |
Uno GND |
Ground | |
VCC |
Uno 5V |
Power | 2.3 to 5.5 V |
SDA |
Uno A4 |
I2C data | |
SCL |
Uno A5 |
I2C clock | |
SQW |
Not used here | Square wave or alarm output | For waking a sleeping board |
32K |
Not used here | 32 kHz reference |
Wiring steps
- Unplug the USB cable.
- Check the cell is fitted, the right way up, and of the type the board expects.
GNDtoGND,VCCto5V.SDAtoA4,SCLtoA5.- Plug the USB cable back in.
- Run the bus scanner from I2C, Two Wires and Many Devices and confirm something answers at 0x68.
Software and libraries
Install RTClib by Adafruit through Library Manager. It covers the DS3231 and several other clock chips with the same interface.
Minimal working example
// Siratek: reading the time, and setting it only when it has been lost.
#include <Wire.h>
#include <RTClib.h>
RTC_DS3231 rtc;
void setup() {
Serial.begin(9600);
while (!Serial) {
;
}
if (!rtc.begin()) {
Serial.println("Siratek: no DS3231 found. Check SDA, SCL and power.");
while (1) {
delay(100);
}
}
if (rtc.lostPower()) {
// Only runs when the chip has actually lost its timekeeping,
// so a normal restart does not reset the clock.
Serial.println("Siratek: clock had lost power, setting from compile time.");
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
Serial.println("Siratek: clock ready.");
}
void loop() {
DateTime now = rtc.now();
if (now.hour() < 10) {
Serial.print('0');
}
Serial.print(now.hour());
Serial.print(':');
if (now.minute() < 10) {
Serial.print('0');
}
Serial.print(now.minute());
Serial.print(':');
if (now.second() < 10) {
Serial.print('0');
}
Serial.print(now.second());
Serial.print(" ");
Serial.print(now.day());
Serial.print('/');
Serial.print(now.month());
Serial.print('/');
Serial.print(now.year());
Serial.print(" ");
Serial.print(rtc.getTemperature(), 1);
Serial.println(" C");
delay(1000);
}
Three things worth noticing.
lostPower() is what makes the clock stay set. It asks the chip whether its own timekeeping has been interrupted, which is true on the very first run and false ever after while the cell holds up.
__DATE__ and __TIME__ are the moment the sketch was compiled, not the moment it was uploaded. Compiling, going for lunch and then uploading sets the clock an hour slow. Verify and upload together, and expect the clock to be a few seconds behind.
getTemperature() is free. The chip measures its own temperature in order to compensate the crystal, and it will tell you the figure. It is accurate to about three degrees and is reading the chip, not the room, but it is a genuine extra sensor at no cost.
Setting the time without recompiling
Relying on compile time means the only way to correct the clock is to recompile. Taking the time from the serial monitor is better for anything that will be used.
// Siratek: set the clock by typing, for example, 2026-10-29 14:05:00
void checkForTimeSetting() {
if (Serial.available() < 19) {
return;
}
char buf[20];
Serial.readBytes(buf, 19);
buf[19] = '\0';
int year, month, day, hour, minute, second;
if (sscanf(buf, "%d-%d-%d %d:%d:%d",
&year, &month, &day, &hour, &minute, &second) == 6) {
rtc.adjust(DateTime(year, month, day, hour, minute, second));
Serial.print("Siratek: clock set to ");
Serial.println(buf);
} else {
Serial.println("Siratek: expected YYYY-MM-DD HH:MM:SS");
}
while (Serial.available()) {
Serial.read(); // discard the newline and anything else
}
}
Call that from loop(). Now the clock can be corrected in the field by anyone with a serial monitor, with no toolchain and no source code.
Expected result
The time printing once a second, advancing correctly, and a plausible temperature a few degrees above room temperature because the chip warms itself slightly.
The real test is the one that takes a day: unplug everything, leave it overnight, plug it back in and check the time is still right. That is what you bought the chip for, and nothing else proves it.
Troubleshooting
| Symptom | Likely cause | Check or fix |
|---|---|---|
begin() fails |
Wiring, or no power | Scan the bus; expect 0x68 |
| Scanner finds nothing | SDA and SCL swapped |
SDA is A4, SCL is A5 |
| Time resets on every power-up | adjust() called unconditionally |
Guard it with lostPower() |
| Time resets despite that | Cell flat, missing or upside down | Check the holder |
| Clock a few seconds slow | Compile time, not upload time | Expected; recompile and upload together |
| Clock minutes out after months | Normal DS3231 drift | A couple of minutes a year is in spec |
| Cell swollen or leaking | CR2032 in a charging board | Remove it carefully; disable the charging circuit |
| A second device at 0x57 | AT24C32 memory on the larger board | Harmless |
| Jumpers will not fit | Female header on the small module | Use male-to-female leads |
| Temperature reads high | It is the chip’s own temperature | Normal, and only accurate to a few degrees |
Next steps
- Easier first: I2C, Two Wires and Many Devices, for the bus and the scanner.
- Related products: a 16×2 LCD to show the time, and an SD card module to timestamp a log.
- Next project: a data logger that records a sensor reading with the real time against it, which is where this chip earns its place.
Sources and attribution
All text, wiring guidance and code in this tutorial are original Siratek work.
- DS3231 datasheet for the I2C address, the temperature-compensated oscillator, the accuracy figures and the onboard temperature sensor.
- RTClib documentation for the calls used.
- The small-format module with a female header and an underside coin cell was read from the Siratek product photographs for item 2522.
Whether a particular board includes a cell-charging circuit varies, so this tutorial tells the reader how to find out rather than assuming either way; the hazard described is a property of charging a non-rechargeable lithium cell. No third-party tutorial text, diagrams, photographs or code has been reproduced here.

