Raspberry PI Pixel Art Animation Display

While browsing Adafruit's Thingiverse designs, one caught my attention – 32×32 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

Supplies

Step 1: 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.

Step 2: 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 32×32 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.

Step 3: 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.

Step 4: 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 32×32, 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 32×32 (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.

Step 5: 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 32×32 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

Source:Raspberry PI Pixel Art Animation Display


About The Author

Muhammad Bilal

I am highly skilled and motivated individual with a Master's degree in Computer Science. I have extensive experience in technical writing and a deep understanding of SEO practices.

Scroll to Top