-
Notifications
You must be signed in to change notification settings - Fork 16
/
ds3231_port.py
146 lines (124 loc) · 5.93 KB
/
ds3231_port.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# FROM HERE: https://github.com/peterhinch/micropython-samples/tree/master/DS3231
# 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:
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, 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, t):
(YY, MM, mday, hh, mm, ss, wday, yday) = t
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))) # 0 == Monday, 6 == 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], 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], 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