2023-02-28 13:26:32 +00:00
|
|
|
# ds3231_gen.py General purpose driver for DS3231 precison real time clock.
|
|
|
|
|
|
|
|
# Author: Peter Hinch
|
|
|
|
# Copyright Peter Hinch 2023 Released under the MIT license.
|
|
|
|
|
|
|
|
# Rewritten from datasheet to support alarms. Sources studied:
|
|
|
|
# WiPy driver at https://github.com/scudderfish/uDS3231
|
|
|
|
# https://github.com/notUnique/DS3231micro
|
|
|
|
|
|
|
|
# Assumes date > Y2K and 24 hour clock.
|
|
|
|
|
|
|
|
import time
|
|
|
|
import machine
|
|
|
|
|
|
|
|
|
|
|
|
_ADDR = const(104)
|
|
|
|
|
|
|
|
EVERY_SECOND = 0x0F # Exported flags
|
|
|
|
EVERY_MINUTE = 0x0E
|
|
|
|
EVERY_HOUR = 0x0C
|
|
|
|
EVERY_DAY = 0x80
|
|
|
|
EVERY_WEEK = 0x40
|
|
|
|
EVERY_MONTH = 0
|
|
|
|
|
|
|
|
try:
|
|
|
|
rtc = machine.RTC()
|
|
|
|
except:
|
|
|
|
print("Warning: machine module does not support the RTC.")
|
|
|
|
rtc = None
|
|
|
|
|
|
|
|
|
|
|
|
class Alarm:
|
|
|
|
def __init__(self, device, n):
|
|
|
|
self._device = device
|
|
|
|
self._i2c = device.ds3231
|
|
|
|
self.alno = n # Alarm no.
|
|
|
|
self.offs = 7 if self.alno == 1 else 0x0B # Offset into address map
|
|
|
|
self.mask = 0
|
|
|
|
|
|
|
|
def _reg(self, offs : int, buf = bytearray(1)) -> int: # Read a register
|
|
|
|
self._i2c.readfrom_mem_into(_ADDR, offs, buf)
|
|
|
|
return buf[0]
|
|
|
|
|
|
|
|
def enable(self, run):
|
|
|
|
flags = self._reg(0x0E) | 4 # Disable square wave
|
|
|
|
flags = (flags | self.alno) if run else (flags & ~self.alno & 0xFF)
|
|
|
|
self._i2c.writeto_mem(_ADDR, 0x0E, flags.to_bytes(1, "little"))
|
|
|
|
|
|
|
|
def __call__(self): # Return True if alarm is set
|
|
|
|
return bool(self._reg(0x0F) & self.alno)
|
|
|
|
|
|
|
|
def clear(self):
|
|
|
|
flags = (self._reg(0x0F) & ~self.alno) & 0xFF
|
|
|
|
self._i2c.writeto_mem(_ADDR, 0x0F, flags.to_bytes(1, "little"))
|
|
|
|
|
|
|
|
def set(self, when, day=0, hr=0, min=0, sec=0):
|
|
|
|
if when not in (0x0F, 0x0E, 0x0C, 0x80, 0x40, 0):
|
|
|
|
raise ValueError("Invalid alarm specifier.")
|
|
|
|
self.mask = when
|
|
|
|
if when == EVERY_WEEK:
|
|
|
|
day += 1 # Setting a day of week
|
|
|
|
self._device.set_time((0, 0, day, hr, min, sec, 0, 0), self)
|
|
|
|
self.enable(True)
|
|
|
|
|
|
|
|
|
|
|
|
class DS3231:
|
|
|
|
def __init__(self, i2c):
|
|
|
|
self.ds3231 = i2c
|
|
|
|
self.alarm1 = Alarm(self, 1)
|
|
|
|
self.alarm2 = Alarm(self, 2)
|
|
|
|
if _ADDR not in self.ds3231.scan():
|
|
|
|
raise RuntimeError(f"DS3231 not found on I2C bus at {_ADDR}")
|
|
|
|
|
2023-02-28 17:54:43 +00:00
|
|
|
def get_time(self, data=bytearray(7)):
|
2023-02-28 13:26:32 +00:00
|
|
|
def bcd2dec(bcd): # Strip MSB
|
|
|
|
return ((bcd & 0x70) >> 4) * 10 + (bcd & 0x0F)
|
|
|
|
|
|
|
|
self.ds3231.readfrom_mem_into(_ADDR, 0, data)
|
|
|
|
ss, mm, hh, wday, DD, MM, YY = [bcd2dec(x) for x in data]
|
|
|
|
YY += 2000
|
|
|
|
# Time from DS3231 in time.localtime() format (less yday)
|
|
|
|
result = YY, MM, DD, hh, mm, ss, wday - 1, 0
|
|
|
|
return result
|
|
|
|
|
|
|
|
# Output time or alarm data to device
|
|
|
|
# args: tt A datetime tuple. If absent uses localtime.
|
|
|
|
# alarm: An Alarm instance or None if setting time
|
|
|
|
def set_time(self, tt=None, alarm=None):
|
|
|
|
# Given BCD value return a binary byte. Modifier:
|
|
|
|
# Set MSB if any of bit(1..4) or bit 7 set, set b6 if mod[6]
|
|
|
|
def gbyte(dec, mod=0):
|
|
|
|
tens, units = divmod(dec, 10)
|
|
|
|
n = (tens << 4) + units
|
|
|
|
n |= 0x80 if mod & 0x0F else mod & 0xC0
|
|
|
|
return n.to_bytes(1, "little")
|
|
|
|
|
|
|
|
YY, MM, mday, hh, mm, ss, wday, yday = time.localtime() if tt is None else tt
|
|
|
|
mask = 0 if alarm is None else alarm.mask
|
|
|
|
offs = 0 if alarm is None else alarm.offs
|
|
|
|
if alarm is None or alarm.alno == 1: # Has a seconds register
|
|
|
|
self.ds3231.writeto_mem(_ADDR, offs, gbyte(ss, mask & 1))
|
|
|
|
offs += 1
|
|
|
|
self.ds3231.writeto_mem(_ADDR, offs, gbyte(mm, mask & 2))
|
|
|
|
offs += 1
|
|
|
|
self.ds3231.writeto_mem(_ADDR, offs, gbyte(hh, mask & 4)) # Sets to 24hr mode
|
|
|
|
offs += 1
|
|
|
|
if alarm is not None: # Setting an alarm - mask holds MS 2 bits
|
|
|
|
self.ds3231.writeto_mem(_ADDR, offs, gbyte(mday, mask))
|
|
|
|
else: # Setting time
|
|
|
|
self.ds3231.writeto_mem(_ADDR, offs, gbyte(wday + 1)) # 1 == Monday, 7 == Sunday
|
|
|
|
offs += 1
|
|
|
|
self.ds3231.writeto_mem(_ADDR, offs, gbyte(mday)) # Day of month
|
|
|
|
offs += 1
|
|
|
|
self.ds3231.writeto_mem(_ADDR, offs, gbyte(MM, 0x80)) # Century bit (>Y2K)
|
|
|
|
offs += 1
|
|
|
|
self.ds3231.writeto_mem(_ADDR, offs, gbyte(YY - 2000))
|
|
|
|
|
|
|
|
def temperature(self):
|
|
|
|
def twos_complement(input_value: int, num_bits: int) -> int:
|
|
|
|
mask = 2 ** (num_bits - 1)
|
|
|
|
return -(input_value & mask) + (input_value & ~mask)
|
|
|
|
|
|
|
|
t = self.ds3231.readfrom_mem(_ADDR, 0x11, 2)
|
|
|
|
i = t[0] << 8 | t[1]
|
|
|
|
return twos_complement(i >> 6, 10) * 0.25
|
|
|
|
|
|
|
|
def __str__(self, buf=bytearray(0x13)): # Debug dump of device registers
|
|
|
|
self.ds3231.readfrom_mem_into(_ADDR, 0, buf)
|
|
|
|
s = ""
|
|
|
|
for n, v in enumerate(buf):
|
|
|
|
s = f"{s}0x{n:02x} 0x{v:02x} {v >> 4:04b} {v & 0xF :04b}\n"
|
|
|
|
if not (n + 1) % 4:
|
|
|
|
s = f"{s}\n"
|
|
|
|
return s
|