o Update rpi-rgb-led-matrix to latest version.

o Make it possible to use command-line flags provided by the
  matrix library.
This commit is contained in:
Henner Zeller 2016-12-06 22:19:55 -08:00
parent 84359580ad
commit cac591441f
7 changed files with 242 additions and 143 deletions

View file

@ -1,9 +1,11 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Matrix configuration parsing class implementation.
// Author: Tony DiCola
#include <sstream>
#include <stdexcept>
#include <vector>
#include <iostream>
#include <string>
#include <libconfig.h++>
@ -11,23 +13,45 @@
using namespace std;
// Get value if it exists, otherwise return default.
static int getWithDefault(const libconfig::Setting& root, const char *key,
int default_value) {
return root.exists(key) ? root[key] : default_value;
}
Config::Config(const string& filename) {
Config::Config(rgb_matrix::RGBMatrix::Options *options,
const string& filename)
: _moptions(options),
_display_width(-1),
_display_height(-1),
_panel_width(-1),
_crop_x(-1),
_crop_y(-1)
{
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"];
// Set default value for optional config values.
_crop_x = -1;
_crop_y = -1;
// Parse out the matrix configuration values. If not given, we use
// reasonable defaults or defaults provided by the flags.
_panel_width = getWithDefault(root, "panel_width", 32);
_moptions->rows = getWithDefault(root, "panel_height", _moptions->rows);
_chain_length = getWithDefault(root, "chain_length",
_moptions->chain_length);
// While all the code for the transformer assumes number of panels, for
// the internal representation for the matrix code, we need to normalize
// that to 32 wide panels.
_moptions->chain_length = _chain_length * (_panel_width / 32);
_moptions->parallel = getWithDefault(root, "parallel_count",
_moptions->parallel);
_display_width = getWithDefault(root, "display_width",
getPanelWidth() * getChainLength());
_display_height = getWithDefault(root, "display_height",
getPanelHeight() * getParallelCount());
// Load optional crop_origin value.
if (root.exists("crop_origin")) {
libconfig::Setting& crop_origin = root["crop_origin"];
@ -37,79 +61,86 @@ Config::Config(const string& filename) {
_crop_x = crop_origin[0];
_crop_y = crop_origin[1];
}
// Do basic validation of configuration.
if (_panel_width % 32 != 0) {
throw invalid_argument("Panel width must be multiple of 32. Typically that is 32, but sometimes 64.");
}
if (_display_width % _panel_width != 0) {
throw invalid_argument("display_width must be a multiple of panel_width!");
}
if (_display_height % _panel_height != 0) {
if (_display_height % getPanelHeight() != 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!");
std::string message;
if (!_moptions->Validate(&message)) {
throw invalid_argument(message);
}
// 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());
if (root.exists("panels")) {
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 == getPanelHeight()) && (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 != getPanelHeight()) && (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);
}
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).
const int expected = (getDisplayWidth() / getPanelWidth())
* (getDisplayHeight() / getPanelHeight());
if (_panels.size() != (unsigned int)expected) {
stringstream error;
error << "Expected " << expected << " panels in configuration but found " << _panels.size() << "!";
throw invalid_argument(error.str());
}
}
// 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) {
}
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 << "Expected " << expected << " panels in configuration but found " << _panels.size() << "!";
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::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!");
}

View file

@ -1,3 +1,4 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Matrix configuration parsing class declaration.
// Author: Tony DiCola
#ifndef CONFIG_H
@ -7,33 +8,41 @@
#include <vector>
#include "GridTransformer.h"
#include "led-matrix.h"
class Config {
public:
Config(const std::string& filename);
Config(rgb_matrix::RGBMatrix::Options *options,
const std::string& filename);
// Attribute accessors:
int getDisplayWidth() const {
return _display_width;
return (_display_width < 0)
? getPanelWidth() * getChainLength()
: _display_width;
}
int getDisplayHeight() const {
return _display_height;
return (_display_height < 0)
? getPanelHeight() * getParallelCount()
: _display_height;
}
int getPanelWidth() const {
return _panel_width;
return (_panel_width) < 0 ? 32 : _panel_width;
}
int getPanelHeight() const {
return _panel_height;
return _moptions->rows;
}
int getChainLength() const {
return _chain_length;
}
int getParallelCount() const {
return _parallel_count;
return _moptions->parallel;
}
bool hasTransformer() const { return !_panels.empty(); }
GridTransformer getGridTransformer() const {
return GridTransformer(_display_width, _display_height, _panel_width,
_panel_height, _chain_length, _panels);
return GridTransformer(getDisplayWidth(), getDisplayHeight(),
getPanelWidth(), getPanelHeight(),
getChainLength(), _panels);
}
bool hasCropOrigin() const {
return (_crop_x > -1) && (_crop_y > -1);
@ -46,12 +55,11 @@ public:
}
private:
rgb_matrix::RGBMatrix::Options* const _moptions;
int _display_width,
_display_height,
_panel_width,
_panel_height,
_chain_length,
_parallel_count,
_crop_x,
_crop_y;
std::vector<GridTransformer::Panel> _panels;

View file

@ -1,13 +1,16 @@
# 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
# The HARDWARE_DESC=adafruit-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 also can set this value at runtime via the
# command line option --led-slowdown-gpio=1).
# 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
export HARDWARE_DESC=adafruit-hat
export USER_DEFINES=-DRGB_SLOWDOWN_GPIO=1
# Configure compiler and libraries:
CXX = g++

View file

@ -27,11 +27,41 @@ Build the project by running make:
Once compiled there will be two executables:
* rpi-fb-matrix: The main program that will copy the contents of the primary
* `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
* `display-test`: A program to display the order and orientation of chained
together LED matrices. Good for building complex display chains.
Both executables understand the standard command line flags provided in the
rpi-rgb-led-matrix library, for instance for choosing the gpio mapping.
The default compile-choice gpio mapping is `adafruit-hat`, but you can change
that to any [supported gpio mapping] depending on your set-up.
The [configuration file](./matrix.cfg) allows to describe the geometry and
panel-layout. It overrides geometry-related settings provided as flags
(e.g. `--led-chain`).
You can get a list of available command line options by giving `--led-help`
```
$ ./rpi-fb-matrix --led-help
Usage: ./rpi-fb-matrix [flags] [config-file]
Flags:
--led-gpio-mapping=<name> : Name of GPIO mapping used. Default "adafruit-hat"
--led-rows=<rows> : Panel rows. 8, 16, 32 or 64. (Default: 32).
--led-chain=<chained> : Number of daisy-chained panels. (Default: 1).
--led-parallel=<parallel> : For A/B+ models or RPi2,3b: parallel chains. range=1..3 (Default: 1).
--led-pwm-bits=<1..11> : PWM bits (Default: 11).
--led-brightness=<percent>: Brightness in percent (Default: 100).
--led-scan-mode=<0..1> : 0 = progressive; 1 = interlaced (Default: 0).
--led-show-refresh : Show refresh rate.
--led-inverse : Switch if your matrix has inverse colors on.
--led-swap-green-blue : Switch if your matrix has green/blue swapped on.
--led-pwm-lsb-nanoseconds : PWM Nanoseconds for LSB (Default: 130)
--led-no-hardware-pulse : Don't use hardware pin-pulse generation.
--led-slowdown-gpio=<0..2>: Slowdown GPIO. Needed for faster Pis and/or slower panels (Default: 1).
--led-daemon : Make the process run in the background as daemon.
```
## Acknowledgments
This program makes use of the following excellent libraries:
@ -44,3 +74,5 @@ Framebuffer capture code based on information from the [rpi-fbcp](https://github
## License
Released under the GPL v2.0 license, see LICENSE.txt for details.
[supported gpio mapping]: https://github.com/hzeller/rpi-rgb-led-matrix/blob/master/lib/Makefile#L19

View file

@ -1,3 +1,4 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Program to aid in the testing of LED matrix chains.
// Author: Tony DiCola
#include <cstdint>
@ -17,13 +18,13 @@ 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.
// 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.
volatile bool running = true;
void printCanvas(Canvas& canvas, int x, int y, const string& message,
int r = 255, int g = 255, int b = 255) {
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) {
@ -35,7 +36,7 @@ void printCanvas(Canvas& canvas, int x, int y, const string& message,
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);
canvas->SetPixel(x, y+j, r, g, b);
}
}
}
@ -44,20 +45,28 @@ void printCanvas(Canvas& canvas, int x, int y, const string& message,
}
}
void sigintHandler(int s) {
static void sigintHandler(int s) {
running = false;
}
static void usage(const char* progname) {
std::cerr << "Usage: " << progname << " [flags] [config-file]" << std::endl;
std::cerr << "Flags:" << std::endl;
rgb_matrix::PrintMatrixFlags(stderr);
}
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");
try {
// Initialize from flags.
rgb_matrix::RGBMatrix::Options matrix_options;
rgb_matrix::RuntimeOptions runtime_options;
if (!rgb_matrix::ParseOptionsFromFlags(&argc, &argv,
&matrix_options, &runtime_options)) {
usage(argv[0]);
return 1;
}
// Load the configuration.
Config config(argv[1]);
Config config(&matrix_options, argc >= 2 ? argv[1] : "/dev/null");
cout << "Using config values: " << endl
<< " display_width: " << config.getDisplayWidth() << endl
<< " display_height: " << config.getDisplayHeight() << endl
@ -67,21 +76,25 @@ int main(int argc, char** argv) {
<< " 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 = CreateMatrixFromOptions(matrix_options, runtime_options);
int panel_rows = config.getParallelCount();
int panel_columns = config.getChainLength();
if (config.hasTransformer()) {
GridTransformer grid = config.getGridTransformer();
canvas->ApplyStaticTransformer(grid);
panel_rows = grid.getRows();
panel_columns = grid.getColumns();
}
// Create canvas and apply GridTransformer.
RGBMatrix canvas(&io, config.getPanelHeight(), config.getChainLength(),
config.getParallelCount());
GridTransformer grid = config.getGridTransformer();
canvas.SetTransformer(&grid);
cout << "Panel rows: " << panel_rows << endl
<< "Panel cols: " << panel_columns << endl;
// 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) {
canvas->Fill(0, 0, 0);
for (int i=0; i<panel_columns; ++i) {
for (int j=0; j<panel_rows; ++j) {
// Compute panel origin position.
int x = i*config.getPanelWidth();
int y = j*config.getPanelHeight();
@ -97,10 +110,12 @@ int main(int argc, char** argv) {
while (running) {
sleep(1);
}
canvas.Clear();
canvas->Clear();
delete canvas;
}
catch (const exception& ex) {
cerr << ex.what() << endl;
usage(argv[0]);
return -1;
}
return 0;

View file

@ -1,3 +1,4 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Program to copy the contents of the Raspberry Pi primary display to LED matrices.
// Author: Tony DiCola
#include <iostream>
@ -19,11 +20,10 @@
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.
// 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.
volatile bool running = true;
// 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.
@ -104,21 +104,33 @@ private:
uint8_t* _screen_data;
};
void sigintHandler(int s) {
static void sigintHandler(int s) {
running = false;
}
static void usage(const char* progname) {
std::cerr << "Usage: " << progname << " [flags] [config-file]" << std::endl;
std::cerr << "Flags:" << std::endl;
rgb_matrix::RGBMatrix::Options matrix_options;
rgb_matrix::RuntimeOptions runtime_options;
runtime_options.drop_privileges = -1; // Need root
rgb_matrix::PrintMatrixFlags(stderr, matrix_options, runtime_options);
}
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");
try {
// Initialize from flags.
rgb_matrix::RGBMatrix::Options matrix_options;
rgb_matrix::RuntimeOptions runtime_options;
runtime_options.drop_privileges = -1; // Need root
if (!rgb_matrix::ParseOptionsFromFlags(&argc, &argv,
&matrix_options, &runtime_options)) {
usage(argv[0]);
return 1;
}
// Load the configuration.
Config config(argv[1]);
// Read additional configuration from config file if it exists
Config config(&matrix_options, argc >= 2 ? argv[1] : "/dev/null");
cout << "Using config values: " << endl
<< " display_width: " << config.getDisplayWidth() << endl
<< " display_height: " << config.getDisplayHeight() << endl
@ -144,18 +156,14 @@ int main(int argc, char** argv) {
y_offset = config.getCropY();
}
// 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.");
}
// Initialize matrix library.
// Create canvas and apply GridTransformer.
RGBMatrix canvas(&io, config.getPanelHeight(), config.getChainLength(),
config.getParallelCount());
GridTransformer grid = config.getGridTransformer();
canvas.SetTransformer(&grid);
canvas.Clear();
RGBMatrix *canvas = CreateMatrixFromOptions(matrix_options, runtime_options);
if (config.hasTransformer()) {
canvas->ApplyStaticTransformer(config.getGridTransformer());
}
canvas->Clear();
// Initialize BCM functions and display capture class.
bcm_host_init();
@ -172,16 +180,18 @@ int main(int argc, char** argv) {
for (int x=0; x<config.getDisplayWidth(); ++x) {
uint8_t red, green, blue;
displayCapture.getPixel(x+x_offset, y+y_offset, &red, &green, &blue);
canvas.SetPixel(x, y, red, green, blue);
canvas->SetPixel(x, y, red, green, blue);
}
}
// Sleep for 25 milliseconds.
// Sleep for 25 milliseconds (40Hz refresh)
usleep(25 * 1000);
}
canvas.Clear();
canvas->Clear();
delete canvas;
}
catch (const exception& ex) {
cerr << ex.what() << endl;
usage(argv[0]);
return -1;
}
return 0;

@ -1 +1 @@
Subproject commit 5cdf1b4954aed4ea8b9d7fb57f3d1f410201408d
Subproject commit 3080a773e771db7489aa49455f58f3dade186949