MyRaspberryAndMe

Tinkering with Raspberry (and other things)

Elevator Information Display using two ILI9341 TFTs

It has been a long time since my last post, well, I have been busy and had significantly less time for tinkering.

But back again. Take an old elevator panel, two displays and a Raspberry Pi and transform it into a home information system.

Ingredients and Original Idea

  • A really old but massive elevator panel with all the original wiring, the buttons and even the led-matrix displays.
  • A Raspberry Pi (of course)
  • Two ILI9341 2.2″ TFT panels
  • Adafruit’s python library for ILI9341

My original idea was to reuse the LED-matrix from the old panels to display some information. This unfortunately was not possible as these are not ‘normal’ matrix displays but do have some sort of logic built in. Applying a voltage on the terminals (in the middle of the picture) does display the preprogrammed floor numbers in the display. Well, this is not a big issue, TFT displays are much cooler.
Here is an image of the unaltered elevator panel:original elevator panel

So using the LED-matrix display was out of the question and I moved on using two 2.2″ TFT displays with ILI9341 chipset. Adafruit has a very nice tutorial on those and a Python library to communicate with the display.

The displays I am using can be acquired easily at ebay or, even cheaper (if you receive them) directly from China. I always tend to like to pay a little more if I can get the items quicker and do have a return address in case something is not in order.
Here’s an image of the displays:

ili-displays

I needed to use two displays, which should not be a problem since the Raspberry Pi is capable of controlling two SPI devices. The two displays should display different “panels” of information. The “panels” should update themselves and overall, the “panel” actually displayed should change after a specified amount of time.
And everything needs to be controllable by only two buttons. The original elevator buttons are illuminated, a behaviour I wanted to keep.

The Display Hardware

Connecting two SPI devices to the Raspberry Pi should be easy, those displays need MOSI, SCK, RESET, DC/RS and CHIP_SELECT lines. And, of course VCC and GND and the supply voltage for the backlight. A breadboard prototype worked fine after some hours of thinking, programming and cursing. See next image:

prototype

The top display is showing the departure times at the station near to my home, the bottom display is showing the weather (and everything is, of course, in German).

Apart from simply controlling the displays I wanted to be able to switch the backlight on or off from the Raspberry Pi and I wanted the two buttons on the panel to be functional. So to summarize:

  • Control backlight from GPIO
  • Top button should wake program from “sleep” mode
  • Bottom button should “flip” (or toggle) through the possible information panels
  • Button illumination should be switched on only when it’s possible to push a button

So I was going to need 5 GPIO pins for button and backlight control and another 4 lines for RESET and DC/RS for the two displays. This would just work with an “old” Raspberry Pi B I have lying around.

While designing the control board I experienced a really weird behaviour. Everything worked fine on the breadboard. As soon as I connected the controller board I designed, the displays stopped working. It took me days to figure out what went wrong. Obviously most instructions on the internet on connecting SPI devices are dead wrong. After reading some implementation notes from Texas Instruments it was clear that when having more than one SPI device you need some additional circuitry on the SCK, MOSI  and CHIP_SELECT lines. These are R5, R6 and R7 in the following PCB. R5 and R6 are pull-ups for the CS lines and R7 prevents echoes (ringing) on the clock line.

schematic

Take care, the PI-GPIO header in my Eagle-library is not showing the new pin naming scheme, the pin numbers in the schematic will differ from those in the source code.

The backlight LED is controlled by using a PNP-transistor in a pretty standard circuit design. The button LEDs are controlled using NPN-transistors, also in a standard circuit design.

In the upper right part I designed an extra 3.3V supply from the 5V power lines, because from the technical specs of the displays I calculated a maximum power draw of 500 mA for both displays. This part is optional, I ended up connecting “VC3.3” directly to the 3.3V pin of the GPIO header, as I was nowhere near drawing that much current.
If you add this “power supply”, be aware that the capacitors are tantalum ones, so the polarity is of importance (I blew on up because of having the polarity wrong.)

S1 and S2 are the two buttons, GPIO4 and GPIO17 are controlling the LEDs inside the buttons. “BTN1-BL-KAT” is connected to the cathode of the button’s backlight LED (and BTN2… respectively). “LEDVCC” is connected to the “LED”-pin (backlight) of the displays.

Refer to the sources on GitHub for which GPIO-pin does what.

The Buttons

The original buttons had LEDs built in, but they did not work with 3.3V or 5V. Which is pretty obvious, as elevators installed over multiple floors in big buildings usually run on 30V AC. So they built in a rectifier and all LEDs were connected in series. This was never going to work with TTL levels. Here’s an image of the original buttons, disassembled. They had SMDs back then (elevator was 20 years old!)

switch_orig

This meant I had to replace the PCB inside the button. Not too great a challenge, apart from the fact that the original PCB’s thickness was 1.0mm and the standard perfboard one could buy is 1.5mm thick. I was able to purchase a 1.0mm perfboard from ebay and soldered some LEDs and a pushbutton directly onto it. To get the same height as the original PCB I needed to sand the button down to level.

switch_pcb

In the end it worked out great. And it was a good exercise in designing a circuit directly on a perfboard without the help of Eagle (just as in old times).

Final Assembly

As my originally designed PCB didn’t work (lacking the resistors mentioned above) I ended up placing everything on a perfboard and soldering wires and headers to it. It works and all in all it came out great. It was even easier to remount all the components with the bigger perfboard.

One goal was to reuse most of the original components and have the elevator panel appear as it was in its original state.

panels

The top button is used as the “ON_BUTTON”. When the displays are switched off to save energy, pressing the top button will reactivate the displays (and all updating of information, as this goes into sleep, too).
The bottom button is what I call the “FLIP_BUTTON”. Pressing this one will toggle displaying the next available panel.

The Software

The software is written in Python and makes use of GPIOZERO, the pygame framework (they now have a terrible website, so I’m linking directly to the download page) and Adafruit’s ILI9341 library mentioned above.

The plan is to have two displays (the actual hardware TFT-display), being able two display different panels of information. Everything (button presses, updates etc.) is handled within a controller class called PanelController.

As always, the source code can be found on my GitHub, under the “Blog” section.

The Display Class “PanelDisplay”

The display class is directly derived from the TFT.ILI9341 class from Adafruit’s library. I added some methods for starting and stopping pygame-timers, attach a panel-object to the display and a redraw method, so that the display can update itself.
The PanelDisplay obtains information about its update interval from the attached panel-object, so that different panels may have different update times.

The Panel Class “InfoPanel”

The class InfoPanel is the base class for all panels. Or in other words: You need to subclass InfoPanel in order to get a panel object that can be attached to the display.

I am using pygame to draw an image of the panel that is to be displayed. This image is the returned as a Canvas-object to the PanelDisplay class. There, the PIL library is used to generate an image, rotate it accordingly and finally let it be displayed on the screen.

A sample implementation of a panel that can be displayed is shown below. It just gets system information from the Raspberry PI and displays it on screen. The rendered panel looks like this:

sysinfo

Here is the code for this panel:

class SystemInfoPanel(InfoPanel):
    '''
    Return some system info
    original idea: http://jeremyblythe.blogspot.de/2013/03/raspberry-pi-system-monitor-embedded-on.html
    '''

    def __init__(self, interval, panelWidth, panelHeight, panelName,
                 netInterface = "wlan0"):
        # call superclass constructor first
        InfoPanel.__init__(self, interval, panelWidth, panelHeight, panelName)

        self.entryFont = pygame.font.Font("/usr/share/fonts/truetype/OCRAStd.otf", 22)
        self.interface = netInterface

    def getIP(self):
        try:
            s = subprocess.check_output(["ip","addr","show",self.interface])
            return s.split('\n')[2].strip().split(' ')[1].split('/')[0]
        except:
            return '?.?.?.?'

    def getUpStats(self):
        "Returns a tuple (uptime, 5 min load average)"
        try:
            s = subprocess.check_output(["uptime"])
            load_split = s.split('load average: ')
            load_five = float(load_split[1].split(',')[1])
            up = load_split[0]
            up_pos = up.rfind(',',0,len(up)-4)
            up = up[:up_pos].split('up ')[1]
            return ( up , load_five )
        except:
            return ( '' , 0 )

    def getTemperature(self):
        "Returns the temperature in degrees C"
        try:
            s = subprocess.check_output(["/opt/vc/bin/vcgencmd","measure_temp"])
            return float(s.split('=')[1][:-3])
        except:
            return 0

    def renderCanvas(self):
        self.canvas.fill((0,0,0))

        linestart = 5
        advance = 28
        indent = 7

        textcol = (255,255,255)

        text = self.entryFont.render("System Information", 1, textcol)
        textrect = text.get_rect()
        textrect.x = self.width/2 - textrect.width/2    # center
        textrect.y = linestart
        self.canvas.blit(text, textrect)
        linestart += advance +16

        text = self.entryFont.render("IP: " + self.getIP(), 1, textcol)
        textrect = text.get_rect()
        textrect.x = 2
        textrect.y = linestart
        self.canvas.blit(text, textrect)
        linestart += advance

        text = self.entryFont.render("CPU-Temp: " + \
                        '{:5.1f}'.format(self.getTemperature()), 1, textcol)
        textrect = text.get_rect()
        textrect.x = 2
        textrect.y = linestart
        self.canvas.blit(text, textrect)
        linestart += advance

        uptime, load = self.getUpStats()
        text = self.entryFont.render("Uptime: " + uptime, 1, textcol)
        textrect = text.get_rect()
        textrect.x = 2
        textrect.y = linestart
        self.canvas.blit(text, textrect)
        linestart += advance
        text = self.entryFont.render("Load: " +'{:6.2f}'.format(load), 1, textcol)
        textrect = text.get_rect()
        textrect.x = 2
        textrect.y = linestart
        self.canvas.blit(text, textrect)

        self.renderLastUpdate()

Putting everything together: “PanelController”

The PanelController class puts all these bits together. It is responsible for reacting to button presses, switch the backlight on/off, switch the button LEDs on/off and more.
It keeps track of the available Panels and which Panel is actually displayed on which Display, so that the Panels can be toggled or flipped.

In addition to this I implemented button handler methods to allow the user to quit the PanelController (long press of top button) and to completely shutdown the Raspberry Pi (long press of bottom button).

A typical invocation sequence would look like this:

# start PanelController with autoOff=30 mins, fliptime=1min
pc = PanelController(30, 1, autoFlip=True)
# create system info panel with refresh time of 5 minutes
info = SystemInfoPanel(5, 320, 240, "SysInfo")
# create some other panels the same way
p1 = FooPanel(5, 320, 240, "FooPanel")
p2 = BarPanel(2, 320, 240, "BarPanel")

# create the display objects (see adafruit library for parameters)
d1 = PanelDisplay(25, 24, 0, 1)
d2 = PanelDisplay(18, 23, 0, 0)

# add display objects to panelcontroller
pc.addDisplay(d1)
pc.addDisplay(d2)

# add panels. they will be displayed in the order they are added
pc.addPanel(info)
pc.addPanel(p1)
pc.addPanel(p2)

# and then start the controller
pc.start()

With my current setup I have implemented panels for:

  • Current weather via weatherunderground
  • Public Transport
  • Appointments from my OwnCloud calendar
  • Status of my Fritz!Box (DSL router)
  • System Information
  • Current ISS position

To give you an idea what they look like, here are some screenshots:

Final Words

I am using pygame because of the events and timers and the easier way to design graphical displays. One drawback is that the PanelController needs to be started as ‘root’, because pygame needs a display, which I am setting to ‘dummy’ to be able to start everything remotely.

In its final installation the controller will be started on boot/reboot via crontab (@reboot, if you wonder).
Adding another button to P6 (or RUN-Jumper on newer Raspberrys) enables me to then shutdown the Raspberry Pi via the elevator buttons and rebooting it with this button attached to the side of it.

I have not published all the panels I implemented, because they are rather specialized for my needs. In the Panels.py file on GitHub you will find a panel for the MVG (Munich Public Transport, FritzBox status information and

As always, comments and likes are welcome.

Advertisements

Comments are closed.