From 4be366fc29f2fdc44fbcd449fe534a7d1b14095c Mon Sep 17 00:00:00 2001 From: Dan Mccreary Date: Mon, 6 Jan 2025 20:15:16 -0600 Subject: [PATCH] updating content --- docs/lessons/13-real-time-clocks.md | 37 ++- src/kits/large-oled-w/lib/ds3231.py | 263 ++++++++++++++++++ .../large-oled-w/set-rtc-from-localtime.py | 37 +++ src/rtc/main.py | 165 +++++++++++ src/rtc/set-rtc-from-localtime.py | 63 +++++ 5 files changed, 562 insertions(+), 3 deletions(-) create mode 100644 src/kits/large-oled-w/lib/ds3231.py create mode 100644 src/kits/large-oled-w/set-rtc-from-localtime.py create mode 100644 src/rtc/main.py create mode 100644 src/rtc/set-rtc-from-localtime.py diff --git a/docs/lessons/13-real-time-clocks.md b/docs/lessons/13-real-time-clocks.md index f2f241fc..f58b1ebf 100644 --- a/docs/lessons/13-real-time-clocks.md +++ b/docs/lessons/13-real-time-clocks.md @@ -352,22 +352,43 @@ Where data is a binary coded list of binary-coded integers that represent the datetime structure. Here is an example program called ```set-rtc-from-localtime.py``` +One other note. The DS3231 has an internal oscillator that +can get turned off. After you set the time it is important +to make sure that the oscillator is running correctly again. The function +```reset_osf()``` will clear the stop flag to make sure +that the clock is ticking. + ```python from machine import Pin, I2C from utime import localtime +# Constants +DS3231_ADDR = 0x68 +STATUS_REG = 0x0F # Status register address + # I2C setup sda = Pin(0, Pin.OUT) scl = Pin(1, Pin.OUT) i2c = I2C(0, scl=scl, sda=sda, freq=3000000) -DS3231_ADDR = 0x68 def dec2bcd(dec): """Convert decimal to binary coded decimal.""" return (dec // 10) << 4 | (dec % 10) +def check_osf(): + """Check the oscillator stop flag.""" + status = i2c.readfrom_mem(DS3231_ADDR, STATUS_REG, 1)[0] + return bool(status >> 7) + +def reset_osf(): + """Clear the oscillator stop flag.""" + status = bytearray(1) + i2c.readfrom_mem_into(DS3231_ADDR, STATUS_REG, status) + i2c.writeto_mem(DS3231_ADDR, STATUS_REG, bytearray([status[0] & 0x7f])) + def set_ds3231(): - now = localtime() # get the time Thonny set on connect + """Set the DS3231 RTC time and ensure oscillator is running.""" + now = localtime() year = now[0] % 100 # Convert to 2-digit year month = now[1] day = now[2] @@ -375,7 +396,11 @@ def set_ds3231(): minute = now[4] second = now[5] - # convert from integers to BCD format and put into a bytearray + # First check if oscillator is stopped + if check_osf(): + print("Oscillator was stopped. Resetting OSF flag...") + reset_osf() + data = bytearray([ dec2bcd(second), dec2bcd(minute), @@ -388,6 +413,12 @@ def set_ds3231(): i2c.writeto_mem(DS3231_ADDR, 0x00, data) print(f"RTC set to: {month}/{day}/{now[0]} {hour:02d}:{minute:02d}:{second:02d}") + + # Verify oscillator is running + if check_osf(): + print("Warning: Oscillator still shows stopped state!") + else: + print("Oscillator running normally") if __name__ == "__main__": set_ds3231() diff --git a/src/kits/large-oled-w/lib/ds3231.py b/src/kits/large-oled-w/lib/ds3231.py new file mode 100644 index 00000000..f8c5780e --- /dev/null +++ b/src/kits/large-oled-w/lib/ds3231.py @@ -0,0 +1,263 @@ +# DS3231 library for micropython +# tested on ESP8266 +# +# Author: Sebastian Maerker +# License: mit +# +# only 24h mode is supported +# +# features: +# - set time +# - read time +# - set alarms + +import machine +from math import floor + +i2cAddr = 0x68 # change I2C Address here if neccessary + +class DS3231: + + def __init__(self, i2cClockPin, i2cDataPin): + # create RTC instance with I2C Pins + self.sclPin = machine.Pin(i2cClockPin, pull = machine.Pin.PULL_UP, mode=machine.Pin.OPEN_DRAIN) + self.sdaPin = machine.Pin(i2cDataPin, pull = machine.Pin.PULL_UP, mode=machine.Pin.OPEN_DRAIN) + + self.i2cVar = machine.I2C(-1, scl=self.sclPin, sda=self.sdaPin) + self.i2cAddr = i2cAddr + + # get times functions ------------------------------------------------------------------------------------------------------- + + def getYear(self): + return decodeToDec(self.i2cVar.readfrom_mem(self.i2cAddr, 0x06, 1)) + + def getMonth(self): + temp = self.i2cVar.readfrom_mem(self.i2cAddr, 0x05, 1) + return decodeToDec(convertToByteType(temp[0] & 0x7F)) + + def getDay(self): + # 0 - 31 + return decodeToDec(self.i2cVar.readfrom_mem(self.i2cAddr, 0x04, 1)) + + def getDayOfWeek(self): + # 1 - 7 + return decodeToDec(self.i2cVar.readfrom_mem(self.i2cAddr, 0x03, 1)) + + def getHour(self): + temp = self.i2cVar.readfrom_mem(self.i2cAddr, 0x02, 1) + return decodeToDec(convertToByteType(temp[0] & 0x3F)) + + def getMinutes(self): + return decodeToDec(self.i2cVar.readfrom_mem(self.i2cAddr, 0x01, 1)) + + def getSeconds(self): + return decodeToDec(self.i2cVar.readfrom_mem(self.i2cAddr, 0x00, 1)) + + def getDateTime(self): + # returns whole date and time as list + # (last two digits of year, month, day, day of week, hour, minutes, seconds) + dateTime = [0, 0, 0, 0, 0, 0, 0] + dateTime[0] = self.getYear() + dateTime[1] = self.getMonth() + dateTime[2] = self.getDay() + dateTime[3] = self.getDayOfWeek() + dateTime[4] = self.getHour() + dateTime[5] = self.getMinutes() + dateTime[6] = self.getSeconds() + return dateTime + + # set times functions ------------------------------------------------------------------------------------------------------- + + def setYear(self, year): + # only last two digits (last two digits are used if longer) + if(year > 99): + thousands = floor(year / 100) + year = year - (thousands * 100) + self.i2cVar.writeto_mem(self.i2cAddr, 0x06, convertToByteType(encodeToByte(year))) + + def setMonth(self, month): + self.i2cVar.writeto_mem(self.i2cAddr, 0x05, convertToByteType(encodeToByte(month) | 0)) + + def setDay(self, day): + # 0 - 31 + self.i2cVar.writeto_mem(self.i2cAddr, 0x04, convertToByteType(encodeToByte(day))) + + def setDayOfWeek(self, dayOfWeek): + # 1 - 7 + self.i2cVar.writeto_mem(self.i2cAddr, 0x03, convertToByteType(encodeToByte(dayOfWeek))) + + def setHour(self, hour): + self.i2cVar.writeto_mem(self.i2cAddr, 0x02, convertToByteType(encodeToByte(hour) & 0x3F)) + + def setMinutes(self, minutes): + self.i2cVar.writeto_mem(self.i2cAddr, 0x01, convertToByteType(encodeToByte(minutes))) + + def setSeconds(self, seconds): + self.i2cVar.writeto_mem(self.i2cAddr, 0x00, convertToByteType(encodeToByte(seconds))) + + def setDateTime(self, year, month, day, dayOfWeek, hour, minutes, seconds): + # set all the date and times (year is last two digits of year) + self.setYear(year) + self.setMonth(month) + self.setDay(day) + self.setDayOfWeek(dayOfWeek) + self.setHour(hour) + self.setMinutes(minutes) + self.setSeconds(seconds) + + # get alarm functions ------------------------------------------------------------------------------------------------------ + + def getAlarm1(self): + # returns list as: + # dayOfWeek or day (depending on setup in setAlarm), hour, minutes, seconds, type of alarm + alarmTime = [0, 0, 0, 0, ""] + alarmTime[0] = self.i2cVar.readfrom_mem(self.i2cAddr, 0x0A, 1)[0] + alarmTime[1] = self.i2cVar.readfrom_mem(self.i2cAddr, 0x09, 1)[0] + alarmTime[2] = self.i2cVar.readfrom_mem(self.i2cAddr, 0x08, 1)[0] + alarmTime[3] = self.i2cVar.readfrom_mem(self.i2cAddr, 0x07, 1)[0] + alarmTime[4] = decodeAlarmType(alarmTime) + alarmTime = decodeAlarmTime(alarmTime) + return alarmTime + + def getAlarm2(self): + # returns list as: + # dayOfWeek or day (depending on setup in setAlarm), hour, minutes, type of alarm + alarmTime = [0, 0, 0, ""] + alarmTime[0] = self.i2cVar.readfrom_mem(self.i2cAddr, 0x0D, 1)[0] + alarmTime[1] = self.i2cVar.readfrom_mem(self.i2cAddr, 0x0C, 1)[0] + alarmTime[2] = self.i2cVar.readfrom_mem(self.i2cAddr, 0x0B, 1)[0] + alarmTime[3] = decodeAlarmType(alarmTime) + alarmTime = decodeAlarmTime(alarmTime) + return alarmTime + + def alarmTriggert(self, alarmNumber): + # check if alarm triggert and reset alarm flag + statusBits = self.i2cVar.readfrom_mem(self.i2cAddr, 0x0F, 1)[0] + if(statusBits & alarmNumber): + self.resetAlarm(alarmNumber) + return True + else: + return False + + # set alarm functions ------------------------------------------------------------------------------------------------------- + + def setAlarm1(self, day, hour, minutes, seconds = 0, alarmType = "everyDay"): + # alarm Types are: + # "everySecond" - alarm every second + # "everyMinute" - alarm when seconds match + # "everyHour" - alarm when minutes and seconds match + # "everyDay" - alarm when hours, minutes and seconds match ! default ! + # "everyWeek" - alarm when day of week, hours, minutes and seconds match + # "everyMonth" - alarm when day of month, hours, minutes and seconds match + + alarmTime = encodeDateTime(day, hour, minutes, seconds, alarmType) + self.i2cVar.writeto_mem(self.i2cAddr, 0x07, convertToByteType(alarmTime[3])) + self.i2cVar.writeto_mem(self.i2cAddr, 0x08, convertToByteType(alarmTime[2])) + self.i2cVar.writeto_mem(self.i2cAddr, 0x09, convertToByteType(alarmTime[1])) + self.i2cVar.writeto_mem(self.i2cAddr, 0x0A, convertToByteType(alarmTime[0])) + + def setAlarm2(self, day, hour, minutes, alarmType = "everyDay"): + # alarm Types are: + # "everyMinute" - alarm every minute (at 00 seconds) + # "everyHour" - alarm when minutes match + # "everyDay" - alarm when hours and minutes match ! default ! + # "everyWeek" - alarm when day of week, hours and minutes match + # "everyMonth" - alarm when day of month, hours and minutes match + seconds = 0 + alarmTime = encodeDateTime(day, hour, minutes, seconds, alarmType) + self.i2cVar.writeto_mem(self.i2cAddr, 0x0B, convertToByteType(alarmTime[2])) + self.i2cVar.writeto_mem(self.i2cAddr, 0x0C, convertToByteType(alarmTime[1])) + self.i2cVar.writeto_mem(self.i2cAddr, 0x0D, convertToByteType(alarmTime[0])) + + def turnOnAlarmIR(self, alarmNumber): + # set alarm interrupt. AlarmNumber 1 or 2 + # when turned on, interrupt pin on DS3231 is "False" when alarm has been triggert + controlRegister = self.i2cVar.readfrom_mem(self.i2cAddr, 0x0E, 1)[0] + setByte = 0x04 + setByte = setByte + alarmNumber + setByte = controlRegister | setByte + self.i2cVar.writeto_mem(self.i2cAddr, 0x0E, convertToByteType(setByte)) + + def turnOffAlarmIR(self, alarmNumber): + # turn off alarm interrupt. Alarmnumber 1 or 2 + # only initiation of interrupt is turned off, + # alarm flag is still set when alarm conditions meet (i don't get it either) + controlRegister = self.i2cVar.readfrom_mem(self.i2cAddr, 0x0E, 1)[0] + setByte = 0xFF + setByte = setByte - alarmNumber + setByte = controlRegister & setByte + self.i2cVar.writeto_mem(self.i2cAddr, 0x0E, convertToByteType(setByte)) + + def resetAlarmFlag(self, alarmNumber): + statusBits = self.i2cVar.readfrom_mem(self.i2cAddr, 0x0F, 1)[0] + self.i2cVar.writeto_mem(self.i2cAddr, 0x0F, convertToByteType(statusBits & (0xFF - alarmNumber))) + +def convertToByteType(number): + return bytes([number]) + +def decodeToDec(byte): + return ((byte[0] >> 4) * 10) + (byte[0] & 0x0F) + +def encodeToByte(dec): + tens = floor(dec / 10) + ones = dec - tens*10 + return (tens << 4) + ones + +def decodeAlarmType(alarmTime): + if(len(alarmTime) > 4): + m1Bit = (alarmTime[3] & 0x80) >> 7 + else: + m1Bit = False + m2Bit = (alarmTime[2] & 0x80) >> 7 + m3Bit = (alarmTime[1] & 0x80) >> 7 + m4Bit = (alarmTime[0] & 0x80) >> 7 + dayBit = (alarmTime[0] & 0x40) >> 6 + + if(m1Bit and m2Bit and m3Bit and m4Bit): + return "everySecond" + elif(not m1Bit and m2Bit and m3Bit and m4Bit): + return "everyMinute" + elif(not m1Bit and not m2Bit and m3Bit and m4Bit): + return "everyHour" + elif(not m1Bit and not m2Bit and not m3Bit and m4Bit): + return "everyDay" + elif(not dayBit and not m1Bit and not m2Bit and not m3Bit and not m4Bit): + return "everyMonth" + elif(dayBit and not m1Bit and not m2Bit and not m3Bit and not m4Bit): + return "everyWeek" + else: + return "noValidAlarmType" + +def decodeAlarmTime(alarmTime): + alarmTime[0] = decodeToDec(convertToByteType(alarmTime[0] & 0x3F)) + alarmTime[1] = decodeToDec(convertToByteType(alarmTime[1] & 0x3F)) + alarmTime[2] = decodeToDec(convertToByteType(alarmTime[2] & 0x7F)) + if(len(alarmTime) > 4): + alarmTime[3] = decodeToDec(convertToByteType(alarmTime[3] & 0x7F)) + return alarmTime + +def encodeAlarmType(alarmType): + if(alarmType == "everySecond"): + return 15 #0b01111 + elif(alarmType == "everyMinute"): + return 14 #0b01110 + elif(alarmType == "everyHour"): + return 12 #0b01100 + elif(alarmType == "everyDay"): + return 8 #0b01000 + elif(alarmType == "everyMonth"): + return 0 #0b00000 + elif(alarmType == "everyWeek"): + return 16 #0b10000 + else: + raise ValueError("""Not a supported alarmType. Options are: + 'everySecond' (only Alarm 1), 'everyMinute', 'everyHour', 'everyDay', 'everyMonth', 'everyWeek'""") + +def encodeDateTime(day, hour, minutes, seconds, alarmType): + alarmBits = encodeAlarmType(alarmType) + alarmTime = [0, 0, 0, 0] + alarmTime[0] = (encodeToByte(day) & 0x3F) | ((alarmBits & 0x10) << 2) | ((alarmBits & 0x08) << 4) + alarmTime[1] = (encodeToByte(hour) & 0x3F) | ((alarmBits & 0x04) << 5) + alarmTime[2] = (encodeToByte(minutes) & 0x7F) | ((alarmBits & 0x02) << 6) + alarmTime[3] = (encodeToByte(seconds) & 0x7F) | ((alarmBits & 0x01) << 7) + return alarmTime \ No newline at end of file diff --git a/src/kits/large-oled-w/set-rtc-from-localtime.py b/src/kits/large-oled-w/set-rtc-from-localtime.py new file mode 100644 index 00000000..36418e92 --- /dev/null +++ b/src/kits/large-oled-w/set-rtc-from-localtime.py @@ -0,0 +1,37 @@ +from machine import Pin, I2C +from utime import localtime + +# I2C setup +sda = Pin(0, Pin.OUT) +scl = Pin(1, Pin.OUT) +i2c = I2C(0, scl=scl, sda=sda, freq=3000000) +DS3231_ADDR = 0x68 + +def dec2bcd(dec): + """Convert decimal to binary coded decimal.""" + return (dec // 10) << 4 | (dec % 10) + +def set_ds3231(): + now = localtime() + year = now[0] % 100 # Convert to 2-digit year + month = now[1] + day = now[2] + hour = now[3] + minute = now[4] + second = now[5] + + data = bytearray([ + dec2bcd(second), + dec2bcd(minute), + dec2bcd(hour), + dec2bcd(now[6] + 1), # Convert weekday from 0-6 to 1-7 + dec2bcd(day), + dec2bcd(month), + dec2bcd(year) + ]) + + i2c.writeto_mem(DS3231_ADDR, 0x00, data) + print(f"RTC set to: {month}/{day}/{now[0]} {hour:02d}:{minute:02d}:{second:02d}") + +if __name__ == "__main__": + set_ds3231() \ No newline at end of file diff --git a/src/rtc/main.py b/src/rtc/main.py new file mode 100644 index 00000000..1ad98ae7 --- /dev/null +++ b/src/rtc/main.py @@ -0,0 +1,165 @@ +from machine import Pin, I2C +import utime +import ssd1306 +from utime import sleep, localtime +led = machine.Pin(25, machine.Pin.OUT) + +SCL = machine.Pin(2) # SPI CLock +SDA = machine.Pin(3) # SPI Data +spi = machine.SPI(0, sck=SCL, mosi=SDA, baudrate=100000) + +RES = machine.Pin(4) +DC = machine.Pin(5) +CS = machine.Pin(6) + +oled = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS) +DS3231_ADDR = 0x68 + +i2c = I2C(0, sda=Pin(0), scl=Pin(1)) + +def bcd2dec(bcd): + """Convert binary coded decimal to decimal.""" + return (((bcd & 0xf0) >> 4) * 10 + (bcd & 0x0f)) + +def read_ds3231(): + """Read time from DS3231 RTC.""" + data = i2c.readfrom_mem(DS3231_ADDR, 0x00, 7) + second = bcd2dec(data[0]) + minute = bcd2dec(data[1]) + hour = bcd2dec(data[2] & 0x3f) # 24 hour mode + day = bcd2dec(data[4]) + month = bcd2dec(data[5] & 0x1f) + year = bcd2dec(data[6]) + 2000 + return (year, month, day, hour, minute, second) + +def read_temperature(): + """Read temperature from DS3231 RTC.""" + # Read temperature registers + i2c.writeto(DS3231_ADDR, b'\x11') + temp_data = i2c.readfrom(DS3231_ADDR, 2) + temp_msb = temp_data[0] + temp_lsb = temp_data[1] + + # Get raw temp value (ignoring sign bit) + raw_temp = temp_msb & 0x7F # Strip off sign bit + + # 0xD7 & 0x7F = 0x57 = 87 decimal (original value minus sign bit) + # If sign bit was set, make it negative + if temp_msb & 0x80: + raw_temp = raw_temp ^ 0x7F # Invert the bits + raw_temp = -(raw_temp + 1) # Two's complement + + # Add fraction from LSB + frac = (temp_lsb >> 6) * 0.25 + temp_c = raw_temp + frac + + # Convert to Fahrenheit + temp_f = (temp_c * 9.0 / 5.0) + 32.0 + + # print(f"Raw temp (after sign bit removal): {raw_temp}") + # print(f"Temperature: {temp_c}°C = {temp_f}°F") + print(f"Temperature: {temp_f}°F") + + return temp_f + +segmentMapping = [ + #a, b, c, d, e, f, g + [1, 1, 1, 1, 1, 1, 0], # 0 + [0, 1, 1, 0, 0, 0, 0], # 1 + [1, 1, 0, 1, 1, 0, 1], # 2 + [1, 1, 1, 1, 0, 0, 1], # 3 + [0, 1, 1, 0, 0, 1, 1], # 4 + [1, 0, 1, 1, 0, 1, 1], # 5 + [1, 0, 1, 1, 1, 1, 1], # 6 + [1, 1, 1, 0, 0, 0, 0], # 7 + [1, 1, 1, 1, 1, 1, 1], # 8 + [1, 1, 1, 1, 0, 1, 1] # 9 +] + +def drawDigit(digit, x, y, width, height, thickness, color): + if digit < 0: + return + segmentOn = segmentMapping[digit] + + # Draw horizontal segments + for i in [0, 3, 6]: + if segmentOn[i]: + if i == 0: # top + yOffset = 0 + elif i == 3: # bottom + yOffset = height - thickness + else: # middle + yOffset = height // 2 - thickness // 2 + oled.fill_rect(x, y+yOffset, width, thickness, color) + + # Draw vertical segments + for i in [1, 2, 4, 5]: + if segmentOn[i]: + if i == 1 or i == 5: # upper segments + startY = y + endY = y + height // 2 + else: # lower segments + startY = y + height // 2 + endY = y + height + xOffset = 0 if (i == 4 or i == 5) else width-thickness + oled.fill_rect(x+xOffset, startY, thickness, endY-startY, color) + +def draw_colon(x, y): + oled.fill_rect(x, y, 3, 3, 1) + oled.fill_rect(x, y+14, 3, 3, 1) + +def update_screen(year, month, day, hour, minute, am_pm, colon_on, temp_f): + left_margin = -28 + y_offset = 11 + digit_width = 33 + digit_height = 40 + digit_spacing = 41 + digit_thickness = 5 + + oled.fill(0) + + # Draw date at top + date_str = f"{month}/{day}/{year}" + oled.text(date_str, 0, 0, 1) + + # Draw temperature in the lower left + temp_str = f"{temp_f:.0f}F" + oled.text(temp_str, 0, 54, 1) + + # Convert 24-hour to 12-hour format + display_hour = hour if hour <= 12 else hour - 12 + if display_hour == 0: + display_hour = 12 + + hour_ten = display_hour // 10 if display_hour >= 10 else -1 + hour_right = display_hour % 10 + minute_ten = minute // 10 + minute_right = minute % 10 + + drawDigit(hour_ten, left_margin, y_offset, digit_width, digit_height, digit_thickness, 1) + drawDigit(hour_right, left_margin + digit_spacing-2, y_offset, digit_width, digit_height, digit_thickness, 1) + drawDigit(minute_ten, left_margin + 2*digit_spacing, y_offset, digit_width, digit_height, digit_thickness, 1) + drawDigit(minute_right, left_margin + 3*digit_spacing, y_offset, digit_width, digit_height, digit_thickness, 1) + + if colon_on: + draw_colon(47, 20) + + oled.text(am_pm, 112, 55, 1) + oled.show() + +counter = 0 +while True: + # now + n = read_ds3231() + print(counter, f'{n[2]}/{n[1]}/{n[0]} {n[3]}:{n[4]} {n[5]}') + year, month, day, hour, minute, second = n + am_pm = "PM" if hour >= 12 else "AM" + + # Read temperature from RTC + temp_f = read_temperature() + + update_screen(year, month, day, hour, minute, am_pm, True, temp_f) + sleep(1) + update_screen(year, month, day, hour, minute, am_pm, False, temp_f) + sleep(1) + counter += 1 \ No newline at end of file diff --git a/src/rtc/set-rtc-from-localtime.py b/src/rtc/set-rtc-from-localtime.py new file mode 100644 index 00000000..a429b109 --- /dev/null +++ b/src/rtc/set-rtc-from-localtime.py @@ -0,0 +1,63 @@ +from machine import Pin, I2C +from utime import localtime + +# Constants +DS3231_ADDR = 0x68 +STATUS_REG = 0x0F # Status register address + +# I2C setup +sda = Pin(0, Pin.OUT) +scl = Pin(1, Pin.OUT) +i2c = I2C(0, scl=scl, sda=sda, freq=3000000) + +def dec2bcd(dec): + """Convert decimal to binary coded decimal.""" + return (dec // 10) << 4 | (dec % 10) + +def check_osf(): + """Check the oscillator stop flag.""" + status = i2c.readfrom_mem(DS3231_ADDR, STATUS_REG, 1)[0] + return bool(status >> 7) + +def reset_osf(): + """Clear the oscillator stop flag.""" + status = bytearray(1) + i2c.readfrom_mem_into(DS3231_ADDR, STATUS_REG, status) + i2c.writeto_mem(DS3231_ADDR, STATUS_REG, bytearray([status[0] & 0x7f])) + +def set_ds3231(): + """Set the DS3231 RTC time and ensure oscillator is running.""" + now = localtime() + year = now[0] % 100 # Convert to 2-digit year + month = now[1] + day = now[2] + hour = now[3] + minute = now[4] + second = now[5] + + # First check if oscillator is stopped + if check_osf(): + print("Oscillator was stopped. Resetting OSF flag...") + reset_osf() + + data = bytearray([ + dec2bcd(second), + dec2bcd(minute), + dec2bcd(hour), + dec2bcd(now[6] + 1), # Convert weekday from 0-6 to 1-7 + dec2bcd(day), + dec2bcd(month), + dec2bcd(year) + ]) + + i2c.writeto_mem(DS3231_ADDR, 0x00, data) + print(f"RTC set to: {month}/{day}/{now[0]} {hour:02d}:{minute:02d}:{second:02d}") + + # Verify oscillator is running + if check_osf(): + print("Warning: Oscillator still shows stopped state!") + else: + print("Oscillator running normally") + +if __name__ == "__main__": + set_ds3231() \ No newline at end of file