forked from malcolmholmes/pico-clock-green-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add buttons, clock, rtc, speaker, scheduler
This is a major jump in functionality. We get: * A working display * A scheduler supporting callbacks of varying trigger durations * Working buttons with min/max press windows * Working speakers with a simple beep only * RTC clock support * A basic clock implementation, with flashing colon
- Loading branch information
1 parent
b82c7c2
commit 503f1f4
Showing
8 changed files
with
397 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
from machine import Pin | ||
import time | ||
|
||
|
||
|
||
STATE_UNPRESSED=1 | ||
STATE_PRESSED=2 | ||
|
||
class Buttons: | ||
PINS = { | ||
1: 2, | ||
2: 17, | ||
3: 15, | ||
} | ||
class Button: | ||
|
||
class Callback: | ||
def __init__(self, callback, min=0, max=-1): | ||
self.callback = callback | ||
self.min = min | ||
self.max = max | ||
|
||
def __init__(self, number): | ||
self.pin = Pin(Buttons.PINS[number], Pin.IN, Pin.PULL_UP) | ||
self.number = number | ||
self.state = STATE_UNPRESSED | ||
self.callbacks = [] | ||
self.pressed_time = None | ||
|
||
def add_callback(self, callback, min=0, max=-1): | ||
callbackObj = self.Callback(callback, min, max) | ||
self.callbacks.append(callbackObj) | ||
return callbackObj | ||
|
||
def __init__(self, scheduler): | ||
self.buttons = [] | ||
scheduler.schedule("button-press", 1, self.millis_callback) | ||
|
||
def add_button(self, number): | ||
button = Buttons.Button(number) | ||
self.buttons.append(button) | ||
return button | ||
|
||
def millis_callback(self, t): | ||
for button in self.buttons: | ||
if len(button.callbacks)>0: | ||
if button.state == STATE_UNPRESSED and button.pin.value() == 0: | ||
button.state = STATE_PRESSED | ||
button.pressed = time.ticks_ms() | ||
elif button.state == STATE_PRESSED and button.pin.value() == 1: | ||
button.state = STATE_UNPRESSED | ||
tm = time.ticks_ms() | ||
press_duration = time.ticks_diff(tm, button.pressed) | ||
print("Button %d pressed for %dms" %(button.number, press_duration)) | ||
for callback in button.callbacks: | ||
if callback.min < press_duration and (callback.max==-1 or press_duration <= callback.max): | ||
callback.callback(t) | ||
button.pressed = None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import time | ||
|
||
class Clock: | ||
def __init__(self, scheduler, display, rtc): | ||
self.display = display | ||
self.rtc = rtc | ||
scheduler.schedule("clock-second", 1000, self.secs_callback) | ||
scheduler.schedule("clock-minute", 60000, self.mins_callback) | ||
|
||
def secs_callback(self, t): | ||
t = time.time() | ||
if t%2==0: | ||
self.display.show_char(":", pos=10) | ||
else: | ||
self.display.show_char(" :", pos=10) | ||
|
||
def mins_callback(self, t): | ||
t = self.rtc.get_time() | ||
now = "%02d:%02d" % (t[3], t[4]) | ||
self.display.show_text(now) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
# 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 -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 | ||
|
Oops, something went wrong.