rm-hull / luma.led_matrix

Python module to drive LED Matrices & 7-segment displays (MAX7219) and RGB NeoPixels (WS2812 / APA102)
https://luma-led-matrix.readthedocs.io
MIT License
522 stars 157 forks source link

Support for 4-digit TM1637 display #51

Open thijstriemstra opened 7 years ago

thijstriemstra commented 7 years ago

Recently got one of these 4 Bits TM1637 Digital Tube LED Clock displays. Would be cool to support this in luma.led_matrix.

thijstriemstra commented 7 years ago

I've ordered one for you @rm-hull if you want to play around with it.

twdkeule commented 7 years ago

I already encountered this while looking to buy one: raspberrytips.nl

Also found this on github

thijstriemstra commented 7 years ago

Ok. It uses wiringPi2. Thanks for info though.

rm-hull commented 7 years ago

@thijstriemstra - Package arrived today

thijstriemstra commented 7 years ago

Yay nice :) @rm-hull any luck getting it to work?

rm-hull commented 7 years ago

I connected this up, but not getting anything displayed (connected to an RPi Zero) using the scripts from https://raspberrytips.nl/tm1637-4-digit-led-display-raspberry-pi/

Not sure if it's a dud or something specific to the pi zero.

thijstriemstra commented 7 years ago

Here's an arduino reference library with a wiring table: https://github.com/bremme/arduino-tm1637

Hardware setup

TM1637 PIN Arduino PIN Description
CLK Any digital pin Clock
DIO Any digital pin Digital output
VCC 5V Supply voltage
GND GND Ground

And using https://github.com/johnlr/raspberrypi-tm1637 I couldn't get it to work with CLK on gpio 21 and DIO on 20.

thijstriemstra commented 7 years ago

Maybe this is easy to port.. https://github.com/blueSolder/tm1637-Chip/blob/master/tm1637_Chip.py

thijstriemstra commented 7 years ago

Also couldn't get https://raspberrytips.nl/tm1637-4-digit-led-display-raspberry-pi/ to work. @twdkeule did you get it to work?

thijstriemstra commented 4 years ago

any chance we can resurrect this @rm-hull? :) one of my most used projects uses this display and it feels weird to use a 'third-party' library for this, I'd love to use luma for this.

Update: I thought I was using a library but turns out it's some code I copy/pasted from somewhere, don't remember where. Anyway, here it is:

import math
import logging
import threading
from time import sleep, localtime

from ..utils import GPIO as IO

logger = logging.getLogger(__name__)

HexDigits = [0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F,
            0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x3D, 0x76,
            0x06, 0x1E, 0x76, 0x38, 0x55, 0x54, 0x3F, 0x73, 0x67,
            0x50, 0x6D, 0x78, 0x3E, 0x1C, 0x2A, 0x76, 0x6E, 0x5B,
            0x00, 0x40, 0x63, 0xFF]

ADDR_AUTO = 0x40
ADDR_FIXED = 0x44
STARTADDR = 0xC0
# DEBUG = False

class TM1637:
    """
    TM1637is a kind of LED (light-emitting diodedisplay) drive control special
    circuit with keyboard scan interface and it's internally integrated with
    MCU digital interface, data latch, LED high pressure drive and keyboard
    scan. Mainly used in small household electrical appliances.
    """
    __doublePoint = False
    __Clkpin = 0
    __Datapin = 0
    __brightness = 1.0  # default to max brightness
    __currentData = [0, 0, 0, 0]

    def __init__(self, CLK, DIO, brightness):
        self.__Clkpin = CLK
        self.__Datapin = DIO
        self.__brightness = brightness
        IO.setup(self.__Clkpin, IO.OUT)
        IO.setup(self.__Datapin, IO.OUT)

    def cleanup(self):
        """Stop updating clock, turn off display, and cleanup GPIO"""
        self.StopClock()
        self.Clear()
        IO.cleanup()

    def Clear(self):
        b = self.__brightness
        point = self.__doublePoint
        self.__brightness = 0
        self.__doublePoint = False
        data = [0x7F, 0x7F, 0x7F, 0x7F]
        self.Show(data)
        # Restore previous settings:
        self.__brightness = b
        self.__doublePoint = point

    def ShowInt(self, i):
        s = str(i)
        self.Clear()
        for i in range(0, len(s)):
            self.Show1(i, int(s[i]))

    def Show(self, data):
        for i in range(0, 4):
            self.__currentData[i] = data[i]

        self.start()
        self.writeByte(ADDR_AUTO)
        self.br()
        self.writeByte(STARTADDR)
        for i in range(0, 4):
            self.writeByte(self.coding(data[i]))
        self.br()
        self.writeByte(0x88 + int(self.__brightness))
        self.stop()

    def Show1(self, DigitNumber, data):
        """show one Digit (number 0...3)"""
        if(DigitNumber < 0 or DigitNumber > 3):
            return  # error

        self.__currentData[DigitNumber] = data

        self.start()
        self.writeByte(ADDR_FIXED)
        self.br()
        self.writeByte(STARTADDR | DigitNumber)
        self.writeByte(self.coding(data))
        self.br()
        self.writeByte(0x88 + int(self.__brightness))
        self.stop()

    # Scrolls any integer n (can be more than 4 digits) from right to left display.
    def ShowScroll(self, n):
        n_str = str(n)
        k = len(n_str)

        for i in range(0, k + 4):
            if (i < k):
                self.Show([int(n_str[i-3]) if i-3 >= 0 else None, int(n_str[i-2]) if i-2 >= 0 else None, int(n_str[i-1]) if i-1 >= 0 else None, int(n_str[i]) if i >= 0 else None])
            elif (i >= k):
                self.Show([int(n_str[i-3]) if (i-3 < k and i-3 >= 0) else None, int(n_str[i-2]) if (i-2 < k and i-2 >= 0) else None, int(n_str[i-1]) if (i-1 < k and i-1 >= 0) else None, None])
            sleep(1)

    def SetBrightness(self, percent):
        """Accepts percent brightness from 0 - 1"""
        max_brightness = 7.0
        brightness = math.ceil(max_brightness * percent)
        if (brightness < 0):
            brightness = 0
        if(self.__brightness != brightness):
            self.__brightness = brightness
            self.Show(self.__currentData)

    def ShowDoublepoint(self, on):
        """Show or hide double point divider"""
        if(self.__doublePoint != on):
            self.__doublePoint = on
            self.Show(self.__currentData)

    def writeByte(self, data):
        for i in range(0, 8):
            IO.output(self.__Clkpin, IO.LOW)
            if(data & 0x01):
                IO.output(self.__Datapin, IO.HIGH)
            else:
                IO.output(self.__Datapin, IO.LOW)
            data = data >> 1
            IO.output(self.__Clkpin, IO.HIGH)

        # wait for ACK
        IO.output(self.__Clkpin, IO.LOW)
        IO.output(self.__Datapin, IO.HIGH)
        IO.output(self.__Clkpin, IO.HIGH)
        IO.setup(self.__Datapin, IO.IN)

        while(IO.input(self.__Datapin)):
            sleep(0.001)
            if(IO.input(self.__Datapin)):
                IO.setup(self.__Datapin, IO.OUT)
                IO.output(self.__Datapin, IO.LOW)
                IO.setup(self.__Datapin, IO.IN)
        IO.setup(self.__Datapin, IO.OUT)

    def start(self):
        """send start signal to TM1637"""
        IO.output(self.__Clkpin, IO.HIGH)
        IO.output(self.__Datapin, IO.HIGH)
        IO.output(self.__Datapin, IO.LOW)
        IO.output(self.__Clkpin, IO.LOW)

    def stop(self):
        IO.output(self.__Clkpin, IO.LOW)
        IO.output(self.__Datapin, IO.LOW)
        IO.output(self.__Clkpin, IO.HIGH)
        IO.output(self.__Datapin, IO.HIGH)

    def br(self):
        """terse break"""
        self.stop()
        self.start()

    def coding(self, data):
        if(self.__doublePoint):
            pointData = 0x80
        else:
            pointData = 0

        if(data == 0x7F or data is None):
            data = 0
        else:
            data = HexDigits[data] + pointData
        return data

    def clock(self, military_time):
        """Clock script modified from:
            https://github.com/johnlr/raspberrypi-tm1637"""
        self.ShowDoublepoint(True)
        while (not self.__stop_event.is_set()):
            t = localtime()
            hour = t.tm_hour
            if not military_time:
                hour = 12 if (t.tm_hour % 12) == 0 else t.tm_hour % 12
            d0 = hour // 10 if hour // 10 else 36
            d1 = hour % 10
            d2 = t.tm_min // 10
            d3 = t.tm_min % 10
            digits = [d0, d1, d2, d3]
            self.Show(digits)
            # # Optional visual feedback of running alarm:
            # print digits
            # for i in tqdm(range(60 - t.tm_sec)):
            for i in range(60 - t.tm_sec):
                if (not self.__stop_event.is_set()):
                    sleep(1)

    def StartClock(self, military_time=True):
        # Stop event based on: http://stackoverflow.com/a/6524542/3219667
        self.__stop_event = threading.Event()
        self.__clock_thread = threading.Thread(
            target=self.clock, args=(military_time,))
        self.__clock_thread.start()

    def StopClock(self):
        try:
            logger.info('Attempting to stop live clock')
            self.__stop_event.set()
        except:
            logger.info('No clock to close')

class TM1637Display(object):
    """
    """
    display = None
    msg = None

    def __init__(self, clk_pin, dio_pin, brightness=1.0):
        self.clk_pin = clk_pin
        self.dio_pin = dio_pin
        self.brightness = brightness

        if IO:
            self.display = TM1637(
                CLK=self.clk_pin,
                DIO=self.dio_pin,
                brightness=self.brightness
            )
            self.clear()

    def clear(self):
        if IO:
            self.display.Clear()

    def setBrightness(self, brightness):
        if IO:
            self.display.SetBrightness(brightness)

    def write(self, msg):
        if IO:
            if msg != self.msg:
                self.msg = msg
                self.clear()
                start_pos = 0
                if len(msg) == 2:
                    start_pos = 1
                for index, ch in enumerate(msg):
                    self.display.Show1(start_pos + index, int(ch))

    def startup(self):
        if IO:
            self.setBrightness(0)
            self.write('0000')