Software for a Led Matrix Display and Led RGB Strip DIY Project

As a continuation of part1, this page will cover the software I put together and how I use it to bring the display alive. From here, you can learn about the main components of the application, as well as a few examples that show how I currently use it. If you would like to skip ahead, grab the code from Github.

By running the program, an embedded web server handles requests that come from a desktop or any other program that wishes to control the display. When the server is 'left alone' it will run as what I call clock mode. Motion and light sensors help conserve energy by keeping tabs on how bright the room is, as well as how long it has been since anything moved near the display. After a certain period of inactivity, the display changes to the screen saver mode, and it stays that way until motion is detected. A third mode, called message mode, is where we can do fun things to the display. You can put the unit in that mode by sending an HTML POST call. In case you do not want to fiddle with variables and values, the web server also offers some very simple pages. There's more on that in the section below.

displayModes

While in message mode, there are a bunch of knobs you can set. They are divided into 3 parts, which I will get into as I describe the message mode in more detail:

  1. Main Message
  2. Background Messages
  3. Background Images

Analogous to the led matrix display, I also have a strip of RGB leds. It provides a number of built-in animations, as well as a method of encoding info representing external states such as weather, number of unread emails, generic countdowns, etc. The interface for controlling it is similar to the one for the led matrix; that is, using an REST-like interface.

But I'm getting ahead of myself... I should start off by visiting the steps to get these bits in the Raspberry Pi.

Section 1: Software Pre-Requisites

Prepare sd with Raspbian

Since I have no need for the desktop environment, I am using Jessie Lite. There are many good pages that explain how to install Raspbian, so I will briefly list the main steps I took (using a Mac laptop):

Use "diskutil list" to find the disk that corresponds to the sd card to be used by your RPI

$ diskutil list
$ diskutil unmountDisk /dev/diskX  ; # replace X with the disk number

Download Raspbian from https://www.raspberrypi.org/downloads/raspbian/ and unzip the image. To make the filename shorter, let's rename it to raspbian.img

$ unzip 20*raspbian-jessie-lite.zip
$ mv 20*raspbian-jessie-lite.img raspbian.img
$ sudo dd bs=1m if=./raspbian.img of=/dev/diskX  ; # replace X with the disk number

Typing ^T (CTRL-T) will give you an update how many blocks have been written. Jessie Lite is 1.3 Gb, so you will need about 1300 "records out". You'll need to be patient. In my system, it took about 25 minutes (883 Kbytes/sec) to complete:

$ sudo dd bs=1m if=./raspbian.img of=/dev/diskX
1298+0 records in
1298+0 records out
1361051648 bytes transferred in 1541.171302 secs (883128 bytes/sec)

Eject disk after dd is finished and use it to boot your RPI

$ diskutil eject /dev/diskX  ; # replace X with the disk number

First-time boot commands

  • Login (user: pi password: raspberry)
  • Run 'sudo raspi-config' and follow these steps:
    • [optional] Select 'Advanced Options' -> 'Hostname'
    • Select 'Internationalisation Options' -> 'Change Timezone'
    • Select 'Expand Filesystem'
    • Select 'Finish' and reboot the rpi
  • After reboot, login again and configure the network, if needed
    • For info on configuring wifi, look for Command line set up in this page.
      • If you are configuring wifi for a ssid that has no password (open), you need to
        'sudo nano /etc/wpa_supplicant/wpa_supplicant.conf' and add the following to that file:
  network={
     ssid="whatever"
     key_mgmt=NONE
  }
  • If your wifi is configured to not broadcast its ssid, make sure to add scan_ssid=1 and mode=0 as shown below
$ sudo cat /etc/wpa_supplicant/wpa_supplicant.conf
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
        ssid="whatever"
        psk="secret"
        scan_ssid=1
        mode=0
}
  • Don't forget that you will need to bounce wlan0 in order to have your changes take effect:
sudo ifdown wlan0 && sudo ifup wlan0
  • Once you are connected to the internet, do an update to ensure you've got the latest and greatest packages.

Note that you may want to reboot after that, to potentially load new linux kernel

$ sudo apt-get update ; sudo apt-get -y dist-upgrade
$ sudo reboot
  • Install git, libevent-devel, and wiringPi.

Once logged in again, there are just a few more pieces to be added to the RPI. I use a web server implementation called pulsar, which is based in the libevent html framework. In order to control the GPIOs in the RPI, I rely on the nice api provided by WiringPi. All are easy to install due to apt-get and git. Life is good when you can ride on the shoulder of giants. :) Here are the steps for installing them:

$ sudo apt-get install -y git libevent-dev

$ cd someDir
$ git clone git://git.drogon.net/wiringPi wiringPi.git
$ cd wiringPi.git && ./build
  • Download and compile the main application.

As mentioned earlier, the source code is available at Github. In order to keep the info in this page from going stale, I will be using the branch rpi-0.1.y, which I will not mess with... unless a nasty bug shows up. :) There's more about the code itself in section 3.

$ cd ; # not really important where, but oclock.service may need to be edited
$ git clone -b rpi-0.1.y https://github.com/flavio-fernandes/oclock oclock.git
$ cd oclock.git && make

With that finished, you are ready to rumble! The executable is called oclock (short for office clock):

  ~/oclock.git/oclock

As you will soon see, it is more than a clock. I must confess I'm not good with names. :) The application provides a simple way of displaying any kind of information and images. Since access to GPIO pins is privileged, the Makefile will make that executable owned by root and set the sticky bit.

There are a few files that you may want to modify, depending on the GPIO pins you end up using. I will cover that as I visit each component of the code, in the code navigation section.

Section 2: Running Oclock

It is easy to make the application start automatically. More on that is explained in the section below (called Making a Systemd Service). Once it is running -- either by invoking ~/oclock.git/oclock or starting the linux service -- you should be able to interact with the display as I show in this section.

Perhaps one of easiest ways to see what you can do is by running the stickManAnimation script:

stickMenAnimation

$ # start program in background if you have not already done so
$ ~/oclock.git/oclock &

$ # add some people to the dance floor :)
$ ~/oclock.git/misc/stickManAnimation.sh

$ # you can stop animation by typing ^C (CTRL-C)

$ # if you started it above, you can stop oclock program by running killall
$ killall oclock

Another simple way is by using the browser and connecting to the server running in the RPI, as shown here:

http://RPI_ADDRESS/msgMode

helloMsgMode

You can control the display from a shell prompt. For an example on how that is done, check out these commands:

$ RPI='192.168.2.236' ; # change this with the ip address of your rpi, or "localhost" if doing it from the rpi
$ MSG='hello world'   ; # say hello
$ TIMEOUT=10          ; # in seconds. 0 means forever
$ X=10                ; # X coordinate in the display
$ curl --request POST "http://${RPI}/msgMode" --data \
     "msg=${MSG}&noScroll=1&bounce=1&timeout=${TIMEOUT}&x=${X}"

There are more knobs you can tweak than the example above, but that should give you an idea of how to make it go.

The Message Mode of the Led Matrix Display

As mentioned earlier, while in message mode there 3 parts that you can tweak:

  1. Main Message
  2. Background Messages
  3. Background Images

The code that serves the pages that provide these options is in webHandlerInternal.cpp. The code that handles the options and makes it happen is in displayInternal.cpp.

Here's a bit about each one of the 3 parts mentioned above.

Main Message

http://${RPI}/msgMode

This represents the main text you want to be displayed in the screen. When it comes to attributes, the sky's the limit on what you can implement, but these are the ones I started with:

  • msg: what characters to display
  • font: font to use for the message text. That is enumerated here
  • alternateFont: as the text scrolls (or bounce) out, change the font used (boolean)
  • confetti: use to sprinkle random dots around the display. The biggest the value, the more dots (integer)
  • bounce: indicate whether text has the jumping effect (boolean)
  • noScroll: indicate whether text moves like a stock market ticker effect (boolean)
  • blink: make it flash (boolean)
  • color: the matrix display has leds that can go green and red. By turning both colors on we get yellow. As the text scrolls or bounce, you can make the color change by selecting alternate. That is enumerated here
  • scroll repeats: similar to timeout, this gives a way of leaving message mode after the message scrolled by a finite number of times (integer)
  • timeout: number of seconds until text is to end (can be infinite if you set it to 0)
  • x and y: zero-based coordinates of where text will start from. You can use negative values if you want to crop from the left (or top)

Background Messages and Images

http://${RPI}/msgBackground
http://${RPI}/imgBackground

While in message mode, you can also stamp text and images around the display. While there are separate URLs for the text (i.e. message) and the image, they are very similar in terms of the parameters you would provide:

  • index: a zero-based value to represent the text/image you are setting (integer)
  • msg or imgArt : what characters to display (background message) or what to draw (background image)
  • enabled: set this to false to not display the provided text/image index (boolean)
  • clear all: set this to true and all other text/image entries will be disabled and cleared (boolean)
  • color: red, green or yellow. That is enumerated here
  • font: font to use for the message text. This does not apply to image. That is enumerated here
  • x and y: zero-based coordinates of where text/image will start from. You can use negative values if you want to crop from the left (or top)
  • animation: see below.

One interesting caveat here is that these are only visible when display is in message mode, and that is controlled by the main message url mentioned above. Therefore, it is possible that you may want to set message mode with an empty msg attribute, so the display provides you with a clean canvas to draw on. The stickMan animation example does exactly that while taking advantage of the confetti behavior.

Animation: Speed, Number of Frames and Frame ID.

To animate the background msg and images, I came up with the idea of using multiple indexes that can be orchestrated using 3 parameters. Let's say I want an animation that says: "this", then "is" and then "fun". I would like it to rotate every 500 milliseconds, and then wait an extra 500ms before starting over. With that in mind, the parameters would look like this:

  • index: 10 msg: this animationStep: 500ms animationPhase: 4 animationPhaseValue: 0
  • index: 11 msg: is animationStep: 500ms animationPhase: 4 animationPhaseValue: 1
  • index: 12 msg: fun animationStep: 500ms animationPhase: 4 animationPhaseValue: 2

The index -- as long as it's unique and within the number of supported indexes -- is not important. Animation step is an enumerated type used to represent the frame rate. In this case, 500ms is actually the value 3. Animation phase is the number of entries you want in the animation (i.e. number of frames). Since we need to introduce a 'blank' frame in the animation, use the value of 4 (3 messages plus an empty phase value). Then, we use animationPhaseValue to indicate when each of the entries provided are to be shown in the animation (i.e. frame id).

These commands would do that trick:

RPI='192.168.2.236' ; # change this with the ip address of your rpi, or "localhost" if doing it from the rpi
urlBase="http://${RPI}:80"
urlImgBg="${urlBase}/imgBackground"
urlMsgBg="${urlBase}/msgBackground"
urlMsgMode="${urlBase}/msgMode"

STEP=3  ; # 500ms
PHASE=4 ; # four frames of animation

curl --request POST  ${urlMsgBg}  --data 'clearAll=1'
curl --request POST  ${urlMsgMode} --data 'timeout=120'  ; # enter message mode for 2 minutes

ANIM_COMMON="enable=y&animationStep=${STEP}&animationPhase=${PHASE}"

IDX=10; ANIM_VALUE=0
curl --request POST  ${urlMsgBg}  --data "${ANIM_COMMON}&index=${IDX}&animationPhaseValue=${ANIM_VALUE}&msg=this"

IDX=$((IDX + 1)); ANIM_VALUE=1
curl --request POST  ${urlMsgBg}  --data "${ANIM_COMMON}&index=${IDX}&animationPhaseValue=${ANIM_VALUE}&msg=is"

IDX=$((IDX + 1)); ANIM_VALUE=2
curl --request POST  ${urlMsgBg}  --data "${ANIM_COMMON}&index=${IDX}&animationPhaseValue=${ANIM_VALUE}&msg=fun"

So, if we wanted to have an extra 500ms delay between each animation cycle, all that is needed would be to use PHASE=5. This implementation allows for a lot of variations when doing animations. To have some more fun, try playing with the background images instead of messages. The main difference being the use of imgArt instead of msg. That is a zero-based list that can be found in this file.

Postman

A handy tool for interacting with a device that responds to HTTP requests is called Postman. I use it as an efficient way of testing various attributes and values for this program. What is also great is that we can easily share a group of HTTP requests as a postman collection. If you've never used Postman, give it a try! Here is a collection you can use for interacting with the oclock application:

https://www.getpostman.com/collections/f3117dd8a2924ede21cb

You can also import from the collection and environment files I added to the Github repo, under the misc directory. Just make sure that rpiAddr and rpiPort are correct for your postman environment and you will be good to go.

Yet another way for getting this collection is by pressing on the orange looking button here:

Run in Postman

Making a Systemd Service

To make this application start automatically upon system boot, do the following steps to make it known by systemd. First, edit the file oclock.service so that the lines that read ExecStart=.../oclock and WorkingDirectory= are correct for your RPI. Once that is done, copy it to the proper place and enable the service:

$ nano ./misc/oclock.service
$ sudo cp misc/oclock.service /lib/systemd/system/
$ sudo systemctl enable oclock.service

You can start and check the status of the service with these commands:

$ sudo systemctl start oclock.service
$ sudo systemctl status oclock.service

You can always check if the server is running properly by sending some http requests to it:

$ wget -O - -q -4 --spider http://localhost
$ wget -q -O - http://localhost:80/status
$ curl -X GET -o /dev/null -sL -w "%{http_code}" http://localhost 2>&1  ; echo ""

To stop application, any of these would work:

$ wget -q -O - http://localhost/stop
$ sudo systemctl stop oclock.service

If you want to stop it from starting automatically, disable it:

$ sudo systemctl disable oclock.service

Section 3: Code Navigation

Oclock code is written in C and C++11. Python would have been a good candidate, but I started off with the need for porting some libraries in C++ and decided to keep the language somewhat homogenous. Coming from my past experiences on developing for the Arduino platform, I am blown away with the level of stability, speed, and memory of the RPI. Having the ability to attach a debugger to the running process is priceless. While I had some initial success in using Valgrind, I ran into a brick wall due to a known issue. Overall, I still find the RPI to be awesome in handling the GPIO pins and giving me a rock solid TCP/IP stack. Definitely a keeper. ;)

Led Matrix Display

A bigger chunk of the work was the porting of libraries from Arduino to the RPI. The code I used to handle the lower levels of the led matrix comes from the HT1632 for Arduino repo. An issue while doing the port had to do with the bit shift operator (i.e. >>). For some reason, the Arduino CPU is okay with shifting values by negative values, like VAR = 0x123 >> -2. RPI does not give a compiler warning, yet a very different result when performing that operation. Even though I pushed the changes into the Arduino repo, I kept the HT1632 code embedded in the oclock as well, under the ht1632 directory. The interface exposed by HT1632 is pretty simple. I would love to add more to that someday. Something like "drawLine(...)", "drawRectangle(...)" and "drawCircle(...)" could be useful. I have that on my todo list, but not at a high priority.

If you are using fewer or more than 4 bicolor displays, all you need to change is the NUM_OF_BICOLOR_UNITS value. The place where you specify what GPIO pins are used for controlling the HT1632 is located in the display.cpp file.

If you look at the code, you will see that the HT1632 object is instantiated and handled by the Display class, also in the display.cpp file. There is a dedicated thread which is constantly monitoring what changes need to be done to HT1632. While in message mode, every fast tick will cause the main message and all the background images/messages to be drawn. If that is not happening fast enough for you, all that needs to be tweaked is the timerTick::millisPerTick value. The speed of the RPI -- even with the 'little' Pi zero -- together with the "vast" amounts of memory to hold the shadow space for the 4 bicolor displays was a great upgrade from using Arduino boards.

Analog to Digital Converter (ADC)

As mentioned in part 1 of this blog, the RPI does not have a native way of reading analog values, which is something I needed for fetching the brightness level of the light sensor.

Ladyada has an awesome video on youtube that talks about ways of working around this limitation. For the fun of it, I connected the photo resistor to an MCP3002. Thanks to great resources, it was not hard to write a little C++ class to abstract the reading of the 10-bit value from that chip. While I'm aware that there are already other implementations for doing this (see wiringPi MCP3002), I opted for a C++ implementation that did not require the use of hardware SPI pins. Aside from it being a lot of fun to write, of course. :) The result of that was the creation of the mcp300x repo, which is small enough to make me go ahead and simply duplicate it under the oclock codebase as well. That is under the mcp300x directory and to be fair, I needed to add an extra knob for using it in the oclock context. Specifically, I wanted a way of coordinating among all the threads of the program that use the GPIOs. That is likely a non-issue, but for debugging sake it is good to ensure access atomicity among the bit bangers: HT1632, LPD8806, and the MCP3002. Thus, I extended them to acquire a lock before they did any banging on the GPIO pins. That mutex is provided to MCP300x upon its constructor, in the file lightSensor.cpp.

To diminish the jitter in the values read by the MCP3002, the light sensor code keeps the last N reads from a periodic interval. It then computes an average from that history on demand.

The place where you specify what GPIO pins are used for controlling the MCP3002 is located in the lightSensor.cpp file.

Embedded Web Server

In my quest for a web server code with a clean and small footprint -- yet powerful enough to handle multiple connections -- I began browsing Github. A quote I really like from "Made to Stick" fits nicely in this context:

"Don't think outside the box, go box shopping," (Heath).

I found many good candidates but settled with abhinavsingh/pulsar due to a few reasons -- which reminded me of rule 7a of RFC 1925

  • Small, but not too small
  • Easy to embed
  • Robust
  • Written in C (C++ is fine, too)

Porting it over was trivial. After addressing a couple of minor issues, I had that running within the oclock process. As it is written to be event driven, it spawns a few worker threads that handle http requests in parallel. The working model is common: each worker thread registers itself as a callback handler for the one listening socket created by the server thread. There are lots of good documentations on libevent, which made this server even easier to understand. The worker thread executes a function called handleRequest, which integrates with the rest of the C++ code, called WebHandlerInternal. Critical sections of that code are protected by using a mutex or a mailbox-like message queue.

There are just a few parameters you can tweak:

  • Number of worker threads
  • Server's tcp port
  • Verbosity
  • Logfile

Look here to see the parameters you can pass into it without having to recompile the program. Otherwise, just edit conf.h and pulsar.c to make some more permanent tweaks.

To keep it self-contained but yet embedded in the oclock program, I added the Pulsar codebase in a directory by itself. Not much more to say on that... it just works perfectly! Big thanks to Abhinav for putting that code together. And to libevent as well. :)

Led Strip

Another porting I did was to use the code that Ladyada and PaintYourDragon wrote to control a strip of RGB leds. That code -- adafruit/LPD8806 in Github -- was implemented to run in 2 different ways: using hardware SPI or using any GPIO pins. Once again I decided to go the any GPIO route since it is plenty fast and gives us the freedom to use any pair of GPIO pins (clock and data). As I studied that code, I made some enhancements to not "bit bang" all the pixels on every refresh. Instead, I added a variable that keeps tabs on the largest pixel referenced since the last refresh (called LPD8806::show()). The API is even simpler than the one used by HT1632, so it was no big deal getting that working. In fact, I spent more time playing with the RGB animations than actually coding this stuff. ;)

A thread in the program is allocated to control the entire strip. It ticks off on its own timer and will bit bang all the 3 bytes per pixel -- on all 240 pixels -- within a millisecond. It can do that non-stop without breaking a sweat (no hardware SPI involved).

Like I mentioned before, the LPD8806 code is what does the real work here, and that has been embedded under the its own directory. The C++ wrapper code that runs the ledStrip thread and 'owns' LPD8806 is located in the file... you guessed it: ledStrip.cpp. ;) If you need to use different GPIO pins, look no further than lines 16 and 17 of that file. We also specify the total number of LEDs in the strip in there (line 15). Look at this Github commit for the stepping stones on how to add a new animation.

Threads

Using threads, I separated the different functionalities of this program into their own corner:

  • Master timer tick
  • Led matrix display
  • Led strip
  • Light sensor
  • Motion sensor
  • Web server

These threads are known and dispatched by the main() function by using the threadsMain.h file, using a common start function.

In order to share a common timer, we have the master timer tick thread which provides a registry API for all the threads that need to do something at a given interval. Special care is needed to stop the timer tick last, so all threads can exit gracefully upon receiving the terminate message. That final message is sent after the pulsar server stops running; due to a signal trap. Such signal may come externally, or artificially generated when handling the stop URL (server_stop function is here).

Another category of functionality -- for the lack of a better word -- is the inbox. Through that, all threads can asynchronously queue messages for each other, or broadcast whatever it deems important. The motion sensor thread, for instance, uses the mailbox to broadcast situations when motion detection starts or stops.

That is pretty much all there is to the code for making oclock do its thing! My hope is that folks will not have trouble using/changing it for whatever purposes they can think of.

Future enhancements

Led Strip Encoding

Probably on the top of my list is the addition of a parser that can take a string of values to represent a group of pixels and colors for the led strip. I already made the html form able to provide that, but the code behind it is still absent. Sorry! A step beyond this goal is to have another set of values to indicate when the pixels should become bright and dim in a loop on their own; like a heartbeat. And blink. Adding animations is an endless and big ball of fun. :)

Sound

I did not get around to that yet, but we can easily add another thread in the program to handle requests for playing mp3s or text synthesizer. With the RPI, this is actually very easy to do, thanks to the helpful blogs available, like this one.

More Images and Fonts

Get creative and add more fonts and images! Gaurav wrote some nice javascript that generates the binary value that represents the pixels selected. I made a convenient place in my server where you can just use it.

ledImageDraw

Start by deciding on the size. Then, pixel dot away to your heart's content. Once done, you can simply paste the generated string into a header file in the ht1632 directory, tweak a few lines of the display code and recompile the program. As a concrete example, look at the Github commit, where I added the stick man drawings.

Connecting from the Clouds

In a nutshell, the oclock program's purpose in life is to provide a "dumb" interface that lets smarter people/devices control the display and the led strip. Thus, showing the time and date is just a side job, really. ;) These smart people/devices can delegate the details of how the display works and get what she/he/it needs by using simple http requests. I have actually written a controller program for doing that in Erlang, but it was for a different "dumb" device. Playing with one for the oclock will be another blast of fun.

There is also room for having the oclock program integrate with MQTT and send notifications, like motion detected. By subscribing to MQTT, we can offer an alternate way of controlling the display too. Using Adafruit.io would be a nice and easy way to get going on that.

Final thoughts

Hopefully, this was useful and will inspire you to build an awesome "Office clock"; much better than what I got. If I can clarify something that was not well described, don't be shy in reaching out to me. There are lots of great resources out there for learning on how to DIY. Check out Adafruit's learn and Adafruit Pi Zero Contest videos, Hackaday, and Collins Lab to name a very few.

Enjoy!


Comments

comments powered by Disqus