Raspberry Pi Project Page - Pi Garage ClockLast on May 13 2020
Pi 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
This is the pHAT fitted to the top of the Pi
Operating SystemI'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 timeThe 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 NeoPixelTo 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.zipInstall the depended-upon software
sudo apt-get install build-essential python-dev scons swigCompile the program
cd rpi_ws281x-master sudo sconsRun the line below to perform testing. You can see the RGB LED flickering
sudo ./testRun the following line to install the python library
cd python sudo python setup.py installRun the demo code for testing
cd examples sudo python lowlevel.py
SuperUser rights requiredNote: 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 textTo 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 1We 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 1And 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