Skip to content

Commit 79fe5ea

Browse files
add DS1307 module
1 parent 15921ac commit 79fe5ea

File tree

3 files changed

+366
-0
lines changed

3 files changed

+366
-0
lines changed

ds1307/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: UTF-8 -*-
3+
4+
from .version import __version__
5+
6+
from .ds1307 import DS1307

ds1307/ds1307.py

Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: UTF-8 -*-
3+
4+
"""
5+
I2C RTC driver for DS1307
6+
7+
https://github.com/mcauser/micropython-tinyrtc-i2c
8+
9+
MIT License
10+
Copyright (c) 2018 Mike Causer
11+
Extended 2023 by brainelectronics
12+
13+
Permission is hereby granted, free of charge, to any person obtaining a copy
14+
of this software and associated documentation files (the "Software"), to deal
15+
in the Software without restriction, including without limitation the rights
16+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17+
copies of the Software, and to permit persons to whom the Software is
18+
furnished to do so, subject to the following conditions:
19+
20+
The above copyright notice and this permission notice shall be included in all
21+
copies or substantial portions of the Software.
22+
23+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29+
SOFTWARE.
30+
31+
BCD Format:
32+
https://en.wikipedia.org/wiki/Binary-coded_decimal
33+
"""
34+
35+
# system packages
36+
from machine import I2C
37+
from micropython import const
38+
39+
40+
class _Subscriptable():
41+
def __getitem__(self, item):
42+
return None
43+
44+
45+
_subscriptable = _Subscriptable()
46+
47+
Optional = _subscriptable
48+
Tuple = _subscriptable
49+
50+
DATETIME_REG = const(0) # 0x00-0x06
51+
CHIP_HALT = const(128)
52+
CONTROL_REG = const(7) # 0x07
53+
RAM_REG = const(8) # 0x08-0x3F
54+
55+
56+
class DS1307(object):
57+
"""Driver for the DS1307 RTC."""
58+
59+
def __init__(self, addr=0x68, i2c: Optional[I2C] = None) -> None:
60+
"""
61+
Constructs a new instance
62+
63+
:param addr: The I2C bus address of the EEPROM
64+
:type addr: int
65+
:param i2c: I2C object
66+
:type i2c: I2C
67+
"""
68+
self._addr = addr
69+
70+
if i2c is None:
71+
# default assignment, check the docs
72+
self._i2c = I2C(0)
73+
else:
74+
self._i2c = i2c
75+
76+
self._weekday_start = 0
77+
self._halt = False
78+
79+
@property
80+
def addr(self) -> int:
81+
"""
82+
Get the DS1307 I2C bus address
83+
84+
:returns: DS1307 I2C bus address
85+
:rtype: int
86+
"""
87+
return self._addr
88+
89+
@property
90+
def weekday_start(self) -> int:
91+
"""
92+
Get the start of the weekday
93+
94+
:returns: Weekday start
95+
:rtype: int
96+
"""
97+
return self._weekday_start
98+
99+
@weekday_start.setter
100+
def weekday_start(self, value: int) -> None:
101+
"""Set the start of the weekday"""
102+
if 0 <= value <= 6:
103+
self._weekday_start = value
104+
else:
105+
raise ValueError("Weekday can only be in range 0-6")
106+
107+
@property
108+
def datetime(self) -> Tuple[int, int, int, int, int, int, int, int]:
109+
"""
110+
Get the current datetime
111+
112+
(2023, 4, 18, 0, 10, 34, 4, 0)
113+
y, m, d,
114+
115+
:returns: (year, month, day, hour, minute, second, weekday, yearday)
116+
:rtype: Tuple[int, int, int, int, int, int, int, int]
117+
"""
118+
buf = bytearray(7)
119+
120+
buf = self._i2c.readfrom_mem(self._addr, DATETIME_REG, 7)
121+
122+
year = self._bcd_to_dec(buf[6]) + 2000
123+
month = self._bcd_to_dec(buf[5])
124+
day = self._bcd_to_dec(buf[4])
125+
yearday = self.day_of_year(year=year, month=month, day=day)
126+
return (
127+
year,
128+
month,
129+
day,
130+
self._bcd_to_dec(buf[2]), # hour
131+
self._bcd_to_dec(buf[1]), # minute
132+
self._bcd_to_dec(buf[0] & 0x7F), # second
133+
self._bcd_to_dec(buf[3] - self._weekday_start), # weekday
134+
yearday,
135+
)
136+
137+
@datetime.setter
138+
def datetime(self, datetime: Tuple[int, int, int, int, int, int, int, int]) -> None: # noqa: E501
139+
"""
140+
Set datetime
141+
142+
(2023, 4, 18, 20, 23, 38, 1, 108) by time.gmtime(time.time())
143+
y, m, d, h, min,sec,wday, yday
144+
0, 1, 2, 3, 4, 5, 6, 7
145+
146+
:param datetime: The datetime
147+
:type datetime: Tuple[int, int, int, int, int, int, int, int]
148+
"""
149+
buf = bytearray(7)
150+
151+
# msb = CH, 1 = halt, 0 = go
152+
buf[0] = self._dec_to_bcd(datetime[5]) & 0x7F # second
153+
buf[1] = self._dec_to_bcd(datetime[4]) # minute
154+
buf[2] = self._dec_to_bcd(datetime[3]) # hour
155+
buf[3] = self._dec_to_bcd(datetime[6] + self._weekday_start)
156+
buf[4] = self._dec_to_bcd(datetime[2]) # day
157+
buf[5] = self._dec_to_bcd(datetime[1]) # month
158+
buf[6] = self._dec_to_bcd(datetime[0] - 2000) # year
159+
160+
if (self._halt):
161+
buf[0] |= (1 << 7)
162+
163+
self._i2c.writeto_mem(self._addr, DATETIME_REG, buf)
164+
165+
@property
166+
def year(self) -> int:
167+
"""
168+
Get the year from the RTC
169+
170+
:returns: Year of RTC
171+
:rtype: int
172+
"""
173+
return self.datetime[0]
174+
175+
@property
176+
def month(self) -> int:
177+
"""
178+
Get the month from the RTC
179+
180+
:returns: Month of RTC
181+
:rtype: int
182+
"""
183+
return self.datetime[1]
184+
185+
@property
186+
def day(self) -> int:
187+
"""
188+
Get the day from the RTC
189+
190+
:returns: Day of RTC
191+
:rtype: int
192+
"""
193+
return self.datetime[2]
194+
195+
@property
196+
def hour(self) -> int:
197+
"""
198+
Get the hour from the RTC
199+
200+
:returns: Hour of RTC
201+
:rtype: int
202+
"""
203+
return self.datetime[3]
204+
205+
@property
206+
def minute(self) -> int:
207+
"""
208+
Get the minute from the RTC
209+
210+
:returns: Minute of RTC
211+
:rtype: int
212+
"""
213+
return self.datetime[4]
214+
215+
@property
216+
def second(self) -> int:
217+
"""
218+
Get the second from the RTC
219+
220+
:returns: Second of RTC
221+
:rtype: int
222+
"""
223+
return self.datetime[5]
224+
225+
@property
226+
def weekday(self) -> int:
227+
"""
228+
Get the weekday from the RTC
229+
230+
:returns: Weekday of RTC
231+
:rtype: int
232+
"""
233+
return self.datetime[5]
234+
235+
@property
236+
def yearday(self) -> int:
237+
"""
238+
Get the yearday from the RTC
239+
240+
:returns: Yearday of RTC
241+
:rtype: int
242+
"""
243+
return self.datetime[6]
244+
245+
def is_leap_year(self, year: int) -> bool:
246+
"""
247+
Determines whether the specified year is a leap year.
248+
249+
:param year: The year
250+
:type year: int
251+
252+
:returns: True if the specified year is leap year, False otherwise.
253+
:rtype: bool
254+
"""
255+
return (year % 4 == 0)
256+
257+
def day_of_year(self, year: int, month: int, day: int) -> int:
258+
"""
259+
Get the day of the year
260+
261+
:param year: The year
262+
:type year: int
263+
:param month: The month
264+
:type month: int
265+
:param day: The day
266+
:type day: int
267+
268+
:returns: Day of the year, January 1 is day 1
269+
:rtype: int
270+
"""
271+
month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
272+
year -= 2000
273+
days16 = day
274+
275+
for x in range(1, month):
276+
days16 += month_days[x - 1]
277+
278+
if month == 2 and self.is_leap_year(year=year):
279+
days16 += 1
280+
281+
return days16
282+
283+
@property
284+
def halt(self) -> bool:
285+
"""
286+
Get status of DS1307
287+
288+
:returns: Status of DS1307
289+
:rtype: bool
290+
"""
291+
return self._halt
292+
293+
@halt.setter
294+
def halt(self, value: bool = False) -> None:
295+
"""
296+
Power up or power down the RTC oscillator
297+
298+
:param value: The value
299+
:type value: bool
300+
"""
301+
reg = self._i2c.readfrom_mem(self._addr, DATETIME_REG, 1)[0]
302+
303+
if value:
304+
reg |= CHIP_HALT
305+
else:
306+
reg &= ~CHIP_HALT
307+
308+
self._halt = bool(value)
309+
self._i2c.writeto_mem(self._addr, DATETIME_REG, bytearray([reg]))
310+
311+
def square_wave(self, sqw: int = 0, out: int = 0) -> None:
312+
"""
313+
Output square wave on pin SQ
314+
315+
at 1Hz, 4.096kHz, 8.192kHz or 32.768kHz, or disable the oscillator
316+
and output logic level high/low.
317+
"""
318+
if sqw not in (0, 1, 4, 8, 32):
319+
raise ValueError(
320+
"Squarewave can be set to 0Hz, 1Hz, {}Hz, {}Hz or {}Hz".format(
321+
2 ** 12, 2 ** 13, 2 ** 15)
322+
)
323+
324+
rs0 = 1 if sqw == 4 or sqw == 32 else 0
325+
rs1 = 1 if sqw == 8 or sqw == 32 else 0
326+
out = 1 if out > 0 else 0
327+
sqw = 1 if sqw > 0 else 0
328+
329+
reg = rs0 | rs1 << 1 | sqw << 4 | out << 7
330+
331+
self._i2c.writeto_mem(self._addr, CONTROL_REG, bytearray([reg]))
332+
333+
def _dec_to_bcd(self, value: int) -> int:
334+
"""
335+
Convert decimal to binary coded decimal (BCD) format
336+
337+
:param value: The value
338+
:type value: int
339+
340+
:returns: Binary coded decimal (BCD) format
341+
:rtype: int
342+
"""
343+
return (value // 10) << 4 | (value % 10)
344+
345+
def _bcd_to_dec(self, value: int) -> int:
346+
"""
347+
Convert binary coded decimal (BCD) format to decimal
348+
349+
:param value: The value
350+
:type value: int
351+
352+
:returns: Decimal value
353+
:rtype: int
354+
"""
355+
return ((value >> 4) * 10) + (value & 0x0F)

ds1307/version.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: UTF-8 -*-
3+
4+
__version_info__ = ("0", "0", "0")
5+
__version__ = '.'.join(__version_info__)

0 commit comments

Comments
 (0)