From c259792a7c83ec4ac701125151b68f9720b033aa Mon Sep 17 00:00:00 2001 From: Peter Hinch Date: Sat, 25 Jan 2020 17:36:58 +0000 Subject: [PATCH] Prior to DS3231 update. --- DS3231/README.md | 7 +++- DS3231/ds3231_pb.py | 95 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 80 insertions(+), 22 deletions(-) diff --git a/DS3231/README.md b/DS3231/README.md index 5b25746..3acd1bf 100644 --- a/DS3231/README.md +++ b/DS3231/README.md @@ -51,7 +51,8 @@ Public methods: # 2. The Pyboard driver -The principal reason to use this driver is to calibrate the Pyboard's RTC. +The principal reason to use this driver is to calibrate the Pyboard's RTC. This +does not yet support the Pyboard D. This assumes that the DS3231 is connected to the hardware I2C port on the `X` or `Y` side of the board, and that the Pyboard's RTC is set to the correct time @@ -85,3 +86,7 @@ Public methods: calibration factor and applies it to the Pyboard. It returns the calibration factor which may be stored in a file if the calibration needs to survive an outage of all power sources. + 4. `getcal(minutes=5, cal=0, verbose=True)` Measures the performance of the + Pyboard RTC against the DS3231. If `cal` is specified, the calibration factor + is applied before the test is run. The default is to zero the calibration and + return the required factor. diff --git a/DS3231/ds3231_pb.py b/DS3231/ds3231_pb.py index 1be6aa2..e2dd129 100644 --- a/DS3231/ds3231_pb.py +++ b/DS3231/ds3231_pb.py @@ -3,9 +3,19 @@ # Includes routine to calibrate the Pyboard's RTC from the DS3231 # delta method now operates to 1mS precision # precison of calibration further improved by timing Pyboard RTC transition -# Adapted by Peter Hinch, Jan 2016 +# Adapted by Peter Hinch, Jan 2016, Jan 2020 for Pyboard D + +# Pyboard D rtc.datetime()[7] counts microseconds. See end of page on +# https://pybd.io/hw/pybd_sfxw.htm + +import utime +import pyb +import os + +d_series = os.uname().machine.split(' ')[0][:4] == 'PYBD' +if d_series: + pyb.Pin.board.EN_3V3.value(1) -import utime, pyb DS3231_I2C_ADDR = 104 class DS3231Exception(OSError): @@ -15,14 +25,21 @@ rtc = pyb.RTC() def now(): # Return the current time from the RTC in millisecs from year 2000 secs = utime.time() - ms = 1000 * (255 -rtc.datetime()[7]) >> 8 - if ms < 50: # Might have just rolled over + if d_series: + ms = rtc.datetime()[7] // 1000 + else: + ms = 1000 * (255 -rtc.datetime()[7]) >> 8 + if ms < 50: # Might have just rolled over secs = utime.time() return 1000 * secs + ms def nownr(): # Return the current time from the RTC: caller ensures transition has occurred - return 1000 * utime.time() + (1000 * (255 -rtc.datetime()[7]) >> 8) + if d_series: + return 1000 * utime.time() + rtc.datetime()[7] // 1000 + return 1000 * utime.time() + (1000 * (255 -rtc.datetime()[7]) >> 8) +def get_us(s): # For Pyboard D: convert datetime to μs. Caller handles rollover + return (s[7] + s[6] * 1_000_000 + s[5] * 60_000_000 + s[4] * 3_600_000_000 ) # Driver for DS3231 accurate RTC module (+- 1 min/yr) needs adapting for Pyboard # source https://github.com/scudderfish/uDS3231 def bcd2dec(bcd): @@ -85,13 +102,13 @@ class DS3231: self.ds3231.mem_write(dec2bcd(MM), DS3231_I2C_ADDR, 5) self.ds3231.mem_write(dec2bcd(YY-1900), DS3231_I2C_ADDR, 6) - def delta(self): # Return no. of mS RTC leads DS3231 + def delta(self): # Return no. of mS RTC leads DS3231 self.await_transition() rtc_ms = now() t_ds3231 = utime.mktime(self.get_time()) # To second precision, still in same sec as transition return rtc_ms - 1000 * t_ds3231 - def await_transition(self): # Wait until DS3231 seconds value changes + def await_transition(self): # Wait until DS3231 seconds value changes data = self.ds3231.mem_read(self.timebuf, DS3231_I2C_ADDR, 0) ss = data[0] while ss == data[0]: @@ -110,33 +127,69 @@ class DS3231: # Note calibration factor is not saved on power down unless an RTC backup battery is used. An option is # to store the calibration factor on disk and issue rtc.calibration(factor) on boot. - def getcal(self, minutes=5): - rtc.calibration(0) # Clear existing cal - self.save_time() # Set DS3231 from RTC - self.await_transition() # Wait for DS3231 to change: on a 1 second boundary + def getcal(self, minutes=5, cal=0, verbose=True): + if d_series: + return self._getcal_d(minutes, cal, verbose) + verbose and print('Pyboard 1.x. Waiting {} minutes for calibration factor.'.format(minutes)) + rtc.calibration(cal) # Clear existing cal + self.save_time() # Set DS3231 from RTC + self.await_transition() # Wait for DS3231 to change: on a 1 second boundary tus = pyb.micros() st = rtc.datetime()[7] - while rtc.datetime()[7] == st: # Wait for RTC to change + while rtc.datetime()[7] == st: # Wait for RTC to change pass - t1 = pyb.elapsed_micros(tus) # t1 is duration (uS) between DS and RTC change (start) - rtcstart = nownr() # RTC start time in mS - dsstart = utime.mktime(self.get_time()) # DS start time in secs + t1 = pyb.elapsed_micros(tus) # t1 is duration (μs) between DS and RTC change (start) + rtcstart = nownr() # RTC start time in mS + dsstart = utime.mktime(self.get_time()) # DS start time in secs pyb.delay(minutes * 60000) - self.await_transition() # DS second boundary + self.await_transition() # DS second boundary tus = pyb.micros() st = rtc.datetime()[7] while rtc.datetime()[7] == st: pass - t2 = pyb.elapsed_micros(tus) # t2 is duration (uS) between DS and RTC change (end) + t2 = pyb.elapsed_micros(tus) # t2 is duration (μs) between DS and RTC change (end) rtcend = nownr() dsend = utime.mktime(self.get_time()) - dsdelta = (dsend - dsstart) * 1000000 # Duration (uS) between DS edges as measured by DS3231 - rtcdelta = (rtcend - rtcstart) * 1000 + t1 -t2 # Duration (uS) between DS edges as measured by RTC and corrected + dsdelta = (dsend - dsstart) * 1000000 # Duration (μs) between DS edges as measured by DS3231 + rtcdelta = (rtcend - rtcstart) * 1000 + t1 -t2 # Duration (μs) between DS edges as measured by RTC and corrected ppm = (1000000* (rtcdelta - dsdelta))/dsdelta - return int(-ppm/0.954) + if cal: + verbose and print('Error {:4.1f}ppm {:4.1f}mins/year.'.format(ppm, ppm * 1.903)) + return 0 + cal = int(-ppm / 0.954) + verbose and print('Error {:4.1f}ppm {:4.1f}mins/year. Cal factor {}'.format(ppm, ppm * 1.903, cal)) + return cal + + # Version for Pyboard D. This has μs resolution. + def _getcal_d(self, minutes, cal, verbose): + verbose and print('Pyboard D. Waiting {} minutes for calibration factor.'.format(minutes)) + rtc.calibration(cal) # Clear existing cal + self.save_time() # Set DS3231 from RTC + self.await_transition() # Wait for DS3231 to change: on a 1 second boundary + t = rtc.datetime() # Get RTC time + # Time of DS3231 transition measured by RTC in μs since start of day + rtc_start_us = get_us(t) + dsstart = utime.mktime(self.get_time()) # DS start time in secs + pyb.delay(minutes * 60_000) + self.await_transition() # Wait for DS second boundary + t = rtc.datetime() + # Time of DS3231 transition measured by RTC in μs since start of day + rtc_end_us = get_us(t) + dsend = utime.mktime(self.get_time()) # DS start time in secs + if rtc_end_us < rtc_start_us: # It's run past midnight. Assumption: run time < 1 day! + rtc_end_us += 24 * 3_600_000_000 + + dsdelta = (dsend - dsstart) * 1_000_000 # Duration (μs) between DS3231 edges as measured by DS3231 + rtcdelta = rtc_end_us - rtc_start_us # Duration (μs) between DS edges as measured by RTC + ppm = (1_000_000 * (rtcdelta - dsdelta)) / dsdelta + if cal: # We've already calibrated. Just report results. + verbose and print('Error {:4.1f}ppm {:4.1f}mins/year.'.format(ppm, ppm * 1.903)) + return 0 + cal = int(-ppm / 0.954) + verbose and print('Error {:4.1f}ppm {:4.1f}mins/year. Cal factor {}'.format(ppm, ppm * 1.903, cal)) + return cal def calibrate(self, minutes=5): - print('Waiting {} minutes to acquire calibration factor...'.format(minutes)) cal = self.getcal(minutes) rtc.calibration(cal) print('Pyboard RTC is calibrated. Factor is {}.'.format(cal))