Prior to DS3231 update.

pull/15/head
Peter Hinch 2020-01-25 17:36:58 +00:00
rodzic d8a1810a90
commit c259792a7c
2 zmienionych plików z 80 dodań i 22 usunięć

Wyświetl plik

@ -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.

Wyświetl plik

@ -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))