Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # ds3231_port.py Portable driver for DS3231 precison real time clock.
- # Adapted from WiPy driver at https://github.com/scudderfish/uDS3231
- # Author: Peter Hinch
- # Copyright Peter Hinch 2018 Released under the MIT license.
- # August 14,2022
- # Author: Daniel Perron
- # Added alarm.
- import utime
- import machine
- import sys
- DS3231_I2C_ADDR = 104
- try:
- rtc = machine.RTC()
- except:
- print('Warning: machine module does not support the RTC.')
- rtc = None
- def bcd2dec(bcd):
- return (((bcd & 0xf0) >> 4) * 10 + (bcd & 0x0f))
- def dec2bcd(dec):
- tens, units = divmod(dec, 10)
- return (tens << 4) + units
- def tobytes(num):
- return num.to_bytes(1, 'little')
- class DS3231:
- ALARM_EVERY_S = [ 0, 0x80, 0x80 , 0x80 , 0x80]
- ALARM_S_MATCH = [ 0, 0x80, 0x80 , 0x80 , 0x00]
- ALARM_MS_MATCH = [ 0, 0x80, 0x80 , 0x00 , 0x00]
- ALARM_HMS_MATCH = [ 0, 0x80, 0x00 , 0x00 , 0x00]
- ALARM_DM_HMS_MATCH = [ 0, 0x00, 0x00 , 0x00 , 0x00]
- ALARM_DW__MATCH = [ 0x40, 0x00, 0x00 , 0x00 , 0x00]
- def __init__(self, i2c):
- self.ds3231 = i2c
- self.timebuf = bytearray(7)
- if DS3231_I2C_ADDR not in self.ds3231.scan():
- raise RuntimeError("DS3231 not found on I2C bus at %d" % DS3231_I2C_ADDR)
- def get_time(self, set_rtc=False):
- if set_rtc:
- self.await_transition() # For accuracy set RTC immediately after a seconds transition
- else:
- self.ds3231.readfrom_mem_into(DS3231_I2C_ADDR, 0, self.timebuf) # don't wait
- return self.convert(set_rtc)
- def convert(self, set_rtc=False): # Return a tuple in localtime() format (less yday)
- data = self.timebuf
- ss = bcd2dec(data[0])
- mm = bcd2dec(data[1])
- if data[2] & 0x40:
- hh = bcd2dec(data[2] & 0x1f)
- if data[2] & 0x20:
- hh += 12
- else:
- hh = bcd2dec(data[2])
- wday = data[3]
- DD = bcd2dec(data[4])
- MM = bcd2dec(data[5] & 0x1f)
- YY = bcd2dec(data[6])
- if data[5] & 0x80:
- YY += 2000
- else:
- YY += 1900
- # Time from DS3231 in time.localtime() format (less yday)
- result = YY, MM, DD, hh, mm, ss, wday -1, 0
- if set_rtc:
- if rtc is None:
- # Best we can do is to set local time
- secs = utime.mktime(result)
- utime.localtime(secs)
- else:
- rtc.datetime((YY, MM, DD, wday, hh, mm, ss, 0))
- return result
- def save_time(self):
- (YY, MM, mday, hh, mm, ss, wday, yday) = utime.localtime() # Based on RTC
- self.ds3231.writeto_mem(DS3231_I2C_ADDR, 0, tobytes(dec2bcd(ss)))
- self.ds3231.writeto_mem(DS3231_I2C_ADDR, 1, tobytes(dec2bcd(mm)))
- self.ds3231.writeto_mem(DS3231_I2C_ADDR, 2, tobytes(dec2bcd(hh))) # Sets to 24hr mode
- self.ds3231.writeto_mem(DS3231_I2C_ADDR, 3, tobytes(dec2bcd(wday + 1))) # 1 == Monday, 7 == Sunday
- self.ds3231.writeto_mem(DS3231_I2C_ADDR, 4, tobytes(dec2bcd(mday))) # Day of month
- if YY >= 2000:
- self.ds3231.writeto_mem(DS3231_I2C_ADDR, 5, tobytes(dec2bcd(MM) | 0b10000000)) # Century bit
- self.ds3231.writeto_mem(DS3231_I2C_ADDR, 6, tobytes(dec2bcd(YY-2000)))
- else:
- self.ds3231.writeto_mem(DS3231_I2C_ADDR, 5, tobytes(dec2bcd(MM)))
- self.ds3231.writeto_mem(DS3231_I2C_ADDR, 6, tobytes(dec2bcd(YY-1900)))
- # Wait until DS3231 seconds value changes before reading and returning data
- def await_transition(self):
- self.ds3231.readfrom_mem_into(DS3231_I2C_ADDR, 0, self.timebuf)
- ss = self.timebuf[0]
- while ss == self.timebuf[0]:
- self.ds3231.readfrom_mem_into(DS3231_I2C_ADDR, 0, self.timebuf)
- return self.timebuf
- # Test hardware RTC against DS3231. Default runtime 10 min. Return amount
- # by which DS3231 clock leads RTC in PPM or seconds per year.
- # Precision is achieved by starting and ending the measurement on DS3231
- # one-seond boundaries and using ticks_ms() to time the RTC.
- # For a 10 minute measurement +-1ms corresponds to 1.7ppm or 53s/yr. Longer
- # runtimes improve this, but the DS3231 is "only" good for +-2ppm over 0-40C.
- def rtc_test(self, runtime=600, ppm=False, verbose=True):
- if rtc is None:
- raise RuntimeError('machine.RTC does not exist')
- verbose and print('Waiting {} minutes for result'.format(runtime//60))
- factor = 1_000_000 if ppm else 114_155_200 # seconds per year
- self.await_transition() # Start on transition of DS3231. Record time in .timebuf
- t = utime.ticks_ms() # Get system time now
- ss = rtc.datetime()[6] # Seconds from system RTC
- while ss == rtc.datetime()[6]:
- pass
- ds = utime.ticks_diff(utime.ticks_ms(), t) # ms to transition of RTC
- ds3231_start = utime.mktime(self.convert()) # Time when transition occurred
- t = rtc.datetime()
- rtc_start = utime.mktime((t[0], t[1], t[2], t[4], t[5], t[6], t[3] - 1, 0)) # y m d h m s wday 0
- utime.sleep(runtime) # Wait a while (precision doesn't matter)
- self.await_transition() # of DS3231 and record the time
- t = utime.ticks_ms() # and get system time now
- ss = rtc.datetime()[6] # Seconds from system RTC
- while ss == rtc.datetime()[6]:
- pass
- de = utime.ticks_diff(utime.ticks_ms(), t) # ms to transition of RTC
- ds3231_end = utime.mktime(self.convert()) # Time when transition occurred
- t = rtc.datetime()
- rtc_end = utime.mktime((t[0], t[1], t[2], t[4], t[5], t[6], t[3] - 1, 0)) # y m d h m s wday 0
- d_rtc = 1000 * (rtc_end - rtc_start) + de - ds # ms recorded by RTC
- d_ds3231 = 1000 * (ds3231_end - ds3231_start) # ms recorded by DS3231
- ratio = (d_ds3231 - d_rtc) / d_ds3231
- ppm = ratio * 1_000_000
- verbose and print('DS3231 leads RTC by {:4.1f}ppm {:4.1f}mins/yr'.format(ppm, ppm*1.903))
- return ratio * factor
- def _twos_complement(self, input_value: int, num_bits: int) -> int:
- mask = 2 ** (num_bits - 1)
- return -(input_value & mask) + (input_value & ~mask)
- def get_temperature(self):
- t = self.ds3231.readfrom_mem(DS3231_I2C_ADDR, 0x11, 2)
- i = t[0] << 8 | t[1]
- return self._twos_complement(i >> 6, 10) * 0.25
- def enable_alarm(self, alarm_id=1, enable=True):
- if (alarm_id != 1) and (alarm_id!=2):
- return
- #first read register
- c_reg = bytearray(self.ds3231.readfrom_mem(DS3231_I2C_ADDR,0xe,2))
- if alarm_id == 1:
- if enable:
- c_reg[0]= c_reg[0] | 0x5
- else:
- c_reg[0]= c_reg[0] & 0xfe
- #clear flag
- c_reg[1] = 0xfe
- else:
- if enable:
- c_reg[0]= c_reg[0] | 0x6
- else:
- c_reg[0]= c_reg[0] & 0xfd
- #clear flag
- c_reg[1] = 0xfd
- self.ds3231.writeto_mem(DS3231_I2C_ADDR,0xe,c_reg)
- def set_alarm(self,alarm_id=1,alarm_time=None,match=ALARM_HMS_MATCH):
- if (alarm_id != 1) and (alarm_id !=2):
- return
- if alarm_time is None:
- #disable alarm
- if alarm_id == 1:
- self.enable_alarm(1,False)
- else:
- self.enable_alarm(2,False)
- return # need to have alarm time
- buffer = bytearray(4)
- #sec
- buffer[0] = dec2bcd(alarm_time[5]) | match[4]
- #min
- buffer[1] = dec2bcd(alarm_time[4]) | match[3]
- #hour 24Hrs
- buffer[2] = dec2bcd(alarm_time[3]) | match[2]
- if match[0] == 0:
- #day of the month
- buffer[3]= dec2bcd(alarm_time[2]) | match[1]
- else:
- buffer[3]= dec2bcd(alarm_time[6]) | match[1]
- #day of the week
- if alarm_id == 1:
- self.ds3231.writeto_mem(DS3231_I2C_ADDR, 7, buffer)
- else:
- self.ds3231.writeto_mem(DS3231_I2C_ADDR,0xb,buffer[1:])
- self.enable_alarm(alarm_id,True)
- % danielperron@MacBook-Air-de-Daniel ~ % cat ds3231_port.py
- # ds3231_port.py Portable driver for DS3231 precison real time clock.
- # Adapted from WiPy driver at https://github.com/scudderfish/uDS3231
- # Author: Peter Hinch
- # Copyright Peter Hinch 2018 Released under the MIT license.
- import utime
- import machine
- import sys
- DS3231_I2C_ADDR = 104
- try:
- rtc = machine.RTC()
- except:
- print('Warning: machine module does not support the RTC.')
- rtc = None
- def bcd2dec(bcd):
- return (((bcd & 0xf0) >> 4) * 10 + (bcd & 0x0f))
- def dec2bcd(dec):
- tens, units = divmod(dec, 10)
- return (tens << 4) + units
- def tobytes(num):
- return num.to_bytes(1, 'little')
- class DS3231:
- # _CONTROL_REG = 0x0e
- # _STATUS_REG = 0x0f
- # _DATETIME_REG = 0x00
- # _ALARM1_REG = (0x08, 0x0b)
- # _SQUARE_WAVE_REG = 0x0e
- ALARM_EVERY_S = [ 0, 0x80, 0x80 , 0x80 , 0x80]
- ALARM_S_MATCH = [ 0, 0x80, 0x80 , 0x80 , 0x00]
- ALARM_MS_MATCH = [ 0, 0x80, 0x80 , 0x00 , 0x00]
- ALARM_HMS_MATCH = [ 0, 0x80, 0x00 , 0x00 , 0x00]
- ALARM_DM_HMS_MATCH = [ 0, 0x00, 0x00 , 0x00 , 0x00]
- ALARM_DW__MATCH = [ 0x40, 0x00, 0x00 , 0x00 , 0x00]
- _year=0
- _month=1
- mday=2
- def __init__(self, i2c):
- self.ds3231 = i2c
- self.timebuf = bytearray(7)
- if DS3231_I2C_ADDR not in self.ds3231.scan():
- raise RuntimeError("DS3231 not found on I2C bus at %d" % DS3231_I2C_ADDR)
- def get_time(self, set_rtc=False):
- if set_rtc:
- self.await_transition() # For accuracy set RTC immediately after a seconds transition
- else:
- self.ds3231.readfrom_mem_into(DS3231_I2C_ADDR, 0, self.timebuf) # don't wait
- return self.convert(set_rtc)
- def convert(self, set_rtc=False): # Return a tuple in localtime() format (less yday)
- data = self.timebuf
- ss = bcd2dec(data[0])
- mm = bcd2dec(data[1])
- if data[2] & 0x40:
- hh = bcd2dec(data[2] & 0x1f)
- if data[2] & 0x20:
- hh += 12
- else:
- hh = bcd2dec(data[2])
- wday = data[3]
- DD = bcd2dec(data[4])
- MM = bcd2dec(data[5] & 0x1f)
- YY = bcd2dec(data[6])
- if data[5] & 0x80:
- YY += 2000
- else:
- YY += 1900
- # Time from DS3231 in time.localtime() format (less yday)
- result = YY, MM, DD, hh, mm, ss, wday -1, 0
- if set_rtc:
- if rtc is None:
- # Best we can do is to set local time
- secs = utime.mktime(result)
- utime.localtime(secs)
- else:
- rtc.datetime((YY, MM, DD, wday, hh, mm, ss, 0))
- return result
- def save_time(self):
- (YY, MM, mday, hh, mm, ss, wday, yday) = utime.localtime() # Based on RTC
- self.ds3231.writeto_mem(DS3231_I2C_ADDR, 0, tobytes(dec2bcd(ss)))
- self.ds3231.writeto_mem(DS3231_I2C_ADDR, 1, tobytes(dec2bcd(mm)))
- self.ds3231.writeto_mem(DS3231_I2C_ADDR, 2, tobytes(dec2bcd(hh))) # Sets to 24hr mode
- self.ds3231.writeto_mem(DS3231_I2C_ADDR, 3, tobytes(dec2bcd(wday + 1))) # 1 == Monday, 7 == Sunday
- self.ds3231.writeto_mem(DS3231_I2C_ADDR, 4, tobytes(dec2bcd(mday))) # Day of month
- if YY >= 2000:
- self.ds3231.writeto_mem(DS3231_I2C_ADDR, 5, tobytes(dec2bcd(MM) | 0b10000000)) # Century bit
- self.ds3231.writeto_mem(DS3231_I2C_ADDR, 6, tobytes(dec2bcd(YY-2000)))
- else:
- self.ds3231.writeto_mem(DS3231_I2C_ADDR, 5, tobytes(dec2bcd(MM)))
- self.ds3231.writeto_mem(DS3231_I2C_ADDR, 6, tobytes(dec2bcd(YY-1900)))
- # Wait until DS3231 seconds value changes before reading and returning data
- def await_transition(self):
- self.ds3231.readfrom_mem_into(DS3231_I2C_ADDR, 0, self.timebuf)
- ss = self.timebuf[0]
- while ss == self.timebuf[0]:
- self.ds3231.readfrom_mem_into(DS3231_I2C_ADDR, 0, self.timebuf)
- return self.timebuf
- # Test hardware RTC against DS3231. Default runtime 10 min. Return amount
- # by which DS3231 clock leads RTC in PPM or seconds per year.
- # Precision is achieved by starting and ending the measurement on DS3231
- # one-seond boundaries and using ticks_ms() to time the RTC.
- # For a 10 minute measurement +-1ms corresponds to 1.7ppm or 53s/yr. Longer
- # runtimes improve this, but the DS3231 is "only" good for +-2ppm over 0-40C.
- def rtc_test(self, runtime=600, ppm=False, verbose=True):
- if rtc is None:
- raise RuntimeError('machine.RTC does not exist')
- verbose and print('Waiting {} minutes for result'.format(runtime//60))
- factor = 1_000_000 if ppm else 114_155_200 # seconds per year
- self.await_transition() # Start on transition of DS3231. Record time in .timebuf
- t = utime.ticks_ms() # Get system time now
- ss = rtc.datetime()[6] # Seconds from system RTC
- while ss == rtc.datetime()[6]:
- pass
- ds = utime.ticks_diff(utime.ticks_ms(), t) # ms to transition of RTC
- ds3231_start = utime.mktime(self.convert()) # Time when transition occurred
- t = rtc.datetime()
- rtc_start = utime.mktime((t[0], t[1], t[2], t[4], t[5], t[6], t[3] - 1, 0)) # y m d h m s wday 0
- utime.sleep(runtime) # Wait a while (precision doesn't matter)
- self.await_transition() # of DS3231 and record the time
- t = utime.ticks_ms() # and get system time now
- ss = rtc.datetime()[6] # Seconds from system RTC
- while ss == rtc.datetime()[6]:
- pass
- de = utime.ticks_diff(utime.ticks_ms(), t) # ms to transition of RTC
- ds3231_end = utime.mktime(self.convert()) # Time when transition occurred
- t = rtc.datetime()
- rtc_end = utime.mktime((t[0], t[1], t[2], t[4], t[5], t[6], t[3] - 1, 0)) # y m d h m s wday 0
- d_rtc = 1000 * (rtc_end - rtc_start) + de - ds # ms recorded by RTC
- d_ds3231 = 1000 * (ds3231_end - ds3231_start) # ms recorded by DS3231
- ratio = (d_ds3231 - d_rtc) / d_ds3231
- ppm = ratio * 1_000_000
- verbose and print('DS3231 leads RTC by {:4.1f}ppm {:4.1f}mins/yr'.format(ppm, ppm*1.903))
- return ratio * factor
- def _twos_complement(self, input_value: int, num_bits: int) -> int:
- mask = 2 ** (num_bits - 1)
- return -(input_value & mask) + (input_value & ~mask)
- def get_temperature(self):
- t = self.ds3231.readfrom_mem(DS3231_I2C_ADDR, 0x11, 2)
- i = t[0] << 8 | t[1]
- return self._twos_complement(i >> 6, 10) * 0.25
- def enable_alarm(self, alarm_id=1, enable=True):
- if (alarm_id != 1) and (alarm_id!=2):
- return
- #first read register
- c_reg = bytearray(self.ds3231.readfrom_mem(DS3231_I2C_ADDR,0xe,2))
- if alarm_id == 1:
- if enable:
- c_reg[0]= c_reg[0] | 0x5
- else:
- c_reg[0]= c_reg[0] & 0xfe
- #clear flag
- c_reg[1] = 0xfe
- else:
- if enable:
- c_reg[0]= c_reg[0] | 0x6
- else:
- c_reg[0]= c_reg[0] & 0xfd
- #clear flag
- c_reg[1] = 0xfd
- self.ds3231.writeto_mem(DS3231_I2C_ADDR,0xe,c_reg)
- def set_alarm(self,alarm_id=1,alarm_time=None,match=ALARM_HMS_MATCH):
- if (alarm_id != 1) and (alarm_id !=2):
- return
- if alarm_time is None:
- #disable alarm
- if alarm_id == 1:
- self.enable_alarm(1,False)
- else:
- self.enable_alarm(2,False)
- return # need to have alarm time
- buffer = bytearray(4)
- #sec
- buffer[0] = dec2bcd(alarm_time[5]) | match[4]
- #min
- buffer[1] = dec2bcd(alarm_time[4]) | match[3]
- #hour 24Hrs
- buffer[2] = dec2bcd(alarm_time[3]) | match[2]
- if match[0] == 0:
- #day of the month
- buffer[3]= dec2bcd(alarm_time[2]) | match[1]
- else:
- buffer[3]= dec2bcd(alarm_time[6]) | match[1]
- #day of the week
- if alarm_id == 1:
- self.ds3231.writeto_mem(DS3231_I2C_ADDR, 7, buffer)
- else:
- self.ds3231.writeto_mem(DS3231_I2C_ADDR,0xb,buffer[1:])
- self.enable_alarm(alarm_id,True)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement