Modul RTC DS3231 adalah modul pencatat waktu yang sangat akurat, dilengkapi fitur untuk mengatur alarm, menghasilkan sinyal gelombang kotak (square wave) dengan berbagai frekuensi, serta membaca suhu internal. Dalam tutorial ini, Anda akan mempelajari cara mengatur dan mengambil waktu, mengonfigurasi alarm, serta membaca data suhu dari modul tersebut.
Pengenalan Modul Real-Time Clock (RTC)
Modul RTC seperti DS3231 dan DS1307 memiliki rangkaian jam internal yang berfungsi menjaga waktu secara mandiri. Umumnya, modul ini dilengkapi dudukan baterai (backup battery) agar tetap dapat mempertahankan pencatatan waktu meskipun board ESP32/ESP8266 mengalami reset atau kehilangan daya.
DS3231 dan DS1307 adalah dua modul RTC yang paling umum digunakan pada berbagai aplikasi mikrokontroler. Keduanya kompatibel dengan ESP32 dan ESP8266 serta berkomunikasi menggunakan protokol I2C. DS3231 menawarkan akurasi yang lebih tinggi karena dilengkapi kompensasi suhu (temperature-compensated), dan juga mendukung konfigurasi alarm eksternal yang sangat berguna untuk berbagai aplikasi otomatisasi.
Pengenalan Modul RTC DS3231
Gambar berikut menunjukkan modul RTC DS3231. Modul ini menggunakan kristal osilator 32 kHz dengan kompensasi suhu (TCXO) untuk menjaga waktu dengan tingkat presisi tinggi, bahkan ketika terjadi perubahan suhu lingkungan. Selain fungsi pencatat waktu, DS3231 juga menyediakan pembacaan suhu internal.
Selain mampu menjaga tanggal dan waktu dengan akurasi tinggi, modul ini juga memiliki memori internal untuk menyimpan hingga dua alarm, serta dapat menghasilkan sinyal gelombang kotak (square wave) pada beberapa frekuensi: 1 Hz, 4 kHz, 8 kHz, dan 32 kHz.
RTC berkomunikasi menggunakan protokol I2C dengan alamat default 0x68.
Modul ini juga dilengkapi EEPROM 24C32 berkapasitas 32 byte yang dapat digunakan untuk menyimpan data non-volatile. EEPROM tersebut dapat diakses melalui I2C menggunakan alamat 0x57.
DS3231 Battery Holder
DS3231 memiliki dudukan baterai untuk menjaga fungsi pencatat waktu tetap berjalan secara akurat meskipun terjadi pemadaman atau pelepasan daya pada mikrokontroler. Seluruh pengaturan waktu dan alarm tetap tersimpan.
Untuk modul ini, gunakan baterai tipe LIR2032, karena baterai tersebut dapat diisi ulang. Jangan menggunakan CR2032, karena baterai tersebut tidak bersifat rechargeable.
Jika Anda tetap ingin menggunakan baterai CR2032 yang tidak dapat diisi ulang, Anda harus menonaktifkan rangkaian pengisian baterai pada modul. Caranya adalah dengan melepas resistor (biasanya ditandai sebagai R4 pada sebagian modul) yang terletak di sebelah dioda.
Alarm DS3231
DS3231 mendukung dua alarm internal, yaitu Alarm 1 dan Alarm 2. Kedua alarm ini dapat dikonfigurasi untuk aktif pada waktu atau tanggal tertentu. Saat alarm aktif, pin SQW pada modul akan mengeluarkan sinyal LOW.
Sinyal ini dapat dibaca oleh ESP32/ESP8266 untuk memicu interrupt atau bahkan membangunkan mikrokontroler dari mode deep sleep. Fitur ini sangat berguna untuk menjalankan wake-up berkala, tugas otomatis berbasis waktu, serta notifikasi satu kali (karena alarm dapat di-reset setelah dipicu).
Alamat I2C Modul DS3231
Secara default, alamat I2C untuk RTC DS3231 adalah 0x68, sedangkan alamat EEPROM pada modul adalah 0x57. Anda dapat menjalankan program I2C scanner untuk memverifikasi alamat tersebut.
Pinout Modul RTC DS3231
Tabel berikut menjelaskan susunan pin (pinout) pada modul RTC DS3231.
Menghubungkan Modul RTC DS3231 ke ESP32 atau ESP8266
Berikut adalah komponen yang diperlukan untuk mengikuti tutorial ini:
- Board ESP32 atau ESP8266
- Modul RTC DS3231
- Kabel jumper
- Breadboard
Sambungkan modul DS3231 ke board ESP32 atau ESP8266 sesuai konfigurasi pin yang direkomendasikan. Anda dapat menggunakan tabel berikut sebagai acuan atau merujuk pada diagram rangkaian yang disediakan.
ESP32
ESP8266 NodeMCU
Bekerja dengan RTC
Dalam menggunakan modul RTC pada sebuah proyek, terdapat dua langkah penting yang harus dilakukan:
1. Mengatur waktu awal: pengaturan waktu dapat dilakukan secara manual dengan memasukkan waktu saat ini (atau waktu lain yang diinginkan) melalui kode program, menggunakan waktu lokal dari sistem, atau mengambil waktu secara otomatis dari server NTP.
2. Mempertahankan waktu: agar RTC tetap menyimpan waktu dengan benar meskipun kehilangan suplai daya, modul harus dipasangkan dengan baterai cadangan. Modul RTC umumnya sudah dilengkapi dudukan baterai tipe koin.
Modul MicroPython untuk DS3231
Terdapat beberapa pustaka (library) yang dapat digunakan untuk mempermudah komunikasi dengan modul RTC DS3231. Dalam tutorial ini, kita akan menggunakan versi modifikasi dari modul urtc.py yang dikembangkan oleh Adafruit.
Ikuti langkah-langkah berikut untuk menginstal modul yang diperlukan:
Mengunduh dan Mengunggah urtc.py
- Unduh file urtc.py dari tautan yang tersedia.
- Salin kode tersebut ke dalam file baru di Thonny IDE.
- Buka File > Save as… kemudian pilih MicroPython Device.
- Simpan file dengan nama urtc.py (jangan mengubah nama file).
# Forked and adapted from https://github.com/adafruit/Adafruit-uRTC/tree/master
import collections
import time
DateTimeTuple = collections.namedtuple("DateTimeTuple", ["year", "month",
"day", "weekday", "hour", "minute", "second", "millisecond"])
def datetime_tuple(year=None, month=None, day=None, weekday=None, hour=None,
minute=None, second=None, millisecond=None):
return DateTimeTuple(year, month, day, weekday, hour, minute,
second, millisecond)
def _bcd2bin(value):
return (value or 0) - 6 * ((value or 0) >> 4)
def _bin2bcd(value):
return (value or 0) + 6 * ((value or 0) // 10)
def tuple2seconds(datetime):
return time.mktime((datetime.year, datetime.month, datetime.day,
datetime.hour, datetime.minute, datetime.second, datetime.weekday, 0))
def seconds2tuple(seconds):
(year, month, day, hour, minute,
second, weekday, _yday) = time.localtime(seconds)
return DateTimeTuple(year, month, day, weekday, hour, minute, second, 0)
class _BaseRTC:
_SWAP_DAY_WEEKDAY = False
def __init__(self, i2c, address=0x68):
self.i2c = i2c
self.address = address
def _register(self, register, buffer=None):
if buffer is None:
return self.i2c.readfrom_mem(self.address, register, 1)[0]
self.i2c.writeto_mem(self.address, register, buffer)
def _flag(self, register, mask, value=None):
data = self._register(register)
if value is None:
return bool(data & mask)
if value:
data |= mask
else:
data &= ~mask
self._register(register, bytearray((data,)))
def datetime(self, datetime=None):
if datetime is None:
buffer = self.i2c.readfrom_mem(self.address,
self._DATETIME_REGISTER, 7)
if self._SWAP_DAY_WEEKDAY:
day = buffer[3]
weekday = buffer[4]
else:
day = buffer[4]
weekday = buffer[3]
return datetime_tuple(
year=_bcd2bin(buffer[6]) + 2000,
month=_bcd2bin(buffer[5]),
day=_bcd2bin(day),
weekday=_bcd2bin(weekday),
hour=_bcd2bin(buffer[2]),
minute=_bcd2bin(buffer[1]),
second=_bcd2bin(buffer[0]),
)
datetime = datetime_tuple(*datetime)
buffer = bytearray(7)
buffer[0] = _bin2bcd(datetime.second)
buffer[1] = _bin2bcd(datetime.minute)
buffer[2] = _bin2bcd(datetime.hour)
if self._SWAP_DAY_WEEKDAY:
buffer[4] = _bin2bcd(datetime.weekday)
buffer[3] = _bin2bcd(datetime.day)
else:
buffer[3] = _bin2bcd(datetime.weekday)
buffer[4] = _bin2bcd(datetime.day)
buffer[5] = _bin2bcd(datetime.month)
buffer[6] = _bin2bcd(datetime.year - 2000)
self._register(self._DATETIME_REGISTER, buffer)
class DS1307(_BaseRTC):
_NVRAM_REGISTER = 0x08
_DATETIME_REGISTER = 0x00
_SQUARE_WAVE_REGISTER = 0x07
def stop(self, value=None):
return self._flag(0x00, 0b10000000, value)
def memory(self, address, buffer=None):
if buffer is not None and address + len(buffer) > 56:
raise ValueError("address out of range")
return self._register(self._NVRAM_REGISTER + address, buffer)
class DS3231(_BaseRTC):
_CONTROL_REGISTER = 0x0e
_STATUS_REGISTER = 0x0f
_DATETIME_REGISTER = 0x00
_TEMPERATURE_MSB_REGISTER = 0x11
_TEMPERATURE_LSB_REGISTER = 0x12
_ALARM_REGISTERS = (0x08, 0x0b)
_SQUARE_WAVE_REGISTER = 0x0e
def lost_power(self):
return self._flag(self._STATUS_REGISTER, 0b10000000)
def alarm(self, value=None, alarm=0):
return self._flag(self._STATUS_REGISTER,
0b00000011 & (1 << alarm), value)
def interrupt(self, alarm=0):
return self._flag(self._CONTROL_REGISTER,
0b00000100 | (1 << alarm), 1)
def no_interrupt(self):
return self._flag(self._CONTROL_REGISTER, 0b00000011, 0)
def stop(self, value=None):
return self._flag(self._CONTROL_REGISTER, 0b10000000, value)
def datetime(self, datetime=None):
if datetime is not None:
status = self._register(self._STATUS_REGISTER) & 0b01111111
self._register(self._STATUS_REGISTER, bytearray((status,)))
return super().datetime(datetime)
def alarm_time(self, datetime=None, alarm=0):
if datetime is None:
buffer = self.i2c.readfrom_mem(self.address,
self._ALARM_REGISTERS[alarm], 3)
day = None
weekday = None
second = None
if buffer[2] & 0b10000000:
pass
elif buffer[2] & 0b01000000:
day = _bcd2bin(buffer[2] & 0x3f)
else:
weekday = _bcd2bin(buffer[2] & 0x3f)
minute = (_bcd2bin(buffer[0] & 0x7f)
if not buffer[0] & 0x80 else None)
hour = (_bcd2bin(buffer[1] & 0x7f)
if not buffer[1] & 0x80 else None)
if alarm == 0:
# handle seconds
buffer = self.i2c.readfrom_mem(
self.address, self._ALARM_REGISTERS[alarm] - 1, 1)
second = (_bcd2bin(buffer[0] & 0x7f)
if not buffer[0] & 0x80 else None)
return datetime_tuple(
day=day,
weekday=weekday,
hour=hour,
minute=minute,
second=second,
)
datetime = datetime_tuple(*datetime)
buffer = bytearray(3)
buffer[0] = (_bin2bcd(datetime.minute)
if datetime.minute is not None else 0x80)
buffer[1] = (_bin2bcd(datetime.hour)
if datetime.hour is not None else 0x80)
if datetime.day is not None:
if datetime.weekday is not None:
raise ValueError("can't specify both day and weekday")
buffer[2] = _bin2bcd(datetime.day)
elif datetime.weekday is not None:
buffer[2] = _bin2bcd(datetime.weekday) | 0b01000000
else:
buffer[2] = 0x80
self._register(self._ALARM_REGISTERS[alarm], buffer)
if alarm == 0:
# handle seconds
buffer = bytearray([_bin2bcd(datetime.second)
if datetime.second is not None else 0x80])
self._register(self._ALARM_REGISTERS[alarm] - 1, buffer)
def get_temperature(self):
"""
Reads the temperature from the DS3231's temperature registers.
Returns the temperature as a float in Celsius.
"""
msb = self._register(self._TEMPERATURE_MSB_REGISTER) # 0x11
lsb = self._register(self._TEMPERATURE_LSB_REGISTER) # 0x12
if msb is None or lsb is None:
print("Error: Register read returned None")
return None
temp = msb + ((lsb >> 6) * 0.25)
if msb & 0x80:
temp -= 256
return temp
class PCF8523(_BaseRTC):
_CONTROL1_REGISTER = 0x00
_CONTROL2_REGISTER = 0x01
_CONTROL3_REGISTER = 0x02
_DATETIME_REGISTER = 0x03
_ALARM_REGISTER = 0x0a
_SQUARE_WAVE_REGISTER = 0x0f
_SWAP_DAY_WEEKDAY = True
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.init()
def init(self):
# Enable battery switchover and low-battery detection.
self._flag(self._CONTROL3_REGISTER, 0b11100000, False)
def reset(self):
self._flag(self._CONTROL1_REGISTER, 0x58, True)
self.init()
def lost_power(self, value=None):
return self._flag(self._CONTROL3_REGISTER, 0b00010000, value)
def stop(self, value=None):
return self._flag(self._CONTROL1_REGISTER, 0b00010000, value)
def battery_low(self):
return self._flag(self._CONTROL3_REGISTER, 0b00000100)
def alarm(self, value=None):
return self._flag(self._CONTROL2_REGISTER, 0b00001000, value)
def datetime(self, datetime=None):
if datetime is not None:
self.lost_power(False) # clear the battery switchover flag
return super().datetime(datetime)
def alarm_time(self, datetime=None):
if datetime is None:
buffer = self.i2c.readfrom_mem(self.address,
self._ALARM_REGISTER, 4)
return datetime_tuple(
weekday=_bcd2bin(buffer[3] &
0x7f) if not buffer[3] & 0x80 else None,
day=_bcd2bin(buffer[2] &
0x7f) if not buffer[2] & 0x80 else None,
hour=_bcd2bin(buffer[1] &
0x7f) if not buffer[1] & 0x80 else None,
minute=_bcd2bin(buffer[0] &
0x7f) if not buffer[0] & 0x80 else None,
)
datetime = datetime_tuple(*datetime)
buffer = bytearray(4)
buffer[0] = (_bin2bcd(datetime.minute)
if datetime.minute is not None else 0x80)
buffer[1] = (_bin2bcd(datetime.hour)
if datetime.hour is not None else 0x80)
buffer[2] = (_bin2bcd(datetime.day)
if datetime.day is not None else 0x80)
buffer[3] = (_bin2bcd(datetime.weekday) | 0b01000000
if datetime.weekday is not None else 0x80)
self._register(self._ALARM_REGISTER, buffer)
Setelah modul tersebut berhasil diunggah ke board, Anda sudah dapat memanfaatkan seluruh fungsinya di dalam program untuk berinteraksi dengan modul RTC DS3231.
DS3231 RTC: Mengatur dan Membaca Waktu dengan MicroPython
Kode berikut digunakan untuk menyinkronkan waktu RTC dengan waktu sistem, kemudian membaca tanggal, waktu, dan suhu secara berkala setiap satu detik.
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/micropython-esp32-esp8266-ds3231/
# Code to synchronize the RTC with the local time
import time
import urtc
from machine import I2C, Pin
days_of_week = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
# Initialize RTC (connected to I2C) - ESP32
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
# Initialize RTC (connected to I2C) - uncomment for ESP8266
#i2c = I2C(scl=Pin(5), sda=Pin(4))
rtc = urtc.DS3231(i2c)
# Set the current time using a specified time tuple
# Time tuple: (year, month, day, day of week, hour, minute, seconds, milliseconds)
#initial_time = (2025, 1, 30, 1, 12, 30, 0, 0)
# Or get the local time from the system
initial_time_tuple = time.localtime() # tuple (microPython)
initial_time_seconds = time.mktime(initial_time_tuple) # local time in seconds
# Convert to tuple compatible with the library
initial_time = urtc.seconds2tuple(initial_time_seconds)
# Sync the RTC
rtc.datetime(initial_time)
while True:
current_datetime = rtc.datetime()
temperature = rtc.get_temperature()
# Display time details
print('Current date and time:')
print('Year:', current_datetime.year)
print('Month:', current_datetime.month)
print('Day:', current_datetime.day)
print('Hour:', current_datetime.hour)
print('Minute:', current_datetime.minute)
print('Second:', current_datetime.second)
print('Day of the Week:', days_of_week[current_datetime.weekday])
print(f"Current temperature: {temperature}°C")
# Format the date and time
formatted_datetime = (
f"{days_of_week[current_datetime.weekday]}, "
f"{current_datetime.year:04d}-{current_datetime.month:02d}-{current_datetime.day:02d} "
f"{current_datetime.hour:02d}:{current_datetime.minute:02d}:{current_datetime.second:02d} "
)
print(f"Current date and time: {formatted_datetime}")
print(" \n");
time.sleep(1)
Cara Kerja Kode
Berikut penjelasan singkat mengenai bagian-bagian penting dari kode tersebut.
Import Library
Pertama, kita perlu mengimpor modul urtc yang sebelumnya telah diunggah ke board karena modul tersebut menyediakan fungsi-fungsi untuk berkomunikasi dengan RTC. Selain itu, kita juga mengimpor kelas Pin dan I2C untuk membangun komunikasi I2C dengan modul RTC.
import time
import urtc
from machine import I2C, Pin
Inisialisasi Modul RTC
Selanjutnya, lakukan inisialisasi komunikasi I2C dan buat sebuah objek bernama rtc yang merepresentasikan modul DS3231. Pada contoh ini, digunakan pin I2C bawaan (default) dari ESP32 maupun ESP8266.
# Initialize RTC (connected to I2C) - ESP32
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
# Initialize RTC (connected to I2C) - uncomment for ESP8266
#i2c = I2C(scl=Pin(5), sda=Pin(4))
rtc = urtc.DS3231(i2c)
Jika Anda menggunakan ESP32, inisialisasi objek I2C dapat dilakukan dengan perintah berikut:
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
Jika Anda menggunakan ESP8266, objek I2C dapat dibuat dengan perintah berikut:
i2c = I2C(scl=Pin(5), sda=Pin(4))
Sinkronisasi Waktu
Untuk menyinkronkan waktu pada RTC, gunakan metode datetime() pada objek rtc dan berikan sebuah time tuple sebagai argumen dengan format berikut:
(year, month, day, day of week, hour, minute, seconds, milliseconds)
Catatan: format tuple ini berbeda dari format tuple yang digunakan pada modul *time* di MicroPython.
Anda dapat mengatur waktu secara manual ke tanggal dan jam tertentu menggunakan contoh berikut:
#initial_time = (2025, 1, 30, 1, 12, 30, 0, 0)
Atau, Anda dapat menyinkronkan RTC dengan waktu lokal pada sistem. Pada contoh ini, kita akan melakukan sinkronisasi menggunakan waktu lokal tersebut.
Pertama, ambil time tuple dari waktu lokal dengan menggunakan fungsi time.localtime().
initial_time_tuple = time.localtime() # tuple (microPython)
Tuple yang dihasilkan memiliki format berbeda dari yang digunakan oleh modul urtc.
Karena itu, langkah pertama adalah mengonversinya ke satuan detik menggunakan fungsi time.mktime().
initial_time_seconds = time.mktime(initial_time_tuple) # local time in seconds
Terakhir, konversikan nilai detik tersebut ke dalam format tuple yang kompatibel dengan pustaka urtc menggunakan fungsi seconds2tuple(). Fungsi ini menerima jumlah detik sejak epoch sebagai argumen dan menghasilkan tuple waktu yang sesuai untuk modul RTC.
initial_time = urtc.seconds2tuple(initial_time_seconds)
Selanjutnya, variabel initial_time yang sudah berisi waktu lokal dalam format tuple yang kompatibel dengan modul urtc dapat diberikan ke fungsi datetime() seperti contoh berikut.
# Convert to tuple compatible with the library
initial_time = urtc.seconds2tuple(initial_time_seconds)
# Sync the RTC
rtc.datetime(initial_time)
Catatan: Setelah waktu berhasil disetel untuk pertama kali, Anda hanya perlu menyetelnya kembali jika RTC kehilangan daya (pastikan baterai terpasang untuk mencegah hal ini). Anda dapat memeriksa apakah RTC kehilangan daya dengan memanggil fungsi lost_power() pada objek rtc. Fungsi ini akan mengembalikan nilai True atau False.
Membaca Waktu
Setelah baris-baris kode sebelumnya dijalankan, modul RTC sudah tersinkronisasi dengan waktu lokal. Anda dapat memanggil rtc.datetime() untuk memperoleh waktu saat ini dari RTC. Fungsi ini mengembalikan objek yang berisi seluruh elemen waktu.
Untuk mengambil dan menampilkan setiap elemen waktu secara terpisah, Anda dapat menggunakan contoh berikut:
current_datetime = rtc.datetime()
print('Current date and time:')
print('Year:', current_datetime.year)
print('Month:', current_datetime.month)
print('Day:', current_datetime.day)
print('Hour:', current_datetime.hour)
print('Minute:', current_datetime.minute)
print('Second:', current_datetime.second)
print('Day of the Week:', days_of_week[current_datetime.weekday])
Anda juga dapat memperoleh data suhu dari modul dengan memanggil metode get_temperature() pada objek rtc.
temperature = rtc.get_temperature()
Data suhu yang diperoleh ditampilkan dalam satuan derajat Celsius.
print(f"Current temperature: {temperature}°C")
Pada contoh ini, waktu dan suhu terkini akan ditampilkan setiap satu detik.
time.sleep(1)
Menjalankan Kode
Jalankan kode tersebut untuk menyinkronkan waktu pada RTC dengan waktu lokal, serta membaca waktu dan suhu saat ini.
Setelah proses sinkronisasi dilakukan, RTC akan mempertahankan waktu secara mandiri selama baterai cadangan terpasang. Dengan demikian, Anda tidak perlu melakukan sinkronisasi ulang, dan cukup memanggil rtc.datetime() untuk mendapatkan waktu saat ini.
DS3231 dengan ESP32/ESP8266 – Mengatur Alarm
Modul RTC DS3231 mendukung hingga dua alarm internal, yaitu Alarm 1 dan Alarm 2. Ketika waktu saat ini mencapai waktu yang telah ditetapkan, modul akan mengubah status pin SQW dari HIGH menjadi LOW (aktif rendah). Perubahan status pada pin ini dapat dideteksi oleh ESP32/ESP8266 untuk menjalankan tugas tertentu saat alarm aktif.
Catatan penting terkait fitur alarm pada DS3231:
- Modul RTC mendukung penyimpanan hingga dua alarm.
- Hanya satu alarm yang dapat diaktifkan pada satu waktu.
- Setelah sebuah alarm dipicu, Anda harus menghapus (*clear*) flag-nya untuk mencegah alarm aktif kembali dan menghindari potensi crash pada ESP32/ESP8266.
- Sebelum mengaktifkan alarm lain, alarm yang sedang aktif harus dinonaktifkan terlebih dahulu.
DS3231 – Konfigurasi Alarm
Contoh berikut menunjukkan cara mengatur dua alarm. Pada contoh ini, LED bawaan pada board akan berubah status (toggle) saat alarm aktif.
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/micropython-esp32-esp8266-ds3231/
import time
import urtc
from machine import Pin, I2C
# Pin setup for SQW pin and LED
CLOCK_INTERRUPT_PIN = 4 # Adjust to your specific GPIO pin for SQW (ESP32)
#CLOCK_INTERRUPT_PIN = 14 # Adjust to your specific GPIO pin for SQW (ESP8266)
LED_PIN = 2 # Adjust to your specific GPIO pin for the LED
# Initialize RTC (connected to I2C) - ESP32
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
# Initialize RTC (connected to I2C) - uncomment for ESP8266
#i2c = I2C(scl=Pin(5), sda=Pin(4))
rtc = urtc.DS3231(i2c)
# Setup GPIO for SQW and LED
sqw_pin = Pin(CLOCK_INTERRUPT_PIN, Pin.IN, Pin.PULL_UP)
led_pin = Pin(LED_PIN, Pin.OUT)
led_pin.off()
# Alarm times (year, month, day, weekday, hour, minute, second, millisecond)
alarm1_time = urtc.datetime_tuple(2025, 1, 2, None, 14, 58, 0, 0) # Alarm 1 uses full datetime
alarm2_time = urtc.datetime_tuple(2025, 1, 2, None, 14, 59, 0, 0) # Alarm 2 uses day, hour, minute, weekday
# Print the current time from the RTC
def print_current_time():
now = rtc.datetime()
formatted_time = f"{now.year}-{now.month:02}-{now.day:02} {now.hour:02}:{now.minute:02}:{now.second:02}"
print(f"Current time: {formatted_time}")
#Callback for handling alarm interrupt.
def on_alarm(pin):
print("Interrupt detected.")
if rtc.alarm(alarm=0): # Check if Alarm 1 triggered
print("Alarm 1 triggered.")
rtc.alarm(False, alarm=0) # Clear Alarm 1 flag
led_pin.value(not led_pin.value()) # Toggle LED for Alarm 1
if rtc.alarm(alarm=1): # Check if Alarm 2 triggered
print("Alarm 2 triggered.")
rtc.alarm(False, alarm=1) # Clear Alarm 2 flag
led_pin.value(not led_pin.value()) # Toggle LED for Alarm 2
# Setup alarms on the DS3231
def setup_alarms():
# Clear any existing alarms
rtc.alarm(False, 0)
rtc.alarm(False, 1)
rtc.no_interrupt()
# Set the desired alarm times
rtc.alarm_time(alarm1_time, 0) # Alarm 1
rtc.alarm_time(alarm2_time, 1) # Alarm 2
# Enable interrupts for the alarms
rtc.interrupt(0)
rtc.interrupt(1)
print("Alarms set successfully.")
# Attach the interrupt callback
sqw_pin.irq(trigger=Pin.IRQ_FALLING, handler=on_alarm)
try:
# Sync time to compile time if RTC lost power
if rtc.lost_power():
initial_time_tuple = time.localtime() # tuple (MicroPython)
initial_time_seconds = time.mktime(initial_time_tuple) # local time in seconds
initial_time = urtc.seconds2tuple(initial_time_seconds) # Convert to tuple compatible with the library
rtc.datetime(initial_time) # Sync the RTC
print("RTC initialized. Setting alarms...")
setup_alarms()
while True:
print_current_time()
time.sleep(5)
except KeyboardInterrupt:
print("Exiting program.")
Cara Kerja Kode
Berikut penjelasan mengenai bagian-bagian penting dari kode yang digunakan untuk mengatur alarm.
Pendefinisian Pin
Pertama, tentukan GPIO pada mikrokontroler yang terhubung ke pin SQW pada modul DS3231. Pin ini akan berubah status ketika alarm aktif. Pada contoh ini, digunakan GPIO 4 untuk ESP32 dan GPIO 14 untuk ESP8266. Anda dapat menggunakan pin lain selama sesuai dengan konfigurasi rangkaian.
CLOCK_INTERRUPT_PIN = 4 # Adjust to your specific GPIO pin for SQW (ESP32)
#CLOCK_INTERRUPT_PIN = 14 # Adjust to your specific GPIO pin for SQW (ESP8266)
Pin SQW kemudian dikonfigurasi sebagai input dengan resistor pull-up internal, karena sinyal pada pin tersebut bersifat active-low.
sqw_pin = Pin(CLOCK_INTERRUPT_PIN, Pin.IN, Pin.PULL_UP)
Waktu Alarm
Selanjutnya, dibuat dua variabel bertipe datetime_tuple untuk menyimpan konfigurasi waktu alarm seperti contoh berikut.
# Alarm times (year, month, day, weekday, hour, minute, second, millisecond)
alarm1_time = urtc.datetime_tuple(2025, 1, 2, None, 14, 44, 0, 0) # Alarm 1 uses full datetime
alarm2_time = urtc.datetime_tuple(2025, 1, 2, None, 14, 46, 0, 0) # Alarm 2 uses day, hour, minute, weekday
Sesuaikan nilai waktu alarm sesuai kebutuhan Anda.
Mengonfigurasi Pin SQW sebagai Interrupt
Selanjutnya, pin SQW dikonfigurasi sebagai sumber interrupt dengan memanggil metode irq().
sqw_pin.irq(trigger=Pin.IRQ_FALLING, handler=on_alarm)
Metode irq() menerima beberapa argumen penting:
- handler: fungsi yang akan dijalankan ketika interrupt terdeteksi — pada contoh ini adalah fungsi on_alarm().
- trigger: menentukan jenis pemicu interrupt. Terdapat tiga mode yang tersedia; dalam kasus ini digunakan IRQ_FALLING untuk memicu interrupt ketika pin berubah dari HIGH ke LOW.
Fungsi on_alarm
Fungsi on_alarm() digunakan untuk memeriksa alarm mana yang aktif, kemudian menonaktifkannya untuk menghapus flag alarm tersebut. Fungsi ini akan dieksekusi setiap kali alarm dipicu.
def on_alarm(pin):
Baris kode berikut memeriksa apakah Alarm 1 telah dipicu. Dengan cara ini, kita dapat langsung menonaktifkan alarm tersebut dan menjalankan tugas tertentu pada waktu yang telah ditentukan.
if rtc.alarm(alarm=0): # Check if Alarm 1 triggered
print("Alarm 1 triggered.")
rtc.alarm(False, alarm=0) # Clear Alarm 1 flag
led_pin.value(not led_pin.value()) # Toggle LED for Alarm 1
Baris kode ini menghapus flag Alarm 1, mengembalikannya ke kondisi awal.
rtc.alarm(False, alarm=0) # Clear Alarm 1 flag
Selanjutnya, kita mengubah status LED (toggle) sesuai kondisi alarm.
led_pin.value(not led_pin.value()) # Toggle LED for Alarm 1
Langkah yang sama juga diterapkan untuk Alarm 2.
if rtc.alarm(alarm=1): # Check if Alarm 2 triggered
print("Alarm 2 triggered.")
rtc.alarm(False, alarm=1) # Clear Alarm 2 flag
led_pin.value(not led_pin.value()) # Toggle LED for Alarm 2
Konfigurasi Alarm
Kita membuat fungsi bernama setup_alarms() untuk mengatur alarm.
Sebelum mengatur alarm baru, pastikan semua alarm sebelumnya telah dihapus dengan kode berikut.
def setup_alarms():
# Clear any existing alarms
rtc.alarm(False, 0)
rtc.alarm(False, 1)
rtc.no_interrupt()
Selanjutnya, kita dapat mengatur waktu alarm dengan memanggil metode alarm_time() pada objek rtc. Berikan sebagai argumen waktu alarm (variabel bertipe datetime_tuple) dan nomor alarm (0 = Alarm 1; 1 = Alarm 2).
# Set the desired alarm times
rtc.alarm_time(alarm1_time, 0) # Alarm 1
rtc.alarm_time(alarm2_time, 1) # Alarm 2
Kita mengaktifkan interrupt untuk alarm, sehingga pin SQW akan terpicu saat salah satu alarm aktif.
rtc.interrupt(0)
rtc.interrupt(1)
Selanjutnya, terdapat blok try yang akan menyesuaikan waktu RTC jika diperlukan (misalnya jika modul kehilangan daya) sekaligus mengatur alarm.
try:
# Sync time to compile time if RTC lost power
if rtc.lost_power():
initial_time_tuple = time.localtime() # tuple (MicroPython)
initial_time_seconds = time.mktime(initial_time_tuple) # local time in seconds
initial_time = urtc.seconds2tuple(initial_time_seconds) # Convert to tuple compatible with the library
rtc.datetime(initial_time) # Sync the RTC
print("RTC initialized. Setting alarms...")
setup_alarms()
Selanjutnya, kita akan menampilkan waktu saat ini secara berkala setiap lima detik.
while True:
print_current_time()
time.sleep(5)
Deteksi alarm berjalan di latar belakang karena kita telah mengonfigurasi interrupt pada pin SQW.
Menguji Alarm
Atur alarm dengan waktu yang dekat agar dapat langsung melihat fungsinya. Setelah itu, jalankan kodenya.
Waktu saat ini akan ditampilkan di shell setiap lima detik. Ketika alarm aktif, pesan akan muncul di shell dan LED bawaan pada ESP32 atau ESP8266 akan berubah status (toggle).
DS3231 – Mengatur Alarm Periodik
Untuk mengatur alarm periodik, kita dapat menyesuaikan waktu alarm setiap kali alarm sebelumnya dipicu. Misalnya, jika ingin memicu alarm setiap 10 menit, caranya adalah:
1. Baca waktu saat ini ketika alarm dipicu.
2. Atur alarm baru dengan menambahkan 600 detik (10 menit) ke waktu saat ini.
3. Setelah 10 menit, alarm akan kembali dipicu.
4. Ulangi langkah-langkah sebelumnya.
Langkah-langkah ini akan diimplementasikan pada contoh berikut.
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/micropython-esp32-esp8266-ds3231/
from machine import Pin, I2C
import time
import urtc
# Pin setup for SQW pin and LED
CLOCK_INTERRUPT_PIN = 4 # Adjust to your specific GPIO pin for SQW (ESP32)
#CLOCK_INTERRUPT_PIN = 14 # Adjust to your specific GPIO pin for SQW (ESP8266)
LED_PIN = 2 # Adjust to your specific GPIO pin for the LED
# Initialize RTC (connected to I2C) - ESP32
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
# Initialize RTC (connected to I2C) - uncomment for ESP8266
#i2c = I2C(scl=Pin(5), sda=Pin(4))
rtc = urtc.DS3231(i2c)
# Setup GPIO for SQW and LED
sqw_pin = Pin(CLOCK_INTERRUPT_PIN, Pin.IN, Pin.PULL_UP)
led_pin = Pin(LED_PIN, Pin.OUT)
led_pin.off()
# Add minutes to a datetime tuple and return a new datetime tuple
def add_minutes_to_time(dt, minutes):
timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, 0, 0))
new_timestamp = timestamp + (minutes * 60)
new_time = time.localtime(new_timestamp)
return urtc.datetime_tuple(new_time[0], new_time[1], new_time[2], None, new_time[3], new_time[4], new_time[5], 0)
# Print the current time from the RTC
def print_current_time():
now = rtc.datetime()
formatted_time = f"{now.year}-{now.month:02}-{now.day:02} {now.hour:02}:{now.minute:02}:{now.second:02}"
print(f"Current time: {formatted_time}")
# Callback for handling alarm interrupt
def on_alarm(pin):
print("Alarm triggered! Toggling the LED.")
led_pin.value(not led_pin.value()) # Toggle the LED
# Clear the alarm flag and schedule the next alarm
if rtc.alarm(alarm=0): # Check if Alarm 0 triggered
print("Clearing Alarm 0 flag.")
rtc.alarm(False, alarm=0) # Clear alarm flag
# Schedule the alarm to repeat 10 minutes from now
now = rtc.datetime()
next_alarm_time = add_minutes_to_time(now, 10) # Add 10 minutes
rtc.alarm_time(next_alarm_time, alarm=0) # Set new alarm
rtc.interrupt(0) # Ensure Alarm 0 interrupt is enabled
print(f"Next alarm set for: {next_alarm_time}")
def setup_alarm():
# Clear any existing alarms
rtc.alarm(False, 0)
rtc.no_interrupt()
# Get the current time and set the first alarm 1 minute from now
now = rtc.datetime()
first_alarm_time = add_minutes_to_time(now, 1) # Set first alarm for 1 minute from now
rtc.alarm_time(first_alarm_time, alarm=0) # Alarm 0
# Enable the interrupt for Alarm 0
rtc.interrupt(0)
print(f"Alarm set for: {first_alarm_time}")
# Attach the interrupt callback
sqw_pin.irq(trigger=Pin.IRQ_FALLING, handler=on_alarm)
try:
# Sync time to compile time if RTC lost power
if rtc.lost_power():
initial_time_tuple = time.localtime() # tuple (MicroPython)
initial_time_seconds = time.mktime(initial_time_tuple) # local time in seconds
initial_time = urtc.seconds2tuple(initial_time_seconds) # Convert to tuple compatible with the library
rtc.datetime(initial_time) # Sync the RTC
print("RTC initialized. Setting up the periodic alarm...")
setup_alarm()
while True:
print_current_time()
time.sleep(5)
except KeyboardInterrupt:
print("Exiting program.")
Pada contoh ini, kita membuat fungsi bernama add_minutes_to_time() yang berfungsi menambahkan sejumlah menit ke waktu saat ini.
# Add minutes to a datetime tuple and return a new datetime tuple
def add_minutes_to_time(dt, minutes):
timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, 0, 0))
new_timestamp = timestamp + (minutes * 60)
new_time = time.localtime(new_timestamp)
return urtc.datetime_tuple(new_time[0], new_time[1], new_time[2], None, new_time[3], new_time[4], new_time[5], 0)
Fungsi ini menerima dua argumen: sebuah datetime tuple (misalnya waktu saat ini) dan jumlah menit yang ingin ditambahkan untuk mengatur alarm baru di masa depan. Fungsi akan mengembalikan datetime tuple baru yang berisi waktu alarm yang telah diperbarui.
Saat alarm dipicu, fungsi on_alarm() akan dijalankan. Pertama-tama, fungsi ini akan mengubah status LED bawaan pada board.
# Callback for handling alarm interrupt
def on_alarm(pin):
print("Alarm triggered! Toggling the LED.")
led_pin.value(not led_pin.value()) # Toggle the LED
Selanjutnya, fungsi ini akan menonaktifkan alarm dengan menghapus flag Alarm 1.
if rtc.alarm(alarm=0): # Check if Alarm 0 triggered
print("Clearing Alarm 0 flag.")
rtc.alarm(False, alarm=0) # Clear alarm flag
Setelah itu, fungsi akan mengatur waktu alarm baru dengan menambahkan sejumlah menit tertentu ke waktu saat ini. Pada contoh ini, alarm diatur untuk aktif setiap 10 menit.
# Schedule the alarm to repeat 10 minutes from now
now = rtc.datetime()
next_alarm_time = add_minutes_to_time(now, 10) # Add 10 minutes
rtc.alarm_time(next_alarm_time, alarm=0) # Set new alarm
rtc.interrupt(0) # Ensure Alarm 0 interrupt is enabled
print(f"Next alarm set for: {next_alarm_time}")
Anda dapat dengan mudah mengubah interval ini dengan menyesuaikannya pada baris kode berikut sesuai waktu yang diinginkan.
next_alarm_time = add_minutes_to_time(now, 10) # Add 10 minutes
Menguji Alarm Periodik
Atur alarm pada waktu yang dekat agar dapat langsung melihat fungsinya. Setelah itu, jalankan kodenya.
Alarm pertama akan aktif setelah satu menit, sedangkan alarm berikutnya akan aktif setiap 10 menit. Saat alarm dipicu, status LED bawaan pada board juga akan diubah (toggle).
Siap Untuk Membuat Proyek Impianmu Menjadi Kenyataan?
Klik di sini untuk chat langsung via WhatsApp dan dapatkan dukungan langsung dari tim ahli kami!







0 on: "Langkah Mudah Menggunakan DS3231 Real Time Clock di ESP32/ESP8266 dengan MicroPython"