The use of RGB LED Matrix panels has become popular in recent years. Are you one of the enthusiasts of these panels? Are you looking to add a lot of color to your project? These massive RGB LED Matrix panels are an awesome place to start. You can create animations, games or all sorts of other fun displays with them. On top of all that, thanks to two IDC connectors, and a seamless frame, these panels can be daisy chained together to form even bigger LED displays. In this tutorial, we will learn how to use these panels. This tutorial consists of 7 sections. Stay with me.
How?As it mentioned, In this tutorial, we will learn how to set up these panels. Now, How to connect one of these panels to the Raspberry Pi? According to Henner Zeller tutorial's, Connecting a panel requires 16 connections, which will make it harder. Look at the below picture! Connecting these wires is very confusing.
The best way to get rid of these wires, Use an alternative Drive Board. The RGB Matrix Panel Drive Board is designed by ElectroDragon team Based on the active adapter board of Henner Zeller RPI matrix.
Features:
- Very cheap, only $2.5!
- Support up to three port output to drive, P0, P1 and P2 (HUB75).
- Support Raspberry Pi 2 and 3 & Zero, most pins used for matrix driving.
- Support E-line select pins (For 64x64 RGB Matrix Panel).
- On board four logic buffers74HCT245. Extra on board RTC DS1307 via I2C interface, can't be used on same time with P3 port, select by switch.
- Extra on board AT24C256 EEPROM, 256K memory on alternative I2C 25 and 26 pins. Raspberry PI 3 can NOT support alternative I2C interface, so can only is used Raspberry Pi 2.
- Full compatible with hzeller adapter board.
After installing the ElectroDragon Drive Board on the Raspberry Pi, This should be noted: A 5V power supply is also required, for power the matrix it self, the Pi cannot do it, to calculate the power, multiply the width of all the chained matrices* 0.12 Amps: A 32 pixel wide matrix can end up drawing 32*0.12 = 3.85A so pickup a 5V 4A power supply. LED matrix panels require 5V power and a lot of it! 5V 2A at a minimum and you can easily need a 5V 4A or 5V 10A supply for big stretches of panels!
Each matrix has 64 pixels (16x32 or 32x32 panels) or 128 pixels (for the 32x64 panels) lit at one time. Each pixel can draw up to 0.06 Amps each if on full white. The total max per panel is thus 64 * 0.06 = 3.95 Amps or 128 * 0.06 =7.68 Amps.That's if all the LEDs are on at once, which is not likely - but still, it's good to have at least half for the power supply in case you get bright.
Install the libraryIn this section, we will briefly explain how to install the hzeller Library. According to hzeller recommendation: The Raspbian Lite distribution is recommended.
Install Prerequisites
sudo apt-get install -y --force-yespython2.7-dev python-pillow python3-dev python3-pillow libgraphicsmagick++-devlibwebp-dev
Install library
git clone https://github.com/hzeller/rpi-rgb-led-matrix.git
cd rpi-rgb-led-matrix
make all
make build-python
make install-python
Enable I2C for RTCFor enable I2C in Raspberry Pi, Use the below tutorial:
https://learn.sparkfun.com/tutorials/raspberry-pi-spi-and-i2c-tutorial#i2c-on-pi
Install matrix panel to RPiThere are various types of displays that come all with the same Hub75 connector. They vary in the way the multiplexing is happening. To connect the matrix panel to the Drive Board, it needs an IDC cable. These can be chained by connecting the output of one panel to the input of the next panel. You can chain quite a few together.
The 64x64 matrix's typically come in two kinds: with 5 address lines (A, B, C, D, E), or(A, B). So-called 'outdoor panels' are typically brighter and allow for faster refresh-rate for the same size, but do some multiplexing internally of which there are a few types out there; they can be chosen with the --led-multiplexing
parameter.
Generally, the higher scan-rate (e.g. 1:8), a.k.a. outdoor panels generally allow faster refresh rate, but you might need to figure out the multiplexing mapping if one of the three provided does not work.
As you can see in the below picture, you can only connect 3 matrix panels in rows to Drive Board. (This is due to the limitations of the Raspberry Pi GPIOs.)
If you connect more than one matrix panel, In this case it is necessary run the examples with the parameter, see hzeller description.
In this tutorial, I connect two matrix panels 32x32 and 64x64 (separately) to Drive Board. If the address lines are less than 5, No changes are required. (For example: 32x32 and smaller panels). But as mentioned, for 64x64 panels, Pin 8 should be connected to pin E on Drive Board.
You have several choices to run the examples:
Also, the utils directory is meant for ready utilities to show images or animated gifs or videos. The best choice is C, C++ language, which has a high execution speed.
Example #1: Scroll News with PythonIn this example, the news (RSS feed) of a site is downloaded and displayed on the matrix panel. This example is written in Python. To run, you must transfer three files (One of the files is a ttf font that is attached at the bottom) to the Python samples folder.
scroll.py
file :
#!/usr/bin/env python
# -*- encoding:utf8 -*-
# By: Ramin Sangesari
import time
import argparse
import sys
import os
import random
import feedparser
from PIL import Image
from PIL import ImageFont
from PIL import Image
from PIL import ImageDraw
from logging import getLogger, StreamHandler, DEBUG
logger = getLogger(__name__)
handler = StreamHandler()
handler.setLevel(DEBUG)
logger.setLevel(DEBUG)
logger.addHandler(handler)
logger.propagate = False
sys.path.append(os.path.abspath(os.path.dirname(__file__) + '/..'))
from rgbmatrix import RGBMatrix, RGBMatrixOptions
def run(image, matrix):
print("Running...")
image.resize((matrix.width, matrix.height), Image.ANTIALIAS)
double_buffer = matrix.CreateFrameCanvas()
img_width, img_height = image.size
xpos = 0
while True:
xpos += 1
if (xpos > img_width):
xpos = 0
break
double_buffer.SetImage(image, -xpos)
double_buffer.SetImage(image, -xpos + img_width)
double_buffer = matrix.SwapOnVSync(double_buffer)
time.sleep(0.04)
def prepareMatrix(parser):
args = parser.parse_args()
options = RGBMatrixOptions()
if args.led_gpio_mapping != None:
options.hardware_mapping = args.led_gpio_mapping
options.rows = args.led_rows
options.cols = args.led_cols
options.chain_length = args.led_chain
options.parallel = args.led_parallel
options.pwm_bits = args.led_pwm_bits
options.brightness = args.led_brightness
options.pwm_lsb_nanoseconds = args.led_pwm_lsb_nanoseconds
options.multiplexing = args.led_multiplexing
if args.led_show_refresh:
options.show_refresh_rate = 1
if args.led_slowdown_gpio != None:
options.gpio_slowdown = args.led_slowdown_gpio
if args.led_no_hardware_pulse:
options.disable_hardware_pulsing = True
return RGBMatrix(options = options)
def getImageFromFile(path):
image = Image.open(path).convert('RGB')
return image
parser = argparse.ArgumentParser()
parser.add_argument("-r", "--led-rows", action="store", help="Display rows. 16 for 16x32, 32 for 32x32. Default: 32", default=32, type=int)
parser.add_argument("-t", "--led-cols", action="store", help="Display rows. 16 for 16x32, 32 for 32x32. Default: 32", default=32, type=int)
parser.add_argument("-c", "--led-chain", action="store", help="Daisy-chained boards. Default: 1.", default=1, type=int)
parser.add_argument("-P", "--led-parallel", action="store", help="For Plus-models or RPi2: parallel chains. 1..3. Default: 1", default=1, type=int)
parser.add_argument("-p", "--led-pwm-bits", action="store", help="Bits used for PWM. Something between 1..11. Default: 11", default=11, type=int)
parser.add_argument("-b", "--led-brightness", action="store", help="Sets brightness level. Default: 100. Range: 1..100", default=10, type=int)
parser.add_argument("-m", "--led-gpio-mapping", help="Hardware Mapping: regular, adafruit-hat, adafruit-hat-pwm" , choices=['regular', 'adafruit-hat', 'adafruit-hat-pwm'], type=str)
parser.add_argument("--led-scan-mode", action="store", help="Progressive or interlaced scan. 0 Progressive, 1 Interlaced (default)", default=1, choices=range(2), type=int)
parser.add_argument("--led-pwm-lsb-nanoseconds", action="store", help="Base time-unit for the on-time in the lowest significant bit in nanoseconds. Default: 130", default=130, type=int)
parser.add_argument("--led-show-refresh", action="store_true", help="Shows the current refresh rate of the LED panel")
parser.add_argument("--led-slowdown-gpio", action="store", help="Slow down writing to GPIO. Range: 1..100. Default: 1", choices=range(3), type=int)
parser.add_argument("--led-no-hardware-pulse", action="store", help="Don't use hardware pin-pulse generation")
parser.add_argument("--led-multiplexing", action="store", help="Multiplexing type: 0=direct; 1=strip; 2=checker; 3=spiral (Default: 0)", default=2, type=int)
parser.add_argument("-i", "--image", help="The image to display", default="./news.ppm")
imgdir = os.path.abspath(os.path.dirname(__file__)) + "/newsimg"
matrix = prepareMatrix(parser)
if not os.path.isdir(imgdir):
print("Error: no img to display, no such directory.")
sys.exit(0)
else:
while True:
files = os.listdir(imgdir)
if len(files)==0:
print("Warning: no img to display, I am going to wait news to come.")
time.sleep(5.0)
else:
frnd = random.sample(files,len(files))
for f in frnd:
if f[-4:] == '.ppm':
f = os.path.join(imgdir, f)
try:
if os.path.exists(f):
run(getImageFromFile(f), matrix)
else:
print("Warning: no such file, next please...")
except IOError:
print("Warning: no such file, next please...")
except KeyboardInterrupt:
print("Exiting\n")
sys.exit(0)
else:
printf("Warning: Please do not include non-ppm files.")
sys.exit(0)
rss.py
file:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# By : Ramin Sangesari
import datetime
import time
import argparse
import sys
import os
import random
import feedparser
import hashlib
from glob import glob
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
import urllib2
from bs4 import BeautifulSoup
from logging import getLogger, StreamHandler, DEBUG
logger = getLogger(__name__)
handler = StreamHandler()
handler.setLevel(DEBUG)
logger.setLevel(DEBUG)
logger.addHandler(handler)
logger.propagate = False
sys.path.append(os.path.abspath(os.path.dirname(__file__) + '/..'))
Imgformat = '.ppm'
def isOkToCrawl():
crawl_interval = 5 #sec.
crawl_interval_file = "./lastcrawl"
now = time.time()
if os.path.isfile(crawl_interval_file):
if os.stat(crawl_interval_file).st_mtime > now - crawl_interval:
return False
f = open(crawl_interval_file, 'w')
f.write(str(now) + "\n")
f.close()
return True
def getImageFromFile(path):
image = Image.open(path).convert('RGB')
return image
def saveImgFromText(text, imgdir, fontsize):
path = os.path.abspath(os.path.dirname(__file__))
print path
if fontsize == 20:
font = [ImageFont.truetype(path + '/VERDANA.TTF', fontsize),2]
color = [(255,0,255),
(0,255,255),
(255,255,0),
(0,255,0),
(255,255,255)]
width, ignore = font[0].getsize(text)
im = Image.new("RGB", (width + 40, fontsize+40), "black")
draw = ImageDraw.Draw(im)
draw.text((0, font[1]), text, random.choice(color), font=font[0])
imgname = imgdir+"/"+str(fontsize)+str(hashlib.md5(text.encode('utf_8')).hexdigest())+Imgformat
if not os.path.exists(imgname):
im.save(imgname)
def removeOldImg(imgdir):
#remove ppm files more than 1 days before.
if not(imgdir=="") and not(imgdir=="/")and not(imgdir=="."):
now = time.time()
for f in os.listdir(imgdir):
if f[-4:] == '.ppm':
f = os.path.join(imgdir, f)
if os.stat(f).st_mtime < now - 0.5 * 86400:
if os.path.isfile(f):
os.remove(f)
def getNewsFromFeed():
news = []
url = ['http://www.france24.com/en/top-stories/rss']
for tg in url:
fd = feedparser.parse(tg)
for ent in fd.entries:
news.append(u" "+unicode(ent.title))
return news
parser = argparse.ArgumentParser()
if isOkToCrawl():
imgdir = os.path.abspath(os.path.dirname(__file__)) + "/newsimg"
print imgdir
if not os.path.isdir(imgdir):
os.mkdir(imgdir)
#clean up old news
removeOldImg(imgdir)
#get from RSS feed
for text in getNewsFromFeed():
saveImgFromText(text, imgdir, 20)
else:
print ("You need to wait for 1min before next crawl.")
And now run the following command:
python rss.py & sudo python scroll.py
The below video shows the output of the above codes. You can easily change the code and add many more.
Note: Python does not have significant speed to execute heavy code.
The below video displays the above code for the 32x32 matrix panel.
Example #2: Show Random Effect with CHere's an example in C language. Open the minimal-example.cc
file in the examples-api-use directory and copy the below codes.
// Small example how to use the library.
// By: Ramin Sangesari
#include "led-matrix.h"
#include <unistd.h>
#include <math.h>
#include <stdio.h>
#include <signal.h>
using rgb_matrix::GPIO;
using rgb_matrix::RGBMatrix;
using rgb_matrix::Canvas;
uint8_t buffer[64][64][3] = { 0 };
unsigned long step = 0;
volatile bool interrupt_received = false;
static void InterruptHandler(int signo) {
interrupt_received = true;
}
static void setPixelp(uint8_t x, uint8_t y, float r, float g, float b) {
buffer[y][x][0] = uint8_t(std::max(0, std::min(255, int(r))));
buffer[y][x][1] = uint8_t(std::max(0, std::min(255, int(g))));
buffer[y][x][2] = uint8_t(std::max(0, std::min(255, int(b))));
}
static void HSVtoRGB(float& r, float& g, float& b, float h, float s, float v) {
if (s == 0.0) {
r = v;
g = v;
b = v;
}
int i = int(h * 6.0);
float f = (h * 6.0) - i;
float p = v * (1.0 - s);
float q = v * (1.0 - s * f);
float t = v * (1.0 - s * (1.0 - f));
i = i % 6;
if (i == 0) {
r = v; g = t; b = p; return; // v, t, p
}
if (i == 1) {
r = q; g = v; b = p; return; // q, v, p
}
if (i == 2) {
r = p; g = v; b = t; return; // p, v, t
}
if (i == 3) {
r = p; g = q; b = v; return; // p, q, v
}
if (i == 4) {
r = t; g = p; b = v; return; // t, p, v
}
if (i == 5) {
r = v; g = p; b = q; return; // v, p, q
}
}
void swirl(uint8_t x, uint8_t y, unsigned long step) {
float fx = x - 31.5;
float fy = y - 31.5;
float dist = sqrt(fx * fx + fy * fy) * 0.5;
float angle = (step * 0.1) + (dist * 1.5);
float s = sin(angle);
float c = cos(angle);
float xs = x * c - y * s;
float ys = x * s + y * c;
float r = abs(xs + ys) * 12.0 - 20;
float g = r + (s * 130);
float b = r + (c * 130);
setPixelp(x, y,
r,
g,
b
);
}
void setPixelU(uint8_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b) {
buffer[y][x][0] = r;
buffer[y][x][1] = g;
buffer[y][x][2] = b;
}
void gradient(uint8_t x, uint8_t y, unsigned long step) {
uint8_t g = x * 64;
uint8_t b = y * 64;
uint8_t r = 255 - (x * 64);
setPixelU(x, y, r, g, b);
}
void rainbowSearch(uint8_t x, uint8_t y, unsigned long step) {
float xs = sin((step) * 0.01) * 20.0;
float ys = cos((step) * 0.01) * 20.0;
float scale = ((sin(step / 60.0) + 1.0) * 0.2) + 0.2;
float r = sin((x + xs) * scale) + cos((y + xs) * scale);
float g = sin((x + xs) * scale) + cos((y + ys) * scale);
float b = sin((x + ys) * scale) + cos((y + ys) * scale);
setPixelp(x, y,
r * 255,
g * 255,
b * 255
);
}
void checker(uint8_t _x, uint8_t _y, unsigned long step) {
//float x = _x - 8;
//float y = _y - 8;
float x = _x - 32;
float y = _y - 32;
float angle = step / 5.0;
float s = sin(angle);
float c = cos(angle);
float xs = x * c - y * s;
float ys = x * s + y * c;
xs -= sin(step / 200.0) * 40.0;
ys -= cos(step / 200.0) * 40.0;
float scale = step % 20;
scale /= 20.0;
scale = (sin(step / 50.0) / 8.0) + 0.25;
xs *= scale;
ys *= scale;
float xo = abs(xs) - int(abs(xs));
float yo = abs(ys) - int(abs(ys));
// l = 0 if @ else 1 if xo > .1 and else .5
float l = int(floor(xs) + floor(ys)) % 2 ? 0 : (xo > 0.1 && yo > .1 ? 1 : 0.5);
float r, g, b;
HSVtoRGB(r, g, b, (step % 255) / 255.0, 10, 121);
setPixelU(_x, _y,
r * (l * 255),
g * (l * 255),
b * (l * 255)
);
}
static void DrawOnCanvas2(Canvas *canvas) {
/*
* Let's create a simple animation. We use the canvas to draw
* pixels. We wait between each step to have a slower animation.
*/
while (true) {
for (uint8_t x = 0; x < 64; x++) {
for (uint8_t y = 0; y < 64; y++) {
rainbowSearch(x, y, step);
}
}
for (int x = 0; x < 64; x++) {
for (int y = 0; y < 64; y++) {
for (int c = 0; c < 3; c++) {
canvas->SetPixel(x, y, buffer[x][y][c], buffer[x][y][c], buffer[x][y][c]);
}
}
}
step++;
}
}
int main(int argc, char *argv[]) {
RGBMatrix::Options defaults;
defaults.hardware_mapping = "regular"; // or e.g. "adafruit-hat"
defaults.rows = 64;
defaults.chain_length = 1;
defaults.parallel = 1;
defaults.show_refresh_rate = true;
Canvas *canvas = rgb_matrix::CreateMatrixFromFlags(&argc, &argv, &defaults);
if (canvas == NULL)
return 1;
// It is always good to set up a signal handler to cleanly exit when we
// receive a CTRL-C for instance. The DrawOnCanvas() routine is looking
// for that.
signal(SIGTERM, InterruptHandler);
signal(SIGINT, InterruptHandler);
DrawOnCanvas2(canvas); // Using the canvas.
// Animation finished. Shut down the RGB matrix.
canvas->Clear();
delete canvas;
return 0;
}
Save the file and compile it with the make
command. To run the demo, run the following command:
sudo ./minimal-example
The below video displays the above code for the 64x64 matrix panel.
Good luck ;)
Comments