DS3231 RTC tutorial: set the time over i2c
A microcontroller forgets the time the moment you unplug it. The DS3231 remembers for years on a coin cell, and drifts about a minute a year — but it sits on the single most contested address on the bus, and it cannot move.
Why this one
DS3231 vs DS1307: temperature is the whole story
Both are i2c real-time clocks. Both sit at 0x68. Both cost a couple of dollars. One of them keeps time and one of them doesn't, and the difference is a crystal.
A quartz crystal's frequency shifts with temperature. The DS1307 uses a bare external 32.768 kHz crystal, so its accuracy tracks the weather — leave it on a windowsill and it'll drift minutes per month. The DS3231 puts the crystal inside the package next to a temperature sensor and continuously corrects for what it reads. The result is roughly ±2 ppm, which works out to about a minute a year.
That's not a small upgrade, it's a different class of part. If a module is cheap enough to make you suspicious, check the chip's markings — DS1307 boards are still sold in DS3231-shaped listings.
The temperature sensor is exposed as a bonus: readable over i2c, about ±3°C in 0.25°C steps. Fine for "how warm is the box", not a substitute for a BME280.
Watch out
The DS3231 i2c address is 0x68, and it can't move
There are no address pins on a DS3231. No jumper, no strap, no software command. Every DS3231 in the world is at 0x68.
That's a problem, because 0x68 is the busiest address on i2c. The MPU-6050 accelerometer/gyro defaults to 0x68. So does the AMG8833 thermal camera on its alternate strap. And "a clock plus a motion sensor" is not an exotic combination — it's a datalogger, which is most of what people build.
Since the DS3231 can't move, the other device has to. Fortunately the MPU-6050 can: pull its AD0 pin high and it goes to 0x69. If your bus-mate has no such option, the answers are a TCA9548A multiplexer or a second bus — the conflict guide ranks them, and the collision roundup covers who else lives up there.
One quirk worth knowing: on a Raspberry Pi with an RTC overlay loaded, i2cdetect shows UU at 0x68 instead of the address. That's not an error — it means the kernel's RTC driver has claimed the chip. See reading the i2cdetect grid.
Step 1
DS3231 wiring and the battery trap
Wiring is the easy part: VCC (3.3–5 V), GND, SDA, SCL. Most modules carry their own pull-ups. SQW is an optional alarm/square-wave output; 32K is a raw 32.768 kHz output you can ignore.
The battery is where cheap modules bite. Check which cell your board wants before you fit one.
Many budget DS3231 modules include a charging circuit — a resistor and diode feeding the coin cell — designed for a rechargeable LIR2032 (3.6 V). Drop a standard non-rechargeable CR2032 (3.0 V) into one of those and the board will try to charge a cell that must never be charged. At best it leaks; it is not a good failure mode.
Two safe options: fit the LIR2032 the board expects, or remove the charging resistor (usually marked 201 near the battery holder) and fit a CR2032. A CR2032 in a properly modified board runs the timekeeping for years, which is why most people do the second.
Then scan — you want 0x68.
Step 2 · Arduino
DS3231 Arduino code: set the time once
Install Adafruit's RTClib, then upload this once:
#include "RTClib.h"
RTC_DS3231 rtc;
void setup() {
Serial.begin(115200);
if (!rtc.begin()) { // fixed at 0x68
Serial.println("No DS3231 at 0x68 — check wiring");
while (1);
}
// Sets the clock to when this sketch was COMPILED.
// Run once, then upload the read-only sketch below — otherwise
// every reset rewinds the clock to compile time.
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
Serial.println("Time set.");
}
void loop() {} That __DATE__/__TIME__ trick is the standard way to set an RTC without typing a timestamp — the compiler bakes in the moment it built the sketch. It's also the standard way to be confused later: leave this in and every reset drags the clock back to compile time, which looks exactly like a dead battery. Set once, then re-upload something that only reads.
#include "RTClib.h"
RTC_DS3231 rtc;
void setup() {
Serial.begin(115200);
rtc.begin();
if (rtc.lostPower()) {
Serial.println("RTC lost power — time is not trustworthy!");
}
}
void loop() {
DateTime now = rtc.now();
Serial.printf("%04d-%02d-%02d %02d:%02d:%02d %.2f C\n",
now.year(), now.month(), now.day(),
now.hour(), now.minute(), now.second(),
rtc.getTemperature());
delay(1000);
} lostPower() is the function to actually use. It reads the chip's oscillator-stop flag, which latches if the DS3231 ever ran out of both VCC and battery — so it tells you the time is fiction rather than letting you log a plausible wrong timestamp for a week.
Then power-cycle it. Unplug the board completely, plug it back in, and read again. If the time held, your battery is good and you're done. If it reset, the battery is dead, missing, or in backwards.
Step 3
DS3231 registers and the BCD trap
Worth seeing once, because it explains a classic bug. The time registers start at 0x00:
| Register | Holds |
|---|---|
| 0x00–0x02 | seconds, minutes, hours |
| 0x03–0x06 | day-of-week, date, month, year |
| 0x07–0x0D | alarm 1 and alarm 2 |
| 0x0E–0x0F | control and status (the lost-power flag lives here) |
| 0x11–0x12 | temperature, MSB then LSB |
Everything is BCD, not binary. Binary-coded decimal stores each decimal digit in its own nibble, so 25 seconds is 0x25 — which as a plain byte is 37. Read the registers raw and your clock will look broken in an oddly consistent way: every value inflated, always by the pattern of the digits.
You can prove the chip works with no code at all, if you can do the conversion in your head:
# seconds register — 0x25 means 25 seconds, not 37
i2cget -y 1 0x68 0x00
# whole time block, 7 bytes from 0x00
i2cdump -y 1 0x68 b Libraries do the BCD conversion for you, which is why you should let them. It's only worth knowing so the numbers make sense when you meet them.
Going further
DS3231 alarms: sleeping for a year
The feature that makes the DS3231 more than a clock. It has two programmable alarms that pull the INT/SQW pin low at a time you choose — every minute, at 03:00 daily, on a specific date.
Wire that pin to a microcontroller's interrupt or enable input, and the shape of your project changes. Instead of staying awake to check the time, it sleeps at microamps and the clock wakes it. An ESP32 logger that takes a reading each hour spends the other 59 minutes drawing almost nothing — which is the difference between a week on a battery and a year.
The DS3231 keeps counting on its coin cell the whole time, so the alarm fires whether or not the rest of the board has any power at all.
Get one
Questions
DS3231 FAQ
What is the DS3231's i2c address?
0x68, fixed. There are no address pins and no jumper — every DS3231 ever made answers 0x68 and nothing else. That matters because 0x68 is the most contested address on the bus: the MPU-6050 defaults there too, and so does the AMG8833 thermal camera on its alternate strap. Since the DS3231 can't move, everything else has to.
Why is the DS3231 better than a DS1307?
Temperature compensation, and it's the whole story. Both are i2c RTCs at 0x68, but the DS1307 uses a bare 32.768 kHz crystal whose frequency drifts with temperature — expect minutes of error per month. The DS3231 puts the crystal inside the package with a temperature sensor and corrects for it continuously, giving roughly ±2 ppm, which is about a minute a year. The DS3231 costs slightly more and is worth it every time.
Do I need the battery, and which one?
You need it if you want the time to survive a power cut — that's the point of an RTC. A CR2032 runs the chip's timekeeping for years. But check your module first: many cheap DS3231 boards ship with a charging circuit designed for a rechargeable LIR2032, and putting a non-rechargeable CR2032 in one of those means it's slowly charged, which it must not be. Either fit a LIR2032, or remove the charging resistor and use a CR2032.
Why does my DS3231 show 2165 or a nonsense date?
Almost always because the time was never set, or the battery is dead or missing and the registers came up with garbage on power-on. Run a set-time sketch once, power-cycle, and read it back — if it survives, the battery is fine. If it resets every time, the battery is dead, absent, or in backwards. A rarer cause: reading BCD registers as plain binary, which turns 0x25 into 37 rather than 25.
What are the DS3231's alarms for?
Waking something up. It has two programmable alarms that pull the INT/SQW pin low at a time you set — wire that to a microcontroller's interrupt or enable pin and your project can sleep at microamps for hours, then be woken by the clock rather than by a loop checking the time. It's how a battery-powered logger runs for a year on a cell.
Can the DS3231 measure temperature?
Yes, and you get it free — the chip needs a temperature reading to compensate its crystal, and exposes it at about ±3°C accuracy in 0.25°C steps. It's fine for logging roughly how warm the enclosure is. Don't use it as your project's actual temperature sensor: a BME280 costs the same and is an order of magnitude better.