Raspberry Pi Project Page

Last Updated on November 18 2024

Pi Zero LED Garage Clock


During the Covid-19 lockdown, I built a digital clock for the garage. It uses the scrolling text code that I wrote for the accelerometer.

So how do we go about building a Pi Clock?

We need a Pi Zero WH, which is the smallest footprint Raspberry-Pi you can get. Its tiny, and has two micro USB ports, a micro HDMI port, a microSD card slot, and a ribbon connector to allow connection of a camera if required. Everything about the Pi Zero is micro, and the first challenge is attaching a keyboard, and mouse, when there is only one micro USB port free. One of the two micro USB ports is for the power supply to power the Pi, and you need to provide a 5V feed, with at least 700mA for the Pi to work. So in order to attach all the other the peripherals required to allow us to develop code on the Zero, we need a USB hub.

This is the Pi zero
Pi zero
This is the pHAT fitted to the top of the Pi
Waveshare LED HAT

Operating System

I've installed Raspbian Stretch Lite on to a 16GB MicroSD card, using Win32DiskImager, and the device quickly boots to the graphical interface. As the code is developed, the device will be set to boot to the Command Line Interface (CLI) as it doesnt need to be running the GUI for the Python code to execute. Booting to CLI also speeds up the boot time.

The LED HAT is made by Allo, and their page can be found here https://www.allo.com/sparky/rainbow.html
The accelerometer is made by Analog Devices, and their page can be found here

I chose the following board from Modmypi, available here https://www.modmypi.com/raspberry-pi/sensors-1061/orientationaccelerometers-1067/adafruit-triple-axis-accelerometer-adxl345/?search=345

Programming the RGB LED pHAT to display the time

The fun part has been working out how to write to the pHAT. The RGB Matrix consists of 32 LEDs, which are arranged in a snake like way. With the address of the first pixel being 0, and 31 for the last, you'd think that the matrix would just run right to left, top to bottom. Wrong. Its right to left, but then the line drops down and runs left to right, then down, and right to left etc.



To switch on an LED on the pHAT, we use the following NeoPixel command from our Python program
strip.setPixelColor(x, Color(grn,red,blu))
Where x is the LED number (refer above), and Color is the RGB color, but in GRB order (Dont ask me why)

Installing NeoPixel

To use the NeoPixel library, involved downloading and installing the zip file, which is available from https://github.com/jgarff/rpi_ws281x.

Copy the ZIP file to the Pi, and run
 unzip rpi_ws281x-master.zip 
Install the depended-upon software
 sudo apt-get install build-essential python-dev scons swig 
Compile the program
cd rpi_ws281x-master
sudo scons
Run the line below to perform testing. You can see the RGB LED flickering
sudo ./test
Run the following line to install the python library
cd python 
sudo python setup.py install
Run the demo code for testing
cd examples
sudo python lowlevel.py

SuperUser rights required

Note: You can only control the pHAT LEDs if the python program is run under the SuperUser context. In other words, if you try to run your Python code using the Python2 IDE, it will fail with an error as you dont have the rights required to access the pHAT. So to develop code, use the IDE, but to test the code, you need to have a command shell open, and run the code from the directory where the code sits, using
sudo python programname.py

Scrolling text

To scroll a message, or a bitmap, across the display, we need to define a viewport, of 4 columns wide by 8 rows tall, and move the viewport from left to right, across the block/bitmap.

Say for example, I want the word Ready to move across the display.

In Python I have defined a block of 32 columns by 8 rows, and the 1's and 0's in the block, determine if the LEDs are on or off on the LED matrix. If you look at the example carefully, you'll see that it shows the word 'Ready'
row1="11110000000000000000010000000000"
row2="10001000000000000000010000000000"
row3="10001001110001100011010100010000"
row4="11111010001000010100110100010000"
row5="10100011111001110100010011110000"
row6="10010010000010010100010000010000"
row7="10001001110001110011110011110000"
row8="00000000000000000000000000000000"
Actually, as its not that easy to see the word Ready in the example, I'll replace the zero's with spaces, and then you'll see what I mean.
row1="1111                 1          "
row2="1   1                1          "
row3="1   1  111   11   11 1 1   1    "
row4="11111 1   1    1 1  11 1   1    "
row5="1 1   11111  111 1   1  1111    "
row6="1  1  1     1  1 1   1     1    "
row7="1   1  111   111  1111  1111    "
row8="                                "
If we take four characters from the first row, row1, and four characters from the second row, row2, and so on, and use the 1 or 0 to switch on or off the LEDs in their corresponding rows in the pHAT, it will make the majority of the letter 'R'.

1111
1
1
1111
1 1
1  1
1
We then move the view port to the right by one character, to take the four characters, starting from the 2nd character of row1, row2, etc.
111  
   1
   1
1111
 1  
  1 
   1
    
And this is how we give the effect of scrolling text. There needs to be a delay between each transition, otherwise the bitmap would flash across the display so fast you wouldnt be able to read it. I've chosen 0.065seconds at present, and this gives a nice smooth scroll, but not too fast to make the text unreadable. The bitmap can of course be any length, I've just chosen 32 columns as an example.

When we write the bitmap to the screen, we have the ability to choose individual pixel colours, but for simplicity, I just choose a single color, and all the LEDs then use that. Here is the code I wrote to scroll the time across the 4x8 matrix. It gets the system time, works out the bitmap required to represent the value of each digit of the 4 digit clock, and compiles the strings in to 8 rows, before passing them to the scrolling text routine.
# !/usr/bin/env python3
# 
# Author: Graham Blackwell (@zetecinsidecom)
#
# Clock 9th May 2020

import datetime, time
from neopixel import *

# LED strip configuration:
LED_COUNT      = 32      # Number of LED pixels.
LED_PIN        = 18      # GPIO pin connected to the pixels (18 uses PWM!).
#LED_PIN        = 10      # GPIO pin connected to the pixels (10 uses SPI /dev/spidev0.0).
LED_FREQ_HZ    = 800000  # LED signal frequency in hertz (usually 800khz)
LED_DMA        = 10      # DMA channel to use for generating signal (try 10)
LED_BRIGHTNESS = 25      # Set to 0 for darkest and 255 for brightest
LED_INVERT     = False   # True to invert the signal (when using NPN transistor level shift)
LED_CHANNEL    = 0       # set to '1' for GPIOs 13, 19, 41, 45 or 53

def scroller(txtLength,sDelay,grn,red,blu): #string len, miliseconds delay between scroll, grn, red, blu values
    for k in range(0,txtLength): 
        for i in range (0,4):
            if row1[k+i]=="1":
                strip.setPixelColor(3-i, Color(grn,red,blu))
            else:
                strip.setPixelColor(3-i, rgbblank) #GRB Blank
        for i in range (0,4):
            if row2[k+i]=="1":
                strip.setPixelColor(4+i, Color(grn,red,blu))
            else:
                strip.setPixelColor(4+i, rgbblank) #GRB Blank
        for i in range (0,4):
            if row3[k+i]=="1":
                strip.setPixelColor(11-i, Color(grn,red,blu))
            else:
                strip.setPixelColor(11-i, rgbblank) #GRB Blank
        for i in range (0,4):
            if row4[k+i]=="1":
                strip.setPixelColor(12+i, Color(grn,red,blu))
            else:
                strip.setPixelColor(12+i, rgbblank) #GRB Blank
        for i in range (0,4):
            if row5[k+i]=="1":
                strip.setPixelColor(19-i, Color(grn,red,blu))
            else:
                strip.setPixelColor(19-i, rgbblank) #GRB Blank
        for i in range (0,4):
            if row6[k+i]=="1":
                strip.setPixelColor(20+i, Color(grn,red,blu))
            else:
                strip.setPixelColor(20+i, rgbblank) #GRB Blank
        for i in range (0,4):
            if row7[k+i]=="1":
                strip.setPixelColor(27-i, Color(grn,red,blu))
            else:
                strip.setPixelColor(27-i, rgbblank) #GRB Blank
        for i in range (0,4):
            if row8[k+i]=="1":
                strip.setPixelColor(28+i, Color(grn,red,blu))
            else:
                strip.setPixelColor(28+i, rgbblank) #GRB Blank
        strip.show()
        time.sleep(sDelay)

# Define functions which animate LEDs in various ways.
def colorWipe(strip, color, wait_ms=50):
    # Wipe color across display a pixel at a time
    for i in range(strip.numPixels()):
        strip.setPixelColor(i, color)
    strip.show()    
    
# Create NeoPixel object with appropriate configuration.
strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS, LED_CHANNEL)
# Intialize the library (must be called once before other functions).
strip.begin()

#     0123456789:
num1="0011100001000011100011100001100111110001100111110011100011100000000000000000000000"
num2="0100110011000100010100010010100100000010000000010100010100010000000000000000000000"
num3="0101010101000000010000010010100111100100000000100100010100010000000000000000000000"
num4="0101010001000000100001100100100000010111100001000011100011110010100000000000000000"
num5="0101010001000001000000010111110000010100010010000100010000010000000000000000000000"
num6="0110010001000010000100010000100100010100010010000100010000100010100000000000000000"
num7="0011100111110111110011100000100011100011100010000011100011000000000000000000000000"
num8="0000000000000000000000000000000000000000000000000000000000000000000000000000000000"

oldminute=0

dig1=0
dig2=0
dig3=0
dig4=0

rgbblank=Color(0,0,0) #set the blank (background) color
    
while True:
    currentDT = datetime.datetime.now()
    hour = currentDT.hour
    minute = currentDT.minute
    if minute <> oldminute:
        oldminute=minute
        txtHour = str(hour) #convert int to string
        if hour >9:
            dig1=int(txtHour[0:1])
            dig2=int(txtHour[1:2])
        if hour<10:
            dig1=0
            dig2=int(txtHour[0:1])
        txtMin = str(minute)
        if minute>9:
            dig3=int(txtMin[0:1])
            dig4=int(txtMin[1:2])
        if minute<10:
            dig3=0
            dig4=int(txtMin[0:1])
 
#now we know the digits, we can construct the strings
        row1="0000"+num1[dig1*6:dig1*6+6]+num1[dig2*6:dig2*6+6]+num1[dig3*6:dig3*6+6]+num1[dig4*6:dig4*6+6]+"0000"
        row2="0000"+num2[dig1*6:dig1*6+6]+num2[dig2*6:dig2*6+6]+num2[dig3*6:dig3*6+6]+num2[dig4*6:dig4*6+6]+"0000"
        row3="0000"+num3[dig1*6:dig1*6+6]+num3[dig2*6:dig2*6+6]+num3[dig3*6:dig3*6+6]+num3[dig4*6:dig4*6+6]+"0000"
        row4="0000"+num4[dig1*6:dig1*6+6]+num4[dig2*6:dig2*6+6]+num4[dig3*6:dig3*6+6]+num4[dig4*6:dig4*6+6]+"0000"
        row5="0000"+num5[dig1*6:dig1*6+6]+num5[dig2*6:dig2*6+6]+num5[dig3*6:dig3*6+6]+num5[dig4*6:dig4*6+6]+"0000"
        row6="0000"+num6[dig1*6:dig1*6+6]+num6[dig2*6:dig2*6+6]+num6[dig3*6:dig3*6+6]+num6[dig4*6:dig4*6+6]+"0000"
        row7="0000"+num7[dig1*6:dig1*6+6]+num7[dig2*6:dig2*6+6]+num7[dig3*6:dig3*6+6]+num7[dig4*6:dig4*6+6]+"0000"
        row8="0000"+num8[dig1*6:dig1*6+6]+num8[dig2*6:dig2*6+6]+num8[dig3*6:dig3*6+6]+num8[dig4*6:dig4*6+6]+"0000"

    scroller(28,0.15,255,0,0) #28 characters and 0.15s between each scroll, in green