Raspberry PI Pixel Art Animation Display
by feiticeir0 in Circuits > Raspberry Pi
7786 Views, 77 Favorites, 0 Comments
Raspberry PI Pixel Art Animation Display
While browsing Adafruit's Thingiverse designs, one caught my attention - 32x32 Pixel Display .
I always love those RGB Matrix displays, and having one just to display some pop culture images and animations, was an awesome idea.
This project takes the idea from Adafruit, but uses a Raspberry PI Zero W instead of a Feather RP2040 and Python programming language.
I used the 3D files from Adafruit (you can get them all from the Thingiverse thing page):
- cover.stl
- foot.stl x2
- frame.stl
- grid.stl
to build the display and support.
Because the frame was created for a Feather, I've create an adapter for the Raspberry PI Zero W . The STL file is here to download - rpi_frame_support.stl
Downloads
Supplies
Install Raspberry PI OS
First, let's install the Raspberry PI OS.
You need an SD Card. 8GB is enough, but those are getting rare, so a 16GB will suffice.
I'm going to use the Raspberry PI Imager . It's an excellent software and allows not only to install Raspberry PI OS, but other img files as well.
Just execute the imager.
Choose the operating system. We just need the Raspberry PI OS 32bit Lite version. No GUI is necessary.
Next, choose the storage.
In this news version, we can - supposedly - customize some things in the image before we write it. I've tried, and it didn't work.
After choosing the storage, just write it.
After a while, it gives the message that the image has been writen.
Remove the drive and insert it again. A new drive should have appeared in the windows (or Mac finder or Linux) explorer.
Headless configuration
Because we're using a Raspberry PI Zero W, to access it, it's going to be by wireless. There's a way to configure the Raspberry PI OS before the first boot - to make it connect automatically to a wireless network and have the SSH service started.
SSH
To activate SSH, just create an empty file named ssh in the drive
Windows PowerShell
New-Item ssh
Linux bash or MacOS
touch ssh
And just like that, when booting, the SSH service will be started.
Wireless
To be able to connect, we need to create and configure the wpa_supplicant.conf file.
Still in the boot partition, create a new file named wpa_supplicant.conf . Edit the file and put the following contents (this is an example to connect to a wpa protected network):
country=<your_country_2_letter_code> update_config=1 ctrl_interface=/var/run/wpa_supplicant network={ ssid="<your_ssid>" psk="<your_password>" proto=RSN key_mgmt=WPA-PSK pairwise=CCMP auth_alg=OPEN }
Save and close it.
This way, on the first boot, you'll be able to ssh to it - just need to find your Raspberry PI on your network.
Configure Adafruit's RGB Matrix Bonnet for Raspberry PI
The Adafruit's RGB Matrix Bonnet for Raspberry PI is what enables the Raspberry PI to drive the RGB Matrix.
You can get all the information needed from their learning page.
RGB Matrix in the Raspberry PI uses the Henner Zeller library.
Adafruit used to use their own version of the library (a fork from Henner's one), but now, they use the same.
First, let's update the Raspberry PI SO.
sudo apt-get update sudo apt-get upgrade
Next, install python3-distutils and python3-dev
sudo apt-get install python3-distutils python3-dev
To install the RGB Matrix library, let's use the script from Adafruit for an automated install:
curl https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/rgb-matrix.sh >rgb-matrix.sh sudo bash rgb-matrix.sh
This script will configure some options for the bonnet.
The first decision to make is to choose the Bonnet type. We choose option 1.
Next, quality versus Convenience. We don't need sound, so we choose option 1- Quality
Finally, we're displayed the chosen options before we continue with the install.
It will take a while
After the install, it will ask to reboot - just reboot.
Now that the libraries are installed, we need the Python bindings. Let's install those too.
cd rpi-rgb-led/bindings/python
Now, let's follow the instructions to install the Python3 bindings
Note: We already have python3-dev installed and python3-pillow no longe exists
sudo apt-get install python3-pill make build-python PYTHON=$(command -v python3) sudo make install-python PYTHON=$(command -v python3)
Now that we have all the software installed, let's get the hardware working.
Because I've chosen quality, I need to bridge together GPIO 18 with GPIO 4 of the RGB Matrix Bonnet. After that, everything is pretty much done.
I'm using a 32x32 RGB Matrix and it's supported without any more steps involved. . For Matrixes above, other steps are needed. Refer to their page for more instructions.
Build the Frame
Building the frame is just putting all the elements together.
You need to strip the RGB Matrix to it's bare components - the Matrix. All the housing must go. Take carefull steps removing the screws.
Start by securing the feet to the outer frame with some screws and nuts.
The LED Acrylic.
The black LED Acrylic is not essential, but it does a great effect if used. I'm using the one sold by Adafruit, that diffuses the LED color.
If you don't have one, a black acrylic with some white self-adhesive paper glued to one of the sides also does a great effect. Just use the white side turned to inside, after the grid.
To assemble:
- Insert the RGB matrix into the yellow frame. LEDs facing up.
- Put the grid on top of the LEDs Matrix. Align them both.
- Put the acrylic in the green frame and then insert the yellow frame into the green one
The Yellow frame supports are made for the Feather RP2040. I've created an adapter for the RPI Zero W to fit on the frame. Use same M2.5 screws and nuts.
Now, insert the t the RGB Matrix in the GPIO pins of the RPI Zero W.
The RGB Matrix usually comes with the wires a bit long. You can cut them to make a short patch.
Connect the Matrix power to the terminal blocks. Take notice of the polarity.
Now, connect the RGB Matrix data cable to the Bonnet IDC. Make sure your connecting it to the right socket - the INPUT. If you don't have INPUT written, an arrow might do the trick. Mine has an arrow.
Powering all
Adafruit recommends to power the Bonnet seperatly from the Raspberry PI.
I have them both using the same power, using the barrel jack with 5v.
The Animation Sprites
To display some animations, we need animations.
What are animations ?
Animation is a big, big topic. I'm no animator and my profession is nothing involved with design, arts, cinema, etc.. .
Turning a long story short and very basic, it's just illusion of motion our brains trick us into when we view multiple static images displayed in quick succession .
Pixel Sprites animation
For this, we're going to use sprite sheets. A sprite sheet is almost like a movie strip, It's a long strip of images, with each image representing a frame.
Because our RGB Matrix is 32x32, we're going to need to create a sprite sheet 32 pixels wide, but as long as our animation calls for it - always a multiple of 32.
If our animation is 6 frames, our sprite sheet - an image that we will build - will have 32 pixels width x ( 6 * 32) height.
It's always good to find a sprite sheet with transparent background. Will make our job easier.
Because the Matrix pitch is high (6mm distance of each LED), the images will be kind of pixelated. It's old fashion (or vintage - Can it be vintage already?). I love them - I'm an 80's kid, I grow up with them.
First, we need to - create or find - a sprite sheet.
For our example, let's use the Mario Frog. Just love it. We can download a sprite sheet from pngaaa.com.
The image has many sprites, but I'm just going to use the frog ones and not all. Let's say, we want an animation with 12 frames.
I'm going to use Gimp for this, but you can use which one you like.
On thing is, the image must have black background and be of the type BMP.
The black background is because the RGB Frame is black and the LEDs outside the main object will not need to be turned on - saving power and performance.
Since we want 12 frames, we create an image that will be 32 pixels in width and 384 (12*32) pixels height .
Next, by using Gimp guides, we put an horizontal guide every 32 pixels. That will be the limits of each frame. If you look closely, in the bottom of the gimp image, the pixels are displayed. You know always where you at.
After you have put guides in the image, you need to open the sprite sheet and copy each sprite individualy to each "frame" of the image.
When pasting in Gimp, it will be a new layer. If the layer is bigger than 32x32 (size of each frame square), you need to resize it.
Always resize the layer uniformly (the same amont for both x and y at the same time). Do it until all the sizes are bellow 32 pixels - widht or height, both need to be bellow 32 pixels, but they don't need to be the same, or the image will be askew and look weird.
Always try to center the image in the square, but make sure none leaves the margins.
Do that until you have all the frames you want.
As you can see, the image with all the frames and the guides is not centered, because that frame has a bigger height than width.
After you finish, just save the image. Always as a BMP.
Disclaimer: This animations are made with freely available sprite sheets around the web. I don't own any of the original sprites from where this are taken or reproduced.
Code
The code is nothing more than Python and PIL functions to display the images.
We use the RGB Matrix Python bindings that we've installed in the steps above.
Henner Zeller Github page explains all the options with very good detail, but I'll try to explain the ones I've used in the code. The Python bindings page also shares some more insights into the options.
If the images are not displayed correctly, you need to "toy" with the options until they do. Some Matrices have different RGB LED sequence (the one I've used uses BGR), etc...
Images location
You need to create a directory named images and put all the images in there.
To add new images, just place them in the directory and execute the code again. If you need/want to change the animation times, just add the image name
Code
#!/usr/bin/env python """ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. """ import time from PIL import Image import time import os import sys import glob from rgbmatrix import RGBMatrix, RGBMatrixOptions #definitions sprite_y = 0 matrix_height = 32 # How many times to repeat the animation animations_repeats = 3 # We can customize the durations of each animation # individually here. # animations durations # based on the filename # image-name: animation_time animation_times = { "parrot.bmp": 0.08, "mario_punch.bmp": 0.1, "nyancat.bmp": 0.1, "mario_frog.bmp": 0.1, "megaman.bmp": 0.1, "sonic.bmp": 0.1 } #directory with images location images_location = "images" #configuring the matrix options options = RGBMatrixOptions() options.rows = 32 options.cols = 32 options.hardware_mapping = 'adafruit-hat-pwm' options.multiplexing = 6 options.led_rgb_sequence = 'BGR' matrix = RGBMatrix(options = options) matrix.Clear() try: while True: # first loop - all images inside images location # sort through directory search for files images_sprites = glob.glob(images_location + '/*.bmp') for images in images_sprites: image = Image.open(images).convert('RGB') width, height = image.size for cur_anim in range (animations_repeats): #print ("Curr anim: {}".format(cur_anim)) for sprite_y in range (0, height, 32): cur_sprite = image.crop((0,sprite_y, 32, (sprite_y + 32))) #cur_sprite.thumbnail((32,32), Image.ANTIALIAS) matrix.SetImage(cur_sprite) # wait the defined time in dictionary # for current image # The following line will match the image name # to the time in the dictionary # string slice to remove word images/ from location and will match # the name in the dictionary time.sleep(animation_times[images[7:]]) except KeyboardInterrupt: matrix.Clear() sys.exit(0)
Let's explain it a bit
The first line will allow the script to be executed like a standalone executable without the need to execute Python beforehand .
The next 8 lines will import the required libraries for the script to work.
These next lines will set some variables:
sprite_y will control the current frame in our "frame strip"
matrix_height sets the number of pixels in the matrix height
animations_repeats sets the number of times that each image file is played.
The dictionary animation_times will set the time that each frame is displayed . Just put the image name (with extension) and the time that each frame should be displayed.
animation_times = { "parrot.bmp": 0.08, "mario_punch.bmp": 0.1, "nyancat.bmp": 0.1, "mario_frog.bmp": 0.1, "megaman.bmp": 0.1, "sonic.bmp": 0.1 }
Next, it's the images location - images_location. You can change this if you like. If you change this, will have to change the following line (this is explained bellow).
time.sleep(animation_times[images[7:]])
the number 7 will have to be changed for the number of characters that the directory name plus the back slash / have.
The next lines set the options for the RGB Matrix .
We set the rows and cols to 32 pixels.
We set the hardware mapping to the Adafruit HAT.
The multiplexing parameter it's about the address lines. The HUB75 of the matrices has several lines and letters and some have more - A, B, C, D, E - or less - A, B, C, D and E is just GND.
The following sites explain this very very well. They also mention other apis and "hats" to connect the Matrices to ESP32, Arduinos, etc...
https://wiki.dfrobot.com/32x32_RGB_LED_Matrix_-_4mm_pitch_SKU_DFR0472
https://github.com/2dom/PxMatrix
https://iot-for-maker.blogspot.com/2020/02/led-8-rgb-led-matrix-drive-with-esp.html
#configuring the matrix options options = RGBMatrixOptions() options.rows = 32 options.cols = 32 options.hardware_mapping = 'adafruit-hat-pwm' options.multiplexing = 6 options.led_rgb_sequence = 'BGR' matrix = RGBMatrix(options = options)
Next, we clear the matrix and start animating.
We start a try except block, so, if we start the script on the command line, we can use Ctrl+c to exit.
After that, a while loop so that we keep animating forever.
Next, we search the images directory for files with bmp extension.
images_sprites = glob.glob(images_location + '/*.bmp')
In the next line, we're going to iterate through each image found on the directory
We open each one and convert them to RGB format.
We store the image width and height. The width will always be 32pixels, but the height will differ.
image = Image.open(images).convert('RGB') width, height = image.size
Next, another loop - the animation repeats and it's here that we're going to display the frames.
We've load an image that's 32pixels width, but more in the heights. In the beginning, when loading the image, we stored the width and height.
The image that we've loaded contains all the frames. What we're going to do is to crop each "frame". When building the image, we set "boxes" of 32x32 pixels. So, at every 32 pixels height, we have a frame.
In a for loop, for a range from 0 to height of the image - with 32 pixels each steps, we crop the image for the current frame. Cropping does not changes the original image - it just stores in cur_sprite that cropped part.
cur_sprite = image.crop((0,sprite_y, 32, (sprite_y + 32)))
The crop function accepts 4 coordinates - top left x and y and bottom right x and y
After that, we display the image in the Matrix
matrix.SetImage(cur_sprite)
To give the illusion of animation, we sleep for a determined period of time.
Like we've already explained, the time waited for each frame can be set in the dictionary above.
time.sleep(animation_times[images[7:]])
The line images[7:] will display the image name .
This is string splicing.
The list images has the list of the bmp images, but with the path
ie: images/parrot.bmp
What images[7:] will do, is remove images/ and only leave parrot.bmp - this will match the animation_times dictionary above.
If you change the images location, this line as to be changed as well.
And finally, the except keyword to break the script - if executed in the CLI.
It clears the Matrix and exists.
Finally, to execute, you need sudo because the RGB Matrix libraries need root access to the hardware.
Replace pop_icons.py with your script name. You can also omit the python word, if you give execution permissions to the script.
sudo python ./pop_icons.py
And let the magic begins.