The Raspberry Pi does not have an onboard hardware real time clock (RTC). When disconnected from the internet, it cannot query an NTP server, causing the system time to drift or reset to the Unix epoch (January 1, 1970). To maintain accurate time offline, you must wire an external I2C RTC module. The DS3231 is the definitive choice for this: it features a temperature-compensated crystal oscillator (TCXO) that guarantees ±2ppm accuracy (roughly 1 minute of drift per year), vastly outperforming the older DS1307.
This guide targets the Raspberry Pi 4 Model B and Raspberry Pi 5 running Raspberry Pi OS (Bookworm). We will cover the physical wiring, provide a complete Python script using smbus2 to read and write the registers, and detail exactly how to debug the most common I2C failure modes.
Parts List & Specification Sheet
Time to Complete: 20 minutes
Tools Required: Female-to-female jumper wires, small flathead screwdriver (for terminal blocks if used), multimeter.
| Component | Exact Variant / Model | Technical Notes & Bench Advice |
|---|---|---|
| Microcontroller | Raspberry Pi 4B (4GB) or Pi 5 | Both share the standard 40-pin I2C1 layout. Pi 5 requires Bookworm OS. |
| RTC Module | DS3231 (Adafruit 3013 or ZS-042) | Warning: Cheap ZS-042 clones include an LIR2032 charging circuit. If using a standard CR2032, you must disable this circuit to prevent battery venting. |
| Backup Battery | CR2032 (3V Lithium) | Do not use LIR2032 unless you specifically want rechargeable (and lower capacity) chemistry. |
| Wiring | 4x Female-to-Female Jumper Wires | Keep I2C runs under 30cm (12 inches) to avoid capacitance issues on the SDA/SCL lines. |
Pin Mapping & Wiring Steps
The DS3231 communicates via the I2C protocol. The Raspberry Pi exposes the primary I2C1 bus on physical pins 3 and 5. While the DS3231 chip itself operates strictly at 3.3V, many generic breakout boards include a low-dropout (LDO) regulator allowing 5V input. However, feeding the LDO 5V generates localized heat, which degrades the TCXO accuracy. Always wire VCC to the Pi's 3.3V pin for maximum precision.
| DS3231 Pin | Raspberry Pi Pin (Physical) | Raspberry Pi GPIO (BCM) | Function |
|---|---|---|---|
| VCC | Pin 1 | 3.3V Power | Power input (3.3V recommended) |
| GND | Pin 6 | Ground | Common ground reference |
| SDA | Pin 3 | GPIO 2 | I2C Data Line |
| SCL | Pin 5 | GPIO 3 | I2C Clock Line |
Step-by-Step Setup
- De-energize the Pi: Unplug the USB-C power supply before connecting jumper wires to the GPIO header to prevent accidental short circuits.
- Connect the I2C Lines: Wire SDA to Pin 3, SCL to Pin 5, VCC to Pin 1, and GND to Pin 6.
- Enable I2C in Software: Boot the Pi, open a terminal, and run
sudo raspi-config. Navigate to Interface Options > I2C and select Yes to enable the ARM I2C interface. - Reboot and Verify: Run
sudo reboot. After restarting, install the I2C tools and scan the bus:
You should seesudo apt update && sudo apt install i2c-tools python3-smbus2 -y i2cdetect -y 168in the output grid, confirming the DS3231 is present at I2C address 0x68.
Python Code: Reading and Syncing the DS3231
The DS3231 stores time in Binary-Coded Decimal (BCD) format across registers 0x00 through 0x06. The following Python script uses the smbus2 library to read these registers, convert them to standard decimal, and output a formatted timestamp. This code targets Raspberry Pi 4/5 on I2C bus 1.
import smbus2
import datetime
import sys
import time
# Hardware configuration
I2C_BUS = 1
DS3231_ADDR = 0x68
def bcd_to_dec(bcd_val):
"""Convert Binary-Coded Decimal to standard integer."""
return (bcd_val & 0x0F) + ((bcd_val >> 4) * 10)
def dec_to_bcd(dec_val):
"""Convert standard integer to Binary-Coded Decimal."""
return ((dec_val // 10) << 4) | (dec_val % 10)
def read_rtc_time(bus):
"""Read time registers from DS3231 and return a datetime object."""
# Read 7 bytes starting from register 0x00 (Seconds)
data = bus.read_i2c_block_data(DS3231_ADDR, 0x00, 7)
seconds = bcd_to_dec(data[0] & 0x7F)
minutes = bcd_to_dec(data[1])
# Mask out the 12/24 hour mode bit (bit 6) for 24-hour format
hours = bcd_to_dec(data[2] & 0x3F)
day = bcd_to_dec(data[3])
date = bcd_to_dec(data[4])
month = bcd_to_dec(data[5] & 0x1F)
year = bcd_to_dec(data[6]) + 2000
return datetime.datetime(year, month, date, hours, minutes, seconds)
def main():
try:
bus = smbus2.SMBus(I2C_BUS)
except FileNotFoundError:
print("Error: I2C bus not found. Is I2C enabled in raspi-config?")
sys.exit(1)
except PermissionError:
print("Error: Permission denied. Run script with sudo or add user to i2c group.")
sys.exit(1)
try:
while True:
current_time = read_rtc_time(bus)
print(f"DS3231 Time: {current_time.strftime('%Y-%m-%d %H:%M:%S')}")
time.sleep(1)
except OSError as e:
print(f"I2C Communication Failed: {e}")
print("Check wiring, pull-up resistors, and run 'i2cdetect -y 1'.")
sys.exit(1)
except KeyboardInterrupt:
print("\nMonitoring stopped.")
finally:
bus.close()
if __name__ == '__main__':
main()
dtoverlay=i2c-rtc,ds3231 to /boot/firmware/config.txt. The Pi will then automatically pull time from the module during the boot sequence.
Debugging: Fixing I2C Communication Failures
When working with I2C on the bench, the most frequent and frustrating roadblock is the OSError: [Errno 121] Remote I/O error. This exact error string means the Raspberry Pi sent a clock pulse and data, but the DS3231 did not acknowledge (ACK) the transaction.
The First 3 Things to Check When It Fails
- Run
i2cdetect -y 1: If the grid is empty, you have a physical wiring fault or the I2C interface is disabled. If you seeUUat address 0x68, the kernel driver has already claimed the device (meaning thedtoverlayis active), and Python cannot access it directly viasmbus2. - Verify VCC Voltage: Use a multimeter to measure the voltage between the module's VCC and GND pins. It must read between 3.1V and 3.4V. If it reads 0V, your jumper wire is faulty or the Pi's 3.3V rail is damaged.
- Check for Backfeeding: Remove the coin cell battery and try running the script. If it works without the battery but fails with it inserted, the module's power management circuit is backfeeding voltage into the SDA/SCL lines, corrupting the logic levels.
Ranked Causes for Errno 121
- Missing or Weak Pull-Up Resistors: The I2C spec requires pull-up resistors on SDA and SCL. While Adafruit modules include 10kΩ pull-ups, some ultra-cheap clones omit them. If your wires are longer than 15cm, parasitic capacitance will pull the rising edges down, causing ACK failures. Fix: Solder 4.7kΩ resistors between SDA/SCL and 3.3V.
- Address Collision or Wrong Module: The DS3231 lives at
0x68. If you are using a module that also includes an AT24C32 EEPROM, that EEPROM lives at0x57. Ensure your Python code is targeting0x68. If you accidentally bought a PCF8523 module instead of a DS3231, the address is the same, but the register map is entirely different, which will result in garbage data rather than an I/O error. - The ZS-042 Charging Circuit Flaw: Generic ZS-042 boards include a diode and resistor designed to charge an LIR2032 battery from a 5V VCC source. If you power the board with 3.3V and use a non-rechargeable CR2032, this circuit can create a voltage divider that confuses the DS3231's internal power-switching logic. Fix: Desolder the diode or the 200Ω resistor near the battery holder.
Extending and Simplifying Your RTC Build
How to Simplify: If your goal is purely to keep the Linux system time accurate across reboots without writing custom Python daemons, rely entirely on the kernel overlay. Add dtoverlay=i2c-rtc,ds3231 to config.txt, disable the fake-hwclock service (sudo systemctl disable fake-hwclock), and let the Linux hwclock utility handle the synchronization automatically. This removes the need for user-space I2C polling.
How to Extend: The DS3231 is not just a clock; it is also a highly capable environmental sensor. Registers 0x11 (MSB) and 0x12 (LSB) contain a 10-bit temperature reading accurate to ±3°C. You can extend the Python script to read these registers, convert the two's complement binary to Celsius, and log the ambient temperature of your server rack or greenhouse alongside your timestamp data. Furthermore, the DS3231 features two programmable alarms (Registers 0x07-0x0E) that can pull the INT/SQW pin low, allowing you to wire the RTC directly to a Pi GPIO to trigger a wake-up interrupt or a hardware watchdog reset.
Frequently Asked Questions
Does the Raspberry Pi 5 have a built-in real time clock?
No, the Raspberry Pi 5 does not have an onboard hardware RTC. Like all previous models, it relies on an NTP internet connection to set the time on boot. However, the Pi 5's power management IC (PMIC) and standard 40-pin header still fully support external I2C RTC modules like the DS3231 using the exact same wiring and kernel overlays detailed in this guide.
Can I use a CR2032 instead of an LIR2032 on the ZS-042 module?
Yes, but you must disable the onboard charging circuit first. The ZS-042 module is designed to trickle-charge an LIR2032 lithium-ion cell. If you insert a standard, non-rechargeable CR2032 and apply power, the charging circuit will attempt to force current into the primary cell, which can lead to overheating, venting, or a fire hazard. Simply remove the surface-mount diode or the resistor bridging the VCC line to the battery holder to make it safe for a CR2032.
How long will the DS3231 keep time without main power?
The DS3231 chip itself draws less than 110µA in battery-backup mode. A high-quality 220mAh CR2032 battery from a reputable brand (like Panasonic or Energizer) will theoretically keep the clock running for over 3 years. However, generic clone modules often suffer from PCB flux residue or poor LDO leakage, which can drain a battery in 6 to 12 months. Always buy name-brand batteries and clean the module PCB with isopropyl alcohol if you experience premature battery death.
What is the difference between DS3231 and DS1307 for Raspberry Pi?
The DS1307 uses a standard 32kHz crystal oscillator that is highly susceptible to temperature fluctuations, leading to time drift of up to 5 minutes per month. The DS3231 integrates a MEMS temperature sensor and a TCXO (Temperature-Compensated Crystal Oscillator) that actively adjusts the clock frequency based on ambient temperature. This results in an accuracy of ±2ppm, meaning it will only drift about 1 minute per year. For any Raspberry Pi project involving data logging, scheduled events, or offline operation, the DS3231 is the mandatory upgrade.






