Learn

Keeping Time with a DS3231 RTC Module on a Raspberry Pi 4B

Wire a DS3231 real-time clock to a Raspberry Pi 4B over I2C, read the date and time in Python, set the clock, and see how the backup battery keeps time while power is off.

Note: the DS3231 RTC Module is a VIP-tier component in the simulator — you’ll need a VIP plan to use it there.

Try this directly in the free Raspberry Pi 4B simulator — no hardware or signup required. New to the GPIO header? Start with the interactive pinout guide.

What you’ll need

  • Raspberry Pi 4B
  • DS3231 RTC Module

Step by step

  1. Drag Raspberry Pi 4B onto the Canvas.
  2. Drag the DS3231 RTC Module onto the Canvas — a real 6-pin breakout that plugs directly into a breadboard.
  3. Hover over its pins to confirm the labels, left to right: VCC, GND, SDA, SCL, SQW, 32K.
  4. Wire it up: VCC → Pi's 3.3V pin. GND → Pi's GND pin. SDA → Pi's SDA pin. SCL → Pi's SCL pin. Only these four are needed to read and set the time.

SQW (square-wave / alarm output) and 32K (32.768 kHz output) are present so the board matches the real hardware, but they don't produce any signal in this simulator. This breakout has no onboard EEPROM, and the clock's I2C address is always 0x68.

  1. Go to the Code tab and write a script that reads the clock. The DS3231 stores each value as BCD (two decimal digits packed into one byte), so the script converts it:
import asyncio
from smbus2 import SMBus
 
bus = SMBus(1)
 
def bcd_to_int(b):
return (b >> 4) * 10 + (b & x0F)
 
def int_to_bcd(n):
return ((n // 10) << 4) | (n % 10)
 
def read_time():
r = bus.read_i2c_block_data(x68, x00, 7)
return "20%02d-%02d-%02d %02d:%02d:%02d" % (
bcd_to_int(r[6]), bcd_to_int(r[5] & x1F), bcd_to_int(r[4]),
bcd_to_int(r[2]), bcd_to_int(r[1]), bcd_to_int(r[0]))
 
print("Clock says:", read_time())
 
# Set the clock: seconds, minutes, hours, weekday, date, month, year
bus.write_i2c_block_data(x68, x00, [
int_to_bcd(50), int_to_bcd(59), int_to_bcd(23), 3,
int_to_bcd(31), int_to_bcd(12), int_to_bcd(29)])
 
while True:
print("Clock says:", read_time())
await asyncio.sleep(1)
  1. Click Start.

Until you set it, the clock starts at your computer's current local date and time. After the script writes a new time, the clock keeps counting up from there in real time — here it rolls over from 2029-12-31 to 2030-01-01.

  1. To see the backup battery at work, click Stop, wait a few seconds, then click Start again: the time has moved forward by the time that passed, because the battery kept the clock running while the module was unpowered.
  2. Optional: select the module and untick "Backup battery installed" in its settings panel (while stopped). Now stopping the simulation freezes the clock; on the next Start the time picks up where it stopped, and reading the status register (0x0F) shows bit 7 (the oscillator-stop flag) set.

What “working correctly” looks like

  • The first line prints your computer's current date and time, then the Console prints a new "Clock says:" line every second.
  • After the script sets the time, the printed value jumps to 2029-12-31 23:59:50 and counts up, rolling into 2030-01-01.
  • Stopping and restarting with the battery installed shows the time has kept moving; with the battery removed it stays where it was.

If something’s wrong

  • Every value reads 0 → confirm both VCC and GND are wired; an unpowered module doesn't answer, exactly like real hardware.
  • The time looks right but the date is odd → remember the month register's top bit is the century flag, which is why the script masks it with & 0x1F.
  • Two chips on one address → the DS3231 and an MPU6050 with AD0 low both use 0x68. The Console warns that only one will respond; wire the MPU6050's AD0 to 3.3V to move it to 0x69.
  • The time resets whenever you reload the page → the simulated clock lives in the page, so a reload starts it again from your computer's current time.
  • Scripts that write a register number and then call read_byte() aren't supported; use read_byte_data() or read_i2c_block_data() as in the example.