Skip to content

Commit

Permalink
updating content
Browse files Browse the repository at this point in the history
  • Loading branch information
dmccreary committed Jan 7, 2025
1 parent 40a40c6 commit 4be366f
Show file tree
Hide file tree
Showing 5 changed files with 562 additions and 3 deletions.
37 changes: 34 additions & 3 deletions docs/lessons/13-real-time-clocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,30 +352,55 @@ 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]
hour = now[3]
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),
Expand All @@ -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()
Expand Down
263 changes: 263 additions & 0 deletions src/kits/large-oled-w/lib/ds3231.py
Original file line number Diff line number Diff line change
@@ -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
37 changes: 37 additions & 0 deletions src/kits/large-oled-w/set-rtc-from-localtime.py
Original file line number Diff line number Diff line change
@@ -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()
Loading

0 comments on commit 4be366f

Please sign in to comment.