kopia lustrzana https://github.com/GuyCarver/MicroPython
Added battery backup clock and 7 segment LED display.
rodzic
38d67df9ab
commit
b88f2050e3
|
@ -759,5 +759,5 @@ def makeg( ) :
|
||||||
t = TFT(1, "X1", "X2")
|
t = TFT(1, "X1", "X2")
|
||||||
print("Initializing")
|
print("Initializing")
|
||||||
t.initg()
|
t.initg()
|
||||||
t.fill(0)
|
# t.fill(0)
|
||||||
return t
|
return t
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
# Pyboard driver for DS3231 precison real time clock.
|
||||||
|
# Adapted from WiPy driver at https://github.com/scudderfish/uDS3231
|
||||||
|
# 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
|
||||||
|
|
||||||
|
import utime, pyb
|
||||||
|
DS3231_I2C_ADDR = 104
|
||||||
|
|
||||||
|
class DS3231Exception(OSError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
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
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Driver for DS3231 accurate RTC module (+- 1 min/yr) needs adapting for Pyboard
|
||||||
|
# source https://github.com/scudderfish/uDS3231
|
||||||
|
def bcd2dec(bcd):
|
||||||
|
return (((bcd & 0xf0) >> 4) * 10 + (bcd & 0x0f))
|
||||||
|
|
||||||
|
def dec2bcd(dec):
|
||||||
|
tens, units = divmod(dec, 10)
|
||||||
|
return (tens << 4) + units
|
||||||
|
|
||||||
|
class DS3231:
|
||||||
|
def __init__(self, side = 'X'):
|
||||||
|
side = side.lower()
|
||||||
|
if side == 'x':
|
||||||
|
bus = 1
|
||||||
|
elif side == 'y':
|
||||||
|
bus = 2
|
||||||
|
else:
|
||||||
|
raise ValueError('Side must be "X" or "Y"')
|
||||||
|
self.ds3231 = pyb.I2C(bus, mode=pyb.I2C.MASTER, baudrate=400000)
|
||||||
|
self.timebuf = bytearray(7)
|
||||||
|
if DS3231_I2C_ADDR not in self.ds3231.scan():
|
||||||
|
raise DS3231Exception("DS3231 not found on I2C bus at %d" % DS3231_I2C_ADDR)
|
||||||
|
|
||||||
|
def get_time(self, set_rtc = False):
|
||||||
|
if set_rtc:
|
||||||
|
data = self.await_transition() # For accuracy set RTC immediately after a seconds transition
|
||||||
|
else:
|
||||||
|
data = self.ds3231.mem_read(self.timebuf, DS3231_I2C_ADDR, 0) # don't wait
|
||||||
|
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
|
||||||
|
if set_rtc:
|
||||||
|
rtc.datetime((YY, MM, DD, wday, hh, mm, ss, 0))
|
||||||
|
return (YY, MM, DD, hh, mm, ss, wday -1, 0) # Time from DS3231 in time.time() format (less yday)
|
||||||
|
|
||||||
|
def save_time(self):
|
||||||
|
(YY, MM, DD, wday, hh, mm, ss, subsecs) = rtc.datetime()
|
||||||
|
self.ds3231.mem_write(dec2bcd(ss), DS3231_I2C_ADDR, 0)
|
||||||
|
self.ds3231.mem_write(dec2bcd(mm), DS3231_I2C_ADDR, 1)
|
||||||
|
self.ds3231.mem_write(dec2bcd(hh), DS3231_I2C_ADDR, 2) # Sets to 24hr mode
|
||||||
|
self.ds3231.mem_write(dec2bcd(wday), DS3231_I2C_ADDR, 3) # 1 == Monday, 7 == Sunday
|
||||||
|
self.ds3231.mem_write(dec2bcd(DD), DS3231_I2C_ADDR, 4)
|
||||||
|
if YY >= 2000:
|
||||||
|
self.ds3231.mem_write(dec2bcd(MM) | 0b10000000, DS3231_I2C_ADDR, 5)
|
||||||
|
self.ds3231.mem_write(dec2bcd(YY-2000), DS3231_I2C_ADDR, 6)
|
||||||
|
else:
|
||||||
|
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
|
||||||
|
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
|
||||||
|
data = self.ds3231.mem_read(self.timebuf, DS3231_I2C_ADDR, 0)
|
||||||
|
ss = data[0]
|
||||||
|
while ss == data[0]:
|
||||||
|
data = self.ds3231.mem_read(self.timebuf, DS3231_I2C_ADDR, 0)
|
||||||
|
return data
|
||||||
|
|
||||||
|
# Get calibration factor for Pyboard RTC. Note that the DS3231 doesn't have millisecond resolution so we
|
||||||
|
# wait for a seconds transition to emulate it.
|
||||||
|
# This function returns the required calibration factor for the RTC (approximately the no. of ppm the
|
||||||
|
# RTC lags the DS3231).
|
||||||
|
# Delay(min) Outcome (successive runs). Note 1min/yr ~= 2ppm
|
||||||
|
# 5 173 169 173 173 173
|
||||||
|
# 10 171 173 171
|
||||||
|
# 20 172 172 174
|
||||||
|
# 40 173 172 173 Mean: 172.3
|
||||||
|
# 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
|
||||||
|
tus = pyb.micros()
|
||||||
|
st = rtc.datetime()[7]
|
||||||
|
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
|
||||||
|
pyb.delay(minutes * 60000)
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
ppm = (1000000* (rtcdelta - dsdelta))/dsdelta
|
||||||
|
return int(-ppm/0.954)
|
||||||
|
|
||||||
|
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))
|
||||||
|
return cal
|
|
@ -40,11 +40,26 @@ class sevenseg(object):
|
||||||
0b01000000 #dash
|
0b01000000 #dash
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def flipdigit( aByte ) :
|
||||||
|
bit = 1
|
||||||
|
f = 0
|
||||||
|
#swap bits 0-2 with 3-5 (0-3, 1-4, 2-5)
|
||||||
|
for x in range(3):
|
||||||
|
f |= (aByte & bit) << 3
|
||||||
|
bit <<= 1
|
||||||
|
for x in range(3, 6):
|
||||||
|
f |= (aByte & bit) >> 3
|
||||||
|
bit <<= 1
|
||||||
|
f |= aByte & 0xC0 #Keep the rest of the bits.
|
||||||
|
return f
|
||||||
|
|
||||||
def __init__(self, clk, dio ):
|
def __init__(self, clk, dio ):
|
||||||
self._clk = pyb.Pin(clk, pyb.Pin.IN)
|
self._clk = pyb.Pin(clk, pyb.Pin.IN)
|
||||||
self._dio = pyb.Pin(dio, pyb.Pin.IN)
|
self._dio = pyb.Pin(dio, pyb.Pin.IN)
|
||||||
self._brightness = 0
|
self._brightness = 0
|
||||||
self._colon = False
|
self._colon = False
|
||||||
|
self._flip = False
|
||||||
self._buffer = bytearray(4)
|
self._buffer = bytearray(4)
|
||||||
|
|
||||||
self._clk.init(Pin.IN)
|
self._clk.init(Pin.IN)
|
||||||
|
@ -60,6 +75,13 @@ class sevenseg(object):
|
||||||
def colon( self, aValue ) :
|
def colon( self, aValue ) :
|
||||||
self._colon = aValue
|
self._colon = aValue
|
||||||
|
|
||||||
|
@property
|
||||||
|
def flip( self ) : return self._flip
|
||||||
|
|
||||||
|
@flip.setter
|
||||||
|
def flip( self, aValue ) :
|
||||||
|
self._flip = aValue
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def brightness( self ) : return self._brightness
|
def brightness( self ) : return self._brightness
|
||||||
|
|
||||||
|
@ -113,16 +135,26 @@ class sevenseg(object):
|
||||||
v = 0
|
v = 0
|
||||||
|
|
||||||
def display( self ) :
|
def display( self ) :
|
||||||
|
colonloc = -2 if self.flip else 1
|
||||||
if self.colon :
|
if self.colon :
|
||||||
self._buffer[1] |= 0x80
|
self._buffer[colonloc] |= 0x80
|
||||||
else:
|
else:
|
||||||
self._buffer[1] &= ~0x80
|
self._buffer[colonloc] &= ~0x80
|
||||||
|
|
||||||
self._write_comm1()
|
self._write_comm1()
|
||||||
self._start()
|
self._start()
|
||||||
self._write_byte(_CMD2)
|
self._write_byte(_CMD2)
|
||||||
|
|
||||||
|
#If screen flipped we need to flip the digits and write in reverse oder.
|
||||||
|
if self.flip:
|
||||||
|
sloc = len(self._buffer) - 1
|
||||||
|
for x in range(sloc, -1, -1):
|
||||||
|
b = self.flipdigit(self._buffer[x])
|
||||||
|
self._write_byte(b)
|
||||||
|
else:
|
||||||
for b in self._buffer:
|
for b in self._buffer:
|
||||||
self._write_byte(b)
|
self._write_byte(b)
|
||||||
|
|
||||||
self._stop()
|
self._stop()
|
||||||
self._write_comm3()
|
self._write_comm3()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
# MicroPython TM1637 quad 7-segment LED display driver
|
||||||
|
|
||||||
|
from machine import Pin
|
||||||
|
from time import sleep_us
|
||||||
|
|
||||||
|
_CMD_SET1 = const(64) # 0x40 data set
|
||||||
|
_CMD_SET2 = const(192) # 0xC0 address command set
|
||||||
|
_CMD_SET3 = const(128) # 0x80 data control command set
|
||||||
|
|
||||||
|
# 0-9, a-f, blank, dash
|
||||||
|
_SEGMENTS = [63,6,91,79,102,109,125,7,127,111,119,124,57,94,121,113,0,64]
|
||||||
|
|
||||||
|
class TM1637(object):
|
||||||
|
"""Library for the quad 7-segment LED display modules based on the TM1637
|
||||||
|
LED driver."""
|
||||||
|
def __init__(self, clk, dio, brightness=7):
|
||||||
|
self.clk = clk
|
||||||
|
self.dio = dio
|
||||||
|
|
||||||
|
if not 0 <= brightness <= 7:
|
||||||
|
raise ValueError("Brightness out of range")
|
||||||
|
self._brightness = brightness
|
||||||
|
|
||||||
|
self.clk.init(Pin.IN)
|
||||||
|
self.dio.init(Pin.IN)
|
||||||
|
self.clk(0)
|
||||||
|
self.dio(0)
|
||||||
|
|
||||||
|
def _start(self):
|
||||||
|
self.dio.init(Pin.OUT)
|
||||||
|
sleep_us(50)
|
||||||
|
|
||||||
|
def _stop(self):
|
||||||
|
self.dio.init(Pin.OUT)
|
||||||
|
sleep_us(50)
|
||||||
|
self.clk.init(Pin.IN)
|
||||||
|
sleep_us(50)
|
||||||
|
self.dio.init(Pin.IN)
|
||||||
|
sleep_us(50)
|
||||||
|
|
||||||
|
def _write_comm1(self):
|
||||||
|
self._start()
|
||||||
|
self._write_byte(_CMD_SET1)
|
||||||
|
self._stop()
|
||||||
|
|
||||||
|
def _write_comm3(self):
|
||||||
|
self._start()
|
||||||
|
self._write_byte(_CMD_SET3 + self._brightness + 7)
|
||||||
|
self._stop()
|
||||||
|
|
||||||
|
def _write_byte(self, b):
|
||||||
|
# send each bit
|
||||||
|
for i in range(8):
|
||||||
|
self.clk.init(Pin.OUT)
|
||||||
|
sleep_us(50)
|
||||||
|
self.dio.init(Pin.IN if b & 1 else Pin.OUT)
|
||||||
|
sleep_us(50)
|
||||||
|
self.clk.init(Pin.IN)
|
||||||
|
sleep_us(50)
|
||||||
|
b >>= 1
|
||||||
|
self.clk.init(Pin.OUT)
|
||||||
|
sleep_us(50)
|
||||||
|
self.clk.init(Pin.IN)
|
||||||
|
sleep_us(50)
|
||||||
|
self.clk.init(Pin.OUT)
|
||||||
|
sleep_us(50)
|
||||||
|
|
||||||
|
def brightness(self, val=None):
|
||||||
|
"""Set the display brightness 0-7."""
|
||||||
|
if val is None:
|
||||||
|
return self._brightness
|
||||||
|
if not 0 <= val <= 7:
|
||||||
|
raise ValueError("Brightness out of range")
|
||||||
|
self._brightness = val
|
||||||
|
self._write_comm1()
|
||||||
|
self._write_comm3()
|
||||||
|
|
||||||
|
def write(self, segments, pos=0):
|
||||||
|
"""Display up to 4 segments moving right from a given position.
|
||||||
|
The MSB in the 2nd segment controls the colon between the 2nd
|
||||||
|
and 3rd segments."""
|
||||||
|
if not 0 <= pos <= 3:
|
||||||
|
raise ValueError("Position out of range")
|
||||||
|
self._write_comm1()
|
||||||
|
|
||||||
|
# write COMM2 + first digit address
|
||||||
|
self._start()
|
||||||
|
self._write_byte(_CMD_SET2 + pos)
|
||||||
|
for seg in segments:
|
||||||
|
self._write_byte(seg)
|
||||||
|
self._stop()
|
||||||
|
self._write_comm3()
|
||||||
|
|
||||||
|
def encode_digit(self, digit):
|
||||||
|
"""Convert a character 0-9, a-f to a segment."""
|
||||||
|
return _SEGMENTS[digit & 0x0f]
|
||||||
|
|
||||||
|
def encode_string(self, string):
|
||||||
|
"""Convert an up to 4 character length string containing 0-9, a-f,
|
||||||
|
space, dash to an array of segments, matching the length of the
|
||||||
|
source string."""
|
||||||
|
segments = bytearray(4)
|
||||||
|
for i in range(0, min(4, len(string))):
|
||||||
|
segments[i] = self.encode_char(string[i])
|
||||||
|
return segments
|
||||||
|
|
||||||
|
def encode_char(self, char):
|
||||||
|
"""Convert a character 0-9, a-f, space or dash to a segment."""
|
||||||
|
o = ord(char)
|
||||||
|
# space
|
||||||
|
if o == 32:
|
||||||
|
return _SEGMENTS[16]
|
||||||
|
# dash
|
||||||
|
if o == 45:
|
||||||
|
return _SEGMENTS[17]
|
||||||
|
# uppercase A-F
|
||||||
|
if o >= 65 and o <= 70:
|
||||||
|
return _SEGMENTS[o-55]
|
||||||
|
# lowercase a-f
|
||||||
|
if o >= 97 and o <= 102:
|
||||||
|
return _SEGMENTS[o-87]
|
||||||
|
# 0-9
|
||||||
|
if o >= 48 and o <= 57:
|
||||||
|
return _SEGMENTS[o-48]
|
||||||
|
raise ValueError("Character out of range")
|
||||||
|
|
||||||
|
def hex(self, val):
|
||||||
|
"""Display a hex value 0x0000 through 0xffff, right aligned."""
|
||||||
|
string = '{:04x}'.format(val & 0xffff)
|
||||||
|
self.write(self.encode_string(string))
|
||||||
|
|
||||||
|
def number(self, num):
|
||||||
|
"""Display a numeric value -999 through 9999, right aligned."""
|
||||||
|
# limit to range -999 to 9999
|
||||||
|
num = max(-999, min(num, 9999))
|
||||||
|
string = '{0: >4d}'.format(num)
|
||||||
|
self.write(self.encode_string(string))
|
||||||
|
|
||||||
|
def numbers(self, num1, num2, colon=True):
|
||||||
|
"""Display two numeric values -9 through 99, with leading zeros
|
||||||
|
and separated by a colon."""
|
||||||
|
num1 = max(-9, min(num1, 99))
|
||||||
|
num2 = max(-9, min(num2, 99))
|
||||||
|
segments = self.encode_string('{0:0>2d}{1:0>2d}'.format(num1, num2))
|
||||||
|
# colon on
|
||||||
|
if colon:
|
||||||
|
segments[1] |= 0x80
|
||||||
|
self.write(segments)
|
Ładowanie…
Reference in New Issue