diff --git a/lib/ST7735.py b/lib/ST7735.py index e894340..088ef65 100644 --- a/lib/ST7735.py +++ b/lib/ST7735.py @@ -759,5 +759,5 @@ def makeg( ) : t = TFT(1, "X1", "X2") print("Initializing") t.initg() - t.fill(0) +# t.fill(0) return t diff --git a/lib/ds3231_pb.py b/lib/ds3231_pb.py new file mode 100644 index 0000000..1be6aa2 --- /dev/null +++ b/lib/ds3231_pb.py @@ -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 diff --git a/lib/sevenseg.py b/lib/sevenseg.py index df3c190..3c7c7f5 100644 --- a/lib/sevenseg.py +++ b/lib/sevenseg.py @@ -40,11 +40,26 @@ class sevenseg(object): 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 ): self._clk = pyb.Pin(clk, pyb.Pin.IN) self._dio = pyb.Pin(dio, pyb.Pin.IN) self._brightness = 0 self._colon = False + self._flip = False self._buffer = bytearray(4) self._clk.init(Pin.IN) @@ -60,6 +75,13 @@ class sevenseg(object): def colon( self, aValue ) : self._colon = aValue + @property + def flip( self ) : return self._flip + + @flip.setter + def flip( self, aValue ) : + self._flip = aValue + @property def brightness( self ) : return self._brightness @@ -113,16 +135,26 @@ class sevenseg(object): v = 0 def display( self ) : + colonloc = -2 if self.flip else 1 if self.colon : - self._buffer[1] |= 0x80 + self._buffer[colonloc] |= 0x80 else: - self._buffer[1] &= ~0x80 + self._buffer[colonloc] &= ~0x80 self._write_comm1() self._start() self._write_byte(_CMD2) - for b in self._buffer: - self._write_byte(b) + + #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: + self._write_byte(b) + self._stop() self._write_comm3() diff --git a/lib/tm1637.py b/lib/tm1637.py new file mode 100644 index 0000000..51314d6 --- /dev/null +++ b/lib/tm1637.py @@ -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)