Initial commit.

This commit is contained in:
Tony DiCola 2015-12-15 12:03:46 +00:00
parent a745890acb
commit 886c692b5a
14 changed files with 992 additions and 0 deletions

2
.gitignore vendored
View file

@ -1,3 +1,5 @@
display-test
rpi-fb-matrix
# Compiled Object files
*.slo
*.lo

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "rpi-rgb-led-matrix"]
path = rpi-rgb-led-matrix
url = https://github.com/hzeller/rpi-rgb-led-matrix.git

104
Config.cpp Normal file
View file

@ -0,0 +1,104 @@
// Matrix configuration parsing class implementation.
// Author: Tony DiCola
#include <sstream>
#include <stdexcept>
#include <vector>
#include <iostream>
#include <libconfig.h++>
#include "Config.h"
using namespace std;
Config::Config(const string& filename) {
try {
// Load config file with libconfig.
libconfig::Config cfg;
cfg.readFile(filename.c_str());
libconfig::Setting& root = cfg.getRoot();
// Parse out the matrix configuration values.
_display_width = root["display_width"];
_display_height = root["display_height"];
_panel_width = root["panel_width"];
_panel_height = root["panel_height"];
_chain_length = root["chain_length"];
_parallel_count = root["parallel_count"];
// Do basic validation of configuration.
if (_display_width % _panel_width != 0) {
throw invalid_argument("display_width must be a multiple of panel_width!");
}
if (_display_height % _panel_height != 0) {
throw invalid_argument("display_height must be a multiple of panel_height!");
}
if ((_parallel_count < 1) || (_parallel_count > 3)) {
throw invalid_argument("parallel_count must be between 1 and 3!");
}
// Parse out the individual panel configurations.
libconfig::Setting& panels_config = root["panels"];
for (int i = 0; i < panels_config.getLength(); ++i) {
libconfig::Setting& row = panels_config[i];
for (int j = 0; j < row.getLength(); ++j) {
GridTransformer::Panel panel;
// Read panel order (required setting for each panel).
panel.order = row[j]["order"];
// Set default values for rotation and parallel chain, then override
// them with any panel-specific configuration values.
panel.rotate = 0;
panel.parallel = 0;
row[j].lookupValue("rotate", panel.rotate);
row[j].lookupValue("parallel", panel.parallel);
// Perform validation of panel values.
// If panels are square allow rotations that are a multiple of 90, otherwise
// only allow a rotation of 180 degrees.
if ((_panel_width == _panel_height) && (panel.rotate % 90 != 0)) {
stringstream error;
error << "Panel " << i << "," << j << " rotation must be a multiple of 90 degrees!";
throw invalid_argument(error.str());
}
else if ((_panel_width != _panel_height) && (panel.rotate % 180 != 0)) {
stringstream error;
error << "Panel row " << j << ", column " << i << " can only be rotated 180 degrees!";
throw invalid_argument(error.str());
}
// Check that parallel is value between 0 and 2 (up to 3 parallel chains).
if ((panel.parallel < 0) || (panel.parallel > 2)) {
stringstream error;
error << "Panel row " << j << ", column " << i << " parallel value must be 0, 1, or 2!";
throw invalid_argument(error.str());
}
// Add the panel to the list of panel configurations.
_panels.push_back(panel);
}
}
// Check the number of configured panels matches the expected number
// of panels (# of panel columns * # of panel rows).
int expected = (_display_width / _panel_width) * (_display_height / _panel_height);
if (_panels.size() != (unsigned int)expected) {
stringstream error;
error << "Expected " << expected << " panels in configuration but found " << _panels.size() << "!";
throw invalid_argument(error.str());
}
}
catch (const libconfig::FileIOException& fioex)
{
throw runtime_error("IO error while reading configuration file. Does the file exist?");
}
catch (const libconfig::ParseException& pex)
{
stringstream error;
error << "Config file error at " << pex.getFile() << ":" << pex.getLine()
<< " - " << pex.getError();
throw invalid_argument(error.str());
}
catch (const libconfig::SettingNotFoundException& nfex)
{
stringstream error;
error << "Expected to find setting: " << nfex.getPath();
throw invalid_argument(error.str());
}
catch (const libconfig::ConfigException& ex) {
throw runtime_error("Error loading configuration!");
}
}

49
Config.h Normal file
View file

@ -0,0 +1,49 @@
// Matrix configuration parsing class declaration.
// Author: Tony DiCola
#ifndef CONFIG_H
#define CONFIG_H
#include <string>
#include <vector>
#include "GridTransformer.h"
class Config {
public:
Config(const std::string& filename);
// Attribute accessors:
int getDisplayWidth() const {
return _display_width;
}
int getDisplayHeight() const {
return _display_height;
}
int getPanelWidth() const {
return _panel_width;
}
int getPanelHeight() const {
return _panel_height;
}
int getChainLength() const {
return _chain_length;
}
int getParallelCount() const {
return _parallel_count;
}
GridTransformer getGridTransformer() const {
return GridTransformer(_display_width, _display_height, _panel_width,
_panel_height, _chain_length, _panels);
}
private:
int _display_width,
_display_height,
_panel_width,
_panel_height,
_chain_length,
_parallel_count;
std::vector<GridTransformer::Panel> _panels;
};
#endif

87
GridTransformer.cpp Normal file
View file

@ -0,0 +1,87 @@
// LED matrix library transformer to map a rectangular canvas onto a complex
// chain of matrices.
// Author: Tony DiCola
#include "GridTransformer.h"
using namespace rgb_matrix;
using namespace std;
GridTransformer::GridTransformer(int width, int height, int panel_width, int panel_height,
int chain_length, const std::vector<Panel>& panels):
_width(width),
_height(height),
_panel_width(panel_width),
_panel_height(panel_height),
_chain_length(chain_length),
_source(NULL),
_panels(panels)
{
// Display width must be a multiple of the panel pixel column count.
assert(_width % _panel_width == 0);
// Display height must be a multiple of the panel pixel row count.
assert(_height % _panel_height == 0);
// Compute number of rows and columns of panels.
_rows = _height / _panel_height;
_cols = _width / _panel_width;
// Check panel definition list has exactly the expected number of panels.
assert((_rows * _cols) == (int)_panels.size());
}
void GridTransformer::SetPixel(int x, int y, uint8_t red, uint8_t green, uint8_t blue) {
assert(_source != NULL);
if ((x < 0) || (y < 0) || (x >= _width) || (y >= _height)) {
return;
}
// Figure out what row and column panel this pixel is within.
int row = y / _panel_height;
int col = x / _panel_width;
// Get the panel information for this pixel.
Panel panel = _panels[_cols*row + col];
// Compute location of the pixel within the panel.
x = x % _panel_width;
y = y % _panel_height;
// Perform any panel rotation to the pixel.
// NOTE: 90 and 270 degree rotation only possible on 32 row (square) panels.
if (panel.rotate == 90) {
assert(_panel_height == _panel_width);
int old_x = x;
x = (_panel_height-1)-y;
y = old_x;
}
else if (panel.rotate == 180) {
x = (_panel_width-1)-x;
y = (_panel_height-1)-y;
}
else if (panel.rotate == 270) {
assert(_panel_height == _panel_width);
int old_y = y;
y = (_panel_width-1)-x;
x = old_y;
}
// Determine x offset into the source panel based on its order along the chain.
// The order needs to be inverted because the matrix library starts with the
// origin of an image at the end of the chain and not at the start (where
// ordering begins for this transformer).
int x_offset = ((_chain_length-1)-panel.order)*_panel_width;
// Determine y offset into the source panel based on its parrallel chain value.
int y_offset = panel.parallel*_panel_height;
_source->SetPixel(x_offset + x,
y_offset + y,
red, green, blue);
}
Canvas* GridTransformer::Transform(Canvas* source) {
assert(source != NULL);
int swidth = source->width();
int sheight = source->height();
assert((_width * _height) == (swidth * sheight));
_source = source;
return this;
}

65
GridTransformer.h Normal file
View file

@ -0,0 +1,65 @@
// LED matrix library transformer to map a rectangular canvas onto a complex
// chain of matrices.
// Author: Tony DiCola
#ifndef GRIDTRANSFORMER_H
#define GRIDTRANSFORMER_H
#include <cassert>
#include <vector>
#include "led-matrix.h"
class GridTransformer: public rgb_matrix::Canvas, public rgb_matrix::CanvasTransformer {
public:
struct Panel {
int order;
int rotate;
int parallel;
};
GridTransformer(int width, int height, int panel_width, int panel_height,
int chain_length, const std::vector<Panel>& panels);
virtual ~GridTransformer() {}
// Canvas interface implementation:
virtual int width() const {
return _width;
}
virtual int height() const {
return _height;
}
virtual void Clear() {
assert(_source != NULL);
_source->Clear();
}
virtual void Fill(uint8_t red, uint8_t green, uint8_t blue) {
assert(_source != NULL);
_source->Fill(red, green, blue);
}
virtual void SetPixel(int x, int y, uint8_t red, uint8_t green, uint8_t blue);
// Transformer interface implementation:
virtual rgb_matrix::Canvas* Transform(rgb_matrix::Canvas* source);
// Other attribute accessors.
int getRows() const {
return _rows;
}
int getColumns() const {
return _cols;
}
private:
int _width,
_height,
_panel_width,
_panel_height,
_chain_length,
_rows,
_cols;
rgb_matrix::Canvas* _source;
std::vector<Panel> _panels;
};
#endif

36
Makefile Normal file
View file

@ -0,0 +1,36 @@
# Configure the rpi-rgb-led-matrix library here:
# The -DADAFRUIT_RGBMATRIX_HAT value configures the library to use the Adafruit
# LED matrix HAT wiring, and the -DRGB_SLOWDOWN_GPIO=1 value configures the
# library to work with a Raspberry Pi 2. For a Pi 1 (or perhaps even on a Pi 2,
# but I found it necessary in my testing) you can remove the -DRGB_SLOWDOWN_GPIO=1
# option. You can also add any other rpi-rgb-led-matrix library defines here
# to configure the library for more special needs. See the library's docs for
# details on options:
# https://github.com/hzeller/rpi-rgb-led-matrix/blob/master/lib/Makefile
export DEFINES = -DADAFRUIT_RGBMATRIX_HAT -DRGB_SLOWDOWN_GPIO=1
# Configure compiler and libraries:
CXX = g++
CXXFLAGS = -Wall -std=c++11 -I. -I./rpi-rgb-led-matrix/include -I/opt/vc/include -I/opt/vc/include/interface/vcos/pthreads -I/opt/vc/include/interface/vmcs_host -I/opt/vc/include/interface/vmcs_host/linux -L./rpi-rgb-led-matrix/lib -L/opt/vc/lib
LIBS = -lrgbmatrix -lrt -lm -lpthread -lbcm_host -lconfig++
# Makefile rules:
all: rpi-fb-matrix display-test
rpi-fb-matrix: rpi-fb-matrix.o GridTransformer.o Config.o ./rpi-rgb-led-matrix/lib/librgbmatrix.a
$(CXX) -o $@ $^ $(CXXFLAGS) $(LIBS)
display-test: display-test.o GridTransformer.o Config.o glcdfont.o ./rpi-rgb-led-matrix/lib/librgbmatrix.a
$(CXX) -o $@ $^ $(CXXFLAGS) $(LIBS)
%.o: %.cpp $(DEPS)
$(CXX) -c -o $@ $< $(CXXFLAGS)
./rpi-rgb-led-matrix/lib/librgbmatrix.a:
$(MAKE) -C ./rpi-rgb-led-matrix/lib
.PHONY: clean
clean:
rm -f *.o rpi-fb-matrix display-test
$(MAKE) -C ./rpi-rgb-led-matrix/lib clean

View file

@ -1,2 +1,45 @@
# rpi-fb-matrix
Raspberry Pi framebuffer copy tool for RGB LED matrices. Show what's on a Pi HDMI output on a big RGB LED matrix chain!
## Setup
You **must** clone this repository with the recursive option so that necessary
submodules are also cloned. Run this command:
git clone --recursive https://github.com/adafruit/rpi-fb-matrix.git
Next you will need a few dependencies (libconfig++) to compile the code. Run
these commands to install the dependencies:
sudo apt-get update
sudo apt-get install -y build-essential libconfig++-dev
Now if necessary you can change how the RGB LED matrix library is configured
by editing the variables set in the Makefile. By default the Makefile is
configured to work with the Adafruit LED matrix HAT and a Raspberry Pi 2. If
you're using a different configuration open the Makefile and edit the `export DEFINES=...`
line at the top.
Build the project by running make:
make
Once compiled there will be two executables:
* rpi-fb-matrix: The main program that will copy the contents of the primary
display (HDMI output) to attached LED matrices.
* display-test: A program to display the order and orientation of chained
together LED matrices. Good for building complex display chains.
## Acknowledgments
This program makes use of the following excellent libraries:
* [rpi-rgb-led-matrix](https://github.com/hzeller/rpi-rgb-led-matrix)
* [libconfig](http://www.hyperrealm.com/libconfig/)
Framebuffer capture code based on information from the [rpi-fbcp](https://github.com/tasanakorn/rpi-fbcp) tool.
## License
Released under the GPL v2.0 license, see LICENSE.txt for details.

107
display-test.cpp Normal file
View file

@ -0,0 +1,107 @@
// Program to aid in the testing of LED matrix chains.
// Author: Tony DiCola
#include <cstdint>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <led-matrix.h>
#include <signal.h>
#include <unistd.h>
#include "Config.h"
#include "glcdfont.h"
#include "GridTransformer.h"
using namespace std;
using namespace rgb_matrix;
bool running = true; // Global to keep track of if the program should run.
// Will be set false by a SIGINT handler when ctrl-c is
// pressed, then the main loop will cleanly exit.
void printCanvas(Canvas& canvas, int x, int y, const string& message,
int r = 255, int g = 255, int b = 255) {
// Loop through all the characters and print them starting at the provided
// coordinates.
for (auto c: message) {
// Loop through each column of the character.
for (int i=0; i<5; ++i) {
unsigned char col = glcdfont[c*5+i];
x += 1;
// Loop through each row of the column.
for (int j=0; j<8; ++j) {
// Put a pixel for each 1 in the column byte.
if ((col >> j) & 0x01) {
canvas.SetPixel(x, y+j, r, g, b);
}
}
}
// Add a column of padding between characters.
x += 1;
}
}
void sigintHandler(int s) {
running = false;
}
int main(int argc, char** argv) {
try
{
// Expect one command line parameter with display configuration filename.
if (argc != 2) {
throw runtime_error("Expected configuration file name as only command line parameter!\r\nUsage: display-test /path/to/display/config.cfg");
}
// Load the configuration.
Config config(argv[1]);
cout << "Using config values: " << endl
<< " display_width: " << config.getDisplayWidth() << endl
<< " display_height: " << config.getDisplayHeight() << endl
<< " panel_width: " << config.getPanelWidth() << endl
<< " panel_height: " << config.getPanelHeight() << endl
<< " chain_length: " << config.getChainLength() << endl
<< " parallel_count: " << config.getParallelCount() << endl;
// Initialize matrix library.
GPIO io;
if (!io.Init()) {
throw runtime_error("Failed to initialize rpi-led-matrix library! Make sure to run as root with sudo.");
}
// Create canvas and apply GridTransformer.
RGBMatrix canvas(&io, config.getPanelHeight(), config.getChainLength(),
config.getParallelCount());
GridTransformer grid = config.getGridTransformer();
canvas.SetTransformer(&grid);
// Clear the canvas, then draw on each panel.
canvas.Fill(0, 0, 0);
for (int i=0; i<grid.getRows(); ++i) {
for (int j=0; j<grid.getColumns(); ++j) {
// Compute panel origin position.
int x = i*config.getPanelWidth();
int y = j*config.getPanelHeight();
// Print the current grid position to the top left (origin) of the panel.
stringstream pos;
pos << i << "," << j;
printCanvas(canvas, x, y, pos.str());
}
}
// Loop forever waiting for Ctrl-C signal to quit.
signal(SIGINT, sigintHandler);
cout << "Press Ctrl-C to quit..." << endl;
while (running) {
sleep(1);
}
canvas.Clear();
}
catch (const exception& ex) {
cerr << ex.what() << endl;
return -1;
}
return 0;
}

259
glcdfont.c Normal file
View file

@ -0,0 +1,259 @@
// Standard ASCII 5x7 font
const unsigned char glcdfont[] = {
0x00, 0x00, 0x00, 0x00, 0x00,
0x3E, 0x5B, 0x4F, 0x5B, 0x3E,
0x3E, 0x6B, 0x4F, 0x6B, 0x3E,
0x1C, 0x3E, 0x7C, 0x3E, 0x1C,
0x18, 0x3C, 0x7E, 0x3C, 0x18,
0x1C, 0x57, 0x7D, 0x57, 0x1C,
0x1C, 0x5E, 0x7F, 0x5E, 0x1C,
0x00, 0x18, 0x3C, 0x18, 0x00,
0xFF, 0xE7, 0xC3, 0xE7, 0xFF,
0x00, 0x18, 0x24, 0x18, 0x00,
0xFF, 0xE7, 0xDB, 0xE7, 0xFF,
0x30, 0x48, 0x3A, 0x06, 0x0E,
0x26, 0x29, 0x79, 0x29, 0x26,
0x40, 0x7F, 0x05, 0x05, 0x07,
0x40, 0x7F, 0x05, 0x25, 0x3F,
0x5A, 0x3C, 0xE7, 0x3C, 0x5A,
0x7F, 0x3E, 0x1C, 0x1C, 0x08,
0x08, 0x1C, 0x1C, 0x3E, 0x7F,
0x14, 0x22, 0x7F, 0x22, 0x14,
0x5F, 0x5F, 0x00, 0x5F, 0x5F,
0x06, 0x09, 0x7F, 0x01, 0x7F,
0x00, 0x66, 0x89, 0x95, 0x6A,
0x60, 0x60, 0x60, 0x60, 0x60,
0x94, 0xA2, 0xFF, 0xA2, 0x94,
0x08, 0x04, 0x7E, 0x04, 0x08,
0x10, 0x20, 0x7E, 0x20, 0x10,
0x08, 0x08, 0x2A, 0x1C, 0x08,
0x08, 0x1C, 0x2A, 0x08, 0x08,
0x1E, 0x10, 0x10, 0x10, 0x10,
0x0C, 0x1E, 0x0C, 0x1E, 0x0C,
0x30, 0x38, 0x3E, 0x38, 0x30,
0x06, 0x0E, 0x3E, 0x0E, 0x06,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x5F, 0x00, 0x00,
0x00, 0x07, 0x00, 0x07, 0x00,
0x14, 0x7F, 0x14, 0x7F, 0x14,
0x24, 0x2A, 0x7F, 0x2A, 0x12,
0x23, 0x13, 0x08, 0x64, 0x62,
0x36, 0x49, 0x56, 0x20, 0x50,
0x00, 0x08, 0x07, 0x03, 0x00,
0x00, 0x1C, 0x22, 0x41, 0x00,
0x00, 0x41, 0x22, 0x1C, 0x00,
0x2A, 0x1C, 0x7F, 0x1C, 0x2A,
0x08, 0x08, 0x3E, 0x08, 0x08,
0x00, 0x80, 0x70, 0x30, 0x00,
0x08, 0x08, 0x08, 0x08, 0x08,
0x00, 0x00, 0x60, 0x60, 0x00,
0x20, 0x10, 0x08, 0x04, 0x02,
0x3E, 0x51, 0x49, 0x45, 0x3E,
0x00, 0x42, 0x7F, 0x40, 0x00,
0x72, 0x49, 0x49, 0x49, 0x46,
0x21, 0x41, 0x49, 0x4D, 0x33,
0x18, 0x14, 0x12, 0x7F, 0x10,
0x27, 0x45, 0x45, 0x45, 0x39,
0x3C, 0x4A, 0x49, 0x49, 0x31,
0x41, 0x21, 0x11, 0x09, 0x07,
0x36, 0x49, 0x49, 0x49, 0x36,
0x46, 0x49, 0x49, 0x29, 0x1E,
0x00, 0x00, 0x14, 0x00, 0x00,
0x00, 0x40, 0x34, 0x00, 0x00,
0x00, 0x08, 0x14, 0x22, 0x41,
0x14, 0x14, 0x14, 0x14, 0x14,
0x00, 0x41, 0x22, 0x14, 0x08,
0x02, 0x01, 0x59, 0x09, 0x06,
0x3E, 0x41, 0x5D, 0x59, 0x4E,
0x7C, 0x12, 0x11, 0x12, 0x7C,
0x7F, 0x49, 0x49, 0x49, 0x36,
0x3E, 0x41, 0x41, 0x41, 0x22,
0x7F, 0x41, 0x41, 0x41, 0x3E,
0x7F, 0x49, 0x49, 0x49, 0x41,
0x7F, 0x09, 0x09, 0x09, 0x01,
0x3E, 0x41, 0x41, 0x51, 0x73,
0x7F, 0x08, 0x08, 0x08, 0x7F,
0x00, 0x41, 0x7F, 0x41, 0x00,
0x20, 0x40, 0x41, 0x3F, 0x01,
0x7F, 0x08, 0x14, 0x22, 0x41,
0x7F, 0x40, 0x40, 0x40, 0x40,
0x7F, 0x02, 0x1C, 0x02, 0x7F,
0x7F, 0x04, 0x08, 0x10, 0x7F,
0x3E, 0x41, 0x41, 0x41, 0x3E,
0x7F, 0x09, 0x09, 0x09, 0x06,
0x3E, 0x41, 0x51, 0x21, 0x5E,
0x7F, 0x09, 0x19, 0x29, 0x46,
0x26, 0x49, 0x49, 0x49, 0x32,
0x03, 0x01, 0x7F, 0x01, 0x03,
0x3F, 0x40, 0x40, 0x40, 0x3F,
0x1F, 0x20, 0x40, 0x20, 0x1F,
0x3F, 0x40, 0x38, 0x40, 0x3F,
0x63, 0x14, 0x08, 0x14, 0x63,
0x03, 0x04, 0x78, 0x04, 0x03,
0x61, 0x59, 0x49, 0x4D, 0x43,
0x00, 0x7F, 0x41, 0x41, 0x41,
0x02, 0x04, 0x08, 0x10, 0x20,
0x00, 0x41, 0x41, 0x41, 0x7F,
0x04, 0x02, 0x01, 0x02, 0x04,
0x40, 0x40, 0x40, 0x40, 0x40,
0x00, 0x03, 0x07, 0x08, 0x00,
0x20, 0x54, 0x54, 0x78, 0x40,
0x7F, 0x28, 0x44, 0x44, 0x38,
0x38, 0x44, 0x44, 0x44, 0x28,
0x38, 0x44, 0x44, 0x28, 0x7F,
0x38, 0x54, 0x54, 0x54, 0x18,
0x00, 0x08, 0x7E, 0x09, 0x02,
0x18, 0xA4, 0xA4, 0x9C, 0x78,
0x7F, 0x08, 0x04, 0x04, 0x78,
0x00, 0x44, 0x7D, 0x40, 0x00,
0x20, 0x40, 0x40, 0x3D, 0x00,
0x7F, 0x10, 0x28, 0x44, 0x00,
0x00, 0x41, 0x7F, 0x40, 0x00,
0x7C, 0x04, 0x78, 0x04, 0x78,
0x7C, 0x08, 0x04, 0x04, 0x78,
0x38, 0x44, 0x44, 0x44, 0x38,
0xFC, 0x18, 0x24, 0x24, 0x18,
0x18, 0x24, 0x24, 0x18, 0xFC,
0x7C, 0x08, 0x04, 0x04, 0x08,
0x48, 0x54, 0x54, 0x54, 0x24,
0x04, 0x04, 0x3F, 0x44, 0x24,
0x3C, 0x40, 0x40, 0x20, 0x7C,
0x1C, 0x20, 0x40, 0x20, 0x1C,
0x3C, 0x40, 0x30, 0x40, 0x3C,
0x44, 0x28, 0x10, 0x28, 0x44,
0x4C, 0x90, 0x90, 0x90, 0x7C,
0x44, 0x64, 0x54, 0x4C, 0x44,
0x00, 0x08, 0x36, 0x41, 0x00,
0x00, 0x00, 0x77, 0x00, 0x00,
0x00, 0x41, 0x36, 0x08, 0x00,
0x02, 0x01, 0x02, 0x04, 0x02,
0x3C, 0x26, 0x23, 0x26, 0x3C,
0x1E, 0xA1, 0xA1, 0x61, 0x12,
0x3A, 0x40, 0x40, 0x20, 0x7A,
0x38, 0x54, 0x54, 0x55, 0x59,
0x21, 0x55, 0x55, 0x79, 0x41,
0x22, 0x54, 0x54, 0x78, 0x42, // a-umlaut
0x21, 0x55, 0x54, 0x78, 0x40,
0x20, 0x54, 0x55, 0x79, 0x40,
0x0C, 0x1E, 0x52, 0x72, 0x12,
0x39, 0x55, 0x55, 0x55, 0x59,
0x39, 0x54, 0x54, 0x54, 0x59,
0x39, 0x55, 0x54, 0x54, 0x58,
0x00, 0x00, 0x45, 0x7C, 0x41,
0x00, 0x02, 0x45, 0x7D, 0x42,
0x00, 0x01, 0x45, 0x7C, 0x40,
0x7D, 0x12, 0x11, 0x12, 0x7D, // A-umlaut
0xF0, 0x28, 0x25, 0x28, 0xF0,
0x7C, 0x54, 0x55, 0x45, 0x00,
0x20, 0x54, 0x54, 0x7C, 0x54,
0x7C, 0x0A, 0x09, 0x7F, 0x49,
0x32, 0x49, 0x49, 0x49, 0x32,
0x3A, 0x44, 0x44, 0x44, 0x3A, // o-umlaut
0x32, 0x4A, 0x48, 0x48, 0x30,
0x3A, 0x41, 0x41, 0x21, 0x7A,
0x3A, 0x42, 0x40, 0x20, 0x78,
0x00, 0x9D, 0xA0, 0xA0, 0x7D,
0x3D, 0x42, 0x42, 0x42, 0x3D, // O-umlaut
0x3D, 0x40, 0x40, 0x40, 0x3D,
0x3C, 0x24, 0xFF, 0x24, 0x24,
0x48, 0x7E, 0x49, 0x43, 0x66,
0x2B, 0x2F, 0xFC, 0x2F, 0x2B,
0xFF, 0x09, 0x29, 0xF6, 0x20,
0xC0, 0x88, 0x7E, 0x09, 0x03,
0x20, 0x54, 0x54, 0x79, 0x41,
0x00, 0x00, 0x44, 0x7D, 0x41,
0x30, 0x48, 0x48, 0x4A, 0x32,
0x38, 0x40, 0x40, 0x22, 0x7A,
0x00, 0x7A, 0x0A, 0x0A, 0x72,
0x7D, 0x0D, 0x19, 0x31, 0x7D,
0x26, 0x29, 0x29, 0x2F, 0x28,
0x26, 0x29, 0x29, 0x29, 0x26,
0x30, 0x48, 0x4D, 0x40, 0x20,
0x38, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x38,
0x2F, 0x10, 0xC8, 0xAC, 0xBA,
0x2F, 0x10, 0x28, 0x34, 0xFA,
0x00, 0x00, 0x7B, 0x00, 0x00,
0x08, 0x14, 0x2A, 0x14, 0x22,
0x22, 0x14, 0x2A, 0x14, 0x08,
0x55, 0x00, 0x55, 0x00, 0x55, // #176 (25% block) missing in old code
0xAA, 0x55, 0xAA, 0x55, 0xAA, // 50% block
0xFF, 0x55, 0xFF, 0x55, 0xFF, // 75% block
0x00, 0x00, 0x00, 0xFF, 0x00,
0x10, 0x10, 0x10, 0xFF, 0x00,
0x14, 0x14, 0x14, 0xFF, 0x00,
0x10, 0x10, 0xFF, 0x00, 0xFF,
0x10, 0x10, 0xF0, 0x10, 0xF0,
0x14, 0x14, 0x14, 0xFC, 0x00,
0x14, 0x14, 0xF7, 0x00, 0xFF,
0x00, 0x00, 0xFF, 0x00, 0xFF,
0x14, 0x14, 0xF4, 0x04, 0xFC,
0x14, 0x14, 0x17, 0x10, 0x1F,
0x10, 0x10, 0x1F, 0x10, 0x1F,
0x14, 0x14, 0x14, 0x1F, 0x00,
0x10, 0x10, 0x10, 0xF0, 0x00,
0x00, 0x00, 0x00, 0x1F, 0x10,
0x10, 0x10, 0x10, 0x1F, 0x10,
0x10, 0x10, 0x10, 0xF0, 0x10,
0x00, 0x00, 0x00, 0xFF, 0x10,
0x10, 0x10, 0x10, 0x10, 0x10,
0x10, 0x10, 0x10, 0xFF, 0x10,
0x00, 0x00, 0x00, 0xFF, 0x14,
0x00, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0x00, 0x1F, 0x10, 0x17,
0x00, 0x00, 0xFC, 0x04, 0xF4,
0x14, 0x14, 0x17, 0x10, 0x17,
0x14, 0x14, 0xF4, 0x04, 0xF4,
0x00, 0x00, 0xFF, 0x00, 0xF7,
0x14, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0xF7, 0x00, 0xF7,
0x14, 0x14, 0x14, 0x17, 0x14,
0x10, 0x10, 0x1F, 0x10, 0x1F,
0x14, 0x14, 0x14, 0xF4, 0x14,
0x10, 0x10, 0xF0, 0x10, 0xF0,
0x00, 0x00, 0x1F, 0x10, 0x1F,
0x00, 0x00, 0x00, 0x1F, 0x14,
0x00, 0x00, 0x00, 0xFC, 0x14,
0x00, 0x00, 0xF0, 0x10, 0xF0,
0x10, 0x10, 0xFF, 0x10, 0xFF,
0x14, 0x14, 0x14, 0xFF, 0x14,
0x10, 0x10, 0x10, 0x1F, 0x00,
0x00, 0x00, 0x00, 0xF0, 0x10,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFF, 0xFF,
0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
0x38, 0x44, 0x44, 0x38, 0x44,
0xFC, 0x4A, 0x4A, 0x4A, 0x34, // sharp-s or beta
0x7E, 0x02, 0x02, 0x06, 0x06,
0x02, 0x7E, 0x02, 0x7E, 0x02,
0x63, 0x55, 0x49, 0x41, 0x63,
0x38, 0x44, 0x44, 0x3C, 0x04,
0x40, 0x7E, 0x20, 0x1E, 0x20,
0x06, 0x02, 0x7E, 0x02, 0x02,
0x99, 0xA5, 0xE7, 0xA5, 0x99,
0x1C, 0x2A, 0x49, 0x2A, 0x1C,
0x4C, 0x72, 0x01, 0x72, 0x4C,
0x30, 0x4A, 0x4D, 0x4D, 0x30,
0x30, 0x48, 0x78, 0x48, 0x30,
0xBC, 0x62, 0x5A, 0x46, 0x3D,
0x3E, 0x49, 0x49, 0x49, 0x00,
0x7E, 0x01, 0x01, 0x01, 0x7E,
0x2A, 0x2A, 0x2A, 0x2A, 0x2A,
0x44, 0x44, 0x5F, 0x44, 0x44,
0x40, 0x51, 0x4A, 0x44, 0x40,
0x40, 0x44, 0x4A, 0x51, 0x40,
0x00, 0x00, 0xFF, 0x01, 0x03,
0xE0, 0x80, 0xFF, 0x00, 0x00,
0x08, 0x08, 0x6B, 0x6B, 0x08,
0x36, 0x12, 0x36, 0x24, 0x36,
0x06, 0x0F, 0x09, 0x0F, 0x06,
0x00, 0x00, 0x18, 0x18, 0x00,
0x00, 0x00, 0x10, 0x10, 0x00,
0x30, 0x40, 0xFF, 0x01, 0x01,
0x00, 0x1F, 0x01, 0x01, 0x1E,
0x00, 0x19, 0x1D, 0x17, 0x12,
0x00, 0x3C, 0x3C, 0x3C, 0x3C,
0x00, 0x00, 0x00, 0x00, 0x00 // #255 NBSP
};

6
glcdfont.h Normal file
View file

@ -0,0 +1,6 @@
#ifndef GLCDFONT_H
#define GLCDFONT_H
extern const unsigned char glcdfont[];
#endif

63
matrix.cfg Normal file
View file

@ -0,0 +1,63 @@
// LED Matrix Display Configuration
// Define the entire width and height of the display in pixels.
// This is the _total_ width and height of the rectangle defined by all the
// chained panels. The width should be a multiple of the panel pixel width (32),
// and the height should be a multiple of the panel pixel height (8, 16, or 32).
display_width = 64;
display_height = 64;
// Define the width of each panel in pixels. This should always be 32 (but can
// in theory be changed).
panel_width = 32;
// Define the height of each panel in pixels. This is typically 8, 16, or 32.
// NOTE: Each panel in the display _must_ be the same height! You cannot mix
// 16 and 32 pixel high panels for example.
panel_height = 32;
// Define the total number of panels in each chain. Count up however many
// panels are connected together and put that value here. If you're using
// multiple parallel chains count each one up separately and pick the largest
// value for this configuration.
chain_length = 4;
// Define the total number of parallel chains. If using the Adafruit HAT you
// can only have one chain so stick with the value 1. The Pi 2 can support up
// to 3 parallel chains, see the rpi-rgb-led-matrix library for more information:
// https://github.com/hzeller/rpi-rgb-led-matrix#chaining-parallel-chains-and-coordinate-system
parallel_count = 1;
// Configure each LED matrix panel.
// This is a two-dimensional array with an entry for each panel. The array
// defines the grid that will subdivide the display, so for example a 64x64 size
// display with 32x32 pixel panels would be a 2x2 array of panel configurations.
//
// For each panel you must set the order that it is within its chain, i.e. the
// first panel in a chain is order = 0, the next one is order = 1, etc. You can
// also set a rotation for each panel to account for changes in panel orientation
// (like when 'snaking' a series of panels end to end for shorter wire runs).
//
// For example the configuration below defines this grid display of panels and
// their wiring (starting from the upper right panel and snaking left, down, and
// right to the bottom right panel):
// ______________ ______________
// | Panel | | Panel |
// /==| order = 1 |<=| order = 0 |<= Chain start (from Pi)
// | | rotate = 0 | | rotate = 0 |
// | |______________| |______________|
// | ______________ ______________
// | | Panel | | Panel |
// \==| order = 2 |=>| order = 3 |
// | rotate = 180 | | rotate = 180 |
// |______________| |______________|
//
// Notice the chain starts in the upper right and snakes around to the bottom
// right. The order of each panel is set as its position along the chain,
// and rotation is applied to the lower panels that are flipped around relative
// to the panels above them.
//
panels = (
( { order = 1; rotate = 0; }, { order = 0; rotate = 0; } ),
( { order = 2; rotate = 180; }, { order = 3; rotate = 180; } )
)

167
rpi-fb-matrix.cpp Normal file
View file

@ -0,0 +1,167 @@
// Program to copy the contents of the Raspberry Pi primary display to LED matrices.
// Author: Tony DiCola
#include <iostream>
#include <stdexcept>
#include <bcm_host.h>
#include <fcntl.h>
#include <led-matrix.h>
#include <linux/fb.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include "Config.h"
#include "GridTransformer.h"
using namespace std;
using namespace rgb_matrix;
bool running = true; // Global to keep track of if the program should run.
// Will be set false by a SIGINT handler when ctrl-c is
// pressed, then the main loop will cleanly exit.
// Class to encapsulate all the logic for capturing an image of the Pi's primary
// display. Manages all the BCM GPU and CPU resources automatically while in scope.
class BCMDisplayCapture {
public:
BCMDisplayCapture(int width, int height):
_width(width),
_height(height),
_display(0),
_screen_resource(0),
_screen_data(NULL)
{
// Get information about primary/HDMI display.
_display = vc_dispmanx_display_open(0);
if (!_display) {
throw runtime_error("Unable to open primary display!");
}
DISPMANX_MODEINFO_T display_info;
if (vc_dispmanx_display_get_info(_display, &display_info)) {
throw runtime_error("Unable to get primary display information!");
}
cout << "Primary display:" << endl
<< " resolution: " << display_info.width << "x" << display_info.height << endl
<< " format: " << display_info.input_format << endl;
// Create a GPU image surface to hold the captured screen.
uint32_t image_prt;
_screen_resource = vc_dispmanx_resource_create(VC_IMAGE_RGB888, width, height, &image_prt);
if (!_screen_resource) {
throw runtime_error("Unable to create screen surface!");
}
// Create a rectangular region of the captured screen size.
vc_dispmanx_rect_set(&_rect, 0, 0, _width, _height);
// Allocate CPU memory for copying out the captured screen. Must be aligned
// to a larger size because of GPU surface memory size constraints.
_pitch = ALIGN_UP(_width*3, 32);
_screen_data = new uint8_t[_pitch*_height];
}
void capture() {
// Capture the primary display and copy it from GPU to CPU memory.
vc_dispmanx_snapshot(_display, _screen_resource, (DISPMANX_TRANSFORM_T)0);
vc_dispmanx_resource_read_data(_screen_resource, &_rect, _screen_data, _pitch);
}
void getPixel(int x, int y, uint8_t* r, uint8_t* g, uint8_t* b) {
// Grab the requested pixel from the last captured display image.
uint8_t* row = _screen_data + (y*_pitch);
*r = row[x*3];
*g = row[x*3+1];
*b = row[x*3+2];
}
~BCMDisplayCapture() {
// Clean up BCM and other resources.
if (_screen_resource != 0) {
vc_dispmanx_resource_delete(_screen_resource);
}
if (_display != 0) {
vc_dispmanx_display_close(_display);
}
if (_screen_data != NULL) {
delete[] _screen_data;
}
}
private:
int _width,
_height,
_pitch;
DISPMANX_DISPLAY_HANDLE_T _display;
DISPMANX_RESOURCE_HANDLE_T _screen_resource;
VC_RECT_T _rect;
uint8_t* _screen_data;
};
void sigintHandler(int s) {
running = false;
}
int main(int argc, char** argv) {
try
{
// Expect one command line parameter with display configuration filename.
if (argc != 2) {
throw runtime_error("Expected configuration file name as only command line parameter!\r\nUsage: rpi-fb-matrix /path/to/display/config.cfg");
}
// Load the configuration.
Config config(argv[1]);
cout << "Using config values: " << endl
<< " display_width: " << config.getDisplayWidth() << endl
<< " display_height: " << config.getDisplayHeight() << endl
<< " panel_width: " << config.getPanelWidth() << endl
<< " panel_height: " << config.getPanelHeight() << endl
<< " chain_length: " << config.getChainLength() << endl
<< " parallel_count: " << config.getParallelCount() << endl;
// Initialize matrix library.
GPIO io;
if (!io.Init()) {
throw runtime_error("Failed to initialize rpi-led-matrix library! Make sure to run as root with sudo.");
}
// Create canvas and apply GridTransformer.
RGBMatrix canvas(&io, config.getPanelHeight(), config.getChainLength(),
config.getParallelCount());
GridTransformer grid = config.getGridTransformer();
canvas.SetTransformer(&grid);
canvas.Clear();
// Initialize BCM functions and display capture class.
bcm_host_init();
BCMDisplayCapture displayCapture(config.getDisplayWidth(),
config.getDisplayHeight());
// Loop forever waiting for Ctrl-C signal to quit.
signal(SIGINT, sigintHandler);
cout << "Press Ctrl-C to quit..." << endl;
while (running) {
// Capture the current display image.
displayCapture.capture();
// Loop through the frame data and set the pixels on the matrix canvas.
for (int y=0; y<config.getDisplayHeight(); ++y) {
for (int x=0; x<config.getDisplayWidth(); ++x) {
uint8_t red, green, blue;
displayCapture.getPixel(x, y, &red, &green, &blue);
canvas.SetPixel(x, y, red, green, blue);
}
}
// Sleep for 25 milliseconds.
usleep(25 * 1000);
}
canvas.Clear();
}
catch (const exception& ex) {
cerr << ex.what() << endl;
return -1;
}
return 0;
}

1
rpi-rgb-led-matrix Submodule

@ -0,0 +1 @@
Subproject commit 5cdf1b4954aed4ea8b9d7fb57f3d1f410201408d