Initial commit

This commit is contained in:
lukas 2020-12-26 13:23:47 +01:00
commit b6231669cc
58 changed files with 9168 additions and 0 deletions

42
Makefile Normal file
View file

@ -0,0 +1,42 @@
.PHONY: default all clean
RGB_LIB_DISTRIBUTION=../../rpi-rgb-led-matrix
RGB_INCDIR=$(RGB_LIB_DISTRIBUTION)/include
RGB_LIBDIR=$(RGB_LIB_DISTRIBUTION)/lib
RGB_LIBRARY_NAME=librgbmatrix
RGB_LIBRARY=$(RGB_LIBDIR)/lib$(RGB_LIBRARY_NAME).a
LDFLAGS+=-L$(RGB_LIBDIR) -l$(RGB_LIBRARY_NAME) -lrt -lm -lpthread
CC = gcc
CFLAGS = -Wall -pthread
LIBS = -levent -levent_pthreads -lrt -lGL -lGLEW -lglfw $(RGB_LIBRARY_NAME).a -lrt -lm -lpthread -lstdc++
TARGET = pixelnuke
default: CFLAGS += -O2 -flto
default: $(TARGET)
all: default
debug: CFLAGS += -DDEBUG -g
debug: $(TARGET)
OBJECTS = $(patsubst %.c, %.o, $(wildcard *.c))
HEADERS = $(wildcard *.h)
%.o: %.c $(HEADERS)
$(CC) $(CFLAGS) -c $< -o $@
% : %.o $(RGB_LIBRARY)
$(CXX) $< -o $@ $(LDFLAGS)
%.o : %.c
$(CC) -I$(RGB_INCDIR) $(CFLAGS) -c -o $@ $<
.PRECIOUS: $(TARGET) $(OBJECTS)
$(TARGET): $(OBJECTS)
$(CC) $(CFLAGS) $(OBJECTS) -Wall $(LIBS) -o $@
clean:
-rm -f *.o $(TARGET)

52
canvas.h Normal file
View file

@ -0,0 +1,52 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2014 Henner Zeller <h.zeller@acm.org>
//
// 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 version 2.
//
// 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 <http://gnu.org/licenses/gpl-2.0.txt>
#ifndef RPI_CANVAS_H
#define RPI_CANVAS_H
#include <stdint.h>
namespace rgb_matrix {
// An interface for things a Canvas can do. The RGBMatrix implements this
// interface, so you can use it directly wherever a canvas is needed.
//
// This abstraction also allows you to e.g. create delegating
// implementations that do a particular transformation, e.g. re-map
// pixels (as you might lay out the physical RGB matrix in a different way),
// compose images (OR, XOR, transparecy), scale, rotate, anti-alias or
// translate coordinates in a funky way.
//
// It is a good idea to have your applications use the concept of
// a Canvas to write the content to instead of directly using the RGBMatrix.
class Canvas {
public:
virtual ~Canvas() {}
virtual int width() const = 0; // Pixels available in x direction.
virtual int height() const = 0; // Pixels available in y direction.
// Set pixel at coordinate (x,y) with given color. Pixel (0,0) is the
// top left corner.
// Each color is 8 bit (24bpp), 0 black, 255 brightest.
virtual void SetPixel(int x, int y,
uint8_t red, uint8_t green, uint8_t blue) = 0;
// Clear screen to be all black.
virtual void Clear() = 0;
// Fill screen with given 24bpp color.
virtual void Fill(uint8_t red, uint8_t green, uint8_t blue) = 0;
};
} // namespace rgb_matrix
#endif // RPI_CANVAS_H

408
canvaspixel.c Normal file
View file

@ -0,0 +1,408 @@
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <unistd.h> // usleep
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h> //memcpy
#include "canvaspixel.h"
typedef struct CanvasLayer {
GLuint size;
GLenum format;
GLuint tex;
GLuint pbo1;
GLuint pbo2;
GLubyte *data;
size_t mem;
} CanvasLayer;
// Global state
static int canvaspixel_display = -1;
static unsigned int canvaspixel_tex_size = 1024;
static int canvaspixel_width=0;
static int canvaspixel_height=0;
static GLFWwindow* canvaspixel_win;
static CanvasLayer *canvaspixel_base;
static CanvasLayer *canvaspixel_overlay;
pthread_t canvaspixel_thread;
void glfw_error_callback(int error, const char* description) {
printf("GLFW Error: %d %s", error, description);
}
static inline int min(int a, int b) {
return a < b ? a : b;
}
static inline int max(int a, int b) {
return a > b ? a : b;
}
// User callbacks
void (*canvaspixel_on_close_cb)();
void (*canvaspixel_on_resize_cb)();
void (*canvaspixel_on_key_cb)(int, int, int);
static int canvaspixel_do_layout = 0;
static CanvasLayer* canvaspixel_layer_alloc(int size, int alpha) {
CanvasLayer * layer = malloc(sizeof(CanvasLayer));
layer->size = size;
layer->format = alpha ? GL_RGBA : GL_RGB;
layer->mem = size * size * (alpha ? 4 : 3);
layer->data = malloc(sizeof(GLubyte) * layer->mem);
memset(layer->data, 0, layer->mem);
return layer;
}
static void canvaspixel_layer_bind(CanvasLayer* layer) {
// Create texture object
glGenTextures(1, &(layer->tex));
glBindTexture( GL_TEXTURE_2D, layer->tex);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture( GL_TEXTURE_2D, 0);
// Create two PBOs
glGenBuffers(1, &(layer->pbo1));
glBindBuffer( GL_PIXEL_UNPACK_BUFFER, layer->pbo1);
glBufferData( GL_PIXEL_UNPACK_BUFFER, layer->mem, NULL, GL_STREAM_DRAW);
glGenBuffers(1, &(layer->pbo2));
glBindBuffer( GL_PIXEL_UNPACK_BUFFER, layer->pbo2);
glBufferData( GL_PIXEL_UNPACK_BUFFER, layer->mem, NULL, GL_STREAM_DRAW);
glBindBuffer( GL_PIXEL_UNPACK_BUFFER, 0);
}
static void canvaspixel_layer_unbind(CanvasLayer * layer) {
if (layer->tex) {
glDeleteTextures(1, &(layer->tex));
glDeleteBuffers(1, &(layer->pbo1));
glDeleteBuffers(1, &(layer->pbo2));
}
}
static void canvaspixel_layer_free(CanvasLayer * layer) {
canvaspixel_layer_unbind(layer);
free(layer->data);
free(layer);
}
static void canvaspixel_on_resize(GLFWwindow* window, int w, int h);
static void canvaspixel_on_key(GLFWwindow* window, int key, int scancode, int action,
int mods);
static void canvaspixel_on_key(GLFWwindow* window, int key, int scancode, int action,
int mods) {
if (action == GLFW_PRESS && canvaspixel_on_key_cb)
(*canvaspixel_on_key_cb)(key, scancode, mods);
}
static void canvaspixel_on_resize(GLFWwindow* window, int w, int h) {
canvaspixel_width = w;
canvaspixel_height = h;
if(canvaspixel_on_resize_cb)
(*canvaspixel_on_resize_cb)();
}
static void canvaspixel_window_setup() {
if(canvaspixel_win) {
glfwDestroyWindow(canvaspixel_win);
}
glfwWindowHint(GLFW_DOUBLEBUFFER, 1);
if (canvaspixel_display >= 0) {
int mcount;
GLFWmonitor** monitors = glfwGetMonitors(&mcount);
canvaspixel_display %= mcount;
GLFWmonitor* monitor = monitors[canvaspixel_display];
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
glfwWindowHint(GLFW_RED_BITS, mode->redBits);
glfwWindowHint(GLFW_GREEN_BITS, mode->greenBits);
glfwWindowHint(GLFW_BLUE_BITS, mode->blueBits);
glfwWindowHint(GLFW_REFRESH_RATE, mode->refreshRate);
canvaspixel_win = glfwCreateWindow(mode->width, mode->height, "Pixelflut", monitor, NULL);
} else {
canvaspixel_win = glfwCreateWindow(800, 600, "Pixelflut", NULL, NULL);
}
if (!canvaspixel_win) {
printf("Could not create OpenGL context and/or window");
return;
}
glfwMakeContextCurrent(canvaspixel_win);
// TODO: Move GL stuff to better place
//glShadeModel(GL_FLAT); // shading mathod: GL_SMOOTH or GL_FLAT
glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // 4-byte pixel alignment
//glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
//glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
//glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
glDisable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);
glDisable(GL_CULL_FACE);
glEnable(GL_TEXTURE_2D);
//glfwSetWindowUserPointer(canvaspixel_win, (void*) this);
glfwSwapInterval(1);
glfwSetKeyCallback(canvaspixel_win, &canvaspixel_on_key);
glfwSetFramebufferSizeCallback(canvaspixel_win, &canvaspixel_on_resize);
glfwGetFramebufferSize(canvaspixel_win, &canvaspixel_width, &canvaspixel_height);
canvaspixel_on_resize(canvaspixel_win, canvaspixel_width, canvaspixel_height);
canvaspixel_do_layout = 0;
}
static void canvaspixel_draw_layer(CanvasLayer * layer) {
if (!layer || !layer->data)
return;
GLuint pboNext = layer->pbo1;
GLuint pboIndex = layer->pbo2;
layer->pbo1 = pboIndex;
layer->pbo2 = pboNext;
// Switch PBOs on each call. One is updated, one is drawn.
// Update texture from first PBO
glBindBuffer( GL_PIXEL_UNPACK_BUFFER, pboIndex);
glBindTexture( GL_TEXTURE_2D, layer->tex);
glTexImage2D( GL_TEXTURE_2D, 0, layer->format, layer->size, layer->size, 0,
layer->format, GL_UNSIGNED_BYTE, 0);
glBindTexture( GL_TEXTURE_2D, 0);
// Update second PBO with new pixel data
glBindBuffer( GL_PIXEL_UNPACK_BUFFER, pboNext);
GLubyte *ptr = (GLubyte*) glMapBuffer( GL_PIXEL_UNPACK_BUFFER,
GL_WRITE_ONLY);
memcpy(ptr, layer->data, layer->mem);
glUnmapBuffer( GL_PIXEL_UNPACK_BUFFER);
glBindBuffer( GL_PIXEL_UNPACK_BUFFER, 0);
//// Actually draw stuff. The texture should be updated in the meantime.
if (layer->format == GL_RGBA) {
glEnable( GL_BLEND);
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
} else {
glDisable( GL_BLEND);
}
glPushMatrix();
glBindTexture( GL_TEXTURE_2D, layer->tex);
glBegin( GL_QUADS);
glTexCoord2f(0, 0);
glVertex3f(0.0f, 0.0f, 0.0f);
glTexCoord2f(0, 1);
glVertex3f(0.0f, layer->size, 0.0f);
glTexCoord2f(1, 1);
glVertex3f(layer->size, layer->size, 0.0f);
glTexCoord2f(1, 0);
glVertex3f(layer->size, 0.0f, 0.0f);
glEnd();
glBindTexture( GL_TEXTURE_2D, 0);
glPopMatrix();
}
static void* canvaspixel_render_loop(void * arg) {
glfwSetErrorCallback(glfw_error_callback);
if (!glfwInit()) {
puts("GLFW initialization failed");
if(canvaspixel_on_close_cb)
(*canvaspixel_on_close_cb)();
glfwTerminate();
return NULL;
}
canvaspixel_window_setup();
int err = glewInit();
if (err != GLEW_OK) {
puts("GLEW initialization failed");
printf("Error: %s\n", glewGetErrorString(err));
if(canvaspixel_on_close_cb)
(*canvaspixel_on_close_cb)();
return NULL;
}
canvaspixel_layer_bind(canvaspixel_base);
canvaspixel_layer_bind(canvaspixel_overlay);
double last_frame = glfwGetTime();
while ("pixels are coming") {
if (canvaspixel_do_layout) {
canvaspixel_layer_unbind(canvaspixel_base);
canvaspixel_layer_unbind(canvaspixel_overlay);
canvaspixel_window_setup();
canvaspixel_layer_bind(canvaspixel_base);
canvaspixel_layer_bind(canvaspixel_overlay);
}
if (glfwWindowShouldClose(canvaspixel_win))
break;
glfwGetFramebufferSize(canvaspixel_win, &canvaspixel_width, &canvaspixel_height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, canvaspixel_width, canvaspixel_height, 0, -1, 1);
glViewport(0, 0, (GLsizei) canvaspixel_width, (GLsizei) canvaspixel_height);
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
glPushMatrix();
GLuint texSize = canvaspixel_base->size;
if(canvaspixel_width > texSize || canvaspixel_height > texSize) {
float scale = ((float) max(canvaspixel_width, canvaspixel_height)) / (float) texSize;
glScalef(scale, scale, 1);
}
canvaspixel_draw_layer(canvaspixel_base);
// TODO: Overlay is not used yet
//canvaspixel_draw_layer(canvaspixel_overlay);
glPopMatrix();
glfwPollEvents();
glfwSwapBuffers(canvaspixel_win);
double now = glfwGetTime();
double dt = now - last_frame;
last_frame = now;
double sleep = 1.0 / 30 - dt;
if (sleep > 0) {
usleep(sleep * 1000000);
}
}
if(canvaspixel_on_close_cb)
(*canvaspixel_on_close_cb)();
glfwTerminate();
canvaspixel_layer_free(canvaspixel_base);
canvaspixel_layer_free(canvaspixel_overlay);
return NULL;
}
// Public functions
void canvaspixel_start(unsigned int texSize, void (*on_close)()) {
canvaspixel_on_close_cb = on_close;
canvaspixel_tex_size = texSize;
canvaspixel_base = canvaspixel_layer_alloc(canvaspixel_tex_size, 0);
canvaspixel_overlay = canvaspixel_layer_alloc(canvaspixel_tex_size, 1);
if (pthread_create(&canvaspixel_thread, NULL, canvaspixel_render_loop, NULL)) {
puts("Failed to start render thread");
exit(1);
}
}
void canvaspixel_setcb_key(void (*on_key)(int key, int scancode, int mods)) {
canvaspixel_on_key_cb = on_key;
}
void canvaspixel_setcb_resize(void (*on_resize)()) {
canvaspixel_on_resize_cb = on_resize;
}
void canvaspixel_close() {
glfwSetWindowShouldClose(canvaspixel_win, 1);
}
void canvaspixel_fullscreen(int display) {
canvaspixel_display = display;
canvaspixel_do_layout = 1;
}
int canvaspixel_get_display() {
return canvaspixel_display;
}
// Return a pointer to the GLubyte for a given pixel, or NULL for out of bound coordinates.
static inline GLubyte* canvaspixel_offset(CanvasLayer * layer, unsigned int x,
unsigned int y) {
if (x < 0 || y < 0 || x >= layer->size || y >= layer->size || layer->data == NULL)
return NULL;
return layer->data
+ ((y * layer->size) + x) * (layer->format == GL_RGBA ? 4 : 3);
}
void canvaspixel_set_px(unsigned int x, unsigned int y, uint32_t rgba) {
CanvasLayer * layer = canvaspixel_base;
GLubyte* ptr = canvaspixel_offset(layer, x, y);
if (ptr == NULL) {
return;
}
GLubyte r = (rgba & 0xff000000) >> 24;
GLubyte g = (rgba & 0x00ff0000) >> 16;
GLubyte b = (rgba & 0x0000ff00) >> 8;
GLubyte a = (rgba & 0x000000ff) >> 0;
if (layer->format == GL_RGBA) {
ptr[0] = r;
ptr[1] = g;
ptr[2] = b;
ptr[3] = a;
return;
}
if (a == 0) {
return;
}
if (a < 0xff) {
GLuint na = 0xff - a;
r = (a * r + na * (ptr[0])) / 0xff;
g = (a * g + na * (ptr[1])) / 0xff;
b = (a * b + na * (ptr[2])) / 0xff;
}
ptr[0] = r;
ptr[1] = g;
ptr[2] = b;
}
void canvaspixel_fill(uint32_t rgba) {
CanvasLayer * layer = canvaspixel_base;
for(int x=0; x<layer->size; x++)
for(int y=0; y<layer->size; y++)
canvaspixel_set_px(x, y, rgba);
}
void canvaspixel_get_px(unsigned int x, unsigned int y, uint32_t *rgba) {
CanvasLayer * layer = canvaspixel_base;
GLubyte* ptr = canvaspixel_offset(layer, x, y);
if (ptr == NULL) {
*rgba = 0x000000;
} else {
*rgba = (ptr[0] << 24) + (ptr[1] << 16) + (ptr[2] << 8) + 0xff;
}
}
void canvaspixel_get_size(unsigned int *w, unsigned int *h) {
int texSize = canvaspixel_base->size;
if(canvaspixel_width > texSize || canvaspixel_height > texSize) {
float scale = ((float) max(canvaspixel_width, canvaspixel_height)) / texSize;
*w = min(texSize, canvaspixel_width/scale);
*h = min(texSize, canvaspixel_height/scale);
} else {
*w = canvaspixel_width;
*h = canvaspixel_height;
}
}

27
canvaspixel.h Normal file
View file

@ -0,0 +1,27 @@
#ifndef CANVAS_H_
#define CANVAS_H_
#include <stdint.h>
// Open the canvaspixel window and start the gui loop (in a separate thread)
void canvaspixel_start(unsigned int texSize, void (*on_close)());
void canvaspixel_setcb_key(void (*on_key)(int key, int scancode, int mods));
void canvaspixel_setcb_resize(void (*on_resize)());
// Close the canvaspixel window and free any resources and contexts
void canvaspixel_close();
void canvaspixel_fullscreen(int display);
int canvaspixel_get_display();
void canvaspixel_fill(uint32_t rgba);
void canvaspixel_set_px(unsigned int x, unsigned int y, uint32_t rgba);
void canvaspixel_get_px(unsigned int x, unsigned int y, uint32_t *rgba);
// get the current visible canvaspixel size in pixel.
// The actual window might be bigger if scaling is enabled.
void canvaspixel_get_size(unsigned int *width, unsigned int *height);
#endif /* CANVAS_H_ */

BIN
canvaspixel.o Normal file

Binary file not shown.

108
content-streamer.h Normal file
View file

@ -0,0 +1,108 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
//
// Abstractions to read and write FrameCanvas objects to streams. This allows
// you to create canned streams of content with minimal overhead at runtime
// to play with extreme pixel-throughput which also minimizes overheads in
// the Pi to avoid stuttering or brightness glitches.
//
// The disadvantage is, that this represents the full expanded internal
// representation of a frame, so is very large memory wise.
//
// These abstractions are used in util/led-image-viewer.cc to read and
// write such animations to disk. It is also used in util/video-viewer.cc
// to write a version to disk that then can be played with the led-image-viewer.
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string>
namespace rgb_matrix {
class FrameCanvas;
// An abstraction of a data stream.
class StreamIO {
public:
virtual ~StreamIO() {}
// Rewind stream.
virtual void Rewind() = 0;
// Read bytes into buffer. Similar to Posix behavior that allows short reads.
virtual ssize_t Read(void *buf, size_t count) = 0;
// Write bytes from buffer. Similar to Posix behavior that allows short
// writes.
virtual ssize_t Append(const void *buf, size_t count) = 0;
};
class FileStreamIO : public StreamIO {
public:
explicit FileStreamIO(int fd);
~FileStreamIO();
virtual void Rewind();
virtual ssize_t Read(void *buf, size_t count);
virtual ssize_t Append(const void *buf, size_t count);
private:
const int fd_;
};
class MemStreamIO : public StreamIO {
public:
virtual void Rewind();
virtual ssize_t Read(void *buf, size_t count);
virtual ssize_t Append(const void *buf, size_t count);
private:
std::string buffer_; // super simplistic.
size_t pos_;
};
class StreamWriter {
public:
// Does not take ownership of StreamIO
StreamWriter(StreamIO *io);
// Stream out given canvas at the given time. "hold_time_us" indicates
// for how long this frame is to be shown in microseconds.
bool Stream(const FrameCanvas &frame, uint32_t hold_time_us);
private:
void WriteFileHeader(const FrameCanvas &frame, size_t len);
StreamIO *const io_;
bool header_written_;
};
class StreamReader {
public:
// Does not take ownership of StreamIO
StreamReader(StreamIO *io);
~StreamReader();
// Go back to the beginning.
void Rewind();
// Get next frame and its timestamp. Returns 'false' if there is an error
// or end of stream reached..
bool GetNext(FrameCanvas *frame, uint32_t* hold_time_us);
private:
enum State {
STREAM_AT_BEGIN,
STREAM_READING,
STREAM_ERROR,
};
bool ReadFileHeader(const FrameCanvas &frame);
StreamIO *io_;
size_t frame_buf_size_;
State state_;
char *header_frame_buffer_;
};
}

144
graphics.h Normal file
View file

@ -0,0 +1,144 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Very simple graphics library to do simple things.
//
// Might be useful to consider using Cairo instead and just have an interface
// between that and the Canvas. Well, this is a quick set of things to get
// started (and nicely self-contained).
#ifndef RPI_GRAPHICS_H
#define RPI_GRAPHICS_H
#include "canvas.h"
#include <stdint.h>
#include <stddef.h>
#include <map>
namespace rgb_matrix {
struct Color {
Color() : r(0), g(0), b(0) {}
Color(uint8_t rr, uint8_t gg, uint8_t bb) : r(rr), g(gg), b(bb) {}
uint8_t r;
uint8_t g;
uint8_t b;
};
// Font loading bdf files. If this ever becomes more types, just make virtual
// base class.
class Font {
public:
// Initialize font, but it is only usable after LoadFont() has been called.
Font();
~Font();
bool LoadFont(const char *path);
// Return height of font in pixels. Returns -1 if font has not been loaded.
int height() const { return font_height_; }
// Return baseline. Pixels from the topline to the baseline.
int baseline() const { return base_line_; }
// Return width of given character, or -1 if font is not loaded or character
// does not exist.
int CharacterWidth(uint32_t unicode_codepoint) const;
// Draws the unicode character at position "x","y"
// with "color" on "background_color" (background_color can be NULL for
// transparency.
// The "y" position is the baseline of the font.
// If we don't have it in the font, draws the replacement character "<22>" if
// available.
// Returns how much we advance on the screen, which is the width of the
// character or 0 if we didn't draw any chracter.
int DrawGlyph(Canvas *c, int x, int y,
const Color &color, const Color *background_color,
uint32_t unicode_codepoint) const;
// Same without background. Deprecated, use the one above instead.
int DrawGlyph(Canvas *c, int x, int y, const Color &color,
uint32_t unicode_codepoint) const;
// Create a new font derived from this font, which represents an outline
// of the original font, essentially pixels tracing around the original
// letter.
// This can be used in situations in which it is desirable to frame a letter
// in a different color to increase contrast.
// The ownership of the returned pointer is passed to the caller.
Font *CreateOutlineFont() const;
private:
Font(const Font& x); // No copy constructor. Use references or pointer instead.
struct Glyph;
typedef std::map<uint32_t, Glyph*> CodepointGlyphMap;
const Glyph *FindGlyph(uint32_t codepoint) const;
int font_height_;
int base_line_;
CodepointGlyphMap glyphs_;
};
// -- Some utility functions.
// Utility function: set an image from the given buffer containting pixels.
//
// Draw image of size "image_width" and "image_height" from pixel at
// canvas-offset "canvas_offset_x", "canvas_offset_y". Image will be shown
// cropped on the edges if needed.
//
// The canvas offset can be negative, i.e. the image start can be shifted
// outside the image frame on the left/top edge.
//
// The buffer needs to be organized as rows with columns of three bytes
// organized as rgb or bgr. Thus the size of the buffer needs to be exactly
// (3 * image_width * image_height) bytes.
//
// The "image_buffer" parameters contains the data, "buffer_size_bytes" the
// size in bytes.
//
// If "is_bgr" is true, the buffer is treated as BGR pixel arrangement instead
// of RGB.
// Returns 'true' if image was shown within canvas.
bool SetImage(Canvas *c, int canvas_offset_x, int canvas_offset_y,
const uint8_t *image_buffer, size_t buffer_size_bytes,
int image_width, int image_height,
bool is_bgr);
// Draw text, a standard NUL terminated C-string encoded in UTF-8,
// with given "font" at "x","y" with "color".
// "color" always needs to be set (hence it is a reference),
// "background_color" is a pointer to optionally be NULL for transparency.
// "kerning_offset" allows for additional spacing between characters (can be
// negative)
// Returns how many pixels we advanced on the screen.
int DrawText(Canvas *c, const Font &font, int x, int y,
const Color &color, const Color *background_color,
const char *utf8_text, int kerning_offset = 0);
// Same without background. Deprecated, use the one above instead.
int DrawText(Canvas *c, const Font &font, int x, int y, const Color &color,
const char *utf8_text);
// Draw text, a standard NUL terminated C-string encoded in UTF-8,
// with given "font" at "x","y" with "color".
// Draw text as above, but vertically (top down).
// The text is a standard NUL terminated C-string encoded in UTF-8.
// "font, "x", "y", "color" and "background_color" are same as DrawText().
// "kerning_offset" allows for additional spacing between characters (can be
// negative).
// Returns font height to advance up on the screen.
int VerticalDrawText(Canvas *c, const Font &font, int x, int y,
const Color &color, const Color *background_color,
const char *utf8_text, int kerning_offset = 0);
// Draw a circle centered at "x", "y", with a radius of "radius" and with "color"
void DrawCircle(Canvas *c, int x, int y, int radius, const Color &color);
// Draw a line from "x0", "y0" to "x1", "y1" and with "color"
void DrawLine(Canvas *c, int x0, int y0, int x1, int y1, const Color &color);
} // namespace rgb_matrix
#endif // RPI_GRAPHICS_H

52
include/canvas.h Normal file
View file

@ -0,0 +1,52 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2014 Henner Zeller <h.zeller@acm.org>
//
// 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 version 2.
//
// 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 <http://gnu.org/licenses/gpl-2.0.txt>
#ifndef RPI_CANVAS_H
#define RPI_CANVAS_H
#include <stdint.h>
namespace rgb_matrix {
// An interface for things a Canvas can do. The RGBMatrix implements this
// interface, so you can use it directly wherever a canvas is needed.
//
// This abstraction also allows you to e.g. create delegating
// implementations that do a particular transformation, e.g. re-map
// pixels (as you might lay out the physical RGB matrix in a different way),
// compose images (OR, XOR, transparecy), scale, rotate, anti-alias or
// translate coordinates in a funky way.
//
// It is a good idea to have your applications use the concept of
// a Canvas to write the content to instead of directly using the RGBMatrix.
class Canvas {
public:
virtual ~Canvas() {}
virtual int width() const = 0; // Pixels available in x direction.
virtual int height() const = 0; // Pixels available in y direction.
// Set pixel at coordinate (x,y) with given color. Pixel (0,0) is the
// top left corner.
// Each color is 8 bit (24bpp), 0 black, 255 brightest.
virtual void SetPixel(int x, int y,
uint8_t red, uint8_t green, uint8_t blue) = 0;
// Clear screen to be all black.
virtual void Clear() = 0;
// Fill screen with given 24bpp color.
virtual void Fill(uint8_t red, uint8_t green, uint8_t blue) = 0;
};
} // namespace rgb_matrix
#endif // RPI_CANVAS_H

108
include/content-streamer.h Normal file
View file

@ -0,0 +1,108 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
//
// Abstractions to read and write FrameCanvas objects to streams. This allows
// you to create canned streams of content with minimal overhead at runtime
// to play with extreme pixel-throughput which also minimizes overheads in
// the Pi to avoid stuttering or brightness glitches.
//
// The disadvantage is, that this represents the full expanded internal
// representation of a frame, so is very large memory wise.
//
// These abstractions are used in util/led-image-viewer.cc to read and
// write such animations to disk. It is also used in util/video-viewer.cc
// to write a version to disk that then can be played with the led-image-viewer.
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string>
namespace rgb_matrix {
class FrameCanvas;
// An abstraction of a data stream.
class StreamIO {
public:
virtual ~StreamIO() {}
// Rewind stream.
virtual void Rewind() = 0;
// Read bytes into buffer. Similar to Posix behavior that allows short reads.
virtual ssize_t Read(void *buf, size_t count) = 0;
// Write bytes from buffer. Similar to Posix behavior that allows short
// writes.
virtual ssize_t Append(const void *buf, size_t count) = 0;
};
class FileStreamIO : public StreamIO {
public:
explicit FileStreamIO(int fd);
~FileStreamIO();
virtual void Rewind();
virtual ssize_t Read(void *buf, size_t count);
virtual ssize_t Append(const void *buf, size_t count);
private:
const int fd_;
};
class MemStreamIO : public StreamIO {
public:
virtual void Rewind();
virtual ssize_t Read(void *buf, size_t count);
virtual ssize_t Append(const void *buf, size_t count);
private:
std::string buffer_; // super simplistic.
size_t pos_;
};
class StreamWriter {
public:
// Does not take ownership of StreamIO
StreamWriter(StreamIO *io);
// Stream out given canvas at the given time. "hold_time_us" indicates
// for how long this frame is to be shown in microseconds.
bool Stream(const FrameCanvas &frame, uint32_t hold_time_us);
private:
void WriteFileHeader(const FrameCanvas &frame, size_t len);
StreamIO *const io_;
bool header_written_;
};
class StreamReader {
public:
// Does not take ownership of StreamIO
StreamReader(StreamIO *io);
~StreamReader();
// Go back to the beginning.
void Rewind();
// Get next frame and its timestamp. Returns 'false' if there is an error
// or end of stream reached..
bool GetNext(FrameCanvas *frame, uint32_t* hold_time_us);
private:
enum State {
STREAM_AT_BEGIN,
STREAM_READING,
STREAM_ERROR,
};
bool ReadFileHeader(const FrameCanvas &frame);
StreamIO *io_;
size_t frame_buf_size_;
State state_;
char *header_frame_buffer_;
};
}

144
include/graphics.h Normal file
View file

@ -0,0 +1,144 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Very simple graphics library to do simple things.
//
// Might be useful to consider using Cairo instead and just have an interface
// between that and the Canvas. Well, this is a quick set of things to get
// started (and nicely self-contained).
#ifndef RPI_GRAPHICS_H
#define RPI_GRAPHICS_H
#include "canvas.h"
#include <stdint.h>
#include <stddef.h>
#include <map>
namespace rgb_matrix {
struct Color {
Color() : r(0), g(0), b(0) {}
Color(uint8_t rr, uint8_t gg, uint8_t bb) : r(rr), g(gg), b(bb) {}
uint8_t r;
uint8_t g;
uint8_t b;
};
// Font loading bdf files. If this ever becomes more types, just make virtual
// base class.
class Font {
public:
// Initialize font, but it is only usable after LoadFont() has been called.
Font();
~Font();
bool LoadFont(const char *path);
// Return height of font in pixels. Returns -1 if font has not been loaded.
int height() const { return font_height_; }
// Return baseline. Pixels from the topline to the baseline.
int baseline() const { return base_line_; }
// Return width of given character, or -1 if font is not loaded or character
// does not exist.
int CharacterWidth(uint32_t unicode_codepoint) const;
// Draws the unicode character at position "x","y"
// with "color" on "background_color" (background_color can be NULL for
// transparency.
// The "y" position is the baseline of the font.
// If we don't have it in the font, draws the replacement character "<22>" if
// available.
// Returns how much we advance on the screen, which is the width of the
// character or 0 if we didn't draw any chracter.
int DrawGlyph(Canvas *c, int x, int y,
const Color &color, const Color *background_color,
uint32_t unicode_codepoint) const;
// Same without background. Deprecated, use the one above instead.
int DrawGlyph(Canvas *c, int x, int y, const Color &color,
uint32_t unicode_codepoint) const;
// Create a new font derived from this font, which represents an outline
// of the original font, essentially pixels tracing around the original
// letter.
// This can be used in situations in which it is desirable to frame a letter
// in a different color to increase contrast.
// The ownership of the returned pointer is passed to the caller.
Font *CreateOutlineFont() const;
private:
Font(const Font& x); // No copy constructor. Use references or pointer instead.
struct Glyph;
typedef std::map<uint32_t, Glyph*> CodepointGlyphMap;
const Glyph *FindGlyph(uint32_t codepoint) const;
int font_height_;
int base_line_;
CodepointGlyphMap glyphs_;
};
// -- Some utility functions.
// Utility function: set an image from the given buffer containting pixels.
//
// Draw image of size "image_width" and "image_height" from pixel at
// canvas-offset "canvas_offset_x", "canvas_offset_y". Image will be shown
// cropped on the edges if needed.
//
// The canvas offset can be negative, i.e. the image start can be shifted
// outside the image frame on the left/top edge.
//
// The buffer needs to be organized as rows with columns of three bytes
// organized as rgb or bgr. Thus the size of the buffer needs to be exactly
// (3 * image_width * image_height) bytes.
//
// The "image_buffer" parameters contains the data, "buffer_size_bytes" the
// size in bytes.
//
// If "is_bgr" is true, the buffer is treated as BGR pixel arrangement instead
// of RGB.
// Returns 'true' if image was shown within canvas.
bool SetImage(Canvas *c, int canvas_offset_x, int canvas_offset_y,
const uint8_t *image_buffer, size_t buffer_size_bytes,
int image_width, int image_height,
bool is_bgr);
// Draw text, a standard NUL terminated C-string encoded in UTF-8,
// with given "font" at "x","y" with "color".
// "color" always needs to be set (hence it is a reference),
// "background_color" is a pointer to optionally be NULL for transparency.
// "kerning_offset" allows for additional spacing between characters (can be
// negative)
// Returns how many pixels we advanced on the screen.
int DrawText(Canvas *c, const Font &font, int x, int y,
const Color &color, const Color *background_color,
const char *utf8_text, int kerning_offset = 0);
// Same without background. Deprecated, use the one above instead.
int DrawText(Canvas *c, const Font &font, int x, int y, const Color &color,
const char *utf8_text);
// Draw text, a standard NUL terminated C-string encoded in UTF-8,
// with given "font" at "x","y" with "color".
// Draw text as above, but vertically (top down).
// The text is a standard NUL terminated C-string encoded in UTF-8.
// "font, "x", "y", "color" and "background_color" are same as DrawText().
// "kerning_offset" allows for additional spacing between characters (can be
// negative).
// Returns font height to advance up on the screen.
int VerticalDrawText(Canvas *c, const Font &font, int x, int y,
const Color &color, const Color *background_color,
const char *utf8_text, int kerning_offset = 0);
// Draw a circle centered at "x", "y", with a radius of "radius" and with "color"
void DrawCircle(Canvas *c, int x, int y, int radius, const Color &color);
// Draw a line from "x0", "y0" to "x1", "y1" and with "color"
void DrawLine(Canvas *c, int x0, int y0, int x1, int y1, const Color &color);
} // namespace rgb_matrix
#endif // RPI_GRAPHICS_H

394
include/led-matrix-c.h Normal file
View file

@ -0,0 +1,394 @@
/* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*-
* Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
*
* 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 version 2.
*
* 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 <http://gnu.org/licenses/gpl-2.0.txt>
*
* Controlling 16x32 or 32x32 RGB matrixes via GPIO. It allows daisy chaining
* of a string of these, and also connecting a parallel string on newer
* Raspberry Pis with more GPIO pins available.
*
* This is a C-binding (for the C++ library) to allow easy binding and
* integration with other languages. The symbols are exported in librgbmatrix.a
* and librgbmatrix.so. You still need to call the final link with
*
* See examples-api-use/c-example.c for a usage example.
*
*/
#ifndef RPI_RGBMATRIX_C_H
#define RPI_RGBMATRIX_C_H
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
struct RGBLedMatrix;
struct LedCanvas;
struct LedFont;
/**
* Parameters to create a new matrix.
*
* To get the defaults, non-set values have to be initialized to zero, so you
* should zero out this struct before setting anything.
*/
struct RGBLedMatrixOptions {
/*
* Name of the hardware mapping used. If passed NULL here, the default
* is used.
*/
const char *hardware_mapping;
/* The "rows" are the number of rows supported by the display, so 32 or 16.
* Default: 32.
* Corresponding flag: --led-rows
*/
int rows;
/* The "cols" are the number of columns per panel. Typically something
* like 32, but also 64 is possible. Sometimes even 40.
* cols * chain_length is the total length of the display, so you can
* represent a 64 wide display as cols=32, chain=2 or cols=64, chain=1;
* same thing.
* Flag: --led-cols
*/
int cols;
/* The chain_length is the number of displays daisy-chained together
* (output of one connected to input of next). Default: 1
* Corresponding flag: --led-chain
*/
int chain_length;
/* The number of parallel chains connected to the Pi; in old Pis with 26
* GPIO pins, that is 1, in newer Pis with 40 interfaces pins, that can
* also be 2 or 3. The effective number of pixels in vertical direction is
* then thus rows * parallel. Default: 1
* Corresponding flag: --led-parallel
*/
int parallel;
/* Set PWM bits used for output. Default is 11, but if you only deal with
* limited comic-colors, 1 might be sufficient. Lower require less CPU and
* increases refresh-rate.
* Corresponding flag: --led-pwm-bits
*/
int pwm_bits;
/* Change the base time-unit for the on-time in the lowest
* significant bit in nanoseconds.
* Higher numbers provide better quality (more accurate color, less
* ghosting), but have a negative impact on the frame rate.
* Corresponding flag: --led-pwm-lsb-nanoseconds
*/
int pwm_lsb_nanoseconds;
/* The lower bits can be time-dithered for higher refresh rate.
* Corresponding flag: --led-pwm-dither-bits
*/
int pwm_dither_bits;
/* The initial brightness of the panel in percent. Valid range is 1..100
* Corresponding flag: --led-brightness
*/
int brightness;
/* Scan mode: 0=progressive, 1=interlaced
* Corresponding flag: --led-scan-mode
*/
int scan_mode;
/* Default row address type is 0, corresponding to direct setting of the
* row, while row address type 1 is used for panels that only have A/B,
* typically some 64x64 panels
*/
int row_address_type; /* Corresponding flag: --led-row-addr-type */
/* Type of multiplexing. 0 = direct, 1 = stripe, 2 = checker (typical 1:8)
*/
int multiplexing;
/* In case the internal sequence of mapping is not "RGB", this contains the
* real mapping. Some panels mix up these colors.
*/
const char *led_rgb_sequence; /* Corresponding flag: --led-rgb-sequence */
/* A string describing a sequence of pixel mappers that should be applied
* to this matrix. A semicolon-separated list of pixel-mappers with optional
* parameter.
*/
const char *pixel_mapper_config; /* Corresponding flag: --led-pixel-mapper */
/*
* Panel type. Typically just NULL, but certain panels (FM6126) require
* an initialization sequence
*/
const char *panel_type; /* Corresponding flag: --led-panel-type */
/** The following are boolean flags, all off by default **/
/* Allow to use the hardware subsystem to create pulses. This won't do
* anything if output enable is not connected to GPIO 18.
* Corresponding flag: --led-hardware-pulse
*/
char disable_hardware_pulsing;
char show_refresh_rate; /* Corresponding flag: --led-show-refresh */
char inverse_colors; /* Corresponding flag: --led-inverse */
/* Limit refresh rate of LED panel. This will help on a loaded system
* to keep a constant refresh rate. <= 0 for no limit.
*/
int limit_refresh_rate_hz; /* Corresponding flag: --led-limit-refresh */
};
/**
* Runtime options to simplify doing common things for many programs such as
* dropping privileges and becoming a daemon.
*/
struct RGBLedRuntimeOptions {
int gpio_slowdown; // 0 = no slowdown. Flag: --led-slowdown-gpio
// ----------
// If the following options are set to disabled with -1, they are not
// even offered via the command line flags.
// ----------
// Thre are three possible values here
// -1 : don't leave choise of becoming daemon to the command line parsing.
// If set to -1, the --led-daemon option is not offered.
// 0 : do not becoma a daemon, run in forgreound (default value)
// 1 : become a daemon, run in background.
//
// If daemon is disabled (= -1), the user has to call
// RGBMatrix::StartRefresh() manually once the matrix is created, to leave
// the decision to become a daemon
// after the call (which requires that no threads have been started yet).
// In the other cases (off or on), the choice is already made, so the thread
// is conveniently already started for you.
int daemon; // -1 disabled. 0=off, 1=on. Flag: --led-daemon
// Drop privileges from 'root' to 'daemon' once the hardware is initialized.
// This is usually a good idea unless you need to stay on elevated privs.
int drop_privileges; // -1 disabled. 0=off, 1=on. flag: --led-drop-privs
// By default, the gpio is initialized for you, but if you run on a platform
// not the Raspberry Pi, this will fail. If you don't need to access GPIO
// e.g. you want to just create a stream output (see content-streamer.h),
// set this to false.
bool do_gpio_init;
};
/**
* Universal way to create and initialize a matrix.
* The "options" struct (if not NULL) contains all default configuration values
* chosen by the programmer to create the matrix.
*
* If "argc" and "argv" are provided, this function also reads command line
* flags provided, that then can override any of the defaults given.
* The arguments that have been used from the command line are removed from
* the argv list (and argc is adjusted) - that way these don't mess with your
* own command line handling.
*
* The actual options used are filled back into the "options" struct if not
* NULL.
*
* Usage:
* ----------------
* int main(int argc, char **argv) {
* struct RGBLedMatrixOptions options;
* memset(&options, 0, sizeof(options));
* options.rows = 32; // You can set defaults if you want.
* options.chain_length = 1;
* struct RGBLedMatrix *matrix = led_matrix_create_from_options(&options,
* &argc, &argv);
* if (matrix == NULL) {
* led_matrix_print_flags(stderr);
* return 1;
* }
* // do additional commandline handling; then use matrix...
* }
* ----------------
*/
struct RGBLedMatrix *led_matrix_create_from_options(
struct RGBLedMatrixOptions *options, int *argc, char ***argv);
/* Same, but does not modify the argv array. */
struct RGBLedMatrix *led_matrix_create_from_options_const_argv(
struct RGBLedMatrixOptions *options, int argc, char **argv);
/**
* The way to completely initialize your matrix without using command line
* flags to initialize some things.
*
* The actual options used are filled back into the "options" and "rt_options"
* struct if not NULL. If they are null, the default value is used.
*
* Usage:
* ----------------
* int main(int argc, char **argv) {
* struct RGBLedMatrixOptions options;
* struct RGBLedRuntimeOptions rt_options;
* memset(&options, 0, sizeof(options));
* memset(&rt_options, 0, sizeof(rt_options));
* options.rows = 32; // You can set defaults if you want.
* options.chain_length = 1;
* rt_options.gpio_slowdown = 4;
* struct RGBLedMatrix *matrix = led_matrix_create_from_options_and_rt_options(&options, &rt_options);
* if (matrix == NULL) {
* return 1;
* }
* // do additional commandline handling; then use matrix...
* }
* ----------------
*/
struct RGBLedMatrix *led_matrix_create_from_options_and_rt_options(
struct RGBLedMatrixOptions *opts, struct RGBLedRuntimeOptions * rt_opts);
/**
* Print available LED matrix options.
*/
void led_matrix_print_flags(FILE *out);
/**
* Simple form of led_matrix_create_from_options() with just the few
* main options. Returns NULL if that was not possible.
* The "rows" are the number of rows supported by the display, so 32, 16 or 8.
*
* Number of "chained_display"s tells many of these are daisy-chained together
* (output of one connected to input of next).
*
* The "parallel_display" number determines if there is one or two displays
* connected in parallel to the GPIO port - this only works with newer
* Raspberry Pi that have 40 interface pins.
*
* This creates a realtime thread and requires root access to access the GPIO
* pins.
* So if you run this in a daemon, this should be called after becoming a
* daemon (as fork/exec stops threads) and before dropping privileges.
*/
struct RGBLedMatrix *led_matrix_create(int rows, int chained, int parallel);
/**
* Stop matrix and free memory.
* Always call before the end of the program to properly reset the hardware
*/
void led_matrix_delete(struct RGBLedMatrix *matrix);
/**
* Get active canvas from LED matrix for you to draw on.
* Ownership of returned pointer stays with the matrix, don't free().
*/
struct LedCanvas *led_matrix_get_canvas(struct RGBLedMatrix *matrix);
/** Return size of canvas. */
void led_canvas_get_size(const struct LedCanvas *canvas,
int *width, int *height);
/** Set pixel at (x, y) with color (r,g,b). */
void led_canvas_set_pixel(struct LedCanvas *canvas, int x, int y,
uint8_t r, uint8_t g, uint8_t b);
/** Clear screen (black). */
void led_canvas_clear(struct LedCanvas *canvas);
/** Fill matrix with given color. */
void led_canvas_fill(struct LedCanvas *canvas, uint8_t r, uint8_t g, uint8_t b);
/*** API to provide double-buffering. ***/
/**
* Create a new canvas to be used with led_matrix_swap_on_vsync()
* Ownership of returned pointer stays with the matrix, don't free().
*/
struct LedCanvas *led_matrix_create_offscreen_canvas(struct RGBLedMatrix *matrix);
/**
* Swap the given canvas (created with create_offscreen_canvas) with the
* currently active canvas on vsync (blocks until vsync is reached).
* Returns the previously active canvas. So with that, you can create double
* buffering:
*
* struct LedCanvas *offscreen = led_matrix_create_offscreen_canvas(...);
* led_canvas_set_pixel(offscreen, ...); // not shown until swap-on-vsync
* offscreen = led_matrix_swap_on_vsync(matrix, offscreen);
* // The returned buffer, assigned to offscreen, is now the inactive buffer
* // fill, then swap again.
*/
struct LedCanvas *led_matrix_swap_on_vsync(struct RGBLedMatrix *matrix,
struct LedCanvas *canvas);
uint8_t led_matrix_get_brightness(struct RGBLedMatrix *matrix);
void led_matrix_set_brightness(struct RGBLedMatrix *matrix, uint8_t brightness);
// Utility function: set an image from the given buffer containting pixels.
//
// Draw image of size "image_width" and "image_height" from pixel at
// canvas-offset "canvas_offset_x", "canvas_offset_y". Image will be shown
// cropped on the edges if needed.
//
// The canvas offset can be negative, i.e. the image start can be shifted
// outside the image frame on the left/top edge.
//
// The buffer needs to be organized as rows with columns of three bytes
// organized as rgb or bgr. Thus the size of the buffer needs to be exactly
// (3 * image_width * image_height) bytes.
//
// The "image_buffer" parameters contains the data, "buffer_size_bytes" the
// size in bytes.
//
// If "is_bgr" is 1, the buffer is treated as BGR pixel arrangement instead
// of RGB with is_bgr = 0.
void set_image(struct LedCanvas *c, int canvas_offset_x, int canvas_offset_y,
const uint8_t *image_buffer, size_t buffer_size_bytes,
int image_width, int image_height,
char is_bgr);
// Load a font given a path to a font file containing a bdf font.
struct LedFont *load_font(const char *bdf_font_file);
// Read the baseline of a font
int baseline_font(struct LedFont *font);
// Read the height of a font
int height_font(struct LedFont *font);
// Creates an outline font based on an existing font instance
struct LedFont *create_outline_font(struct LedFont *font);
// Delete a font originally created from load_font.
void delete_font(struct LedFont *font);
int draw_text(struct LedCanvas *c, struct LedFont *font, int x, int y,
uint8_t r, uint8_t g, uint8_t b,
const char *utf8_text, int kerning_offset);
int vertical_draw_text(struct LedCanvas *c, struct LedFont *font, int x, int y,
uint8_t r, uint8_t g, uint8_t b,
const char *utf8_text, int kerning_offset);
void draw_circle(struct LedCanvas *c, int x, int y, int radius,
uint8_t r, uint8_t g, uint8_t b);
void draw_line(struct LedCanvas *c, int x0, int y0, int x1, int y1,
uint8_t r, uint8_t g, uint8_t b);
#ifdef __cplusplus
} // extern C
#endif
#endif

110
include/pixel-mapper.h Normal file
View file

@ -0,0 +1,110 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2018 Henner Zeller <h.zeller@acm.org>
//
// 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 version 2.
//
// 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 <http://gnu.org/licenses/gpl-2.0.txt>
#ifndef RGBMATRIX_PIXEL_MAPPER
#define RGBMATRIX_PIXEL_MAPPER
#include <string>
#include <vector>
namespace rgb_matrix {
// A pixel mapper is a way for you to map pixels of LED matrixes to a different
// layout. If you have an implementation of a PixelMapper, you can give it
// to the RGBMatrix::ApplyPixelMapper(), which then presents you a canvas
// that has the new "visible_width", "visible_height".
class PixelMapper {
public:
virtual ~PixelMapper() {}
// Get the name of this PixelMapper. Each PixelMapper needs to have a name
// so that it can be referred to with command line flags.
virtual const char *GetName() const = 0;
// Pixel mappers receive the chain and parallel information and
// might receive optional user-parameters, e.g. from command line flags.
//
// This is a single string containing the parameters.
// You can be used from simple scalar parameters, such as the angle for
// the rotate transformer, or more complex parameters that describe a mapping
// of panels for instance.
// Keep it concise (as people will give parameters on the command line) and
// don't use semicolons in your string (as they are
// used to separate pixel mappers on the command line).
//
// For instance, the rotate transformer is invoked like this
// --led-pixel-mapper=rotate:90
// And the parameter that is passed to SetParameter() is "90".
//
// Returns 'true' if parameter was parsed successfully.
virtual bool SetParameters(int chain, int parallel,
const char *parameter_string) {
return true;
}
// Given a underlying matrix (width, height), returns the
// visible (width, height) after the mapping.
// E.g. a 90 degree rotation might map matrix=(64, 32) -> visible=(32, 64)
// Some multiplexing matrices will double the height and half the width.
//
// While not technically necessary, one would expect that the number of
// pixels stay the same, so
// matrix_width * matrix_height == (*visible_width) * (*visible_height);
//
// Returns boolean "true" if the mapping can be successfully done with this
// mapper.
virtual bool GetSizeMapping(int matrix_width, int matrix_height,
int *visible_width, int *visible_height)
const = 0;
// Map where a visible pixel (x,y) is mapped to the underlying matrix (x,y).
//
// To be convienently stateless, the first parameters are the full
// matrix width and height.
//
// So for many multiplexing methods this means to map a panel to a double
// length and half height panel (32x16 -> 64x8).
// The logic_x, logic_y are output parameters and guaranteed not to be
// nullptr.
virtual void MapVisibleToMatrix(int matrix_width, int matrix_height,
int visible_x, int visible_y,
int *matrix_x, int *matrix_y) const = 0;
};
// This is a place to register PixelMappers globally. If you register your
// PixelMapper before calling RGBMatrix::CreateFromFlags(), the named
// PixelMapper is available in the --led-pixel-mapper options.
//
// Note, you don't _have_ to register your mapper, you can always call
// RGBMatrix::ApplyPixelMapper() directly. Registering is for convenience and
// commandline-flag support.
//
// There are a few standard mappers registered by default.
void RegisterPixelMapper(PixelMapper *mapper);
// Get a list of the names of available pixel mappers.
std::vector<std::string> GetAvailablePixelMappers();
// Given a name (e.g. "rotate") and a parameter (e.g. "90"), return the
// parametrized PixelMapper with that name. Returns NULL if mapper
// can not be found or parameter is invalid.
// Ownership of the returned object is _NOT_ transferred to the caller.
// Current available mappers are "U-mapper" and "Rotate". The "Rotate"
// gets a parameter denoting the angle.
const PixelMapper *FindPixelMapper(const char *name,
int chain, int parallel,
const char *parameter = NULL);
} // namespace rgb_matrix
#endif // RGBMATRIX_PIXEL_MAPPER

86
include/thread.h Normal file
View file

@ -0,0 +1,86 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
//
// 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 version 2.
//
// 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 <http://gnu.org/licenses/gpl-2.0.txt>
#ifndef RPI_THREAD_H
#define RPI_THREAD_H
#include <stdint.h>
#include <pthread.h>
namespace rgb_matrix {
// Simple thread abstraction.
class Thread {
public:
Thread();
// The destructor waits for Run() to return so make sure it does.
virtual ~Thread();
// Wait for the Run() method to return.
void WaitStopped();
// Start thread. If realtime_priority is > 0, then this will be a
// thread with SCHED_FIFO and the given priority.
// If cpu_affinity is set !=, chooses the given bitmask of CPUs
// this thread should have an affinity to.
// On a Raspberry Pi 1, this doesn't matter, as there is only one core,
// Raspberry Pi 2 can has 4 cores, so any combination of (1<<0) .. (1<<3) is
// valid.
virtual void Start(int realtime_priority = 0, uint32_t cpu_affinity_mask = 0);
// Override this to do the work.
//
// This will be called in a thread once Start() has been called. You typically
// will have an endless loop doing stuff.
//
// It is a good idea to provide a way to communicate to the thread that
// it should stop (see ThreadedCanvasManipulator for an example)
virtual void Run() = 0;
private:
static void *PthreadCallRun(void *tobject);
bool started_;
pthread_t thread_;
};
// Non-recursive Mutex.
class Mutex {
public:
Mutex() { pthread_mutex_init(&mutex_, NULL); }
~Mutex() { pthread_mutex_destroy(&mutex_); }
void Lock() { pthread_mutex_lock(&mutex_); }
void Unlock() { pthread_mutex_unlock(&mutex_); }
// Wait on condition. If "timeout_ms" is < 0, it waits forever, otherwise
// until timeout is reached.
// Returns 'true' if condition is met, 'false', if wait timed out.
bool WaitOn(pthread_cond_t *cond, long timeout_ms = -1);
private:
pthread_mutex_t mutex_;
};
// Useful RAII wrapper around mutex.
class MutexLock {
public:
MutexLock(Mutex *m) : mutex_(m) { mutex_->Lock(); }
~MutexLock() { mutex_->Unlock(); }
private:
Mutex *const mutex_;
};
} // end namespace rgb_matrix
#endif // RPI_THREAD_H

View file

@ -0,0 +1,103 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2014 Henner Zeller <h.zeller@acm.org>
//
// 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 version 2.
//
// 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 <http://gnu.org/licenses/gpl-2.0.txt>
// Utility base class for continuously updating the canvas.
// Note: considering removing this, as real applications likely have something
// similar, but this might not be quite usable.
// Since it is just a few lines of code, it is probably better
// implemented in the application for readability.
//
// So for simplicity of the API, consider ThreadedCanvasManipulator deprecated.
#ifndef RPI_THREADED_CANVAS_MANIPULATOR_H
#define RPI_THREADED_CANVAS_MANIPULATOR_H
#include "thread.h"
#include "canvas.h"
namespace rgb_matrix {
//
// Typically, your programs will crate a canvas and then updating the image
// in a loop. If you want to do stuff in parallel, then this utility class
// helps you doing that. Also a demo for how to use the Thread class.
//
// Extend it, then just implement Run(). Example:
/*
class MyCrazyDemo : public ThreadedCanvasManipulator {
public:
MyCrazyDemo(Canvas *canvas) : ThreadedCanvasManipulator(canvas) {}
virtual void Run() {
unsigned char c;
while (running()) {
// Calculate the next frame.
c++;
for (int x = 0; x < canvas()->width(); ++x) {
for (int y = 0; y < canvas()->height(); ++y) {
canvas()->SetPixel(x, y, c, c, c);
}
}
usleep(15 * 1000);
}
}
};
// Later, in your main method.
RGBMatrix *matrix = RGBMatrix::CreateFromOptions(...);
MyCrazyDemo *demo = new MyCrazyDemo(matrix);
demo->Start(); // Start doing things.
// This now runs in the background, you can do other things here,
// e.g. aquiring new data or simply wait. But for waiting, you wouldn't
// need a thread in the first place.
demo->Stop();
delete demo;
*/
class ThreadedCanvasManipulator : public Thread {
public:
ThreadedCanvasManipulator(Canvas *m) : running_(false), canvas_(m) {}
virtual ~ThreadedCanvasManipulator() { Stop(); }
virtual void Start(int realtime_priority=0, uint32_t affinity_mask=0) {
{
MutexLock l(&mutex_);
running_ = true;
}
Thread::Start(realtime_priority, affinity_mask);
}
// Stop the thread at the next possible time Run() checks the running_ flag.
void Stop() {
MutexLock l(&mutex_);
running_ = false;
}
// Implement this and run while running() returns true.
virtual void Run() = 0;
protected:
inline Canvas *canvas() { return canvas_; }
inline bool running() {
MutexLock l(&mutex_);
return running_;
}
private:
Mutex mutex_;
bool running_;
Canvas *const canvas_;
};
} // namespace rgb_matrix
#endif // RPI_THREADED_CANVAS_MANIPULATOR_H

394
led-matrix-c.h Normal file
View file

@ -0,0 +1,394 @@
/* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*-
* Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
*
* 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 version 2.
*
* 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 <http://gnu.org/licenses/gpl-2.0.txt>
*
* Controlling 16x32 or 32x32 RGB matrixes via GPIO. It allows daisy chaining
* of a string of these, and also connecting a parallel string on newer
* Raspberry Pis with more GPIO pins available.
*
* This is a C-binding (for the C++ library) to allow easy binding and
* integration with other languages. The symbols are exported in librgbmatrix.a
* and librgbmatrix.so. You still need to call the final link with
*
* See examples-api-use/c-example.c for a usage example.
*
*/
#ifndef RPI_RGBMATRIX_C_H
#define RPI_RGBMATRIX_C_H
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
struct RGBLedMatrix;
struct LedCanvas;
struct LedFont;
/**
* Parameters to create a new matrix.
*
* To get the defaults, non-set values have to be initialized to zero, so you
* should zero out this struct before setting anything.
*/
struct RGBLedMatrixOptions {
/*
* Name of the hardware mapping used. If passed NULL here, the default
* is used.
*/
const char *hardware_mapping;
/* The "rows" are the number of rows supported by the display, so 32 or 16.
* Default: 32.
* Corresponding flag: --led-rows
*/
int rows;
/* The "cols" are the number of columns per panel. Typically something
* like 32, but also 64 is possible. Sometimes even 40.
* cols * chain_length is the total length of the display, so you can
* represent a 64 wide display as cols=32, chain=2 or cols=64, chain=1;
* same thing.
* Flag: --led-cols
*/
int cols;
/* The chain_length is the number of displays daisy-chained together
* (output of one connected to input of next). Default: 1
* Corresponding flag: --led-chain
*/
int chain_length;
/* The number of parallel chains connected to the Pi; in old Pis with 26
* GPIO pins, that is 1, in newer Pis with 40 interfaces pins, that can
* also be 2 or 3. The effective number of pixels in vertical direction is
* then thus rows * parallel. Default: 1
* Corresponding flag: --led-parallel
*/
int parallel;
/* Set PWM bits used for output. Default is 11, but if you only deal with
* limited comic-colors, 1 might be sufficient. Lower require less CPU and
* increases refresh-rate.
* Corresponding flag: --led-pwm-bits
*/
int pwm_bits;
/* Change the base time-unit for the on-time in the lowest
* significant bit in nanoseconds.
* Higher numbers provide better quality (more accurate color, less
* ghosting), but have a negative impact on the frame rate.
* Corresponding flag: --led-pwm-lsb-nanoseconds
*/
int pwm_lsb_nanoseconds;
/* The lower bits can be time-dithered for higher refresh rate.
* Corresponding flag: --led-pwm-dither-bits
*/
int pwm_dither_bits;
/* The initial brightness of the panel in percent. Valid range is 1..100
* Corresponding flag: --led-brightness
*/
int brightness;
/* Scan mode: 0=progressive, 1=interlaced
* Corresponding flag: --led-scan-mode
*/
int scan_mode;
/* Default row address type is 0, corresponding to direct setting of the
* row, while row address type 1 is used for panels that only have A/B,
* typically some 64x64 panels
*/
int row_address_type; /* Corresponding flag: --led-row-addr-type */
/* Type of multiplexing. 0 = direct, 1 = stripe, 2 = checker (typical 1:8)
*/
int multiplexing;
/* In case the internal sequence of mapping is not "RGB", this contains the
* real mapping. Some panels mix up these colors.
*/
const char *led_rgb_sequence; /* Corresponding flag: --led-rgb-sequence */
/* A string describing a sequence of pixel mappers that should be applied
* to this matrix. A semicolon-separated list of pixel-mappers with optional
* parameter.
*/
const char *pixel_mapper_config; /* Corresponding flag: --led-pixel-mapper */
/*
* Panel type. Typically just NULL, but certain panels (FM6126) require
* an initialization sequence
*/
const char *panel_type; /* Corresponding flag: --led-panel-type */
/** The following are boolean flags, all off by default **/
/* Allow to use the hardware subsystem to create pulses. This won't do
* anything if output enable is not connected to GPIO 18.
* Corresponding flag: --led-hardware-pulse
*/
char disable_hardware_pulsing;
char show_refresh_rate; /* Corresponding flag: --led-show-refresh */
char inverse_colors; /* Corresponding flag: --led-inverse */
/* Limit refresh rate of LED panel. This will help on a loaded system
* to keep a constant refresh rate. <= 0 for no limit.
*/
int limit_refresh_rate_hz; /* Corresponding flag: --led-limit-refresh */
};
/**
* Runtime options to simplify doing common things for many programs such as
* dropping privileges and becoming a daemon.
*/
struct RGBLedRuntimeOptions {
int gpio_slowdown; // 0 = no slowdown. Flag: --led-slowdown-gpio
// ----------
// If the following options are set to disabled with -1, they are not
// even offered via the command line flags.
// ----------
// Thre are three possible values here
// -1 : don't leave choise of becoming daemon to the command line parsing.
// If set to -1, the --led-daemon option is not offered.
// 0 : do not becoma a daemon, run in forgreound (default value)
// 1 : become a daemon, run in background.
//
// If daemon is disabled (= -1), the user has to call
// RGBMatrix::StartRefresh() manually once the matrix is created, to leave
// the decision to become a daemon
// after the call (which requires that no threads have been started yet).
// In the other cases (off or on), the choice is already made, so the thread
// is conveniently already started for you.
int daemon; // -1 disabled. 0=off, 1=on. Flag: --led-daemon
// Drop privileges from 'root' to 'daemon' once the hardware is initialized.
// This is usually a good idea unless you need to stay on elevated privs.
int drop_privileges; // -1 disabled. 0=off, 1=on. flag: --led-drop-privs
// By default, the gpio is initialized for you, but if you run on a platform
// not the Raspberry Pi, this will fail. If you don't need to access GPIO
// e.g. you want to just create a stream output (see content-streamer.h),
// set this to false.
bool do_gpio_init;
};
/**
* Universal way to create and initialize a matrix.
* The "options" struct (if not NULL) contains all default configuration values
* chosen by the programmer to create the matrix.
*
* If "argc" and "argv" are provided, this function also reads command line
* flags provided, that then can override any of the defaults given.
* The arguments that have been used from the command line are removed from
* the argv list (and argc is adjusted) - that way these don't mess with your
* own command line handling.
*
* The actual options used are filled back into the "options" struct if not
* NULL.
*
* Usage:
* ----------------
* int main(int argc, char **argv) {
* struct RGBLedMatrixOptions options;
* memset(&options, 0, sizeof(options));
* options.rows = 32; // You can set defaults if you want.
* options.chain_length = 1;
* struct RGBLedMatrix *matrix = led_matrix_create_from_options(&options,
* &argc, &argv);
* if (matrix == NULL) {
* led_matrix_print_flags(stderr);
* return 1;
* }
* // do additional commandline handling; then use matrix...
* }
* ----------------
*/
struct RGBLedMatrix *led_matrix_create_from_options(
struct RGBLedMatrixOptions *options, int *argc, char ***argv);
/* Same, but does not modify the argv array. */
struct RGBLedMatrix *led_matrix_create_from_options_const_argv(
struct RGBLedMatrixOptions *options, int argc, char **argv);
/**
* The way to completely initialize your matrix without using command line
* flags to initialize some things.
*
* The actual options used are filled back into the "options" and "rt_options"
* struct if not NULL. If they are null, the default value is used.
*
* Usage:
* ----------------
* int main(int argc, char **argv) {
* struct RGBLedMatrixOptions options;
* struct RGBLedRuntimeOptions rt_options;
* memset(&options, 0, sizeof(options));
* memset(&rt_options, 0, sizeof(rt_options));
* options.rows = 32; // You can set defaults if you want.
* options.chain_length = 1;
* rt_options.gpio_slowdown = 4;
* struct RGBLedMatrix *matrix = led_matrix_create_from_options_and_rt_options(&options, &rt_options);
* if (matrix == NULL) {
* return 1;
* }
* // do additional commandline handling; then use matrix...
* }
* ----------------
*/
struct RGBLedMatrix *led_matrix_create_from_options_and_rt_options(
struct RGBLedMatrixOptions *opts, struct RGBLedRuntimeOptions * rt_opts);
/**
* Print available LED matrix options.
*/
void led_matrix_print_flags(FILE *out);
/**
* Simple form of led_matrix_create_from_options() with just the few
* main options. Returns NULL if that was not possible.
* The "rows" are the number of rows supported by the display, so 32, 16 or 8.
*
* Number of "chained_display"s tells many of these are daisy-chained together
* (output of one connected to input of next).
*
* The "parallel_display" number determines if there is one or two displays
* connected in parallel to the GPIO port - this only works with newer
* Raspberry Pi that have 40 interface pins.
*
* This creates a realtime thread and requires root access to access the GPIO
* pins.
* So if you run this in a daemon, this should be called after becoming a
* daemon (as fork/exec stops threads) and before dropping privileges.
*/
struct RGBLedMatrix *led_matrix_create(int rows, int chained, int parallel);
/**
* Stop matrix and free memory.
* Always call before the end of the program to properly reset the hardware
*/
void led_matrix_delete(struct RGBLedMatrix *matrix);
/**
* Get active canvas from LED matrix for you to draw on.
* Ownership of returned pointer stays with the matrix, don't free().
*/
struct LedCanvas *led_matrix_get_canvas(struct RGBLedMatrix *matrix);
/** Return size of canvas. */
void led_canvas_get_size(const struct LedCanvas *canvas,
int *width, int *height);
/** Set pixel at (x, y) with color (r,g,b). */
void led_canvas_set_pixel(struct LedCanvas *canvas, int x, int y,
uint8_t r, uint8_t g, uint8_t b);
/** Clear screen (black). */
void led_canvas_clear(struct LedCanvas *canvas);
/** Fill matrix with given color. */
void led_canvas_fill(struct LedCanvas *canvas, uint8_t r, uint8_t g, uint8_t b);
/*** API to provide double-buffering. ***/
/**
* Create a new canvas to be used with led_matrix_swap_on_vsync()
* Ownership of returned pointer stays with the matrix, don't free().
*/
struct LedCanvas *led_matrix_create_offscreen_canvas(struct RGBLedMatrix *matrix);
/**
* Swap the given canvas (created with create_offscreen_canvas) with the
* currently active canvas on vsync (blocks until vsync is reached).
* Returns the previously active canvas. So with that, you can create double
* buffering:
*
* struct LedCanvas *offscreen = led_matrix_create_offscreen_canvas(...);
* led_canvas_set_pixel(offscreen, ...); // not shown until swap-on-vsync
* offscreen = led_matrix_swap_on_vsync(matrix, offscreen);
* // The returned buffer, assigned to offscreen, is now the inactive buffer
* // fill, then swap again.
*/
struct LedCanvas *led_matrix_swap_on_vsync(struct RGBLedMatrix *matrix,
struct LedCanvas *canvas);
uint8_t led_matrix_get_brightness(struct RGBLedMatrix *matrix);
void led_matrix_set_brightness(struct RGBLedMatrix *matrix, uint8_t brightness);
// Utility function: set an image from the given buffer containting pixels.
//
// Draw image of size "image_width" and "image_height" from pixel at
// canvas-offset "canvas_offset_x", "canvas_offset_y". Image will be shown
// cropped on the edges if needed.
//
// The canvas offset can be negative, i.e. the image start can be shifted
// outside the image frame on the left/top edge.
//
// The buffer needs to be organized as rows with columns of three bytes
// organized as rgb or bgr. Thus the size of the buffer needs to be exactly
// (3 * image_width * image_height) bytes.
//
// The "image_buffer" parameters contains the data, "buffer_size_bytes" the
// size in bytes.
//
// If "is_bgr" is 1, the buffer is treated as BGR pixel arrangement instead
// of RGB with is_bgr = 0.
void set_image(struct LedCanvas *c, int canvas_offset_x, int canvas_offset_y,
const uint8_t *image_buffer, size_t buffer_size_bytes,
int image_width, int image_height,
char is_bgr);
// Load a font given a path to a font file containing a bdf font.
struct LedFont *load_font(const char *bdf_font_file);
// Read the baseline of a font
int baseline_font(struct LedFont *font);
// Read the height of a font
int height_font(struct LedFont *font);
// Creates an outline font based on an existing font instance
struct LedFont *create_outline_font(struct LedFont *font);
// Delete a font originally created from load_font.
void delete_font(struct LedFont *font);
int draw_text(struct LedCanvas *c, struct LedFont *font, int x, int y,
uint8_t r, uint8_t g, uint8_t b,
const char *utf8_text, int kerning_offset);
int vertical_draw_text(struct LedCanvas *c, struct LedFont *font, int x, int y,
uint8_t r, uint8_t g, uint8_t b,
const char *utf8_text, int kerning_offset);
void draw_circle(struct LedCanvas *c, int x, int y, int radius,
uint8_t r, uint8_t g, uint8_t b);
void draw_line(struct LedCanvas *c, int x0, int y0, int x1, int y1,
uint8_t r, uint8_t g, uint8_t b);
#ifdef __cplusplus
} // extern C
#endif
#endif

504
led-matrix.h Normal file
View file

@ -0,0 +1,504 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
//
// 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 version 2.
//
// 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 <http://gnu.org/licenses/gpl-2.0.txt>
// Controlling 16x32 or 32x32 RGB matrixes via GPIO. It allows daisy chaining
// of a string of these, and also connecting a parallel string on newer
// Raspberry Pis with more GPIO pins available.
#ifndef RPI_RGBMATRIX_H
#define RPI_RGBMATRIX_H
#include <stdint.h>
#include <stddef.h>
#include <string>
#include <vector>
#include "canvas.h"
#include "thread.h"
#include "pixel-mapper.h"
namespace rgb_matrix {
class RGBMatrix;
class FrameCanvas; // Canvas for Double- and Multibuffering
struct RuntimeOptions;
// The RGB matrix provides the framebuffer and the facilities to constantly
// update the LED matrix.
//
// This implement the Canvas interface that represents the display with
// (led_cols * chained_displays)x(rows * parallel_displays) pixels.
//
// If can do multi-buffering using the CreateFrameCanvas() and SwapOnVSync()
// methods. This is useful for animations and to prevent tearing.
//
// If you arrange the panels in a different way in the physical space, write
// a CanvasTransformer that does coordinate remapping and which should be added
// to the transformers, like with UArrangementTransformer in demo-main.cc.
class RGBMatrix : public Canvas {
public:
// Options to initialize the RGBMatrix. Also see the main README.md for
// detailed descriptions of the command line flags.
struct Options {
Options(); // Creates a default option set.
// Validate the options and possibly output a message to string. If
// "err" is NULL, outputs validation problems to stderr.
// Returns 'true' if all options look good.
bool Validate(std::string *err) const;
// Name of the hardware mapping. Something like "regular" or "adafruit-hat"
const char *hardware_mapping;
// The "rows" are the number
// of rows supported by the display, so 32 or 16. Default: 32.
// Flag: --led-rows
int rows;
// The "cols" are the number of columns per panel. Typically something
// like 32, but also 64 is possible. Sometimes even 40.
// cols * chain_length is the total length of the display, so you can
// represent a 64 wide display as cols=32, chain=2 or cols=64, chain=1;
// same thing, but more convenient to think of.
// Flag: --led-cols
int cols;
// The chain_length is the number of displays daisy-chained together
// (output of one connected to input of next). Default: 1
// Flag: --led-chain
int chain_length;
// The number of parallel chains connected to the Pi; in old Pis with 26
// GPIO pins, that is 1, in newer Pis with 40 interfaces pins, that can
// also be 2 or 3. The effective number of pixels in vertical direction is
// then thus rows * parallel. Default: 1
// Flag: --led-parallel
int parallel;
// Set PWM bits used for output. Default is 11, but if you only deal with
// limited comic-colors, 1 might be sufficient. Lower require less CPU and
// increases refresh-rate.
// Flag: --led-pwm-bits
int pwm_bits;
// Change the base time-unit for the on-time in the lowest
// significant bit in nanoseconds.
// Higher numbers provide better quality (more accurate color, less
// ghosting), but have a negative impact on the frame rate.
// Flag: --led-pwm-lsb-nanoseconds
int pwm_lsb_nanoseconds;
// The lower bits can be time-dithered for higher refresh rate.
// Flag: --led-pwm-dither-bits
int pwm_dither_bits;
// The initial brightness of the panel in percent. Valid range is 1..100
// Default: 100
// Flag: --led-brightness
int brightness;
// Scan mode: 0=progressive, 1=interlaced.
// Flag: --led-scan-mode
int scan_mode;
// Default row address type is 0, corresponding to direct setting of the
// row, while row address type 1 is used for panels that only have A/B,
// typically some 64x64 panels
int row_address_type; // Flag --led-row-addr-type
// Type of multiplexing. 0 = direct, 1 = stripe, 2 = checker,...
// Flag: --led-multiplexing
int multiplexing;
// Disable the PWM hardware subsystem to create pulses.
// Typically, you don't want to disable hardware pulsing, this is mostly
// for debugging and figuring out if there is interference with the
// sound system.
// This won't do anything if output enable is not connected to GPIO 18 in
// non-standard wirings.
bool disable_hardware_pulsing; // Flag: --led-hardware-pulse
// Show refresh rate on the terminal for debugging and tweaking purposes.
bool show_refresh_rate; // Flag: --led-show-refresh
// Some panels have inversed colors.
bool inverse_colors; // Flag: --led-inverse
// In case the internal sequence of mapping is not "RGB", this contains the
// real mapping. Some panels mix up these colors. String of length three
// which has to contain all characters R, G and B.
const char *led_rgb_sequence; // Flag: --led-rgb-sequence
// A string describing a sequence of pixel mappers that should be applied
// to this matrix. A semicolon-separated list of pixel-mappers with optional
// parameter.
const char *pixel_mapper_config; // Flag: --led-pixel-mapper
// Panel type. Typically an empty string or NULL, but some panels need
// a particular initialization sequence, so this is used for that.
// This can be e.g. "FM6126A" for that particular panel type.
const char *panel_type; // Flag: --led-panel-type
// Limit refresh rate of LED panel. This will help on a loaded system
// to keep a constant refresh rate. <= 0 for no limit.
int limit_refresh_rate_hz; // Flag: --led-limit-refresh
};
// Factory to create a matrix. Additional functionality includes dropping
// privileges and becoming a daemon.
// Returns NULL, if there was a problem (a message then is written to stderr).
static RGBMatrix *CreateFromOptions(const Options &options,
const RuntimeOptions &runtime_options);
// A factory that parses your main() commandline flags to read options
// meant to configure the the matrix and returns a freshly allocated matrix.
//
// Optionally, you can pass in option structs with a couple of defaults
// which are used unless overwritten on the command line.
// A matrix is created and returned; also the options structs are
// updated to reflect the values that were used and set on the command line.
//
// If you allow the user to start a daemon with --led-daemon, make sure to
// call this function before you have started any threads, so early on in
// main() (see RuntimeOptions documentation).
//
// Note, the permissions are dropped by default from 'root' to 'daemon', so
// if you are required to stay root after this, disable this option in
// the default RuntimeOptions (set drop_privileges = -1).
// Returns NULL, if there was a problem (a message then is written to stderr).
static RGBMatrix *CreateFromFlags(int *argc, char ***argv,
RGBMatrix::Options *default_options = NULL,
RuntimeOptions *default_runtime_opts = NULL,
bool remove_consumed_flags = true);
// Stop matrix, delete all resources.
virtual ~RGBMatrix();
// -- Canvas interface. These write to the active FrameCanvas
// (see documentation in canvas.h)
//
// Since this is updating the canvas that is currently displayed, this
// might result in tearing.
// Prefer using a FrameCanvas and do double-buffering, see section below.
virtual int width() const;
virtual int height() const;
virtual void SetPixel(int x, int y,
uint8_t red, uint8_t green, uint8_t blue);
virtual void Clear();
virtual void Fill(uint8_t red, uint8_t green, uint8_t blue);
// -- Double- and Multibuffering.
// Create a new buffer to be used for multi-buffering. The returned new
// Buffer implements a Canvas with the same size of thie RGBMatrix.
// You can use it to draw off-screen on it, then swap it with the active
// buffer using SwapOnVSync(). That would be classic double-buffering.
//
// You can also create as many FrameCanvas as you like and for instance use
// them to pre-fill scenes of an animation for fast playback later.
//
// The ownership of the created Canvases remains with the RGBMatrix, so you
// don't have to worry about deleting them (but you also don't want to create
// more than needed as this will fill up your memory as they are only deleted
// when the RGBMatrix is deleted).
FrameCanvas *CreateFrameCanvas();
// This method waits to the next VSync and swaps the active buffer with the
// supplied buffer. The formerly active buffer is returned.
//
// If you pass in NULL, the active buffer is returned, but it won't be
// replaced with NULL. You can use the NULL-behavior to just wait on
// VSync or to retrieve the initial buffer when preparing a multi-buffer
// animation.
//
// The optional "framerate_fraction" parameter allows to choose which
// multiple of the global frame-count to use. So it slows down your animation
// to an exact integer fraction of the refresh rate.
// Default is 1, so immediately next available frame.
// (Say you have 140Hz refresh rate, then a value of 5 would give you an
// 28Hz animation, nicely locked to the refresh-rate).
// If you combine this with Options::limit_refresh_rate_hz you can create
// time-correct animations.
FrameCanvas *SwapOnVSync(FrameCanvas *other, unsigned framerate_fraction = 1);
// -- Setting shape and behavior of matrix.
// Apply a pixel mapper. This is used to re-map pixels according to some
// scheme implemented by the PixelMapper. Does _not_ take ownership of the
// mapper. Mapper can be NULL, in which case nothing happens.
// Returns a boolean indicating if this was successful.
bool ApplyPixelMapper(const PixelMapper *mapper);
// Note, there used to be ApplyStaticTransformer(), which has been deprecated
// since 2018 and changed to a compile-time option, then finally removed
// in 2020. Use PixelMapper instead, which is simpler and more intuitive.
// Set PWM bits used for output. Default is 11, but if you only deal with
// limited comic-colors, 1 might be sufficient. Lower require less CPU and
// increases refresh-rate.
//
// Returns boolean to signify if value was within range.
//
// This sets the PWM bits for the current active FrameCanvas and future
// ones that are created with CreateFrameCanvas().
bool SetPWMBits(uint8_t value);
uint8_t pwmbits(); // return the pwm-bits of the currently active buffer.
// Map brightness of output linearly to input with CIE1931 profile.
void set_luminance_correct(bool on);
bool luminance_correct() const;
// Set brightness in percent for all created FrameCanvas. 1%..100%.
// This will only affect newly set pixels.
void SetBrightness(uint8_t brightness);
uint8_t brightness();
//-- GPIO interaction.
// This library uses the GPIO pins to drive the matrix; this is a safe way
// to request the 'remaining' bits to be used for user purposes.
// Request user readable GPIO bits.
// This function allows you to request pins you'd like to read with
// AwaitInputChange().
// Only bits that are not already in use for reading or wrtiting
// by the matrix are allowed.
// Input is a bitmap of all the GPIO bits you're interested in; returns all
// the bits that are actually available.
uint64_t RequestInputs(uint64_t all_interested_bits);
// This function will return whenever the GPIO input pins
// change (pins that are not already in use for output, that is) or the
// timeout is reached. You need to have reserved the inputs with
// matrix->RequestInputs(...) first (e.g.
// matrix->RequestInputs((1<<25)|(1<<24));
//
// A positive timeout waits the given amount of milliseconds for a change
// (e.g. a button-press) to occur; if there is no change, it will just
// return the last value.
// If you just want to know how the pins are right now, call with zero
// timeout.
// A negative number waits forever and will only return if there is a change.
//
// This function only samples between display refreshes so polling some
// input does not generate flicker and provide a convenient change interface.
//
// Returns the bitmap of all GPIO input pins.
uint64_t AwaitInputChange(int timeout_ms);
// Request user writable GPIO bits.
// This allows to request a bitmap of GPIO-bits to be used by the user for
// writing.
// Only bits that are not already in use for reading or wrtiting
// by the matrix are allowed.
// Returns the subset bits that are _actually_ available,
uint64_t RequestOutputs(uint64_t output_bits);
// Set the user-settable bits according to output bits.
void OutputGPIO(uint64_t output_bits);
// Legacy way to set gpio pins. We're not doing this anymore but need to
// be source-compatible with old calls of the form
// matrix->gpio()->RequestInputs(...)
//
// Don't use, use AwaitInputChange() directly.
RGBMatrix *gpio() __attribute__((deprecated)) { return this; }
//-- Rarely needed
// Start the refresh thread.
// This is only needed if you chose RuntimeOptions::daemon = -1 (see below),
// otherwise the refresh thread is already started.
bool StartRefresh();
private:
class Impl;
RGBMatrix(Impl *impl) : impl_(impl) {}
Impl *const impl_;
};
namespace internal {
class Framebuffer;
}
class FrameCanvas : public Canvas {
public:
// Set PWM bits used for this Frame.
// Simple comic-colors, 1 might be sufficient (111 RGB, i.e. 8 colors).
// Lower require less CPU.
// Returns boolean to signify if value was within range.
bool SetPWMBits(uint8_t value);
uint8_t pwmbits();
// Map brightness of output linearly to input with CIE1931 profile.
void set_luminance_correct(bool on);
bool luminance_correct() const;
void SetBrightness(uint8_t brightness);
uint8_t brightness();
//-- Serialize()/Deserialize() are fast ways to store and re-create a canvas.
// Provides a pointer to a buffer of the internal representation to
// be copied out for later Deserialize().
//
// Returns a "data" pointer and the data "len" in the given out-paramters;
// the content can be copied from there by the caller.
//
// Note, the content is not simply RGB, it is the opaque and platform
// specific representation which allows to make deserialization very fast.
// It is also bigger than just RGB; if you want to store it somewhere,
// using compression is a good idea.
void Serialize(const char **data, size_t *len) const;
// Load data previously stored with Serialize(). Needs to be restored into
// a FrameCanvas with exactly the same settings (rows, chain, transformer,...)
// as serialized.
// Returns 'false' if size is unexpected.
// This method should only be called if FrameCanvas is off-screen.
bool Deserialize(const char *data, size_t len);
// Copy content from other FrameCanvas owned by the same RGBMatrix.
void CopyFrom(const FrameCanvas &other);
// -- Canvas interface.
virtual int width() const;
virtual int height() const;
virtual void SetPixel(int x, int y,
uint8_t red, uint8_t green, uint8_t blue);
virtual void Clear();
virtual void Fill(uint8_t red, uint8_t green, uint8_t blue);
private:
friend class RGBMatrix;
FrameCanvas(internal::Framebuffer *frame) : frame_(frame){}
virtual ~FrameCanvas(); // Any FrameCanvas is owned by RGBMatrix.
internal::Framebuffer *framebuffer() { return frame_; }
internal::Framebuffer *const frame_;
};
// Runtime options to simplify doing common things for many programs such as
// dropping privileges and becoming a daemon.
struct RuntimeOptions {
RuntimeOptions();
int gpio_slowdown; // 0 = no slowdown. Flag: --led-slowdown-gpio
// ----------
// If the following options are set to disabled with -1, they are not
// even offered via the command line flags.
// ----------
// Thre are three possible values here
// -1 : don't leave choise of becoming daemon to the command line parsing.
// If set to -1, the --led-daemon option is not offered.
// 0 : do not becoma a daemon, run in forgreound (default value)
// 1 : become a daemon, run in background.
//
// If daemon is disabled (= -1), the user has to call
// RGBMatrix::StartRefresh() manually once the matrix is created, to leave
// the decision to become a daemon
// after the call (which requires that no threads have been started yet).
// In the other cases (off or on), the choice is already made, so the thread
// is conveniently already started for you.
int daemon; // -1 disabled. 0=off, 1=on. Flag: --led-daemon
// Drop privileges from 'root' to 'daemon' once the hardware is initialized.
// This is usually a good idea unless you need to stay on elevated privs.
int drop_privileges; // -1 disabled. 0=off, 1=on. flag: --led-drop-privs
// By default, the gpio is initialized for you, but if you run on a platform
// not the Raspberry Pi, this will fail. If you don't need to access GPIO
// e.g. you want to just create a stream output (see content-streamer.h),
// set this to false.
bool do_gpio_init;
};
// Convenience utility functions to read standard rgb-matrix flags and create
// a RGBMatrix. Commandline flags are something like --led-rows, --led-chain,
// --led-parallel. See output of PrintMatrixFlags() for all available options
// and detailed description in
// https://github.com/hzeller/rpi-rgb-led-matrix#changing-parameters-via-command-line-flags
//
// Example use:
/*
using rgb_matrix::RGBMatrix;
int main(int argc, char **argv) {
RGBMatrix::Options led_options;
rgb_matrix::RuntimeOptions runtime;
// Set defaults
led_options.chain_length = 3;
led_options.show_refresh_rate = true;
runtime.drop_privileges = 1;
if (!rgb_matrix::ParseOptionsFromFlags(&argc, &argv, &led_options, &runtime)) {
rgb_matrix::PrintMatrixFlags(stderr);
return 1;
}
// Do your own command line handling with the remaining flags.
while (getopt()) {...}
// Looks like we're ready to start
RGBMatrix *matrix = RGBMatrix::CreateFromOptions(led_options, runtime);
if (matrix == NULL) {
return 1;
}
// .. now use matrix
delete matrix; // Make sure to delete it in the end to switch off LEDs.
return 0;
}
*/
// This parses the flags from argv and updates the structs with the parsed-out
// values. Structs can be NULL if you are not interested in it.
//
// The recongized flags are removed from argv if "remove_consumed_flags" is
// true; this simplifies your command line processing for the remaining options.
//
// Returns 'true' on success, 'false' if there was flag parsing problem.
bool ParseOptionsFromFlags(int *argc, char ***argv,
RGBMatrix::Options *default_options,
RuntimeOptions *rt_options,
bool remove_consumed_flags = true);
// Show all the available options in a style that can be used in a --help
// output on the command line.
void PrintMatrixFlags(FILE *out,
const RGBMatrix::Options &defaults = RGBMatrix::Options(),
const RuntimeOptions &rt_opt = RuntimeOptions());
// Legacy version of RGBMatrix::CreateFromOptions()
inline RGBMatrix *CreateMatrixFromOptions(
const RGBMatrix::Options &options,
const RuntimeOptions &runtime_options) {
return RGBMatrix::CreateFromOptions(options, runtime_options);
}
// Legacy version of RGBMatrix::CreateFromFlags()
inline RGBMatrix *CreateMatrixFromFlags(
int *argc, char ***argv,
RGBMatrix::Options *default_options = NULL,
RuntimeOptions *default_runtime_opts = NULL,
bool remove_consumed_flags = true) {
return RGBMatrix::CreateFromFlags(argc, argv,
default_options, default_runtime_opts,
remove_consumed_flags);
}
} // end namespace rgb_matrix
#endif // RPI_RGBMATRIX_H

3
lib/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
compiler-flags
librgbmatrix.a
librgbmatrix.so.1

196
lib/Makefile Normal file
View file

@ -0,0 +1,196 @@
# Creating RGB matrix library
# When you link this library with your binary, you need to add -lrt -lm -lpthread
# So
# -lrgbmatrix
##
OBJECTS=gpio.o led-matrix.o options-initialize.o framebuffer.o \
thread.o bdf-font.o graphics.o led-matrix-c.o hardware-mapping.o \
pixel-mapper.o multiplex-mappers.o \
content-streamer.o
TARGET=librgbmatrix
###
# After you change any of the following DEFINES, make sure to 'make' again.
#
# ########### NOTE ###########
# all of these options can now can be set programmatically and
# via command line flags as well. No real need to change them in the Makefile.
# (So be prepared for these to be removed at some point)
###
# There are several different pinouts for various breakout boards that uses
# this library. If you are using the described pinout in the toplevel README.md
# or the standard active-3 breakout board, then 'regular' is the one you'd like
# to use.
#
# Adafruit also made a breakout board, if you want to use that, choose
# 'adafruit-hat'
#
# These are the choices
# regular # Following this project wiring and using these PCBs
# adafruit-hat # If you have a RGB matrix HAT from Adafruit
# adafruit-hat-pwm # If you have an Adafruit HAT with PWM hardware mod.
# regular-pi1 # If you have an old Pi1 and regular didn't work.
# classic # (deprecated) Classic Pi1/2/. Not used anymore.
# classic-pi1 # (deprecated) Classic pinout on Rasperry Pi 1
HARDWARE_DESC?=regular
# If you see that your display is inverse, you might have a matrix variant
# has uses inverse logic for the RGB bits. In that case: uncomment this.
# Flag: --led-inverse
#DEFINES+=-DINVERSE_RGB_DISPLAY_COLORS
# For curiosity reasons and while tweaking values for LSB_PWM_NANOSECONDS,
# uncomment to see refresh rate in terminal.
# Flag: --led-show-refresh
#DEFINES+=-DSHOW_REFRESH_RATE
# For low refresh rates below 100Hz (e.g. a lot of panels), the eye will notice
# some flicker. With this option enabled, the refreshed lines are interleaved,
# so it is less noticeable. But looks less pleasant with fast eye movements.
# Flag: --led-scan-mode=1
#DEFINES+=-DRGB_SCAN_INTERLACED=1
# The signal can be too fast for some LED panels, in particular with newer
# (faster) Raspberry Pi 2s - in that case, the LED matrix only shows garbage.
# This allows to slow down the GPIO for these cases.
#
# Set to 1 for RPi2 or RPi3 (default below), because they are typically
# faster than the panels can digest.
#
# Set to 0 (or comment out) for RPi1, that are slow enough.
#
# Sometimes, you even have to give RGB_SLOWDOWN_GPIO=2 or even 3 for
# particularly slow panels or bad signal cable situations. If that happens, you
# typically should double check cables and add TTL level converter if you
# haven't.
# Flag: --led-slowdown-gpio
#DEFINES+=-DRGB_SLOWDOWN_GPIO=1
# This allows to change the base time-unit for the on-time in the lowest
# significant bit in nanoseconds.
# Higher numbers provide better quality (more accurate color, less ghosting),
# but have a negative impact on the frame rate.
#
# For the same frame-rate, displays with higher multiplexing (e.g. 1:16 or 1:32)
# require lower values.
#
# Good values for full-color display (PWM=11) are somewhere between 100 and 300.
#
# If you you use reduced bit color (e.g. PWM=1 for 8 colors like for text),
# then higher values might be good to minimize ghosting (and you can afford
# that, because lower PWM values result in higher frame-rates).
#
# How to decide ? Just leave the default if things are fine. If you see
# ghosting in high-contrast applications (e.g. text), increase the value.
# If you want to tweak, watch the framerate (-DSHOW_FRAME_RATE) while playing
# with this number and the PWM values.
# Flag: --led-pwm-lsb-nanoseconds
#DEFINES+=-DLSB_PWM_NANOSECONDS=130
# This is to debug problems with the hardware pulse generation. The PWM hardware
# module is also used by Raspberry Pi sound system, so there might be
# interference. Note, you typically don't want the hardware pulses disabled, as
# the image will have visible brightness glitches; but for debugging, this is
# a good choice.
# Flag: --led-no-hardware-pulses
#DEFINES+=-DDISABLE_HARDWARE_PULSES
# This allows to fix the refresh rate to a particular refresh time in
# microseconds.
#
# This can be used to mitigate some situations in which you have a rare
# faint flicker, which can happen due to hardware events (network access)
# or other situations such as other IO or heavy memory access by other
# processes (all of which seem to break the isolation we request from the
# kernel. You did set isolcpus=3 right ?)
# You trade a slightly slower refresh rate and display brightness for less
# visible flicker situations.
#
# For this to calibrate, run your program for a while with --led-show-refresh
# and watch the line that shows the refresh time and the maximum microseconds
# for a frame observed. The maximum number is updated whenever the frame
# refresh take a little bit longer. So wait a while until that value doesn't
# change anymore (at least a minute, so that you catch tasks that happen once
# a minute). Some value might read e.g.
# 204.6Hz max: 5133usec
# Now take this maximum value you see there (here: 5133) and put in
# this define (don't forget to remove the # in front).
#
# The refresh rate will now be adapted to always have this amount of time
# between frames, so faster refreshes will be slowed down, but the occasional
# delayed frame will fit into the time-window as well, thus reducing visible
# brightness fluctuations.
#
# You can play with value a little and reduce until you find a good balance
# between refresh rate (which is reduce the higher this value is) and
# flicker suppression (which is better with higher values).
# Flag: --led-limit-refresh
#DEFINES+=-DFIXED_FRAME_MICROSECONDS=5000
# Enable wide 64 bit GPIO offered with the compute module.
# This will use more memory to internally represent the frame buffer, so
# caches can't be utilized as much.
# So only switch this on if you really use the compute module and use more
# than 3 parallel chains.
# (this is untested right now, waiting for hardware to arrive for testing)
#DEFINES+=-DENABLE_WIDE_GPIO_COMPUTE_MODULE
# ---- Pinout options for hardware variants; usually no change needed here ----
# Uncomment if you want to use the Adafruit HAT with stable PWM timings.
# The newer version of this library allows for much more stable (less flicker)
# output, but it does not work with the Adafruit HAT unless you do a
# simple hardware hack on them:
# connect GPIO 4 (old OE) with 18 (the new OE); there are
# convenient solder holes labeled 4 and 18 on the Adafruit HAT, pretty
# close together.
# Then you can set the flag --led-gpio-mapping=adafruit-hat-pwm
# .. or uncomment the following line.
#HARDWARE_DESC=adafruit-hat-pwm
# Typically, a Hub75 panel is split in two half displays, so that a 1:16
# multiplexing actually multiplexes over two half displays and gives 32 lines.
# There are some other displays out there that you might experiment with
# that are internally wired to only have one sub-panel. In that case you might
# want to try this define to get a more reasonable canvas mapping.
# This option is typically _not_ needed, only use when you attempt to connect
# some oddball old (typically one-colored) display, such as Hub12.
#DEFINES+=-DONLY_SINGLE_SUB_PANEL
# If someone gives additional values on the make commandline e.g.
# make USER_DEFINES="-DSHOW_REFRESH_RATE"
DEFINES+=$(USER_DEFINES)
DEFINES+=-DDEFAULT_HARDWARE='"$(HARDWARE_DESC)"'
INCDIR=../include
CFLAGS=-W -Wall -Wextra -Wno-unused-parameter -O3 -g -fPIC $(DEFINES)
CXXFLAGS=$(CFLAGS) -fno-exceptions -std=c++11
all : $(TARGET).a $(TARGET).so.1
$(TARGET).a : $(OBJECTS)
$(AR) rcs $@ $^
$(TARGET).so.1 : $(OBJECTS)
$(CXX) -shared -Wl,-soname,$@ -o $@ $^ -lpthread -lrt -lm -lpthread
led-matrix.o: led-matrix.cc $(INCDIR)/led-matrix.h
thread.o : thread.cc $(INCDIR)/thread.h
framebuffer.o: framebuffer.cc framebuffer-internal.h
graphics.o: graphics.cc utf8-internal.h
%.o : %.cc compiler-flags
$(CXX) -I$(INCDIR) $(CXXFLAGS) -c -o $@ $<
%.o : %.c compiler-flags
$(CC) -I$(INCDIR) $(CFLAGS) -c -o $@ $<
clean:
rm -f $(OBJECTS) $(TARGET).a $(TARGET).so.1
compiler-flags: FORCE
@echo '$(CXX) $(CXXFLAGS)' | cmp -s - $@ || echo '$(CXX) $(CXXFLAGS)' > $@
.PHONY: FORCE

188
lib/bdf-font.cc Normal file
View file

@ -0,0 +1,188 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2014 Henner Zeller <h.zeller@acm.org>
//
// 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 version 2.
//
// 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 <http://gnu.org/licenses/gpl-2.0.txt>
// Some old g++ installations need this macro to be defined for PRIx64.
#ifndef __STDC_FORMAT_MACROS
# define __STDC_FORMAT_MACROS
#endif
#include <inttypes.h>
#include "graphics.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
// The little question-mark box "<22>" for unknown code.
static const uint32_t kUnicodeReplacementCodepoint = 0xFFFD;
// Bitmap for one row. This limits the number of available columns.
// Make wider if running into trouble.
typedef uint64_t rowbitmap_t;
namespace rgb_matrix {
struct Font::Glyph {
int device_width, device_height;
int width, height;
int x_offset, y_offset;
rowbitmap_t bitmap[0]; // contains 'height' elements.
};
Font::Font() : font_height_(-1), base_line_(0) {}
Font::~Font() {
for (CodepointGlyphMap::iterator it = glyphs_.begin();
it != glyphs_.end(); ++it) {
free(it->second);
}
}
// TODO: that might not be working for all input files yet.
bool Font::LoadFont(const char *path) {
if (!path || !*path) return false;
FILE *f = fopen(path, "r");
if (f == NULL)
return false;
uint32_t codepoint;
char buffer[1024];
int dummy;
Glyph tmp;
Glyph *current_glyph = NULL;
int row = 0;
int bitmap_shift = 0;
while (fgets(buffer, sizeof(buffer), f)) {
if (sscanf(buffer, "FONTBOUNDINGBOX %d %d %d %d",
&dummy, &font_height_, &dummy, &base_line_) == 4) {
base_line_ += font_height_;
}
else if (sscanf(buffer, "ENCODING %ud", &codepoint) == 1) {
// parsed.
}
else if (sscanf(buffer, "DWIDTH %d %d", &tmp.device_width, &tmp.device_height
) == 2) {
// parsed.
}
else if (sscanf(buffer, "BBX %d %d %d %d", &tmp.width, &tmp.height,
&tmp.x_offset, &tmp.y_offset) == 4) {
current_glyph = (Glyph*) malloc(sizeof(Glyph)
+ tmp.height * sizeof(rowbitmap_t));
*current_glyph = tmp;
// We only get number of bytes large enough holding our width. We want
// it always left-aligned.
bitmap_shift =
8 * (sizeof(rowbitmap_t) - ((current_glyph->width + 7) / 8))
- current_glyph->x_offset;
row = -1; // let's not start yet, wait for BITMAP
}
else if (strncmp(buffer, "BITMAP", strlen("BITMAP")) == 0) {
row = 0;
}
else if (current_glyph && row >= 0 && row < current_glyph->height
&& (sscanf(buffer, "%" PRIx64, &current_glyph->bitmap[row]) == 1)) {
current_glyph->bitmap[row] <<= bitmap_shift;
row++;
}
else if (strncmp(buffer, "ENDCHAR", strlen("ENDCHAR")) == 0) {
if (current_glyph && row == current_glyph->height) {
free(glyphs_[codepoint]); // just in case there was one.
glyphs_[codepoint] = current_glyph;
current_glyph = NULL;
}
}
}
fclose(f);
return true;
}
Font *Font::CreateOutlineFont() const {
Font *r = new Font();
const int kBorder = 1;
r->font_height_ = font_height_ + 2*kBorder;
r->base_line_ = base_line_ + kBorder;
for (CodepointGlyphMap::const_iterator it = glyphs_.begin();
it != glyphs_.end(); ++it) {
const Glyph *orig = it->second;
const int height = orig->height + 2 * kBorder;
const size_t alloc_size = sizeof(Glyph) + height * sizeof(rowbitmap_t);
Glyph *const tmp_glyph = (Glyph*) calloc(1, alloc_size);
tmp_glyph->width = orig->width + 2*kBorder;
tmp_glyph->height = height;
tmp_glyph->device_width = orig->device_width + 2*kBorder;
tmp_glyph->device_height = height;
tmp_glyph->y_offset = orig->y_offset - kBorder;
// TODO: we don't really need bounding box, right ?
const rowbitmap_t fill_pattern = 0b111;
const rowbitmap_t start_mask = 0b010;
// Fill the border
for (int h = 0; h < orig->height; ++h) {
rowbitmap_t fill = fill_pattern;
rowbitmap_t orig_bitmap = orig->bitmap[h] >> kBorder;
for (rowbitmap_t m = start_mask; m; m <<= 1, fill <<= 1) {
if (orig_bitmap & m) {
tmp_glyph->bitmap[h+kBorder-1] |= fill;
tmp_glyph->bitmap[h+kBorder+0] |= fill;
tmp_glyph->bitmap[h+kBorder+1] |= fill;
}
}
}
// Remove original font again.
for (int h = 0; h < orig->height; ++h) {
rowbitmap_t orig_bitmap = orig->bitmap[h] >> kBorder;
tmp_glyph->bitmap[h+kBorder] &= ~orig_bitmap;
}
r->glyphs_[it->first] = tmp_glyph;
}
return r;
}
const Font::Glyph *Font::FindGlyph(uint32_t unicode_codepoint) const {
CodepointGlyphMap::const_iterator found = glyphs_.find(unicode_codepoint);
if (found == glyphs_.end())
return NULL;
return found->second;
}
int Font::CharacterWidth(uint32_t unicode_codepoint) const {
const Glyph *g = FindGlyph(unicode_codepoint);
return g ? g->device_width : -1;
}
int Font::DrawGlyph(Canvas *c, int x_pos, int y_pos,
const Color &color, const Color *bgcolor,
uint32_t unicode_codepoint) const {
const Glyph *g = FindGlyph(unicode_codepoint);
if (g == NULL) g = FindGlyph(kUnicodeReplacementCodepoint);
if (g == NULL) return 0;
y_pos = y_pos - g->height - g->y_offset;
for (int y = 0; y < g->height; ++y) {
const rowbitmap_t row = g->bitmap[y];
rowbitmap_t x_mask = (1LL<<63);
for (int x = 0; x < g->device_width; ++x, x_mask >>= 1) {
if (row & x_mask) {
c->SetPixel(x_pos + x, y_pos + y, color.r, color.g, color.b);
} else if (bgcolor) {
c->SetPixel(x_pos + x, y_pos + y, bgcolor->r, bgcolor->g, bgcolor->b);
}
}
}
return g->device_width;
}
int Font::DrawGlyph(Canvas *c, int x_pos, int y_pos, const Color &color,
uint32_t unicode_codepoint) const {
return DrawGlyph(c, x_pos, y_pos, color, NULL, unicode_codepoint);
}
} // namespace rgb_matrix

BIN
lib/bdf-font.o Normal file

Binary file not shown.

203
lib/content-streamer.cc Normal file
View file

@ -0,0 +1,203 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
#include "content-streamer.h"
#include "led-matrix.h"
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include "gpio-bits.h"
namespace rgb_matrix {
// Pre-c++11 helper
#define STATIC_ASSERT(msg, c) typedef int static_assert_##msg[(c) ? 1 : -1]
namespace {
// We write magic values as integers to automatically detect endian issues.
// Streams are stored in little-endian. This is the ARM default (running
// the Raspberry Pi, but also x86; so it is possible to create streams easily
// on a different x86 Linux PC.
static const uint32_t kFileMagicValue = 0xED0C5A48;
struct FileHeader {
uint32_t magic; // kFileMagicValue
uint32_t buf_size;
uint32_t width;
uint32_t height;
uint64_t future_use1;
uint64_t is_wide_gpio : 1;
uint64_t flags_future_use : 63;
};
STATIC_ASSERT(file_header_size_changed, sizeof(FileHeader) == 32);
static const uint32_t kFrameMagicValue = 0x12345678;
struct FrameHeader {
uint32_t magic; // kFrameMagic
uint32_t size;
uint32_t hold_time_us; // How long this frame lasts in usec.
uint32_t future_use1;
uint64_t future_use2;
uint64_t future_use3;
};
STATIC_ASSERT(file_header_size_changed, sizeof(FrameHeader) == 32);
}
FileStreamIO::FileStreamIO(int fd) : fd_(fd) {
posix_fadvise(fd_, 0, 0, POSIX_FADV_SEQUENTIAL);
}
FileStreamIO::~FileStreamIO() { close(fd_); }
void FileStreamIO::Rewind() { lseek(fd_, 0, SEEK_SET); }
ssize_t FileStreamIO::Read(void *buf, const size_t count) {
return read(fd_, buf, count);
}
ssize_t FileStreamIO::Append(const void *buf, const size_t count) {
return write(fd_, buf, count);
}
void MemStreamIO::Rewind() { pos_ = 0; }
ssize_t MemStreamIO::Read(void *buf, size_t count) {
const size_t amount = std::min(count, buffer_.size() - pos_);
memcpy(buf, buffer_.data() + pos_, amount);
pos_ += amount;
return amount;
}
ssize_t MemStreamIO::Append(const void *buf, size_t count) {
buffer_.append((const char*)buf, count);
return count;
}
// Read exactly count bytes including retries. Returns success.
static bool FullRead(StreamIO *io, void *buf, const size_t count) {
int remaining = count;
char *char_buffer = (char*)buf;
while (remaining > 0) {
int r = io->Read(char_buffer, remaining);
if (r < 0) return false;
if (r == 0) break; // EOF.
char_buffer += r; remaining -= r;
}
return remaining == 0;
}
// Write exactly count bytes including retries. Returns success.
static bool FullAppend(StreamIO *io, const void *buf, const size_t count) {
int remaining = count;
const char *char_buffer = (const char*) buf;
while (remaining > 0) {
int w = io->Append(char_buffer, remaining);
if (w < 0) return false;
char_buffer += w; remaining -= w;
}
return remaining == 0;
}
StreamWriter::StreamWriter(StreamIO *io) : io_(io), header_written_(false) {}
bool StreamWriter::Stream(const FrameCanvas &frame, uint32_t hold_time_us) {
const char *data;
size_t len;
frame.Serialize(&data, &len);
if (!header_written_) {
WriteFileHeader(frame, len);
}
FrameHeader h = {};
h.magic = kFrameMagicValue;
h.size = len;
h.hold_time_us = hold_time_us;
FullAppend(io_, &h, sizeof(h));
return FullAppend(io_, data, len) == (ssize_t)len;
}
void StreamWriter::WriteFileHeader(const FrameCanvas &frame, size_t len) {
FileHeader header = {};
header.magic = kFileMagicValue;
header.width = frame.width();
header.height = frame.height();
header.buf_size = len;
header.is_wide_gpio = (sizeof(gpio_bits_t) > 4);
FullAppend(io_, &header, sizeof(header));
header_written_ = true;
}
StreamReader::StreamReader(StreamIO *io)
: io_(io), state_(STREAM_AT_BEGIN), header_frame_buffer_(NULL) {
io_->Rewind();
}
StreamReader::~StreamReader() { delete [] header_frame_buffer_; }
void StreamReader::Rewind() {
io_->Rewind();
state_ = STREAM_AT_BEGIN;
}
bool StreamReader::GetNext(FrameCanvas *frame, uint32_t* hold_time_us) {
if (state_ == STREAM_AT_BEGIN && !ReadFileHeader(*frame)) return false;
if (state_ != STREAM_READING) return false;
// Read header and expected buffer size.
if (!FullRead(io_, header_frame_buffer_,
sizeof(FrameHeader) + frame_buf_size_)) {
return false;
}
const FrameHeader &h = *reinterpret_cast<FrameHeader*>(header_frame_buffer_);
// TODO: we might allow for this to be a kFileMagicValue, to allow people
// to just concatenate streams. In that case, we just would need to read
// ahead past this header (both headers are designed to be same size)
if (h.magic != kFrameMagicValue) {
state_ = STREAM_ERROR;
return false;
}
// In the future, we might allow larger buffers (audio?), but never smaller.
// For now, we need to make sure to exactly match the size, as our assumption
// above is that we can read the full header + frame in one FullRead().
if (h.size != frame_buf_size_)
return false;
if (hold_time_us) *hold_time_us = h.hold_time_us;
return frame->Deserialize(header_frame_buffer_ + sizeof(FrameHeader),
frame_buf_size_);
}
bool StreamReader::ReadFileHeader(const FrameCanvas &frame) {
FileHeader header;
FullRead(io_, &header, sizeof(header));
if (header.magic != kFileMagicValue) {
state_ = STREAM_ERROR;
return false;
}
if ((int)header.width != frame.width()
|| (int)header.height != frame.height()) {
fprintf(stderr, "This stream is for %dx%d, can't play on %dx%d. "
"Please use the same settings for record/replay\n",
header.width, header.height, frame.width(), frame.height());
state_ = STREAM_ERROR;
return false;
}
if (header.is_wide_gpio != (sizeof(gpio_bits_t) == 8)) {
fprintf(stderr, "This stream was written with %s GPIO width support but "
"this library is compiled with %d bit GPIO width (see "
"ENABLE_WIDE_GPIO_COMPUTE_MODULE setting in lib/Makefile)\n",
header.is_wide_gpio ? "wide (64-bit)" : "narrow (32-bit)",
int(sizeof(gpio_bits_t) * 8));
state_ = STREAM_ERROR;
return false;
}
state_ = STREAM_READING;
frame_buf_size_ = header.buf_size;
if (!header_frame_buffer_)
header_frame_buffer_ = new char [ sizeof(FrameHeader) + header.buf_size ];
return true;
}
} // namespace rgb_matrix

BIN
lib/content-streamer.o Normal file

Binary file not shown.

175
lib/framebuffer-internal.h Normal file
View file

@ -0,0 +1,175 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
//
// 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 version 2.
//
// 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 <http://gnu.org/licenses/gpl-2.0.txt>
#ifndef RPI_RGBMATRIX_FRAMEBUFFER_INTERNAL_H
#define RPI_RGBMATRIX_FRAMEBUFFER_INTERNAL_H
#include <stdint.h>
#include <stdlib.h>
#include "hardware-mapping.h"
namespace rgb_matrix {
class GPIO;
class PinPulser;
namespace internal {
class RowAddressSetter;
// An opaque type used within the framebuffer that can be used
// to copy between PixelMappers.
struct PixelDesignator {
PixelDesignator() : gpio_word(-1), r_bit(0), g_bit(0), b_bit(0), mask(~0u){}
long gpio_word;
gpio_bits_t r_bit;
gpio_bits_t g_bit;
gpio_bits_t b_bit;
gpio_bits_t mask;
};
class PixelDesignatorMap {
public:
PixelDesignatorMap(int width, int height, const PixelDesignator &fill_bits);
~PixelDesignatorMap();
// Get a writable version of the PixelDesignator. Outside Framebuffer used
// by the RGBMatrix to re-assign mappings to new PixelDesignatorMappers.
PixelDesignator *get(int x, int y);
inline int width() const { return width_; }
inline int height() const { return height_; }
// All bits that set red/green/blue pixels; used for Fill().
const PixelDesignator &GetFillColorBits() { return fill_bits_; }
private:
const int width_;
const int height_;
const PixelDesignator fill_bits_; // Precalculated for fill.
PixelDesignator *const buffer_;
};
// Internal representation of the frame-buffer that as well can
// write itself to GPIO.
// Our internal memory layout mimicks as much as possible what needs to be
// written out.
class Framebuffer {
public:
// Maximum usable bitplanes.
//
// 11 bits seems to be a sweet spot in which we still get somewhat useful
// refresh rate and have good color richness. This is the default setting
// However, in low-light situations, we want to be able to scale down
// brightness more, having more bits at the bottom.
// TODO(hzeller): make the default 15 bit or so, but slide the use of
// timing to lower bits if fewer bits requested to not affect the overall
// refresh in that case.
// This needs to be balanced to not create too agressive timing however.
// To be explored in a separete commit.
//
// For now, if someone needs very low level of light, change this to
// say 13 and recompile. Run with --led-pwm-bits=13. Also, consider
// --led-pwm-dither-bits=2 to have the refresh rate not suffer too much.
static constexpr int kBitPlanes = 11;
static constexpr int kDefaultBitPlanes = 11;
Framebuffer(int rows, int columns, int parallel,
int scan_mode,
const char* led_sequence, bool inverse_color,
PixelDesignatorMap **mapper);
~Framebuffer();
// Initialize GPIO bits for output. Only call once.
static void InitHardwareMapping(const char *named_hardware);
static void InitGPIO(GPIO *io, int rows, int parallel,
bool allow_hardware_pulsing,
int pwm_lsb_nanoseconds,
int dither_bits,
int row_address_type);
static void InitializePanels(GPIO *io, const char *panel_type, int columns);
// Set PWM bits used for output. Default is 11, but if you only deal with
// simple comic-colors, 1 might be sufficient. Lower require less CPU.
// Returns boolean to signify if value was within range.
bool SetPWMBits(uint8_t value);
uint8_t pwmbits() { return pwm_bits_; }
// Map brightness of output linearly to input with CIE1931 profile.
void set_luminance_correct(bool on) { do_luminance_correct_ = on; }
bool luminance_correct() const { return do_luminance_correct_; }
// Set brightness in percent; range=1..100
// This will only affect newly set pixels.
void SetBrightness(uint8_t b) {
brightness_ = (b <= 100 ? (b != 0 ? b : 1) : 100);
}
uint8_t brightness() { return brightness_; }
void DumpToMatrix(GPIO *io, int pwm_bits_to_show);
void Serialize(const char **data, size_t *len) const;
bool Deserialize(const char *data, size_t len);
void CopyFrom(const Framebuffer *other);
// Canvas-inspired methods, but we're not implementing this interface to not
// have an unnecessary vtable.
int width() const;
int height() const;
void SetPixel(int x, int y, uint8_t red, uint8_t green, uint8_t blue);
void Clear();
void Fill(uint8_t red, uint8_t green, uint8_t blue);
private:
static const struct HardwareMapping *hardware_mapping_;
static RowAddressSetter *row_setter_;
// This returns the gpio-bit for given color (one of 'R', 'G', 'B'). This is
// returning the right value in case "led_sequence" is _not_ "RGB"
static gpio_bits_t GetGpioFromLedSequence(char col, const char *led_sequence,
gpio_bits_t default_r,
gpio_bits_t default_g,
gpio_bits_t default_b);
void InitDefaultDesignator(int x, int y, const char *led_sequence,
PixelDesignator *designator);
inline void MapColors(uint8_t r, uint8_t g, uint8_t b,
uint16_t *red, uint16_t *green, uint16_t *blue);
const int rows_; // Number of rows. 16 or 32.
const int parallel_; // Parallel rows of chains. 1 or 2.
const int height_; // rows * parallel
const int columns_; // Number of columns. Number of chained boards * 32.
const int scan_mode_;
const bool inverse_color_;
uint8_t pwm_bits_; // PWM bits to display.
bool do_luminance_correct_;
uint8_t brightness_;
const int double_rows_;
const size_t buffer_size_;
// The frame-buffer is organized in bitplanes.
// Highest level (slowest to cycle through) are double rows.
// For each double-row, we store pwm-bits columns of a bitplane.
// Each bitplane-column is pre-filled IoBits, of which the colors are set.
// Of course, that means that we store unrelated bits in the frame-buffer,
// but it allows easy access in the critical section.
gpio_bits_t *bitplane_buffer_;
inline gpio_bits_t *ValueAt(int double_row, int column, int bit);
PixelDesignatorMap **shared_mapper_; // Storage in RGBMatrix.
};
} // namespace internal
} // namespace rgb_matrix
#endif // RPI_RGBMATRIX_FRAMEBUFFER_INTERNAL_H

878
lib/framebuffer.cc Normal file
View file

@ -0,0 +1,878 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
//
// 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 version 2.
//
// 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 <http://gnu.org/licenses/gpl-2.0.txt>
// The framebuffer is the workhorse: it represents the frame in some internal
// format that is friendly to be dumped to the matrix quickly. Provides methods
// to manipulate the content.
#include "framebuffer-internal.h"
#include <assert.h>
#include <ctype.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include "gpio.h"
namespace rgb_matrix {
namespace internal {
// We need one global instance of a timing correct pulser. There are different
// implementations depending on the context.
static PinPulser *sOutputEnablePulser = NULL;
#ifdef ONLY_SINGLE_SUB_PANEL
# define SUB_PANELS_ 1
#else
# define SUB_PANELS_ 2
#endif
PixelDesignator *PixelDesignatorMap::get(int x, int y) {
if (x < 0 || y < 0 || x >= width_ || y >= height_)
return NULL;
return buffer_ + (y*width_) + x;
}
PixelDesignatorMap::PixelDesignatorMap(int width, int height,
const PixelDesignator &fill_bits)
: width_(width), height_(height), fill_bits_(fill_bits),
buffer_(new PixelDesignator[width * height]) {
}
PixelDesignatorMap::~PixelDesignatorMap() {
delete [] buffer_;
}
// Different panel types use different techniques to set the row address.
// We abstract that away with different implementations of RowAddressSetter
class RowAddressSetter {
public:
virtual ~RowAddressSetter() {}
virtual gpio_bits_t need_bits() const = 0;
virtual void SetRowAddress(GPIO *io, int row) = 0;
};
namespace {
// The default DirectRowAddressSetter just sets the address in parallel
// output lines ABCDE with A the LSB and E the MSB.
class DirectRowAddressSetter : public RowAddressSetter {
public:
DirectRowAddressSetter(int double_rows, const HardwareMapping &h)
: row_mask_(0), last_row_(-1) {
assert(double_rows <= 32); // need to resize row_lookup_
if (double_rows > 16) row_mask_ |= h.e;
if (double_rows > 8) row_mask_ |= h.d;
if (double_rows > 4) row_mask_ |= h.c;
if (double_rows > 2) row_mask_ |= h.b;
row_mask_ |= h.a;
for (int i = 0; i < double_rows; ++i) {
// To avoid the bit-fiddle in the critical path, utilize
// a lookup-table for all possible rows.
gpio_bits_t row_address = (i & 0x01) ? h.a : 0;
row_address |= (i & 0x02) ? h.b : 0;
row_address |= (i & 0x04) ? h.c : 0;
row_address |= (i & 0x08) ? h.d : 0;
row_address |= (i & 0x10) ? h.e : 0;
row_lookup_[i] = row_address;
}
}
virtual gpio_bits_t need_bits() const { return row_mask_; }
virtual void SetRowAddress(GPIO *io, int row) {
if (row == last_row_) return;
io->WriteMaskedBits(row_lookup_[row], row_mask_);
last_row_ = row;
}
private:
gpio_bits_t row_mask_;
gpio_bits_t row_lookup_[32];
int last_row_;
};
// The SM5266RowAddressSetter (ABC Shifter + DE direct) sets bits ABC using
// a 8 bit shifter and DE directly. The panel this works with has 8 SM5266
// shifters (4 for the top 32 rows and 4 for the bottom 32 rows).
// DE is used to select the active shifter
// (rows 1-8/33-40, 9-16/41-48, 17-24/49-56, 25-32/57-64).
// Rows are enabled by shifting in 8 bits (high bit first) with a high bit
// enabling that row. This allows up to 8 rows per group to be active at the
// same time (if they have the same content), but that isn't implemented here.
// BK, DIN and DCK are the designations on the SM5266P datasheet.
// BK = Enable Input, DIN = Serial In, DCK = Clock
class SM5266RowAddressSetter : public RowAddressSetter {
public:
SM5266RowAddressSetter(int double_rows, const HardwareMapping &h)
: row_mask_(h.a | h.b | h.c),
last_row_(-1),
bk_(h.c),
din_(h.b),
dck_(h.a) {
assert(double_rows <= 32); // designed for up to 1/32 panel
if (double_rows > 8) row_mask_ |= h.d;
if (double_rows > 16) row_mask_ |= h.e;
for (int i = 0; i < double_rows; ++i) {
gpio_bits_t row_address = 0;
row_address |= (i & 0x08) ? h.d : 0;
row_address |= (i & 0x10) ? h.e : 0;
row_lookup_[i] = row_address;
}
}
virtual gpio_bits_t need_bits() const { return row_mask_; }
virtual void SetRowAddress(GPIO *io, int row) {
if (row == last_row_) return;
io->SetBits(bk_); // Enable serial input for the shifter
for (int r = 7; r >= 0; r--) {
if (row % 8 == r) {
io->SetBits(din_);
} else {
io->ClearBits(din_);
}
io->SetBits(dck_);
io->SetBits(dck_); // Longer clock time; tested with Pi3
io->ClearBits(dck_);
}
io->ClearBits(bk_); // Disable serial input to keep unwanted bits out of the shifters
last_row_ = row;
// Set bits D and E to enable the proper shifter to display the selected
// row.
io->WriteMaskedBits(row_lookup_[row], row_mask_);
}
private:
gpio_bits_t row_mask_;
int last_row_;
const gpio_bits_t bk_;
const gpio_bits_t din_;
const gpio_bits_t dck_;
gpio_bits_t row_lookup_[32];
};
class ShiftRegisterRowAddressSetter : public RowAddressSetter {
public:
ShiftRegisterRowAddressSetter(int double_rows, const HardwareMapping &h)
: double_rows_(double_rows),
row_mask_(h.a | h.b), clock_(h.a), data_(h.b),
last_row_(-1) {
}
virtual gpio_bits_t need_bits() const { return row_mask_; }
virtual void SetRowAddress(GPIO *io, int row) {
if (row == last_row_) return;
for (int activate = 0; activate < double_rows_; ++activate) {
io->ClearBits(clock_);
if (activate == double_rows_ - 1 - row) {
io->ClearBits(data_);
} else {
io->SetBits(data_);
}
io->SetBits(clock_);
}
io->ClearBits(clock_);
io->SetBits(clock_);
last_row_ = row;
}
private:
const int double_rows_;
const gpio_bits_t row_mask_;
const gpio_bits_t clock_;
const gpio_bits_t data_;
int last_row_;
};
// Issue #823
// An shift register row address setter that does not use B but C for the
// data. Clock is inverted.
class ABCShiftRegisterRowAddressSetter : public RowAddressSetter {
public:
ABCShiftRegisterRowAddressSetter(int double_rows, const HardwareMapping &h)
: double_rows_(double_rows),
row_mask_(h.a | h.c),
clock_(h.a),
data_(h.c),
last_row_(-1) {
}
virtual gpio_bits_t need_bits() const { return row_mask_; }
virtual void SetRowAddress(GPIO *io, int row) {
for (int activate = 0; activate < double_rows_; ++activate) {
io->ClearBits(clock_);
if (activate == double_rows_ - 1 - row) {
io->SetBits(data_);
} else {
io->ClearBits(data_);
}
io->SetBits(clock_);
}
io->SetBits(clock_);
io->ClearBits(clock_);
last_row_ = row;
}
private:
const int double_rows_;
const gpio_bits_t row_mask_;
const gpio_bits_t clock_;
const gpio_bits_t data_;
int last_row_;
};
// The DirectABCDRowAddressSetter sets the address by one of
// row pin ABCD for 32х16 matrix 1:4 multiplexing. The matrix has
// 4 addressable rows. Row is selected by a low level on the
// corresponding row address pin. Other row address pins must be in high level.
//
// Row addr| 0 | 1 | 2 | 3
// --------+---+---+---+---
// Line A | 0 | 1 | 1 | 1
// Line B | 1 | 0 | 1 | 1
// Line C | 1 | 1 | 0 | 1
// Line D | 1 | 1 | 1 | 0
class DirectABCDLineRowAddressSetter : public RowAddressSetter {
public:
DirectABCDLineRowAddressSetter(int double_rows, const HardwareMapping &h)
: last_row_(-1) {
row_mask_ = h.a | h.b | h.c | h.d;
row_lines_[0] = /*h.a |*/ h.b | h.c | h.d;
row_lines_[1] = h.a /*| h.b*/ | h.c | h.d;
row_lines_[2] = h.a | h.b /*| h.c */| h.d;
row_lines_[3] = h.a | h.b | h.c /*| h.d*/;
}
virtual gpio_bits_t need_bits() const { return row_mask_; }
virtual void SetRowAddress(GPIO *io, int row) {
if (row == last_row_) return;
gpio_bits_t row_address = row_lines_[row % 4];
io->WriteMaskedBits(row_address, row_mask_);
last_row_ = row;
}
private:
gpio_bits_t row_lines_[4];
gpio_bits_t row_mask_;
int last_row_;
};
}
const struct HardwareMapping *Framebuffer::hardware_mapping_ = NULL;
RowAddressSetter *Framebuffer::row_setter_ = NULL;
Framebuffer::Framebuffer(int rows, int columns, int parallel,
int scan_mode,
const char *led_sequence, bool inverse_color,
PixelDesignatorMap **mapper)
: rows_(rows),
parallel_(parallel),
height_(rows * parallel),
columns_(columns),
scan_mode_(scan_mode),
inverse_color_(inverse_color),
pwm_bits_(kBitPlanes), do_luminance_correct_(true), brightness_(100),
double_rows_(rows / SUB_PANELS_),
buffer_size_(double_rows_ * columns_ * kBitPlanes * sizeof(gpio_bits_t)),
shared_mapper_(mapper) {
assert(hardware_mapping_ != NULL); // Called InitHardwareMapping() ?
assert(shared_mapper_ != NULL); // Storage should be provided by RGBMatrix.
assert(rows_ >=4 && rows_ <= 64 && rows_ % 2 == 0);
if (parallel > hardware_mapping_->max_parallel_chains) {
fprintf(stderr, "The %s GPIO mapping only supports %d parallel chain%s, "
"but %d was requested.\n", hardware_mapping_->name,
hardware_mapping_->max_parallel_chains,
hardware_mapping_->max_parallel_chains > 1 ? "s" : "", parallel);
abort();
}
assert(parallel >= 1 && parallel <= 6);
bitplane_buffer_ = new gpio_bits_t[double_rows_ * columns_ * kBitPlanes];
// If we're the first Framebuffer created, the shared PixelMapper is
// still NULL, so create one.
// The first PixelMapper represents the physical layout of a standard matrix
// with the specific knowledge of the framebuffer, setting up PixelDesignators
// in a way that they are useful for this Framebuffer.
//
// Newly created PixelMappers then can just re-arrange PixelDesignators
// from the parent PixelMapper opaquely without having to know the details.
if (*shared_mapper_ == NULL) {
// Gather all the bits for given color for fast Fill()s and use the right
// bits according to the led sequence
const struct HardwareMapping &h = *hardware_mapping_;
gpio_bits_t r = h.p0_r1 | h.p0_r2 | h.p1_r1 | h.p1_r2 | h.p2_r1 | h.p2_r2 | h.p3_r1 | h.p3_r2 | h.p4_r1 | h.p4_r2 | h.p5_r1 | h.p5_r2;
gpio_bits_t g = h.p0_g1 | h.p0_g2 | h.p1_g1 | h.p1_g2 | h.p2_g1 | h.p2_g2 | h.p3_g1 | h.p3_g2 | h.p4_g1 | h.p4_g2 | h.p5_g1 | h.p5_g2;
gpio_bits_t b = h.p0_b1 | h.p0_b2 | h.p1_b1 | h.p1_b2 | h.p2_b1 | h.p2_b2 | h.p3_b1 | h.p3_b2 | h.p4_b1 | h.p4_b2 | h.p5_b1 | h.p5_b2;
PixelDesignator fill_bits;
fill_bits.r_bit = GetGpioFromLedSequence('R', led_sequence, r, g, b);
fill_bits.g_bit = GetGpioFromLedSequence('G', led_sequence, r, g, b);
fill_bits.b_bit = GetGpioFromLedSequence('B', led_sequence, r, g, b);
*shared_mapper_ = new PixelDesignatorMap(columns_, height_, fill_bits);
for (int y = 0; y < height_; ++y) {
for (int x = 0; x < columns_; ++x) {
InitDefaultDesignator(x, y, led_sequence, (*shared_mapper_)->get(x, y));
}
}
}
Clear();
}
Framebuffer::~Framebuffer() {
delete [] bitplane_buffer_;
}
// TODO: this should also be parsed from some special formatted string, e.g.
// {addr={22,23,24,25,15},oe=18,clk=17,strobe=4, p0={11,27,7,8,9,10},...}
/* static */ void Framebuffer::InitHardwareMapping(const char *named_hardware) {
if (named_hardware == NULL || *named_hardware == '\0') {
named_hardware = "regular";
}
struct HardwareMapping *mapping = NULL;
for (HardwareMapping *it = matrix_hardware_mappings; it->name; ++it) {
if (strcasecmp(it->name, named_hardware) == 0) {
mapping = it;
break;
}
}
if (!mapping) {
fprintf(stderr, "There is no hardware mapping named '%s'.\nAvailable: ",
named_hardware);
for (HardwareMapping *it = matrix_hardware_mappings; it->name; ++it) {
if (it != matrix_hardware_mappings) fprintf(stderr, ", ");
fprintf(stderr, "'%s'", it->name);
}
fprintf(stderr, "\n");
abort();
}
if (mapping->max_parallel_chains == 0) {
// Auto determine.
struct HardwareMapping *h = mapping;
if ((h->p0_r1 | h->p0_g1 | h->p0_g1 | h->p0_r2 | h->p0_g2 | h->p0_g2) > 0)
++mapping->max_parallel_chains;
if ((h->p1_r1 | h->p1_g1 | h->p1_g1 | h->p1_r2 | h->p1_g2 | h->p1_g2) > 0)
++mapping->max_parallel_chains;
if ((h->p2_r1 | h->p2_g1 | h->p2_g1 | h->p2_r2 | h->p2_g2 | h->p2_g2) > 0)
++mapping->max_parallel_chains;
if ((h->p3_r1 | h->p3_g1 | h->p3_g1 | h->p3_r2 | h->p3_g2 | h->p3_g2) > 0)
++mapping->max_parallel_chains;
if ((h->p4_r1 | h->p4_g1 | h->p4_g1 | h->p4_r2 | h->p4_g2 | h->p4_g2) > 0)
++mapping->max_parallel_chains;
if ((h->p5_r1 | h->p5_g1 | h->p5_g1 | h->p5_r2 | h->p5_g2 | h->p5_g2) > 0)
++mapping->max_parallel_chains;
}
hardware_mapping_ = mapping;
}
/* static */ void Framebuffer::InitGPIO(GPIO *io, int rows, int parallel,
bool allow_hardware_pulsing,
int pwm_lsb_nanoseconds,
int dither_bits,
int row_address_type) {
if (sOutputEnablePulser != NULL)
return; // already initialized.
const struct HardwareMapping &h = *hardware_mapping_;
// Tell GPIO about all bits we intend to use.
gpio_bits_t all_used_bits = 0;
all_used_bits |= h.output_enable | h.clock | h.strobe;
all_used_bits |= h.p0_r1 | h.p0_g1 | h.p0_b1 | h.p0_r2 | h.p0_g2 | h.p0_b2;
if (parallel >= 2) {
all_used_bits |= h.p1_r1 | h.p1_g1 | h.p1_b1 | h.p1_r2 | h.p1_g2 | h.p1_b2;
}
if (parallel >= 3) {
all_used_bits |= h.p2_r1 | h.p2_g1 | h.p2_b1 | h.p2_r2 | h.p2_g2 | h.p2_b2;
}
if (parallel >= 4) {
all_used_bits |= h.p3_r1 | h.p3_g1 | h.p3_b1 | h.p3_r2 | h.p3_g2 | h.p3_b2;
}
if (parallel >= 5) {
all_used_bits |= h.p4_r1 | h.p4_g1 | h.p4_b1 | h.p4_r2 | h.p4_g2 | h.p4_b2;
}
if (parallel >= 6) {
all_used_bits |= h.p5_r1 | h.p5_g1 | h.p5_b1 | h.p5_r2 | h.p5_g2 | h.p5_b2;
}
const int double_rows = rows / SUB_PANELS_;
switch (row_address_type) {
case 0:
row_setter_ = new DirectRowAddressSetter(double_rows, h);
break;
case 1:
row_setter_ = new ShiftRegisterRowAddressSetter(double_rows, h);
break;
case 2:
row_setter_ = new DirectABCDLineRowAddressSetter(double_rows, h);
break;
case 3:
row_setter_ = new ABCShiftRegisterRowAddressSetter(double_rows, h);
break;
case 4:
row_setter_ = new SM5266RowAddressSetter(double_rows, h);
break;
default:
assert(0); // unexpected type.
}
all_used_bits |= row_setter_->need_bits();
// Adafruit HAT identified by the same prefix.
const bool is_some_adafruit_hat = (0 == strncmp(h.name, "adafruit-hat",
strlen("adafruit-hat")));
// Initialize outputs, make sure that all of these are supported bits.
const gpio_bits_t result = io->InitOutputs(all_used_bits,
is_some_adafruit_hat);
assert(result == all_used_bits); // Impl: all bits declared in gpio.cc ?
std::vector<int> bitplane_timings;
uint32_t timing_ns = pwm_lsb_nanoseconds;
for (int b = 0; b < kBitPlanes; ++b) {
bitplane_timings.push_back(timing_ns);
if (b >= dither_bits) timing_ns *= 2;
}
sOutputEnablePulser = PinPulser::Create(io, h.output_enable,
allow_hardware_pulsing,
bitplane_timings);
}
// NOTE: first version for panel initialization sequence, need to refine
// until it is more clear how different panel types are initialized to be
// able to abstract this more.
static void InitFM6126(GPIO *io, const struct HardwareMapping &h, int columns) {
const gpio_bits_t bits_on
= h.p0_r1 | h.p0_g1 | h.p0_b1 | h.p0_r2 | h.p0_g2 | h.p0_b2
| h.p1_r1 | h.p1_g1 | h.p1_b1 | h.p1_r2 | h.p1_g2 | h.p1_b2
| h.p2_r1 | h.p2_g1 | h.p2_b1 | h.p2_r2 | h.p2_g2 | h.p2_b2
| h.p3_r1 | h.p3_g1 | h.p3_b1 | h.p3_r2 | h.p3_g2 | h.p3_b2
| h.p4_r1 | h.p4_g1 | h.p4_b1 | h.p4_r2 | h.p4_g2 | h.p4_b2
| h.p5_r1 | h.p5_g1 | h.p5_b1 | h.p5_r2 | h.p5_g2 | h.p5_b2
| h.a; // Address bit 'A' is always on.
const gpio_bits_t bits_off = h.a;
const gpio_bits_t mask = bits_on | h.strobe;
// Init bits. TODO: customize, as we can do things such as brightness here,
// which would allow more higher quality output.
static const char* init_b12 = "0111111111111111"; // full bright
static const char* init_b13 = "0000000001000000"; // panel on.
io->ClearBits(h.clock | h.strobe);
for (int i = 0; i < columns; ++i) {
gpio_bits_t value = init_b12[i % 16] == '0' ? bits_off : bits_on;
if (i > columns - 12) value |= h.strobe;
io->WriteMaskedBits(value, mask);
io->SetBits(h.clock);
io->ClearBits(h.clock);
}
io->ClearBits(h.strobe);
for (int i = 0; i < columns; ++i) {
gpio_bits_t value = init_b13[i % 16] == '0' ? bits_off : bits_on;
if (i > columns - 13) value |= h.strobe;
io->WriteMaskedBits(value, mask);
io->SetBits(h.clock);
io->ClearBits(h.clock);
}
io->ClearBits(h.strobe);
}
// The FM6217 is very similar to the FM6216.
// FM6217 adds Register 3 to allow for automatic bad pixel supression.
static void InitFM6127(GPIO *io, const struct HardwareMapping &h, int columns) {
const gpio_bits_t bits_r_on= h.p0_r1 | h.p0_r2;
const gpio_bits_t bits_g_on= h.p0_g1 | h.p0_g2;
const gpio_bits_t bits_b_on= h.p0_b1 | h.p0_b2;
const gpio_bits_t bits_on= bits_r_on | bits_g_on | bits_b_on;
const gpio_bits_t bits_off = 0;
const gpio_bits_t mask = bits_on | h.strobe;
static const char* init_b12 = "1111111111001110"; // register 1
static const char* init_b13 = "1110000001100010"; // register 2.
static const char* init_b11 = "0101111100000000"; // register 3.
io->ClearBits(h.clock | h.strobe);
for (int i = 0; i < columns; ++i) {
gpio_bits_t value = init_b12[i % 16] == '0' ? bits_off : bits_on;
if (i > columns - 12) value |= h.strobe;
io->WriteMaskedBits(value, mask);
io->SetBits(h.clock);
io->ClearBits(h.clock);
}
io->ClearBits(h.strobe);
for (int i = 0; i < columns; ++i) {
gpio_bits_t value = init_b13[i % 16] == '0' ? bits_off : bits_on;
if (i > columns - 13) value |= h.strobe;
io->WriteMaskedBits(value, mask);
io->SetBits(h.clock);
io->ClearBits(h.clock);
}
io->ClearBits(h.strobe);
for (int i = 0; i < columns; ++i) {
gpio_bits_t value = init_b11[i % 16] == '0' ? bits_off : bits_on;
if (i > columns - 11) value |= h.strobe;
io->WriteMaskedBits(value, mask);
io->SetBits(h.clock);
io->ClearBits(h.clock);
}
io->ClearBits(h.strobe);
}
/*static*/ void Framebuffer::InitializePanels(GPIO *io,
const char *panel_type,
int columns) {
if (!panel_type || panel_type[0] == '\0') return;
if (strncasecmp(panel_type, "fm6126", 6) == 0) {
InitFM6126(io, *hardware_mapping_, columns);
}
else if (strncasecmp(panel_type, "fm6127", 6) == 0) {
InitFM6127(io, *hardware_mapping_, columns);
}
// else if (strncasecmp(...)) // more init types
else {
fprintf(stderr, "Unknown panel type '%s'; typo ?\n", panel_type);
}
}
bool Framebuffer::SetPWMBits(uint8_t value) {
if (value < 1 || value > kBitPlanes)
return false;
pwm_bits_ = value;
return true;
}
inline gpio_bits_t *Framebuffer::ValueAt(int double_row, int column, int bit) {
return &bitplane_buffer_[ double_row * (columns_ * kBitPlanes)
+ bit * columns_
+ column ];
}
void Framebuffer::Clear() {
if (inverse_color_) {
Fill(0, 0, 0);
} else {
// Cheaper.
memset(bitplane_buffer_, 0,
sizeof(*bitplane_buffer_) * double_rows_ * columns_ * kBitPlanes);
}
}
// Do CIE1931 luminance correction and scale to output bitplanes
static uint16_t luminance_cie1931(uint8_t c, uint8_t brightness) {
float out_factor = ((1 << internal::Framebuffer::kBitPlanes) - 1);
float v = (float) c * brightness / 255.0;
return roundf(out_factor * ((v <= 8) ? v / 902.3 : pow((v + 16) / 116.0, 3)));
}
struct ColorLookup {
uint16_t color[256];
};
static ColorLookup *CreateLuminanceCIE1931LookupTable() {
ColorLookup *for_brightness = new ColorLookup[100];
for (int c = 0; c < 256; ++c)
for (int b = 0; b < 100; ++b)
for_brightness[b].color[c] = luminance_cie1931(c, b + 1);
return for_brightness;
}
static inline uint16_t CIEMapColor(uint8_t brightness, uint8_t c) {
static ColorLookup *luminance_lookup = CreateLuminanceCIE1931LookupTable();
return luminance_lookup[brightness - 1].color[c];
}
// Non luminance correction. TODO: consider getting rid of this.
static inline uint16_t DirectMapColor(uint8_t brightness, uint8_t c) {
// simple scale down the color value
c = c * brightness / 100;
// shift to be left aligned with top-most bits.
constexpr int shift = internal::Framebuffer::kBitPlanes - 8;
return (shift > 0) ? (c << shift) : (c >> -shift);
}
inline void Framebuffer::MapColors(
uint8_t r, uint8_t g, uint8_t b,
uint16_t *red, uint16_t *green, uint16_t *blue) {
if (do_luminance_correct_) {
*red = CIEMapColor(brightness_, r);
*green = CIEMapColor(brightness_, g);
*blue = CIEMapColor(brightness_, b);
} else {
*red = DirectMapColor(brightness_, r);
*green = DirectMapColor(brightness_, g);
*blue = DirectMapColor(brightness_, b);
}
if (inverse_color_) {
*red = ~(*red);
*green = ~(*green);
*blue = ~(*blue);
}
}
void Framebuffer::Fill(uint8_t r, uint8_t g, uint8_t b) {
uint16_t red, green, blue;
MapColors(r, g, b, &red, &green, &blue);
const PixelDesignator &fill = (*shared_mapper_)->GetFillColorBits();
for (int b = kBitPlanes - pwm_bits_; b < kBitPlanes; ++b) {
uint16_t mask = 1 << b;
gpio_bits_t plane_bits = 0;
plane_bits |= ((red & mask) == mask) ? fill.r_bit : 0;
plane_bits |= ((green & mask) == mask) ? fill.g_bit : 0;
plane_bits |= ((blue & mask) == mask) ? fill.b_bit : 0;
for (int row = 0; row < double_rows_; ++row) {
gpio_bits_t *row_data = ValueAt(row, 0, b);
for (int col = 0; col < columns_; ++col) {
*row_data++ = plane_bits;
}
}
}
}
int Framebuffer::width() const { return (*shared_mapper_)->width(); }
int Framebuffer::height() const { return (*shared_mapper_)->height(); }
void Framebuffer::SetPixel(int x, int y, uint8_t r, uint8_t g, uint8_t b) {
const PixelDesignator *designator = (*shared_mapper_)->get(x, y);
if (designator == NULL) return;
const long pos = designator->gpio_word;
if (pos < 0) return; // non-used pixel marker.
uint16_t red, green, blue;
MapColors(r, g, b, &red, &green, &blue);
gpio_bits_t *bits = bitplane_buffer_ + pos;
const int min_bit_plane = kBitPlanes - pwm_bits_;
bits += (columns_ * min_bit_plane);
const gpio_bits_t r_bits = designator->r_bit;
const gpio_bits_t g_bits = designator->g_bit;
const gpio_bits_t b_bits = designator->b_bit;
const gpio_bits_t designator_mask = designator->mask;
for (uint16_t mask = 1<<min_bit_plane; mask != 1<<kBitPlanes; mask <<=1 ) {
gpio_bits_t color_bits = 0;
if (red & mask) color_bits |= r_bits;
if (green & mask) color_bits |= g_bits;
if (blue & mask) color_bits |= b_bits;
*bits = (*bits & designator_mask) | color_bits;
bits += columns_;
}
}
// Strange LED-mappings such as RBG or so are handled here.
gpio_bits_t Framebuffer::GetGpioFromLedSequence(char col,
const char *led_sequence,
gpio_bits_t default_r,
gpio_bits_t default_g,
gpio_bits_t default_b) {
const char *pos = strchr(led_sequence, col);
if (pos == NULL) pos = strchr(led_sequence, tolower(col));
if (pos == NULL) {
fprintf(stderr, "LED sequence '%s' does not contain any '%c'.\n",
led_sequence, col);
abort();
}
switch (pos - led_sequence) {
case 0: return default_r;
case 1: return default_g;
case 2: return default_b;
}
return default_r; // String too long, should've been caught earlier.
}
void Framebuffer::InitDefaultDesignator(int x, int y, const char *seq,
PixelDesignator *d) {
const struct HardwareMapping &h = *hardware_mapping_;
gpio_bits_t *bits = ValueAt(y % double_rows_, x, 0);
d->gpio_word = bits - bitplane_buffer_;
d->r_bit = d->g_bit = d->b_bit = 0;
if (y < rows_) {
if (y < double_rows_) {
d->r_bit = GetGpioFromLedSequence('R', seq, h.p0_r1, h.p0_g1, h.p0_b1);
d->g_bit = GetGpioFromLedSequence('G', seq, h.p0_r1, h.p0_g1, h.p0_b1);
d->b_bit = GetGpioFromLedSequence('B', seq, h.p0_r1, h.p0_g1, h.p0_b1);
} else {
d->r_bit = GetGpioFromLedSequence('R', seq, h.p0_r2, h.p0_g2, h.p0_b2);
d->g_bit = GetGpioFromLedSequence('G', seq, h.p0_r2, h.p0_g2, h.p0_b2);
d->b_bit = GetGpioFromLedSequence('B', seq, h.p0_r2, h.p0_g2, h.p0_b2);
}
}
else if (y >= rows_ && y < 2 * rows_) {
if (y - rows_ < double_rows_) {
d->r_bit = GetGpioFromLedSequence('R', seq, h.p1_r1, h.p1_g1, h.p1_b1);
d->g_bit = GetGpioFromLedSequence('G', seq, h.p1_r1, h.p1_g1, h.p1_b1);
d->b_bit = GetGpioFromLedSequence('B', seq, h.p1_r1, h.p1_g1, h.p1_b1);
} else {
d->r_bit = GetGpioFromLedSequence('R', seq, h.p1_r2, h.p1_g2, h.p1_b2);
d->g_bit = GetGpioFromLedSequence('G', seq, h.p1_r2, h.p1_g2, h.p1_b2);
d->b_bit = GetGpioFromLedSequence('B', seq, h.p1_r2, h.p1_g2, h.p1_b2);
}
}
else if (y >= 2*rows_ && y < 3 * rows_) {
if (y - 2*rows_ < double_rows_) {
d->r_bit = GetGpioFromLedSequence('R', seq, h.p2_r1, h.p2_g1, h.p2_b1);
d->g_bit = GetGpioFromLedSequence('G', seq, h.p2_r1, h.p2_g1, h.p2_b1);
d->b_bit = GetGpioFromLedSequence('B', seq, h.p2_r1, h.p2_g1, h.p2_b1);
} else {
d->r_bit = GetGpioFromLedSequence('R', seq, h.p2_r2, h.p2_g2, h.p2_b2);
d->g_bit = GetGpioFromLedSequence('G', seq, h.p2_r2, h.p2_g2, h.p2_b2);
d->b_bit = GetGpioFromLedSequence('B', seq, h.p2_r2, h.p2_g2, h.p2_b2);
}
}
else if (y >= 3*rows_ && y < 4 * rows_) {
if (y - 3*rows_ < double_rows_) {
d->r_bit = GetGpioFromLedSequence('R', seq, h.p3_r1, h.p3_g1, h.p3_b1);
d->g_bit = GetGpioFromLedSequence('G', seq, h.p3_r1, h.p3_g1, h.p3_b1);
d->b_bit = GetGpioFromLedSequence('B', seq, h.p3_r1, h.p3_g1, h.p3_b1);
} else {
d->r_bit = GetGpioFromLedSequence('R', seq, h.p3_r2, h.p3_g2, h.p3_b2);
d->g_bit = GetGpioFromLedSequence('G', seq, h.p3_r2, h.p3_g2, h.p3_b2);
d->b_bit = GetGpioFromLedSequence('B', seq, h.p3_r2, h.p3_g2, h.p3_b2);
}
}
else if (y >= 4*rows_ && y < 5 * rows_){
if (y - 4*rows_ < double_rows_) {
d->r_bit = GetGpioFromLedSequence('R', seq, h.p4_r1, h.p4_g1, h.p4_b1);
d->g_bit = GetGpioFromLedSequence('G', seq, h.p4_r1, h.p4_g1, h.p4_b1);
d->b_bit = GetGpioFromLedSequence('B', seq, h.p4_r1, h.p4_g1, h.p4_b1);
} else {
d->r_bit = GetGpioFromLedSequence('R', seq, h.p4_r2, h.p4_g2, h.p4_b2);
d->g_bit = GetGpioFromLedSequence('G', seq, h.p4_r2, h.p4_g2, h.p4_b2);
d->b_bit = GetGpioFromLedSequence('B', seq, h.p4_r2, h.p4_g2, h.p4_b2);
}
}
else {
if (y - 5*rows_ < double_rows_) {
d->r_bit = GetGpioFromLedSequence('R', seq, h.p5_r1, h.p5_g1, h.p5_b1);
d->g_bit = GetGpioFromLedSequence('G', seq, h.p5_r1, h.p5_g1, h.p5_b1);
d->b_bit = GetGpioFromLedSequence('B', seq, h.p5_r1, h.p5_g1, h.p5_b1);
} else {
d->r_bit = GetGpioFromLedSequence('R', seq, h.p5_r2, h.p5_g2, h.p5_b2);
d->g_bit = GetGpioFromLedSequence('G', seq, h.p5_r2, h.p5_g2, h.p5_b2);
d->b_bit = GetGpioFromLedSequence('B', seq, h.p5_r2, h.p5_g2, h.p5_b2);
}
}
d->mask = ~(d->r_bit | d->g_bit | d->b_bit);
}
void Framebuffer::Serialize(const char **data, size_t *len) const {
*data = reinterpret_cast<const char*>(bitplane_buffer_);
*len = buffer_size_;
}
bool Framebuffer::Deserialize(const char *data, size_t len) {
if (len != buffer_size_) return false;
memcpy(bitplane_buffer_, data, len);
return true;
}
void Framebuffer::CopyFrom(const Framebuffer *other) {
if (other == this) return;
memcpy(bitplane_buffer_, other->bitplane_buffer_, buffer_size_);
}
void Framebuffer::DumpToMatrix(GPIO *io, int pwm_low_bit) {
const struct HardwareMapping &h = *hardware_mapping_;
gpio_bits_t color_clk_mask = 0; // Mask of bits while clocking in.
color_clk_mask |= h.p0_r1 | h.p0_g1 | h.p0_b1 | h.p0_r2 | h.p0_g2 | h.p0_b2;
if (parallel_ >= 2) {
color_clk_mask |= h.p1_r1 | h.p1_g1 | h.p1_b1 | h.p1_r2 | h.p1_g2 | h.p1_b2;
}
if (parallel_ >= 3) {
color_clk_mask |= h.p2_r1 | h.p2_g1 | h.p2_b1 | h.p2_r2 | h.p2_g2 | h.p2_b2;
}
if (parallel_ >= 4) {
color_clk_mask |= h.p3_r1 | h.p3_g1 | h.p3_b1 | h.p3_r2 | h.p3_g2 | h.p3_b2;
}
if (parallel_ >= 5) {
color_clk_mask |= h.p4_r1 | h.p4_g1 | h.p4_b1 | h.p4_r2 | h.p4_g2 | h.p4_b2;
}
if (parallel_ >= 6) {
color_clk_mask |= h.p5_r1 | h.p5_g1 | h.p5_b1 | h.p5_r2 | h.p5_g2 | h.p5_b2;
}
color_clk_mask |= h.clock;
// Depending if we do dithering, we might not always show the lowest bits.
const int start_bit = std::max(pwm_low_bit, kBitPlanes - pwm_bits_);
const uint8_t half_double = double_rows_/2;
for (uint8_t row_loop = 0; row_loop < double_rows_; ++row_loop) {
uint8_t d_row;
switch (scan_mode_) {
case 0: // progressive
default:
d_row = row_loop;
break;
case 1: // interlaced
d_row = ((row_loop < half_double)
? (row_loop << 1)
: ((row_loop - half_double) << 1) + 1);
}
// Rows can't be switched very quickly without ghosting, so we do the
// full PWM of one row before switching rows.
for (int b = start_bit; b < kBitPlanes; ++b) {
gpio_bits_t *row_data = ValueAt(d_row, 0, b);
// While the output enable is still on, we can already clock in the next
// data.
for (int col = 0; col < columns_; ++col) {
const gpio_bits_t &out = *row_data++;
io->WriteMaskedBits(out, color_clk_mask); // col + reset clock
io->SetBits(h.clock); // Rising edge: clock color in.
}
io->ClearBits(color_clk_mask); // clock back to normal.
// OE of the previous row-data must be finished before strobe.
sOutputEnablePulser->WaitPulseFinished();
// Setting address and strobing needs to happen in dark time.
row_setter_->SetRowAddress(io, d_row);
io->SetBits(h.strobe); // Strobe in the previously clocked in row.
io->ClearBits(h.strobe);
// Now switch on for the sleep time necessary for that bit-plane.
sOutputEnablePulser->SendPulse(b);
}
}
}
} // namespace internal
} // namespace rgb_matrix

BIN
lib/framebuffer.o Normal file

Binary file not shown.

28
lib/gpio-bits.h Normal file
View file

@ -0,0 +1,28 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
//
// 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 version 2.
//
// 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 <http://gnu.org/licenses/gpl-2.0.txt>
// This file needs to compile in C and C++ context, so deliberately broken out.
#ifndef RPI_GPIOBITS_H
#define RPI_GPIOBITS_H
#include <stdint.h>
#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
typedef uint64_t gpio_bits_t;
#else
typedef uint32_t gpio_bits_t;
#endif
#endif

787
lib/gpio.cc Normal file
View file

@ -0,0 +1,787 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
//
// 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 version 2.
//
// 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 <http://gnu.org/licenses/gpl-2.0.txt>
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
#include "gpio.h"
#include <assert.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
/*
* nanosleep() takes longer than requested because of OS jitter.
* In about 99.9% of the cases, this is <= 25 microcseconds on
* the Raspberry Pi (empirically determined with a Raspbian kernel), so
* we substract this value whenever we do nanosleep(); the remaining time
* we then busy wait to get a good accurate result.
*
* You can measure the overhead using DEBUG_SLEEP_JITTER below.
*
* Note: A higher value here will result in more CPU use because of more busy
* waiting inching towards the real value (for all the cases that nanosleep()
* actually was better than this overhead).
*
* This might be interesting to tweak in particular if you have a realtime
* kernel with different characteristics.
*/
#define EMPIRICAL_NANOSLEEP_OVERHEAD_US 12
/*
* In case of non-hardware pulse generation, use nanosleep if we want to wait
* longer than these given microseconds beyond the general overhead.
* Below that, just use busy wait.
*/
#define MINIMUM_NANOSLEEP_TIME_US 5
/* In order to determine useful values for above, set this to 1 and use the
* hardware pin-pulser.
* It will output a histogram atexit() of how much how often we were over
* the requested time.
* (The full histogram will be shifted by the EMPIRICAL_NANOSLEEP_OVERHEAD_US
* value above. To get a full histogram of OS overhead, set it to 0 first).
*/
#define DEBUG_SLEEP_JITTER 0
// Raspberry 1 and 2 have different base addresses for the periphery
#define BCM2708_PERI_BASE 0x20000000
#define BCM2709_PERI_BASE 0x3F000000
#define BCM2711_PERI_BASE 0xFE000000
#define GPIO_REGISTER_OFFSET 0x200000
#define COUNTER_1Mhz_REGISTER_OFFSET 0x3000
#define GPIO_PWM_BASE_OFFSET (GPIO_REGISTER_OFFSET + 0xC000)
#define GPIO_CLK_BASE_OFFSET 0x101000
#define REGISTER_BLOCK_SIZE (4*1024)
#define PWM_CTL (0x00 / 4)
#define PWM_STA (0x04 / 4)
#define PWM_RNG1 (0x10 / 4)
#define PWM_FIFO (0x18 / 4)
#define PWM_CTL_CLRF1 (1<<6) // CH1 Clear Fifo (1 Clears FIFO 0 has no effect)
#define PWM_CTL_USEF1 (1<<5) // CH1 Use Fifo (0=data reg transmit 1=Fifo used for transmission)
#define PWM_CTL_POLA1 (1<<4) // CH1 Polarity (0=(0=low 1=high) 1=(1=low 0=high)
#define PWM_CTL_SBIT1 (1<<3) // CH1 Silence Bit (state of output when 0 transmission takes place)
#define PWM_CTL_MODE1 (1<<1) // CH1 Mode (0=pwm 1=serialiser mode)
#define PWM_CTL_PWEN1 (1<<0) // CH1 Enable (0=disable 1=enable)
#define PWM_STA_EMPT1 (1<<1)
#define PWM_STA_FULL1 (1<<0)
#define CLK_PASSWD (0x5A<<24)
#define CLK_CTL_MASH(x)((x)<<9)
#define CLK_CTL_BUSY (1 <<7)
#define CLK_CTL_KILL (1 <<5)
#define CLK_CTL_ENAB (1 <<4)
#define CLK_CTL_SRC(x) ((x)<<0)
#define CLK_CTL_SRC_PLLD 6 /* 500.0 MHz */
#define CLK_DIV_DIVI(x) ((x)<<12)
#define CLK_DIV_DIVF(x) ((x)<< 0)
#define CLK_PWMCTL 40
#define CLK_PWMDIV 41
// We want to have the last word in the fifo free
#define MAX_PWM_BIT_USE 224
#define PWM_BASE_TIME_NS 2
// GPIO setup macros. Always use INP_GPIO(x) before using OUT_GPIO(x).
#define INP_GPIO(g) *(s_GPIO_registers+((g)/10)) &= ~(7ull<<(((g)%10)*3))
#define OUT_GPIO(g) *(s_GPIO_registers+((g)/10)) |= (1ull<<(((g)%10)*3))
#define GPIO_SET *(gpio+7) // sets bits which are 1 ignores bits which are 0
#define GPIO_CLR *(gpio+10) // clears bits which are 1 ignores bits which are 0
// We're pre-mapping all the registers on first call of GPIO::Init(),
// so that it is possible to drop privileges afterwards and still have these
// usable.
static volatile uint32_t *s_GPIO_registers = NULL;
static volatile uint32_t *s_Timer1Mhz = NULL;
static volatile uint32_t *s_PWM_registers = NULL;
static volatile uint32_t *s_CLK_registers = NULL;
namespace rgb_matrix {
#define GPIO_BIT(x) (1ull << x)
GPIO::GPIO() : output_bits_(0), input_bits_(0), reserved_bits_(0),
slowdown_(1)
#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
, uses_64_bit_(false)
#endif
{
}
gpio_bits_t GPIO::InitOutputs(gpio_bits_t outputs,
bool adafruit_pwm_transition_hack_needed) {
if (s_GPIO_registers == NULL) {
fprintf(stderr, "Attempt to init outputs but not yet Init()-ialized.\n");
return 0;
}
// Hack: for the PWM mod, the user soldered together GPIO 18 (new OE)
// with GPIO 4 (old OE).
// Since they are connected inside the HAT, want to make extra sure that,
// whatever the outside system set as pinmux, the old OE is _not_ also
// set as output so that these GPIO outputs don't fight each other.
//
// So explicitly set both of these pins as input initially, so the user
// can switch between the two modes "adafruit-hat" and "adafruit-hat-pwm"
// without trouble.
if (adafruit_pwm_transition_hack_needed) {
INP_GPIO(4);
INP_GPIO(18);
// Even with PWM enabled, GPIO4 still can not be used, because it is
// now connected to the GPIO18 and thus must stay an input.
// So reserve this bit if it is not set in outputs.
reserved_bits_ = GPIO_BIT(4) & ~outputs;
}
outputs &= ~(output_bits_ | input_bits_ | reserved_bits_);
#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
const int kMaxAvailableBit = 45;
uses_64_bit_ |= (outputs >> 32) != 0;
#else
const int kMaxAvailableBit = 31;
#endif
for (int b = 0; b <= kMaxAvailableBit; ++b) {
if (outputs & GPIO_BIT(b)) {
INP_GPIO(b); // for writing, we first need to set as input.
OUT_GPIO(b);
}
}
output_bits_ |= outputs;
return outputs;
}
gpio_bits_t GPIO::RequestInputs(gpio_bits_t inputs) {
if (s_GPIO_registers == NULL) {
fprintf(stderr, "Attempt to init inputs but not yet Init()-ialized.\n");
return 0;
}
inputs &= ~(output_bits_ | input_bits_ | reserved_bits_);
#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
const int kMaxAvailableBit = 45;
uses_64_bit_ |= (inputs >> 32) != 0;
#else
const int kMaxAvailableBit = 31;
#endif
for (int b = 0; b <= kMaxAvailableBit; ++b) {
if (inputs & GPIO_BIT(b)) {
INP_GPIO(b);
}
}
input_bits_ |= inputs;
return inputs;
}
// We are not interested in the _exact_ model, just good enough to determine
// What to do.
enum RaspberryPiModel {
PI_MODEL_1,
PI_MODEL_2,
PI_MODEL_3,
PI_MODEL_4
};
static int ReadFileToBuffer(char *buffer, size_t size, const char *filename) {
const int fd = open(filename, O_RDONLY);
if (fd < 0) return -1;
ssize_t r = read(fd, buffer, size - 1); // assume one read enough
buffer[r >= 0 ? r : 0] = '\0';
close(fd);
return r;
}
static RaspberryPiModel DetermineRaspberryModel() {
char buffer[4096];
if (ReadFileToBuffer(buffer, sizeof(buffer), "/proc/cpuinfo") < 0) {
fprintf(stderr, "Reading cpuinfo: Could not determine Pi model\n");
return PI_MODEL_3; // safe guess fallback.
}
static const char RevisionTag[] = "Revision";
const char *revision_key;
if ((revision_key = strstr(buffer, RevisionTag)) == NULL) {
fprintf(stderr, "non-existent Revision: Could not determine Pi model\n");
return PI_MODEL_3;
}
unsigned int pi_revision;
if (sscanf(index(revision_key, ':') + 1, "%x", &pi_revision) != 1) {
fprintf(stderr, "Unknown Revision: Could not determine Pi model\n");
return PI_MODEL_3;
}
// https://www.raspberrypi.org/documentation/hardware/raspberrypi/revision-codes/README.md
const unsigned pi_type = (pi_revision >> 4) & 0xff;
switch (pi_type) {
case 0x00: /* A */
case 0x01: /* B, Compute Module 1 */
case 0x02: /* A+ */
case 0x03: /* B+ */
case 0x05: /* Alpha ?*/
case 0x06: /* Compute Module1 */
case 0x09: /* Zero */
case 0x0c: /* Zero W */
return PI_MODEL_1;
case 0x04: /* Pi 2 */
return PI_MODEL_2;
case 0x11: /* Pi 4 */
return PI_MODEL_4;
default: /* a bunch of versions represneting Pi 3 */
return PI_MODEL_3;
}
}
static RaspberryPiModel GetPiModel() {
static RaspberryPiModel pi_model = DetermineRaspberryModel();
return pi_model;
}
static int GetNumCores() {
return GetPiModel() == PI_MODEL_1 ? 1 : 4;
}
static uint32_t *mmap_bcm_register(off_t register_offset) {
off_t base = BCM2709_PERI_BASE; // safe fallback guess.
switch (GetPiModel()) {
case PI_MODEL_1: base = BCM2708_PERI_BASE; break;
case PI_MODEL_2: base = BCM2709_PERI_BASE; break;
case PI_MODEL_3: base = BCM2709_PERI_BASE; break;
case PI_MODEL_4: base = BCM2711_PERI_BASE; break;
}
int mem_fd;
if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
// Try to fall back to /dev/gpiomem. Unfortunately, that device
// is implemented in a way that it _only_ supports GPIO, not the
// other registers we need, such as PWM or COUNTER_1Mhz, which means
// we only can operate with degraded performance.
//
// But, instead of failing, mmap() then silently succeeds with the
// unsupported offset. So bail out here.
if (register_offset != GPIO_REGISTER_OFFSET)
return NULL;
mem_fd = open("/dev/gpiomem", O_RDWR|O_SYNC);
if (mem_fd < 0) return NULL;
}
uint32_t *result =
(uint32_t*) mmap(NULL, // Any adddress in our space will do
REGISTER_BLOCK_SIZE, // Map length
PROT_READ|PROT_WRITE, // Enable r/w on GPIO registers.
MAP_SHARED,
mem_fd, // File to map
base + register_offset // Offset to bcm register
);
close(mem_fd);
if (result == MAP_FAILED) {
perror("mmap error: ");
fprintf(stderr, "MMapping from base 0x%lx, offset 0x%lx\n",
base, register_offset);
return NULL;
}
return result;
}
static bool mmap_all_bcm_registers_once() {
if (s_GPIO_registers != NULL) return true; // alrady done.
// The common GPIO registers.
s_GPIO_registers = mmap_bcm_register(GPIO_REGISTER_OFFSET);
if (s_GPIO_registers == NULL) {
return false;
}
// Time measurement. Might fail when run as non-root.
uint32_t *timereg = mmap_bcm_register(COUNTER_1Mhz_REGISTER_OFFSET);
if (timereg != NULL) {
s_Timer1Mhz = timereg + 1;
}
// Hardware pin-pulser. Might fail when run as non-root.
s_PWM_registers = mmap_bcm_register(GPIO_PWM_BASE_OFFSET);
s_CLK_registers = mmap_bcm_register(GPIO_CLK_BASE_OFFSET);
return true;
}
bool GPIO::Init(int slowdown) {
slowdown_ = slowdown;
// Pre-mmap all bcm registers we need now and possibly in the future, as to
// allow dropping privileges after GPIO::Init() even as some of these
// registers might be needed later.
if (!mmap_all_bcm_registers_once())
return false;
gpio_set_bits_low_ = s_GPIO_registers + (0x1C / sizeof(uint32_t));
gpio_clr_bits_low_ = s_GPIO_registers + (0x28 / sizeof(uint32_t));
gpio_read_bits_low_ = s_GPIO_registers + (0x34 / sizeof(uint32_t));
#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
gpio_set_bits_high_ = s_GPIO_registers + (0x20 / sizeof(uint32_t));
gpio_clr_bits_high_ = s_GPIO_registers + (0x2C / sizeof(uint32_t));
gpio_read_bits_high_ = s_GPIO_registers + (0x38 / sizeof(uint32_t));
#endif
return true;
}
/*
* We support also other pinouts that don't have the OE- on the hardware
* PWM output pin, so we need to provide (impefect) 'manual' timing as well.
* Hence all various busy_wait_nano() implementations depending on the hardware.
*/
// --- PinPulser. Private implementation parts.
namespace {
// Manual timers.
class Timers {
public:
static bool Init();
static void sleep_nanos(long t);
};
// Simplest of PinPulsers. Uses somewhat jittery and manual timers
// to get the timing, but not optimal.
class TimerBasedPinPulser : public PinPulser {
public:
TimerBasedPinPulser(GPIO *io, gpio_bits_t bits,
const std::vector<int> &nano_specs)
: io_(io), bits_(bits), nano_specs_(nano_specs) {
if (!s_Timer1Mhz) {
fprintf(stderr, "FYI: not running as root which means we can't properly "
"control timing unless this is a real-time kernel. Expect color "
"degradation. Consider running as root with sudo.\n");
}
}
virtual void SendPulse(int time_spec_number) {
io_->ClearBits(bits_);
Timers::sleep_nanos(nano_specs_[time_spec_number]);
io_->SetBits(bits_);
}
private:
GPIO *const io_;
const gpio_bits_t bits_;
const std::vector<int> nano_specs_;
};
static bool LinuxHasModuleLoaded(const char *name) {
FILE *f = fopen("/proc/modules", "r");
if (f == NULL) return false; // don't care.
char buf[256];
const size_t namelen = strlen(name);
bool found = false;
while (fgets(buf, sizeof(buf), f) != NULL) {
if (strncmp(buf, name, namelen) == 0) {
found = true;
break;
}
}
fclose(f);
return found;
}
static void busy_wait_nanos_rpi_1(long nanos);
static void busy_wait_nanos_rpi_2(long nanos);
static void busy_wait_nanos_rpi_3(long nanos);
static void busy_wait_nanos_rpi_4(long nanos);
static void (*busy_wait_impl)(long) = busy_wait_nanos_rpi_3;
// Best effort write to file. Used to set kernel parameters.
static void WriteTo(const char *filename, const char *str) {
const int fd = open(filename, O_WRONLY);
if (fd < 0) return;
(void) write(fd, str, strlen(str)); // Best effort. Ignore return value.
close(fd);
}
// By default, the kernel applies some throtteling for realtime
// threads to prevent starvation of non-RT threads. But we
// really want all we can get iff the machine has more cores and
// our RT-thread is locked onto one of these.
// So let's tell it not to do that.
static void DisableRealtimeThrottling() {
if (GetNumCores() == 1) return; // Not safe if we don't have > 1 core.
// We need to leave the kernel a little bit of time, as it does not like
// us to hog the kernel solidly. The default of 950000 leaves 50ms that
// can generate visible flicker, so we reduce that to 1ms.
WriteTo("/proc/sys/kernel/sched_rt_runtime_us", "999000");
}
bool Timers::Init() {
if (!mmap_all_bcm_registers_once())
return false;
// Choose the busy-wait loop that fits our Pi.
switch (GetPiModel()) {
case PI_MODEL_1: busy_wait_impl = busy_wait_nanos_rpi_1; break;
case PI_MODEL_2: busy_wait_impl = busy_wait_nanos_rpi_2; break;
case PI_MODEL_3: busy_wait_impl = busy_wait_nanos_rpi_3; break;
case PI_MODEL_4: busy_wait_impl = busy_wait_nanos_rpi_4; break;
}
DisableRealtimeThrottling();
// If we have it, we run the update thread on core3. No perf-compromises:
WriteTo("/sys/devices/system/cpu/cpu3/cpufreq/scaling_governor",
"performance");
return true;
}
static uint32_t JitterAllowanceMicroseconds() {
// If this is a Raspberry Pi with more than one core, we add a bit of
// additional overhead measured up to the 99.999%-ile: we can allow to burn
// a bit more busy-wait CPU cycles to get the timing accurate as we have
// more CPU to spare.
switch (GetPiModel()) {
case PI_MODEL_1:
return EMPIRICAL_NANOSLEEP_OVERHEAD_US; // 99.9%-ile
case PI_MODEL_2: case PI_MODEL_3:
return EMPIRICAL_NANOSLEEP_OVERHEAD_US + 35; // 99.999%-ile
case PI_MODEL_4:
return EMPIRICAL_NANOSLEEP_OVERHEAD_US + 10; // this one is fast.
}
return EMPIRICAL_NANOSLEEP_OVERHEAD_US;
}
void Timers::sleep_nanos(long nanos) {
// For smaller durations, we go straight to busy wait.
// For larger duration, we use nanosleep() to give the operating system
// a chance to do something else.
// However, these timings have a lot of jitter, so if we have the 1Mhz timer
// available, we use that to accurately mesure time spent and do the
// remaining time with busy wait. If we don't have the timer available
// (not running as root), we just use nanosleep() for larger values.
if (s_Timer1Mhz) {
static long kJitterAllowanceNanos = JitterAllowanceMicroseconds() * 1000;
if (nanos > kJitterAllowanceNanos + MINIMUM_NANOSLEEP_TIME_US*1000) {
const uint32_t before = *s_Timer1Mhz;
struct timespec sleep_time = { 0, nanos - kJitterAllowanceNanos };
nanosleep(&sleep_time, NULL);
const uint32_t after = *s_Timer1Mhz;
const long nanoseconds_passed = 1000 * (uint32_t)(after - before);
if (nanoseconds_passed > nanos) {
return; // darn, missed it.
} else {
nanos -= nanoseconds_passed; // remaining time with busy-loop
}
}
} else {
// Not running as root, not having access to 1Mhz timer. Approximate large
// durations with nanosleep(); small durations are done with busy wait.
if (nanos > (EMPIRICAL_NANOSLEEP_OVERHEAD_US + MINIMUM_NANOSLEEP_TIME_US)*1000) {
struct timespec sleep_time
= { 0, nanos - EMPIRICAL_NANOSLEEP_OVERHEAD_US*1000 };
nanosleep(&sleep_time, NULL);
return;
}
}
busy_wait_impl(nanos); // Use model-specific busy-loop for remaining time.
}
static void busy_wait_nanos_rpi_1(long nanos) {
if (nanos < 70) return;
// The following loop is determined empirically on a 700Mhz RPi
for (uint32_t i = (nanos - 70) >> 2; i != 0; --i) {
asm("nop");
}
}
static void busy_wait_nanos_rpi_2(long nanos) {
if (nanos < 20) return;
// The following loop is determined empirically on a 900Mhz RPi 2
for (uint32_t i = (nanos - 20) * 100 / 110; i != 0; --i) {
asm("");
}
}
static void busy_wait_nanos_rpi_3(long nanos) {
if (nanos < 20) return;
for (uint32_t i = (nanos - 15) * 100 / 73; i != 0; --i) {
asm("");
}
}
static void busy_wait_nanos_rpi_4(long nanos) {
if (nanos < 20) return;
// Interesting, the Pi4 is _slower_ than the Pi3 ? At least for this busy loop
for (uint32_t i = (nanos - 5) * 100 / 132; i != 0; --i) {
asm("");
}
}
#if DEBUG_SLEEP_JITTER
static int overshoot_histogram_us[256] = {0};
static void print_overshoot_histogram() {
fprintf(stderr, "Overshoot histogram >= empirical overhead of %dus\n"
"%6s | %7s | %7s\n",
JitterAllowanceMicroseconds(), "usec", "count", "accum");
int total_count = 0;
for (int i = 0; i < 256; ++i) total_count += overshoot_histogram_us[i];
int running_count = 0;
for (int us = 0; us < 256; ++us) {
const int count = overshoot_histogram_us[us];
if (count > 0) {
running_count += count;
fprintf(stderr, "%s%3dus: %8d %7.3f%%\n", (us == 0) ? "<=" : " +",
us, count, 100.0 * running_count / total_count);
}
}
}
#endif
// A PinPulser that uses the PWM hardware to create accurate pulses.
// It only works on GPIO-12 or 18 though.
class HardwarePinPulser : public PinPulser {
public:
static bool CanHandle(gpio_bits_t gpio_mask) {
#ifdef DISABLE_HARDWARE_PULSES
return false;
#else
const bool can_handle = gpio_mask==GPIO_BIT(18) || gpio_mask==GPIO_BIT(12);
if (can_handle && (s_PWM_registers == NULL || s_CLK_registers == NULL)) {
// Instead of silently not using the hardware pin pulser and falling back
// to timing based loops, complain loudly and request the user to make
// a choice before continuing.
fprintf(stderr, "Need root. You are configured to use the hardware pulse "
"generator "
"for\n\tsmooth color rendering, however the necessary hardware\n"
"\tregisters can't be accessed because you probably don't run\n"
"\twith root permissions or privileges have been dropped.\n"
"\tSo you either have to run as root (e.g. using sudo) or\n"
"\tsupply the --led-no-hardware-pulse command-line flag.\n\n"
"\tExiting; run as root or with --led-no-hardware-pulse\n\n");
exit(1);
}
return can_handle;
#endif
}
HardwarePinPulser(gpio_bits_t pins, const std::vector<int> &specs)
: triggered_(false) {
assert(CanHandle(pins));
assert(s_CLK_registers && s_PWM_registers && s_Timer1Mhz);
#if DEBUG_SLEEP_JITTER
atexit(print_overshoot_histogram);
#endif
if (LinuxHasModuleLoaded("snd_bcm2835")) {
fprintf(stderr,
"\n%s=== snd_bcm2835: found that the Pi sound module is loaded. ===%s\n"
"Don't use the built-in sound of the Pi together with this lib; it is known to be\n"
"incompatible and cause trouble and hangs (you can still use external USB sound adapters).\n\n"
"See Troubleshooting section in README how to disable the sound module.\n"
"You can also run with --led-no-hardware-pulse to avoid the incompatibility,\n"
"but you will have more flicker.\n"
"Exiting; fix the above first or use --led-no-hardware-pulse\n\n",
"\033[1;31m", "\033[0m");
exit(1);
}
for (size_t i = 0; i < specs.size(); ++i) {
// Hints how long to nanosleep, already corrected for system overhead.
sleep_hints_us_.push_back(specs[i]/1000 - JitterAllowanceMicroseconds());
}
const int base = specs[0];
// Get relevant registers
fifo_ = s_PWM_registers + PWM_FIFO;
if (pins == GPIO_BIT(18)) {
// set GPIO 18 to PWM0 mode (Alternative 5)
SetGPIOMode(s_GPIO_registers, 18, 2);
} else if (pins == GPIO_BIT(12)) {
// set GPIO 12 to PWM0 mode (Alternative 0)
SetGPIOMode(s_GPIO_registers, 12, 4);
} else {
assert(false); // should've been caught by CanHandle()
}
InitPWMDivider((base/2) / PWM_BASE_TIME_NS);
for (size_t i = 0; i < specs.size(); ++i) {
pwm_range_.push_back(2 * specs[i] / base);
}
}
virtual void SendPulse(int c) {
if (pwm_range_[c] < 16) {
s_PWM_registers[PWM_RNG1] = pwm_range_[c];
*fifo_ = pwm_range_[c];
} else {
// Keep the actual range as short as possible, as we have to
// wait for one full period of these in the zero phase.
// The hardware can't deal with values < 2, so only do this when
// have enough of these.
s_PWM_registers[PWM_RNG1] = pwm_range_[c] / 8;
*fifo_ = pwm_range_[c] / 8;
*fifo_ = pwm_range_[c] / 8;
*fifo_ = pwm_range_[c] / 8;
*fifo_ = pwm_range_[c] / 8;
*fifo_ = pwm_range_[c] / 8;
*fifo_ = pwm_range_[c] / 8;
*fifo_ = pwm_range_[c] / 8;
*fifo_ = pwm_range_[c] / 8;
}
/*
* We need one value at the end to have it go back to
* default state (otherwise it just repeats the last
* value, so will be constantly 'on').
*/
*fifo_ = 0; // sentinel.
/*
* For some reason, we need a second empty sentinel in the
* fifo, otherwise our way to detect the end of the pulse,
* which relies on 'is the queue empty' does not work. It is
* not entirely clear why that is from the datasheet,
* but probably there is some buffering register in which data
* elements are kept after the fifo is emptied.
*/
*fifo_ = 0;
sleep_hint_us_ = sleep_hints_us_[c];
start_time_ = *s_Timer1Mhz;
triggered_ = true;
s_PWM_registers[PWM_CTL] = PWM_CTL_USEF1 | PWM_CTL_PWEN1 | PWM_CTL_POLA1;
}
virtual void WaitPulseFinished() {
if (!triggered_) return;
// Determine how long we already spent and sleep to get close to the
// actual end-time of our sleep period.
//
// TODO(hzeller): find if it is possible to get some sort of interrupt from
// the hardware once it is done with the pulse. Sounds silly that there is
// not (so far, only tested GPIO interrupt with a feedback line, but that
// is super-slow with 20μs overhead).
if (sleep_hint_us_ > 0) {
const uint32_t already_elapsed_usec = *s_Timer1Mhz - start_time_;
const int to_sleep_us = sleep_hint_us_ - already_elapsed_usec;
if (to_sleep_us > 0) {
struct timespec sleep_time = { 0, 1000 * to_sleep_us };
nanosleep(&sleep_time, NULL);
#if DEBUG_SLEEP_JITTER
{
// Record histogram of realtime jitter how much longer we actually
// took.
const int total_us = *s_Timer1Mhz - start_time_;
const int nanoslept_us = total_us - already_elapsed_usec;
int overshoot = nanoslept_us - (to_sleep_us + JitterAllowanceMicroseconds());
if (overshoot < 0) overshoot = 0;
if (overshoot > 255) overshoot = 255;
overshoot_histogram_us[overshoot]++;
}
#endif
}
}
while ((s_PWM_registers[PWM_STA] & PWM_STA_EMPT1) == 0) {
// busy wait until done.
}
s_PWM_registers[PWM_CTL] = PWM_CTL_USEF1 | PWM_CTL_POLA1 | PWM_CTL_CLRF1;
triggered_ = false;
}
private:
void SetGPIOMode(volatile uint32_t *gpioReg, unsigned gpio, unsigned mode) {
const int reg = gpio / 10;
const int mode_pos = (gpio % 10) * 3;
gpioReg[reg] = (gpioReg[reg] & ~(7 << mode_pos)) | (mode << mode_pos);
}
void InitPWMDivider(uint32_t divider) {
assert(divider < (1<<12)); // we only have 12 bits.
s_PWM_registers[PWM_CTL] = PWM_CTL_USEF1 | PWM_CTL_POLA1 | PWM_CTL_CLRF1;
// reset PWM clock
s_CLK_registers[CLK_PWMCTL] = CLK_PASSWD | CLK_CTL_KILL;
// set PWM clock source as 500 MHz PLLD
s_CLK_registers[CLK_PWMCTL] = CLK_PASSWD | CLK_CTL_SRC(CLK_CTL_SRC_PLLD);
// set PWM clock divider
s_CLK_registers[CLK_PWMDIV]
= CLK_PASSWD | CLK_DIV_DIVI(divider) | CLK_DIV_DIVF(0);
// enable PWM clock
s_CLK_registers[CLK_PWMCTL]
= CLK_PASSWD | CLK_CTL_ENAB | CLK_CTL_SRC(CLK_CTL_SRC_PLLD);
}
private:
std::vector<uint32_t> pwm_range_;
std::vector<int> sleep_hints_us_;
volatile uint32_t *fifo_;
uint32_t start_time_;
int sleep_hint_us_;
bool triggered_;
};
} // end anonymous namespace
// Public PinPulser factory
PinPulser *PinPulser::Create(GPIO *io, gpio_bits_t gpio_mask,
bool allow_hardware_pulsing,
const std::vector<int> &nano_wait_spec) {
if (!Timers::Init()) return NULL;
if (allow_hardware_pulsing && HardwarePinPulser::CanHandle(gpio_mask)) {
return new HardwarePinPulser(gpio_mask, nano_wait_spec);
} else {
return new TimerBasedPinPulser(io, gpio_mask, nano_wait_spec);
}
}
// For external use, e.g. in the matrix for extra time.
uint32_t GetMicrosecondCounter() {
if (s_Timer1Mhz) return *s_Timer1Mhz;
// When run as non-root, we can't read the timer. Fall back to slow
// operating-system ways.
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
const uint64_t micros = ts.tv_nsec / 1000;
const uint64_t epoch_usec = (uint64_t)ts.tv_sec * 1000000 + micros;
return epoch_usec & 0xFFFFFFFF;
}
} // namespace rgb_matrix

153
lib/gpio.h Normal file
View file

@ -0,0 +1,153 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
//
// 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 version 2.
//
// 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 <http://gnu.org/licenses/gpl-2.0.txt>
#ifndef RPI_GPIO_INTERNAL_H
#define RPI_GPIO_INTERNAL_H
#include "gpio-bits.h"
#include <vector>
// Putting this in our namespace to not collide with other things called like
// this.
namespace rgb_matrix {
// For now, everything is initialized as output.
class GPIO {
public:
GPIO();
// Initialize before use. Returns 'true' if successful, 'false' otherwise
// (e.g. due to a permission problem).
bool Init(int
#if RGB_SLOWDOWN_GPIO
slowdown = RGB_SLOWDOWN_GPIO
#else
slowdown = 1
#endif
);
// Initialize outputs.
// Returns the bits that were available and could be set for output.
// (never use the optional adafruit_hack_needed parameter, it is used
// internally to this library).
gpio_bits_t InitOutputs(gpio_bits_t outputs,
bool adafruit_hack_needed = false);
// Request given bitmap of GPIO inputs.
// Returns the bits that were available and could be reserved.
gpio_bits_t RequestInputs(gpio_bits_t inputs);
// Set the bits that are '1' in the output. Leave the rest untouched.
inline void SetBits(gpio_bits_t value) {
if (!value) return;
WriteSetBits(value);
for (int i = 0; i < slowdown_; ++i) {
WriteSetBits(value);
}
}
// Clear the bits that are '1' in the output. Leave the rest untouched.
inline void ClearBits(gpio_bits_t value) {
if (!value) return;
WriteClrBits(value);
for (int i = 0; i < slowdown_; ++i) {
WriteClrBits(value);
}
}
// Write all the bits of "value" mentioned in "mask". Leave the rest untouched.
inline void WriteMaskedBits(gpio_bits_t value, gpio_bits_t mask) {
// Writing a word is two operations. The IO is actually pretty slow, so
// this should probably be unnoticable.
ClearBits(~value & mask);
SetBits(value & mask);
}
inline gpio_bits_t Read() const { return ReadRegisters() & input_bits_; }
private:
inline gpio_bits_t ReadRegisters() const {
return (static_cast<gpio_bits_t>(*gpio_read_bits_low_)
#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
| (static_cast<gpio_bits_t>(*gpio_read_bits_low_) << 32)
#endif
);
}
inline void WriteSetBits(gpio_bits_t value) {
*gpio_set_bits_low_ = static_cast<uint32_t>(value & 0xFFFFFFFF);
#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
if (uses_64_bit_)
*gpio_set_bits_high_ = static_cast<uint32_t>(value >> 32);
#endif
}
inline void WriteClrBits(gpio_bits_t value) {
*gpio_clr_bits_low_ = static_cast<uint32_t>(value & 0xFFFFFFFF);
#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
if (uses_64_bit_)
*gpio_clr_bits_high_ = static_cast<uint32_t>(value >> 32);
#endif
}
private:
gpio_bits_t output_bits_;
gpio_bits_t input_bits_;
gpio_bits_t reserved_bits_;
int slowdown_;
volatile uint32_t *gpio_set_bits_low_;
volatile uint32_t *gpio_clr_bits_low_;
volatile uint32_t *gpio_read_bits_low_;
#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
bool uses_64_bit_;
volatile uint32_t *gpio_set_bits_high_;
volatile uint32_t *gpio_clr_bits_high_;
volatile uint32_t *gpio_read_bits_high_;
#endif
};
// A PinPulser is a utility class that pulses a GPIO pin. There can be various
// implementations.
class PinPulser {
public:
// Factory for a PinPulser. Chooses the right implementation depending
// on the context (CPU and which pins are affected).
// "gpio_mask" is the mask that should be output (since we only
// need negative pulses, this is what it does)
// "nano_wait_spec" contains a list of time periods we'd like
// invoke later. This can be used to pre-process timings if needed.
static PinPulser *Create(GPIO *io, gpio_bits_t gpio_mask,
bool allow_hardware_pulsing,
const std::vector<int> &nano_wait_spec);
virtual ~PinPulser() {}
// Send a pulse with a given length (index into nano_wait_spec array).
virtual void SendPulse(int time_spec_number) = 0;
// If SendPulse() is asynchronously implemented, wait for pulse to finish.
virtual void WaitPulseFinished() {}
};
// Get rolling over microsecond counter. We get this from a hardware register
// if possible and a terrible slow fallback otherwise.
uint32_t GetMicrosecondCounter();
} // end namespace rgb_matrix
#endif // RPI_GPIO_INGERNALH

BIN
lib/gpio.o Normal file

Binary file not shown.

172
lib/graphics.cc Normal file
View file

@ -0,0 +1,172 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2014 Henner Zeller <h.zeller@acm.org>
//
// 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 version 2.
//
// 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 <http://gnu.org/licenses/gpl-2.0.txt>
#include "graphics.h"
#include "utf8-internal.h"
#include <stdlib.h>
#include <functional>
#include <algorithm>
namespace rgb_matrix {
bool SetImage(Canvas *c, int canvas_offset_x, int canvas_offset_y,
const uint8_t *buffer, size_t size,
const int width, const int height,
bool is_bgr) {
if (3 * width * height != (int)size) // Sanity check
return false;
int image_display_w = width;
int image_display_h = height;
size_t skip_start_row = 0; // Bytes to skip before each row
if (canvas_offset_x < 0) {
skip_start_row = -canvas_offset_x * 3;
image_display_w += canvas_offset_x;
if (image_display_w <= 0) return false; // Done. outside canvas.
canvas_offset_x = 0;
}
if (canvas_offset_y < 0) {
// Skip buffer to the first row we'll be showing
buffer += 3 * width * -canvas_offset_y;
image_display_h += canvas_offset_y;
if (image_display_h <= 0) return false; // Done. outside canvas.
canvas_offset_y = 0;
}
const int w = std::min(c->width(), canvas_offset_x + image_display_w);
const int h = std::min(c->height(), canvas_offset_y + image_display_h);
// Bytes to skip for wider than canvas image at the end of a row
const size_t skip_end_row = (canvas_offset_x + image_display_w > w)
? (canvas_offset_x + image_display_w - w) * 3
: 0;
// Let's make this a combined skip per row and ajust where we start.
const size_t next_row_skip = skip_start_row + skip_end_row;
buffer += skip_start_row;
if (is_bgr) {
for (int y = canvas_offset_y; y < h; ++y) {
for (int x = canvas_offset_x; x < w; ++x) {
c->SetPixel(x, y, buffer[2], buffer[1], buffer[0]);
buffer += 3;
}
buffer += next_row_skip;
}
} else {
for (int y = canvas_offset_y; y < h; ++y) {
for (int x = canvas_offset_x; x < w; ++x) {
c->SetPixel(x, y, buffer[0], buffer[1], buffer[2]);
buffer += 3;
}
buffer += next_row_skip;
}
}
return true;
}
int DrawText(Canvas *c, const Font &font,
int x, int y, const Color &color,
const char *utf8_text) {
return DrawText(c, font, x, y, color, NULL, utf8_text);
}
int DrawText(Canvas *c, const Font &font,
int x, int y, const Color &color, const Color *background_color,
const char *utf8_text, int extra_spacing) {
const int start_x = x;
while (*utf8_text) {
const uint32_t cp = utf8_next_codepoint(utf8_text);
x += font.DrawGlyph(c, x, y, color, background_color, cp);
x += extra_spacing;
}
return x - start_x;
}
// There used to be a symbol without the optional extra_spacing parameter. Let's
// define this here so that people linking against an old library will still
// have their code usable. Now: 2017-06-04; can probably be removed in a couple
// of months.
int DrawText(Canvas *c, const Font &font,
int x, int y, const Color &color, const Color *background_color,
const char *utf8_text) {
return DrawText(c, font, x, y, color, background_color, utf8_text, 0);
}
int VerticalDrawText(Canvas *c, const Font &font, int x, int y,
const Color &color, const Color *background_color,
const char *utf8_text, int extra_spacing) {
const int start_y = y;
while (*utf8_text) {
const uint32_t cp = utf8_next_codepoint(utf8_text);
font.DrawGlyph(c, x, y, color, background_color, cp);
y += font.height() + extra_spacing;
}
return y - start_y;
}
void DrawCircle(Canvas *c, int x0, int y0, int radius, const Color &color) {
int x = radius, y = 0;
int radiusError = 1 - x;
while (y <= x) {
c->SetPixel(x + x0, y + y0, color.r, color.g, color.b);
c->SetPixel(y + x0, x + y0, color.r, color.g, color.b);
c->SetPixel(-x + x0, y + y0, color.r, color.g, color.b);
c->SetPixel(-y + x0, x + y0, color.r, color.g, color.b);
c->SetPixel(-x + x0, -y + y0, color.r, color.g, color.b);
c->SetPixel(-y + x0, -x + y0, color.r, color.g, color.b);
c->SetPixel(x + x0, -y + y0, color.r, color.g, color.b);
c->SetPixel(y + x0, -x + y0, color.r, color.g, color.b);
y++;
if (radiusError<0){
radiusError += 2 * y + 1;
} else {
x--;
radiusError+= 2 * (y - x + 1);
}
}
}
void DrawLine(Canvas *c, int x0, int y0, int x1, int y1, const Color &color) {
int dy = y1 - y0, dx = x1 - x0, gradient, x, y, shift = 0x10;
if (abs(dx) > abs(dy)) {
// x variation is bigger than y variation
if (x1 < x0) {
std::swap(x0, x1);
std::swap(y0, y1);
}
gradient = (dy << shift) / dx ;
for (x = x0 , y = 0x8000 + (y0 << shift); x <= x1; ++x, y += gradient) {
c->SetPixel(x, y >> shift, color.r, color.g, color.b);
}
} else if (dy != 0) {
// y variation is bigger than x variation
if (y1 < y0) {
std::swap(x0, x1);
std::swap(y0, y1);
}
gradient = (dx << shift) / dy;
for (y = y0 , x = 0x8000 + (x0 << shift); y <= y1; ++y, x += gradient) {
c->SetPixel(x >> shift, y, color.r, color.g, color.b);
}
} else {
c->SetPixel(x0, y0, color.r, color.g, color.b);
}
}
}//namespace

BIN
lib/graphics.o Normal file

Binary file not shown.

287
lib/hardware-mapping.c Normal file
View file

@ -0,0 +1,287 @@
/* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*-
* Copyright (C) 2013, 2016 Henner Zeller <h.zeller@acm.org>
*
* 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 version 2.
*
* 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 <http: *gnu.org/licenses/gpl-2.0.txt>
*/
/*
* We do this in plain C so that we can use designated initializers.
*/
#include "hardware-mapping.h"
#define GPIO_BIT(b) ((uint64_t)1<<(b))
struct HardwareMapping matrix_hardware_mappings[] = {
/*
* The regular hardware mapping described in the wiring.md and used
* by the adapter PCBs.
*/
{
.name = "regular",
.output_enable = GPIO_BIT(18),
.clock = GPIO_BIT(17),
.strobe = GPIO_BIT(4),
/* Address lines */
.a = GPIO_BIT(22),
.b = GPIO_BIT(23),
.c = GPIO_BIT(24),
.d = GPIO_BIT(25),
.e = GPIO_BIT(15), /* RxD kept free unless 1:64 */
/* Parallel chain 0, RGB for both sub-panels */
.p0_r1 = GPIO_BIT(11), /* masks: SPI0_SCKL */
.p0_g1 = GPIO_BIT(27), /* Not on RPi1, Rev1; use "regular-pi1" instead */
.p0_b1 = GPIO_BIT(7), /* masks: SPI0_CE1 */
.p0_r2 = GPIO_BIT(8), /* masks: SPI0_CE0 */
.p0_g2 = GPIO_BIT(9), /* masks: SPI0_MISO */
.p0_b2 = GPIO_BIT(10), /* masks: SPI0_MOSI */
/* All the following are only available with 40 GPIP pins, on A+/B+/Pi2,3 */
/* Chain 1 */
.p1_r1 = GPIO_BIT(12),
.p1_g1 = GPIO_BIT(5),
.p1_b1 = GPIO_BIT(6),
.p1_r2 = GPIO_BIT(19),
.p1_g2 = GPIO_BIT(13),
.p1_b2 = GPIO_BIT(20),
/* Chain 2 */
.p2_r1 = GPIO_BIT(14), /* masks TxD when parallel=3 */
.p2_g1 = GPIO_BIT(2), /* masks SCL when parallel=3 */
.p2_b1 = GPIO_BIT(3), /* masks SDA when parallel=3 */
.p2_r2 = GPIO_BIT(26),
.p2_g2 = GPIO_BIT(16),
.p2_b2 = GPIO_BIT(21),
},
/*
* This is used if you have an Adafruit HAT in the default configuration
*/
{
.name = "adafruit-hat",
.output_enable = GPIO_BIT(4),
.clock = GPIO_BIT(17),
.strobe = GPIO_BIT(21),
.a = GPIO_BIT(22),
.b = GPIO_BIT(26),
.c = GPIO_BIT(27),
.d = GPIO_BIT(20),
.e = GPIO_BIT(24), /* Needs manual wiring, see README.md */
.p0_r1 = GPIO_BIT(5),
.p0_g1 = GPIO_BIT(13),
.p0_b1 = GPIO_BIT(6),
.p0_r2 = GPIO_BIT(12),
.p0_g2 = GPIO_BIT(16),
.p0_b2 = GPIO_BIT(23),
},
/*
* An Adafruit HAT with the PWM modification
*/
{
.name = "adafruit-hat-pwm",
.output_enable = GPIO_BIT(18), /* The only change compared to above */
.clock = GPIO_BIT(17),
.strobe = GPIO_BIT(21),
.a = GPIO_BIT(22),
.b = GPIO_BIT(26),
.c = GPIO_BIT(27),
.d = GPIO_BIT(20),
.e = GPIO_BIT(24),
.p0_r1 = GPIO_BIT(5),
.p0_g1 = GPIO_BIT(13),
.p0_b1 = GPIO_BIT(6),
.p0_r2 = GPIO_BIT(12),
.p0_g2 = GPIO_BIT(16),
.p0_b2 = GPIO_BIT(23),
},
/*
* The regular pin-out, but for Raspberry Pi1. The very first Pi1 Rev1 uses
* the same pin for GPIO-21 as later Pis use GPIO-27. Make it work for both.
*/
{
.name = "regular-pi1",
.output_enable = GPIO_BIT(18),
.clock = GPIO_BIT(17),
.strobe = GPIO_BIT(4),
/* Address lines */
.a = GPIO_BIT(22),
.b = GPIO_BIT(23),
.c = GPIO_BIT(24),
.d = GPIO_BIT(25),
.e = GPIO_BIT(15), /* RxD kept free unless 1:64 */
/* Parallel chain 0, RGB for both sub-panels */
.p0_r1 = GPIO_BIT(11), /* masks: SPI0_SCKL */
/* On Pi1 Rev1, the pin other Pis have GPIO27, these have GPIO21. So make
* this work for both Rev1 and Rev2.
*/
.p0_g1 = GPIO_BIT(21) | GPIO_BIT(27),
.p0_b1 = GPIO_BIT(7), /* masks: SPI0_CE1 */
.p0_r2 = GPIO_BIT(8), /* masks: SPI0_CE0 */
.p0_g2 = GPIO_BIT(9), /* masks: SPI0_MISO */
.p0_b2 = GPIO_BIT(10), /* masks: SPI0_MOSI */
/* No more chains - there are not enough GPIO */
},
/*
* Classic: Early forms of this library had this as default mapping, mostly
* derived from the 26 GPIO-header version so that it also can work
* on 40 Pin GPIO headers with more parallel chains.
* Not used anymore.
*/
{
.name = "classic",
.output_enable = GPIO_BIT(27), /* Not available on RPi1, Rev 1 */
.clock = GPIO_BIT(11),
.strobe = GPIO_BIT(4),
.a = GPIO_BIT(7),
.b = GPIO_BIT(8),
.c = GPIO_BIT(9),
.d = GPIO_BIT(10),
.p0_r1 = GPIO_BIT(17),
.p0_g1 = GPIO_BIT(18),
.p0_b1 = GPIO_BIT(22),
.p0_r2 = GPIO_BIT(23),
.p0_g2 = GPIO_BIT(24),
.p0_b2 = GPIO_BIT(25),
.p1_r1 = GPIO_BIT(12),
.p1_g1 = GPIO_BIT(5),
.p1_b1 = GPIO_BIT(6),
.p1_r2 = GPIO_BIT(19),
.p1_g2 = GPIO_BIT(13),
.p1_b2 = GPIO_BIT(20),
.p2_r1 = GPIO_BIT(14), /* masks TxD if parallel = 3 */
.p2_g1 = GPIO_BIT(2), /* masks SDA if parallel = 3 */
.p2_b1 = GPIO_BIT(3), /* masks SCL if parallel = 3 */
.p2_r2 = GPIO_BIT(15),
.p2_g2 = GPIO_BIT(26),
.p2_b2 = GPIO_BIT(21),
},
/*
* Classic pin-out for Rev-A Raspberry Pi.
*/
{
.name = "classic-pi1",
/* The Revision-1 and Revision-2 boards have different GPIO mappings
* on the P1-3 and P1-5. So we use both interpretations.
* To keep the I2C pins free, we avoid these in later mappings.
*/
.output_enable = GPIO_BIT(0) | GPIO_BIT(2),
.clock = GPIO_BIT(1) | GPIO_BIT(3),
.strobe = GPIO_BIT(4),
.a = GPIO_BIT(7),
.b = GPIO_BIT(8),
.c = GPIO_BIT(9),
.d = GPIO_BIT(10),
.p0_r1 = GPIO_BIT(17),
.p0_g1 = GPIO_BIT(18),
.p0_b1 = GPIO_BIT(22),
.p0_r2 = GPIO_BIT(23),
.p0_g2 = GPIO_BIT(24),
.p0_b2 = GPIO_BIT(25),
},
#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
/*
* Custom pin-out for compute-module
*/
{
.name = "compute-module",
/* This GPIO mapping is made for the official I/O development
* board. No pin is left free when using 6 parallel chains.
*/
.output_enable = GPIO_BIT(18),
.clock = GPIO_BIT(16),
.strobe = GPIO_BIT(17),
.a = GPIO_BIT(2),
.b = GPIO_BIT(3),
.c = GPIO_BIT(4),
.d = GPIO_BIT(5),
.e = GPIO_BIT(6), /* RxD kept free unless 1:64 */
/* Chain 0 */
.p0_r1 = GPIO_BIT(7),
.p0_g1 = GPIO_BIT(8),
.p0_b1 = GPIO_BIT(9),
.p0_r2 = GPIO_BIT(10),
.p0_g2 = GPIO_BIT(11),
.p0_b2 = GPIO_BIT(12),
/* Chain 1 */
.p1_r1 = GPIO_BIT(13),
.p1_g1 = GPIO_BIT(14),
.p1_b1 = GPIO_BIT(15),
.p1_r2 = GPIO_BIT(19),
.p1_g2 = GPIO_BIT(20),
.p1_b2 = GPIO_BIT(21),
/* Chain 2 */
.p2_r1 = GPIO_BIT(22),
.p2_g1 = GPIO_BIT(23),
.p2_b1 = GPIO_BIT(24),
.p2_r2 = GPIO_BIT(25),
.p2_g2 = GPIO_BIT(26),
.p2_b2 = GPIO_BIT(27),
/* Chain 3 */
.p3_r1 = GPIO_BIT(28),
.p3_g1 = GPIO_BIT(29),
.p3_b1 = GPIO_BIT(30),
.p3_r2 = GPIO_BIT(31),
.p3_g2 = GPIO_BIT(32),
.p3_b2 = GPIO_BIT(33),
/* Chain 4 */
.p4_r1 = GPIO_BIT(34),
.p4_g1 = GPIO_BIT(35),
.p4_b1 = GPIO_BIT(36),
.p4_r2 = GPIO_BIT(37),
.p4_g2 = GPIO_BIT(38),
.p4_b2 = GPIO_BIT(39),
/* Chain 5 */
.p5_r1 = GPIO_BIT(40),
.p5_g1 = GPIO_BIT(41),
.p5_b1 = GPIO_BIT(42),
.p5_r2 = GPIO_BIT(43),
.p5_g2 = GPIO_BIT(44),
.p5_b2 = GPIO_BIT(45),
},
#endif
{0}
};

60
lib/hardware-mapping.h Normal file
View file

@ -0,0 +1,60 @@
/* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*-
* Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
*
* 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 version 2.
*
* 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 <http: *gnu.org/licenses/gpl-2.0.txt>
*/
#ifndef RPI_HARDWARE_MAPPING_H
#define RPI_HARDWARE_MAPPING_H
#ifdef __cplusplus
extern "C" {
#endif
#include "gpio-bits.h"
struct HardwareMapping {
const char *name;
int max_parallel_chains;
gpio_bits_t output_enable;
gpio_bits_t clock;
gpio_bits_t strobe;
gpio_bits_t a, b, c, d, e;
gpio_bits_t p0_r1, p0_g1, p0_b1;
gpio_bits_t p0_r2, p0_g2, p0_b2;
gpio_bits_t p1_r1, p1_g1, p1_b1;
gpio_bits_t p1_r2, p1_g2, p1_b2;
gpio_bits_t p2_r1, p2_g1, p2_b1;
gpio_bits_t p2_r2, p2_g2, p2_b2;
gpio_bits_t p3_r1, p3_g1, p3_b1;
gpio_bits_t p3_r2, p3_g2, p3_b2;
gpio_bits_t p4_r1, p4_g1, p4_b1;
gpio_bits_t p4_r2, p4_g2, p4_b2;
gpio_bits_t p5_r1, p5_g1, p5_b1;
gpio_bits_t p5_r2, p5_g2, p5_b2;
};
extern struct HardwareMapping matrix_hardware_mappings[];
#ifdef __cplusplus
} // extern C
#endif
#endif

BIN
lib/hardware-mapping.o Normal file

Binary file not shown.

304
lib/led-matrix-c.cc Normal file
View file

@ -0,0 +1,304 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
//
// 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 version 2.
//
// 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 <http://gnu.org/licenses/gpl-2.0.txt>
//
// C-bridge for led matrix.
#include "led-matrix-c.h"
#include <string.h>
#include <stdio.h>
#include "led-matrix.h"
#include "graphics.h"
// Make sure C++ is in sync with C
static_assert(sizeof(rgb_matrix::RGBMatrix::Options) == sizeof(RGBLedMatrixOptions));
static_assert(sizeof(rgb_matrix::RuntimeOptions) == sizeof(RGBLedRuntimeOptions));
// Our opaque dummy structs to communicate with the c-world
struct RGBLedMatrix {};
struct LedCanvas {};
struct LedFont {};
static rgb_matrix::RGBMatrix *to_matrix(struct RGBLedMatrix *matrix) {
return reinterpret_cast<rgb_matrix::RGBMatrix*>(matrix);
}
static struct RGBLedMatrix *from_matrix(rgb_matrix::RGBMatrix *matrix) {
return reinterpret_cast<struct RGBLedMatrix*>(matrix);
}
static rgb_matrix::FrameCanvas *to_canvas(struct LedCanvas *canvas) {
return reinterpret_cast<rgb_matrix::FrameCanvas*>(canvas);
}
static struct LedCanvas *from_canvas(rgb_matrix::FrameCanvas *canvas) {
return reinterpret_cast<struct LedCanvas*>(canvas);
}
static rgb_matrix::Font *to_font(struct LedFont *font) {
return reinterpret_cast<rgb_matrix::Font*>(font);
}
static struct LedFont *from_font(rgb_matrix::Font *font) {
return reinterpret_cast<struct LedFont*>(font);
}
static struct RGBLedMatrix *led_matrix_create_from_options_optional_edit(
struct RGBLedMatrixOptions *opts, struct RGBLedRuntimeOptions *rt_opts,
int *argc, char ***argv, bool remove_consumed_flags) {
rgb_matrix::RuntimeOptions default_rt;
rgb_matrix::RGBMatrix::Options default_opts;
if (opts) {
// Copy between C struct and C++ struct. The C++ struct already has a
// default constructor that sets some values. These we override with the
// C-struct values if available.
// We assume everything non-zero has an explicit value.
#define OPT_COPY_IF_SET(o) if (opts->o) default_opts.o = opts->o
OPT_COPY_IF_SET(hardware_mapping);
OPT_COPY_IF_SET(rows);
OPT_COPY_IF_SET(cols);
OPT_COPY_IF_SET(chain_length);
OPT_COPY_IF_SET(parallel);
OPT_COPY_IF_SET(pwm_bits);
OPT_COPY_IF_SET(pwm_lsb_nanoseconds);
OPT_COPY_IF_SET(pwm_dither_bits);
OPT_COPY_IF_SET(brightness);
OPT_COPY_IF_SET(scan_mode);
OPT_COPY_IF_SET(row_address_type);
OPT_COPY_IF_SET(multiplexing);
OPT_COPY_IF_SET(disable_hardware_pulsing);
OPT_COPY_IF_SET(show_refresh_rate);
OPT_COPY_IF_SET(inverse_colors);
OPT_COPY_IF_SET(led_rgb_sequence);
OPT_COPY_IF_SET(pixel_mapper_config);
OPT_COPY_IF_SET(panel_type);
OPT_COPY_IF_SET(limit_refresh_rate_hz);
#undef OPT_COPY_IF_SET
}
if (rt_opts) {
// Same story as RGBMatrix::Options
#define RT_OPT_COPY_IF_SET(o) if (rt_opts->o) default_rt.o = rt_opts->o
RT_OPT_COPY_IF_SET(gpio_slowdown);
RT_OPT_COPY_IF_SET(daemon);
RT_OPT_COPY_IF_SET(drop_privileges);
RT_OPT_COPY_IF_SET(do_gpio_init);
#undef RT_OPT_COPY_IF_SET
}
rgb_matrix::RGBMatrix::Options matrix_options = default_opts;
rgb_matrix::RuntimeOptions runtime_opt = default_rt;
if (argc != NULL && argv != NULL) {
if (!ParseOptionsFromFlags(argc, argv, &matrix_options, &runtime_opt,
remove_consumed_flags)) {
rgb_matrix::PrintMatrixFlags(stderr, default_opts, default_rt);
return NULL;
}
}
if (opts) {
#define ACTUAL_VALUE_BACK_TO_OPT(o) opts->o = matrix_options.o
ACTUAL_VALUE_BACK_TO_OPT(hardware_mapping);
ACTUAL_VALUE_BACK_TO_OPT(rows);
ACTUAL_VALUE_BACK_TO_OPT(cols);
ACTUAL_VALUE_BACK_TO_OPT(chain_length);
ACTUAL_VALUE_BACK_TO_OPT(parallel);
ACTUAL_VALUE_BACK_TO_OPT(pwm_bits);
ACTUAL_VALUE_BACK_TO_OPT(pwm_lsb_nanoseconds);
ACTUAL_VALUE_BACK_TO_OPT(pwm_dither_bits);
ACTUAL_VALUE_BACK_TO_OPT(brightness);
ACTUAL_VALUE_BACK_TO_OPT(scan_mode);
ACTUAL_VALUE_BACK_TO_OPT(row_address_type);
ACTUAL_VALUE_BACK_TO_OPT(multiplexing);
ACTUAL_VALUE_BACK_TO_OPT(disable_hardware_pulsing);
ACTUAL_VALUE_BACK_TO_OPT(show_refresh_rate);
ACTUAL_VALUE_BACK_TO_OPT(inverse_colors);
ACTUAL_VALUE_BACK_TO_OPT(led_rgb_sequence);
ACTUAL_VALUE_BACK_TO_OPT(pixel_mapper_config);
ACTUAL_VALUE_BACK_TO_OPT(panel_type);
ACTUAL_VALUE_BACK_TO_OPT(limit_refresh_rate_hz);
#undef ACTUAL_VALUE_BACK_TO_OPT
}
if (rt_opts) {
#define ACTUAL_VALUE_BACK_TO_RT_OPT(o) rt_opts->o = runtime_opt.o
ACTUAL_VALUE_BACK_TO_RT_OPT(gpio_slowdown);
ACTUAL_VALUE_BACK_TO_RT_OPT(daemon);
ACTUAL_VALUE_BACK_TO_RT_OPT(drop_privileges);
ACTUAL_VALUE_BACK_TO_RT_OPT(do_gpio_init);
#undef ACTUAL_VALUE_BACK_TO_RT_OPT
}
rgb_matrix::RGBMatrix *matrix
= rgb_matrix::RGBMatrix::CreateFromOptions(matrix_options, runtime_opt);
return from_matrix(matrix);
}
struct RGBLedMatrix *led_matrix_create_from_options(
struct RGBLedMatrixOptions *opts, int *argc, char ***argv) {
return led_matrix_create_from_options_optional_edit(opts, NULL, argc, argv,
true);
}
struct RGBLedMatrix *led_matrix_create_from_options_const_argv(
struct RGBLedMatrixOptions *opts, int argc, char **argv) {
return led_matrix_create_from_options_optional_edit(opts, NULL, &argc, &argv,
false);
}
struct RGBLedMatrix *led_matrix_create_from_options_and_rt_options(
struct RGBLedMatrixOptions *opts, struct RGBLedRuntimeOptions * rt_opts) {
return led_matrix_create_from_options_optional_edit(opts, rt_opts, NULL, NULL,
false);
}
struct RGBLedMatrix *led_matrix_create(int rows, int chained, int parallel) {
struct RGBLedMatrixOptions opts;
memset(&opts, 0, sizeof(opts));
opts.rows = rows;
opts.chain_length = chained;
opts.parallel = parallel;
return led_matrix_create_from_options(&opts, NULL, NULL);
}
void led_matrix_print_flags(FILE *out) {
rgb_matrix::RGBMatrix::Options defaults;
rgb_matrix::RuntimeOptions rt_opt;
rt_opt.daemon = -1;
rt_opt.drop_privileges = -1;
rgb_matrix::PrintMatrixFlags(out, defaults, rt_opt);
}
void led_matrix_delete(struct RGBLedMatrix *matrix) {
delete to_matrix(matrix);
}
struct LedCanvas *led_matrix_get_canvas(struct RGBLedMatrix *matrix) {
return from_canvas(to_matrix(matrix)->SwapOnVSync(NULL));
}
struct LedCanvas *led_matrix_create_offscreen_canvas(struct RGBLedMatrix *m) {
return from_canvas(to_matrix(m)->CreateFrameCanvas());
}
struct LedCanvas *led_matrix_swap_on_vsync(struct RGBLedMatrix *matrix,
struct LedCanvas *canvas) {
return from_canvas(to_matrix(matrix)->SwapOnVSync(to_canvas(canvas)));
}
void led_matrix_set_brightness(struct RGBLedMatrix *matrix,
uint8_t brightness) {
to_matrix(matrix)->SetBrightness(brightness);
}
uint8_t led_matrix_get_brightness(struct RGBLedMatrix *matrix) {
return to_matrix(matrix)->brightness();
}
void led_canvas_get_size(const struct LedCanvas *canvas,
int *width, int *height) {
rgb_matrix::FrameCanvas *c = to_canvas((struct LedCanvas*)canvas);
if (c == NULL ) return;
if (width != NULL) *width = c->width();
if (height != NULL) *height = c->height();
}
void led_canvas_set_pixel(struct LedCanvas *canvas, int x, int y,
uint8_t r, uint8_t g, uint8_t b) {
to_canvas(canvas)->SetPixel(x, y, r, g, b);
}
void led_canvas_clear(struct LedCanvas *canvas) {
to_canvas(canvas)->Clear();
}
void led_canvas_fill(struct LedCanvas *canvas, uint8_t r, uint8_t g, uint8_t b) {
to_canvas(canvas)->Fill(r, g, b);
}
struct LedFont *load_font(const char *bdf_font_file) {
rgb_matrix::Font* font = new rgb_matrix::Font();
font->LoadFont(bdf_font_file);
return from_font(font);
}
int baseline_font(struct LedFont * font) {
return to_font(font)->baseline();
}
int height_font(struct LedFont * font) {
return to_font(font)->height();
}
struct LedFont *create_outline_font(struct LedFont * font) {
rgb_matrix::Font* outlineFont = to_font(font)->CreateOutlineFont();
return from_font(outlineFont);
}
void delete_font(struct LedFont *font) {
delete to_font(font);
}
// -- Some utility functions.
void set_image(struct LedCanvas *c, int canvas_offset_x, int canvas_offset_y,
const uint8_t *image_buffer, size_t buffer_size_bytes,
int image_width, int image_height,
char is_bgr) {
SetImage(to_canvas(c), canvas_offset_x, canvas_offset_y,
image_buffer, buffer_size_bytes,
image_width, image_height,
is_bgr);
}
// Draw text, a standard NUL terminated C-string encoded in UTF-8,
// with given "font" at "x","y" with "color".
// "color" always needs to be set (hence it is a reference),
// "background_color" is a pointer to optionally be NULL for transparency.
// "kerning_offset" allows for additional spacing between characters (can be
// negative)
// Returns how many pixels we advanced on the screen.
int draw_text(struct LedCanvas *c, struct LedFont *font, int x, int y,
uint8_t r, uint8_t g, uint8_t b, const char *utf8_text, int kerning_offset) {
const rgb_matrix::Color col = rgb_matrix::Color(r, g, b);
return DrawText(to_canvas(c), *to_font(font), x, y, col, NULL, utf8_text, kerning_offset);
}
// Draw text, a standard NUL terminated C-string encoded in UTF-8,
// with given "font" at "x","y" with "color".
// Draw text as above, but vertically (top down).
// The text is a standard NUL terminated C-string encoded in UTF-8.
// "font, "x", "y", "color" and "background_color" are same as DrawText().
// "kerning_offset" allows for additional spacing between characters (can be
// negative).
// Returns font height to advance up on the screen.
int vertical_draw_text(struct LedCanvas *c, struct LedFont *font, int x, int y,
uint8_t r, uint8_t g, uint8_t b,
const char *utf8_text, int kerning_offset = 0) {
const rgb_matrix::Color col = rgb_matrix::Color(r, g, b);
return VerticalDrawText(to_canvas(c), *to_font(font), x, y, col, NULL, utf8_text, kerning_offset);
}
// Draw a circle centered at "x", "y", with a radius of "radius" and with "color"
void draw_circle(struct LedCanvas *c, int xx, int y, int radius, uint8_t r, uint8_t g, uint8_t b) {
const rgb_matrix::Color col = rgb_matrix::Color( r,g,b );
DrawCircle(to_canvas(c), xx, y, radius, col);
}
// Draw a line from "x0", "y0" to "x1", "y1" and with "color"
void draw_line(struct LedCanvas *c, int x0, int y0, int x1, int y1, uint8_t r, uint8_t g, uint8_t b) {
const rgb_matrix::Color col = rgb_matrix::Color(r, g, b);
DrawLine(to_canvas(c), x0, y0, x1, y1, col);
}

BIN
lib/led-matrix-c.o Normal file

Binary file not shown.

763
lib/led-matrix.cc Normal file
View file

@ -0,0 +1,763 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
//
// 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 version 2.
//
// 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 <http://gnu.org/licenses/gpl-2.0.txt>
#include "led-matrix.h"
#include <assert.h>
#include <grp.h>
#include <pwd.h>
#include <math.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include "gpio.h"
#include "thread.h"
#include "framebuffer-internal.h"
#include "multiplex-mappers-internal.h"
// Leave this in here for a while. Setting things from old defines.
#if defined(ADAFRUIT_RGBMATRIX_HAT)
# error "ADAFRUIT_RGBMATRIX_HAT has long been deprecated. Please use the Options struct or --led-gpio-mapping=adafruit-hat commandline flag"
#endif
#if defined(ADAFRUIT_RGBMATRIX_HAT_PWM)
# error "ADAFRUIT_RGBMATRIX_HAT_PWM has long been deprecated. Please use the Options struct or --led-gpio-mapping=adafruit-hat-pwm commandline flag"
#endif
namespace rgb_matrix {
// Implementation details of RGBmatrix.
class RGBMatrix::Impl {
class UpdateThread;
friend class UpdateThread;
public:
// Create an RGBMatrix.
//
// Needs an initialized GPIO object and configuration options from the
// RGBMatrix::Options struct.
//
// If you pass an GPIO object (which has to be Init()ialized), it will start // the internal thread to start the screen immediately.
//
// If you need finer control over when the refresh thread starts (which you
// might when you become a daemon), pass NULL here and see SetGPIO() method.
//
// The resulting canvas is (options.rows * options.parallel) high and
// (32 * options.chain_length) wide.
Impl(GPIO *io, const Options &options);
~Impl();
// Used to be there to help user delay initialization of thread starting,
// these days only used internally.
void SetGPIO(GPIO *io, bool start_thread = true);
bool StartRefresh();
FrameCanvas *CreateFrameCanvas();
FrameCanvas *SwapOnVSync(FrameCanvas *other, unsigned framerate_fraction);
bool ApplyPixelMapper(const PixelMapper *mapper);
bool SetPWMBits(uint8_t value);
uint8_t pwmbits(); // return the pwm-bits of the currently active buffer.
void set_luminance_correct(bool on);
bool luminance_correct() const;
// Set brightness in percent for all created FrameCanvas. 1%..100%.
// This will only affect newly set pixels.
void SetBrightness(uint8_t brightness);
uint8_t brightness();
uint64_t RequestInputs(uint64_t);
uint64_t AwaitInputChange(int timeout_ms);
uint64_t RequestOutputs(uint64_t output_bits);
void OutputGPIO(uint64_t output_bits);
void Clear();
private:
friend class RGBMatrix;
// Apply pixel mappers that have been passed down via a configuration
// string.
void ApplyNamedPixelMappers(const char *pixel_mapper_config,
int chain, int parallel);
Options params_;
bool do_luminance_correct_;
FrameCanvas *active_;
GPIO *io_;
Mutex active_frame_sync_;
UpdateThread *updater_;
std::vector<FrameCanvas*> created_frames_;
internal::PixelDesignatorMap *shared_pixel_mapper_;
uint64_t user_output_bits_;
};
using namespace internal;
// Pump pixels to screen. Needs to be high priority real-time because jitter
class RGBMatrix::Impl::UpdateThread : public Thread {
public:
UpdateThread(GPIO *io, FrameCanvas *initial_frame,
int pwm_dither_bits, bool show_refresh,
int limit_refresh_hz)
: io_(io), show_refresh_(show_refresh),
target_frame_usec_(limit_refresh_hz < 1 ? 0 : 1e6/limit_refresh_hz),
running_(true),
current_frame_(initial_frame), next_frame_(NULL),
requested_frame_multiple_(1) {
pthread_cond_init(&frame_done_, NULL);
pthread_cond_init(&input_change_, NULL);
switch (pwm_dither_bits) {
case 0:
start_bit_[0] = 0; start_bit_[1] = 0;
start_bit_[2] = 0; start_bit_[3] = 0;
break;
case 1:
start_bit_[0] = 0; start_bit_[1] = 1;
start_bit_[2] = 0; start_bit_[3] = 1;
break;
case 2:
start_bit_[0] = 0; start_bit_[1] = 1;
start_bit_[2] = 2; start_bit_[3] = 2;
break;
}
}
void Stop() {
MutexLock l(&running_mutex_);
running_ = false;
}
virtual void Run() {
unsigned frame_count = 0;
unsigned low_bit_sequence = 0;
uint32_t largest_time = 0;
gpio_bits_t last_gpio_bits = 0;
// Let's start measure max time only after a we were running for a few
// seconds to not pick up start-up glitches.
static const int kHoldffTimeUs = 2000 * 1000;
uint32_t initial_holdoff_start = GetMicrosecondCounter();
bool max_measure_enabled = false;
while (running()) {
const uint32_t start_time_us = GetMicrosecondCounter();
current_frame_->framebuffer()
->DumpToMatrix(io_, start_bit_[low_bit_sequence % 4]);
// SwapOnVSync() exchange.
{
MutexLock l(&frame_sync_);
// Do fast equality test first (likely due to frame_count reset).
if (frame_count == requested_frame_multiple_
|| frame_count % requested_frame_multiple_ == 0) {
// We reset to avoid frame hick-up every couple of weeks
// run-time iff requested_frame_multiple_ is not a factor of 2^32.
frame_count = 0;
if (next_frame_ != NULL) {
current_frame_ = next_frame_;
next_frame_ = NULL;
}
pthread_cond_signal(&frame_done_);
}
}
// Read input bits.
const gpio_bits_t inputs = io_->Read();
if (inputs != last_gpio_bits) {
last_gpio_bits = inputs;
MutexLock l(&input_sync_);
gpio_inputs_ = inputs;
pthread_cond_signal(&input_change_);
}
++frame_count;
++low_bit_sequence;
if (target_frame_usec_) {
while ((GetMicrosecondCounter() - start_time_us) < target_frame_usec_) {
// busy wait. We have our dedicated core, so ok to burn cycles.
}
}
const uint32_t end_time_us = GetMicrosecondCounter();
if (show_refresh_) {
uint32_t usec = end_time_us - start_time_us;
printf("\b\b\b\b\b\b\b\b%6.1fHz", 1e6 / usec);
if (usec > largest_time && max_measure_enabled) {
largest_time = usec;
const float lowest_hz = 1e6 / largest_time;
printf(" (lowest: %.1fHz)"
"\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b", lowest_hz);
} else {
// Don't measure at startup, as times will be janky.
max_measure_enabled = (end_time_us - initial_holdoff_start) > kHoldffTimeUs;
}
}
}
}
FrameCanvas *SwapOnVSync(FrameCanvas *other, unsigned frame_fraction) {
MutexLock l(&frame_sync_);
FrameCanvas *previous = current_frame_;
next_frame_ = other;
requested_frame_multiple_ = frame_fraction;
frame_sync_.WaitOn(&frame_done_);
return previous;
}
gpio_bits_t AwaitInputChange(int timeout_ms) {
MutexLock l(&input_sync_);
input_sync_.WaitOn(&input_change_, timeout_ms);
return gpio_inputs_;
}
private:
inline bool running() {
MutexLock l(&running_mutex_);
return running_;
}
GPIO *const io_;
const bool show_refresh_;
const uint32_t target_frame_usec_;
uint32_t start_bit_[4];
Mutex running_mutex_;
bool running_;
Mutex input_sync_;
pthread_cond_t input_change_;
gpio_bits_t gpio_inputs_;
Mutex frame_sync_;
pthread_cond_t frame_done_;
FrameCanvas *current_frame_;
FrameCanvas *next_frame_;
unsigned requested_frame_multiple_;
};
// Some defaults. See options-initialize.cc for the command line parsing.
RGBMatrix::Options::Options() :
// Historically, we provided these options only as #defines. Make sure that
// things still behave as before if someone has set these.
// At some point: remove them from the Makefile. Later: remove them here.
#ifdef DEFAULT_HARDWARE
hardware_mapping(DEFAULT_HARDWARE),
#else
hardware_mapping("regular"),
#endif
rows(32), cols(32), chain_length(1), parallel(1),
pwm_bits(internal::Framebuffer::kDefaultBitPlanes),
#ifdef LSB_PWM_NANOSECONDS
pwm_lsb_nanoseconds(LSB_PWM_NANOSECONDS),
#else
pwm_lsb_nanoseconds(130),
#endif
pwm_dither_bits(0),
brightness(100),
#ifdef RGB_SCAN_INTERLACED
scan_mode(1),
#else
scan_mode(0),
#endif
row_address_type(0),
multiplexing(0),
#ifdef DISABLE_HARDWARE_PULSES
disable_hardware_pulsing(true),
#else
disable_hardware_pulsing(false),
#endif
#ifdef SHOW_REFRESH_RATE
show_refresh_rate(true),
#else
show_refresh_rate(false),
#endif
#ifdef INVERSE_RGB_DISPLAY_COLORS
inverse_colors(true),
#else
inverse_colors(false),
#endif
led_rgb_sequence("RGB"),
pixel_mapper_config(NULL),
panel_type(NULL),
#ifdef FIXED_FRAME_MICROSECONDS
limit_refresh_rate_hz(1e6 / FIXED_FRAME_MICROSECONDS)
#else
limit_refresh_rate_hz(0)
#endif
{
// Nothing to see here.
}
#define DEBUG_MATRIX_OPTIONS 0
#if DEBUG_MATRIX_OPTIONS
static void PrintOptions(const RGBMatrix::Options &o) {
#define P_INT(val) fprintf(stderr, "%s : %d\n", #val, o.val)
#define P_STR(val) fprintf(stderr, "%s : %s\n", #val, o.val)
#define P_BOOL(val) fprintf(stderr, "%s : %s\n", #val, o.val ? "true":"false")
P_STR(hardware_mapping);
P_INT(rows);
P_INT(cols);
P_INT(chain_length);
P_INT(parallel);
P_INT(pwm_bits);
P_INT(pwm_lsb_nanoseconds);
P_INT(pwm_dither_bits);
P_INT(brightness);
P_INT(scan_mode);
P_INT(row_address_type);
P_INT(multiplexing);
P_BOOL(disable_hardware_pulsing);
P_BOOL(show_refresh_rate);
P_BOOL(inverse_colors);
P_STR(led_rgb_sequence);
P_STR(pixel_mapper_config);
P_STR(panel_type);
P_INT(limit_refresh_rate_hz);
#undef P_INT
#undef P_STR
#undef P_BOOL
}
#endif // DEBUG_MATRIX_OPTIONS
RGBMatrix::Impl::Impl(GPIO *io, const Options &options)
: params_(options), io_(NULL), updater_(NULL), shared_pixel_mapper_(NULL),
user_output_bits_(0) {
assert(params_.Validate(NULL));
#if DEBUG_MATRIX_OPTIONS
PrintOptions(params_);
#endif
const MultiplexMapper *multiplex_mapper = NULL;
if (params_.multiplexing > 0) {
const MuxMapperList &multiplexers = GetRegisteredMultiplexMappers();
if (params_.multiplexing <= (int) multiplexers.size()) {
// TODO: we could also do a find-by-name here, but not sure if worthwhile
multiplex_mapper = multiplexers[params_.multiplexing - 1];
}
}
if (multiplex_mapper) {
// The multiplexers might choose to have a different physical layout.
// We need to configure that first before setting up the hardware.
multiplex_mapper->EditColsRows(&params_.cols, &params_.rows);
}
Framebuffer::InitHardwareMapping(params_.hardware_mapping);
active_ = CreateFrameCanvas();
active_->Clear();
SetGPIO(io, true);
// We need to apply the mapping for the panels first.
ApplyPixelMapper(multiplex_mapper);
// .. followed by higher level mappers that might arrange panels.
ApplyNamedPixelMappers(options.pixel_mapper_config,
params_.chain_length, params_.parallel);
}
RGBMatrix::Impl::~Impl() {
if (updater_) {
updater_->Stop();
updater_->WaitStopped();
}
delete updater_;
// Make sure LEDs are off.
active_->Clear();
if (io_) active_->framebuffer()->DumpToMatrix(io_, 0);
for (size_t i = 0; i < created_frames_.size(); ++i) {
delete created_frames_[i];
}
delete shared_pixel_mapper_;
}
RGBMatrix::~RGBMatrix() {
delete impl_;
}
uint64_t RGBMatrix::Impl::RequestInputs(uint64_t bits) {
return io_->RequestInputs(bits);
}
uint64_t RGBMatrix::Impl::RequestOutputs(uint64_t output_bits) {
uint64_t success_bits = io_->InitOutputs(output_bits);
user_output_bits_ |= success_bits;
return success_bits;
}
void RGBMatrix::Impl::OutputGPIO(uint64_t output_bits) {
io_->WriteMaskedBits(output_bits, user_output_bits_);
}
void RGBMatrix::Impl::ApplyNamedPixelMappers(const char *pixel_mapper_config,
int chain, int parallel) {
if (pixel_mapper_config == NULL || strlen(pixel_mapper_config) == 0)
return;
char *const writeable_copy = strdup(pixel_mapper_config);
const char *const end = writeable_copy + strlen(writeable_copy);
char *s = writeable_copy;
while (s < end) {
char *const semicolon = strchrnul(s, ';');
*semicolon = '\0';
char *optional_param_start = strchr(s, ':');
if (optional_param_start) {
*optional_param_start++ = '\0';
}
if (*s == '\0' && optional_param_start && *optional_param_start != '\0') {
fprintf(stderr, "Stray parameter ':%s' without mapper name ?\n", optional_param_start);
}
if (*s) {
ApplyPixelMapper(FindPixelMapper(s, chain, parallel, optional_param_start));
}
s = semicolon + 1;
}
free(writeable_copy);
}
void RGBMatrix::Impl::SetGPIO(GPIO *io, bool start_thread) {
if (io != NULL && io_ == NULL) {
io_ = io;
Framebuffer::InitGPIO(io_, params_.rows, params_.parallel,
!params_.disable_hardware_pulsing,
params_.pwm_lsb_nanoseconds, params_.pwm_dither_bits,
params_.row_address_type);
Framebuffer::InitializePanels(io_, params_.panel_type,
params_.cols * params_.chain_length);
}
if (start_thread) {
StartRefresh();
}
}
bool RGBMatrix::Impl::StartRefresh() {
if (updater_ == NULL && io_ != NULL) {
updater_ = new UpdateThread(io_, active_, params_.pwm_dither_bits,
params_.show_refresh_rate,
params_.limit_refresh_rate_hz);
// If we have multiple processors, the kernel
// jumps around between these, creating some global flicker.
// So let's tie it to the last CPU available.
// The Raspberry Pi2 has 4 cores, our attempt to bind it to
// core #3 will succeed.
// The Raspberry Pi1 only has one core, so this affinity
// call will simply fail and we keep using the only core.
updater_->Start(99, (1<<3)); // Prio: high. Also: put on last CPU.
}
return updater_ != NULL;
}
FrameCanvas *RGBMatrix::Impl::CreateFrameCanvas() {
FrameCanvas *result =
new FrameCanvas(new Framebuffer(params_.rows,
params_.cols * params_.chain_length,
params_.parallel,
params_.scan_mode,
params_.led_rgb_sequence,
params_.inverse_colors,
&shared_pixel_mapper_));
if (created_frames_.empty()) {
// First time. Get defaults from initial Framebuffer.
do_luminance_correct_ = result->framebuffer()->luminance_correct();
}
result->framebuffer()->SetPWMBits(params_.pwm_bits);
result->framebuffer()->set_luminance_correct(do_luminance_correct_);
result->framebuffer()->SetBrightness(params_.brightness);
created_frames_.push_back(result);
return result;
}
FrameCanvas *RGBMatrix::Impl::SwapOnVSync(FrameCanvas *other,
unsigned frame_fraction) {
if (frame_fraction == 0) frame_fraction = 1; // correct user error.
if (!updater_) return NULL;
FrameCanvas *const previous = updater_->SwapOnVSync(other, frame_fraction);
if (other) active_ = other;
return previous;
}
uint64_t RGBMatrix::Impl::AwaitInputChange(int timeout_ms) {
if (!updater_) return 0;
return updater_->AwaitInputChange(timeout_ms);
}
bool RGBMatrix::Impl::SetPWMBits(uint8_t value) {
const bool success = active_->framebuffer()->SetPWMBits(value);
if (success) {
params_.pwm_bits = value;
}
return success;
}
uint8_t RGBMatrix::Impl::pwmbits() { return params_.pwm_bits; }
// Map brightness of output linearly to input with CIE1931 profile.
void RGBMatrix::Impl::set_luminance_correct(bool on) {
active_->framebuffer()->set_luminance_correct(on);
do_luminance_correct_ = on;
}
bool RGBMatrix::Impl::luminance_correct() const {
return do_luminance_correct_;
}
void RGBMatrix::Impl::SetBrightness(uint8_t brightness) {
for (size_t i = 0; i < created_frames_.size(); ++i) {
created_frames_[i]->framebuffer()->SetBrightness(brightness);
}
params_.brightness = brightness;
}
uint8_t RGBMatrix::Impl::brightness() {
return params_.brightness;
}
bool RGBMatrix::Impl::ApplyPixelMapper(const PixelMapper *mapper) {
if (mapper == NULL) return true;
using internal::PixelDesignatorMap;
const int old_width = shared_pixel_mapper_->width();
const int old_height = shared_pixel_mapper_->height();
int new_width, new_height;
if (!mapper->GetSizeMapping(old_width, old_height, &new_width, &new_height)) {
return false;
}
PixelDesignatorMap *new_mapper = new PixelDesignatorMap(
new_width, new_height, shared_pixel_mapper_->GetFillColorBits());
for (int y = 0; y < new_height; ++y) {
for (int x = 0; x < new_width; ++x) {
int orig_x = -1, orig_y = -1;
mapper->MapVisibleToMatrix(old_width, old_height,
x, y, &orig_x, &orig_y);
if (orig_x < 0 || orig_y < 0 ||
orig_x >= old_width || orig_y >= old_height) {
fprintf(stderr, "Error in PixelMapper: (%d, %d) -> (%d, %d) [range: "
"%dx%d]\n", x, y, orig_x, orig_y, old_width, old_height);
continue;
}
const internal::PixelDesignator *orig_designator;
orig_designator = shared_pixel_mapper_->get(orig_x, orig_y);
*new_mapper->get(x, y) = *orig_designator;
}
}
delete shared_pixel_mapper_;
shared_pixel_mapper_ = new_mapper;
return true;
}
// -- Public interface of RGBMatrix. Delegate everything to impl_
static bool drop_privs(const char *priv_user, const char *priv_group) {
uid_t ruid, euid, suid;
if (getresuid(&ruid, &euid, &suid) >= 0) {
if (euid != 0) // not root anyway. No priv dropping.
return true;
}
struct group *g = getgrnam(priv_group);
if (g == NULL) {
perror("group lookup.");
return false;
}
if (setresgid(g->gr_gid, g->gr_gid, g->gr_gid) != 0) {
perror("setresgid()");
return false;
}
struct passwd *p = getpwnam(priv_user);
if (p == NULL) {
perror("user lookup.");
return false;
}
if (setresuid(p->pw_uid, p->pw_uid, p->pw_uid) != 0) {
perror("setresuid()");
return false;
}
return true;
}
RGBMatrix *RGBMatrix::CreateFromOptions(const RGBMatrix::Options &options,
const RuntimeOptions &runtime_options) {
std::string error;
if (!options.Validate(&error)) {
fprintf(stderr, "%s\n", error.c_str());
return NULL;
}
// For the Pi4, we might need 2, maybe up to 4. Let's open up to 5.
if (runtime_options.gpio_slowdown < 0 || runtime_options.gpio_slowdown > 5) {
fprintf(stderr, "--led-slowdown-gpio=%d is outside usable range\n",
runtime_options.gpio_slowdown);
return NULL;
}
static GPIO io; // This static var is a little bit icky.
if (runtime_options.do_gpio_init
&& !io.Init(runtime_options.gpio_slowdown)) {
fprintf(stderr, "Must run as root to be able to access /dev/mem\n"
"Prepend 'sudo' to the command\n");
return NULL;
}
if (runtime_options.daemon > 0 && daemon(1, 0) != 0) {
perror("Failed to become daemon");
}
RGBMatrix::Impl *result = new RGBMatrix::Impl(NULL, options);
// Allowing daemon also means we are allowed to start the thread now.
const bool allow_daemon = !(runtime_options.daemon < 0);
if (runtime_options.do_gpio_init)
result->SetGPIO(&io, allow_daemon);
// TODO(hzeller): if we disallow daemon, then we might also disallow
// drop privileges: we can't drop privileges until we have created the
// realtime thread that usually requires root to be established.
// Double check and document.
if (runtime_options.drop_privileges > 0) {
drop_privs("daemon", "daemon");
}
return new RGBMatrix(result);
}
// Public interface.
RGBMatrix *RGBMatrix::CreateFromFlags(int *argc, char ***argv,
RGBMatrix::Options *m_opt_in,
RuntimeOptions *rt_opt_in,
bool remove_consumed_options) {
RGBMatrix::Options scratch_matrix;
RGBMatrix::Options *mopt = (m_opt_in != NULL) ? m_opt_in : &scratch_matrix;
RuntimeOptions scratch_rt;
RuntimeOptions *ropt = (rt_opt_in != NULL) ? rt_opt_in : &scratch_rt;
if (!ParseOptionsFromFlags(argc, argv, mopt, ropt, remove_consumed_options))
return NULL;
return CreateFromOptions(*mopt, *ropt);
}
FrameCanvas *RGBMatrix::CreateFrameCanvas() {
return impl_->CreateFrameCanvas();
}
FrameCanvas *RGBMatrix::SwapOnVSync(FrameCanvas *other,
unsigned framerate_fraction) {
return impl_->SwapOnVSync(other, framerate_fraction);
}
bool RGBMatrix::ApplyPixelMapper(const PixelMapper *mapper) {
return impl_->ApplyPixelMapper(mapper);
}
bool RGBMatrix::SetPWMBits(uint8_t value) { return impl_->SetPWMBits(value); }
uint8_t RGBMatrix::pwmbits() { return impl_->pwmbits(); }
void RGBMatrix::set_luminance_correct(bool on) {
return impl_->set_luminance_correct(on);
}
bool RGBMatrix::luminance_correct() const { return impl_->luminance_correct(); }
void RGBMatrix::SetBrightness(uint8_t brightness) {
impl_->SetBrightness(brightness);
}
uint8_t RGBMatrix::brightness() { return impl_->brightness(); }
uint64_t RGBMatrix::RequestInputs(uint64_t all_interested_bits) {
return impl_->RequestInputs(all_interested_bits);
}
uint64_t RGBMatrix::AwaitInputChange(int timeout_ms) {
return impl_->AwaitInputChange(timeout_ms);
}
uint64_t RGBMatrix::RequestOutputs(uint64_t all_interested_bits) {
return impl_->RequestOutputs(all_interested_bits);
}
void RGBMatrix::OutputGPIO(uint64_t output_bits) {
impl_->OutputGPIO(output_bits);
}
bool RGBMatrix::StartRefresh() { return impl_->StartRefresh(); }
// -- Implementation of RGBMatrix Canvas: delegation to ContentBuffer
int RGBMatrix::width() const {
return impl_->active_->width();
}
int RGBMatrix::height() const {
return impl_->active_->height();
}
void RGBMatrix::SetPixel(int x, int y, uint8_t red, uint8_t green, uint8_t blue) {
impl_->active_->SetPixel(x, y, red, green, blue);
}
void RGBMatrix::Clear() {
impl_->active_->Clear();
}
void RGBMatrix::Fill(uint8_t red, uint8_t green, uint8_t blue) {
impl_->active_->Fill(red, green, blue);
}
// FrameCanvas implementation of Canvas
FrameCanvas::~FrameCanvas() { delete frame_; }
int FrameCanvas::width() const { return frame_->width(); }
int FrameCanvas::height() const { return frame_->height(); }
void FrameCanvas::SetPixel(int x, int y,
uint8_t red, uint8_t green, uint8_t blue) {
frame_->SetPixel(x, y, red, green, blue);
}
void FrameCanvas::Clear() { return frame_->Clear(); }
void FrameCanvas::Fill(uint8_t red, uint8_t green, uint8_t blue) {
frame_->Fill(red, green, blue);
}
bool FrameCanvas::SetPWMBits(uint8_t value) { return frame_->SetPWMBits(value); }
uint8_t FrameCanvas::pwmbits() { return frame_->pwmbits(); }
// Map brightness of output linearly to input with CIE1931 profile.
void FrameCanvas::set_luminance_correct(bool on) { frame_->set_luminance_correct(on); }
bool FrameCanvas::luminance_correct() const { return frame_->luminance_correct(); }
void FrameCanvas::SetBrightness(uint8_t brightness) { frame_->SetBrightness(brightness); }
uint8_t FrameCanvas::brightness() { return frame_->brightness(); }
void FrameCanvas::Serialize(const char **data, size_t *len) const {
frame_->Serialize(data, len);
}
bool FrameCanvas::Deserialize(const char *data, size_t len) {
return frame_->Deserialize(data, len);
}
void FrameCanvas::CopyFrom(const FrameCanvas &other) {
frame_->CopyFrom(other.frame_);
}
} // end namespace rgb_matrix

BIN
lib/led-matrix.o Normal file

Binary file not shown.

View file

@ -0,0 +1,38 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2017 Henner Zeller <h.zeller@acm.org>
//
// 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 version 2.
//
// 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 <http://gnu.org/licenses/gpl-2.0.txt>
#include <vector>
#include "pixel-mapper.h"
namespace rgb_matrix {
namespace internal {
class MultiplexMapper : public PixelMapper {
public:
// Function that edits the original rows and columns of the panels
// provided by the user to the actual underlying mapping. This is called
// before we do the actual set-up of the GPIO mapping as this influences
// the hardware interface.
// This is so that the user can provide the rows/columns they see.
virtual void EditColsRows(int *cols, int *rows) const = 0;
};
// Returns a vector of the registered Multiplex mappers.
typedef std::vector<const MultiplexMapper*> MuxMapperList;
const MuxMapperList &GetRegisteredMultiplexMappers();
} // namespace internal
} // namespace rgb_matrix

476
lib/multiplex-mappers.cc Normal file
View file

@ -0,0 +1,476 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2017 Henner Zeller <h.zeller@acm.org>
//
// 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 version 2.
//
// 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 <http://gnu.org/licenses/gpl-2.0.txt>
#include "multiplex-mappers-internal.h"
namespace rgb_matrix {
namespace internal {
// A Pixel Mapper maps physical pixels locations to the internal logical
// mapping in a panel or panel-assembly, which depends on the wiring.
class MultiplexMapperBase : public MultiplexMapper {
public:
MultiplexMapperBase(const char *name, int stretch_factor)
: name_(name), panel_stretch_factor_(stretch_factor) {}
// This method is const, but we sneakily remember the original size
// of the panels so that we can more easily quantize things.
// So technically, we're stateful, but let's pretend we're not changing
// state. In the context this is used, it is never accessed in multiple
// threads.
virtual void EditColsRows(int *cols, int *rows) const {
panel_rows_ = *rows;
panel_cols_ = *cols;
*rows /= panel_stretch_factor_;
*cols *= panel_stretch_factor_;
}
virtual bool GetSizeMapping(int matrix_width, int matrix_height,
int *visible_width, int *visible_height) const {
// Matrix width has been altered. Alter it back.
*visible_width = matrix_width / panel_stretch_factor_;
*visible_height = matrix_height * panel_stretch_factor_;
return true;
}
virtual const char *GetName() const { return name_; }
// The MapVisibleToMatrix() as required by PanelMatrix here breaks it
// down to the individual panel, so that derived classes only need to
// implement MapSinglePanel().
virtual void MapVisibleToMatrix(int matrix_width, int matrix_height,
int visible_x, int visible_y,
int *matrix_x, int *matrix_y) const {
const int chained_panel = visible_x / panel_cols_;
const int parallel_panel = visible_y / panel_rows_;
const int within_panel_x = visible_x % panel_cols_;
const int within_panel_y = visible_y % panel_rows_;
int new_x, new_y;
MapSinglePanel(within_panel_x, within_panel_y, &new_x, &new_y);
*matrix_x = chained_panel * panel_stretch_factor_*panel_cols_ + new_x;
*matrix_y = parallel_panel * panel_rows_/panel_stretch_factor_ + new_y;
}
// Map the coordinates for a single panel. This is to be overridden in
// derived classes.
// Input parameter is the visible position on the matrix, and this method
// should return the internal multiplexed position.
virtual void MapSinglePanel(int visible_x, int visible_y,
int *matrix_x, int *matrix_y) const = 0;
protected:
const char *const name_;
const int panel_stretch_factor_;
mutable int panel_cols_;
mutable int panel_rows_;
};
/* ========================================================================
* Multiplexer implementations.
*
* Extend MultiplexMapperBase and implement MapSinglePanel. You only have
* to worry about the mapping within a single panel, the overall panel
* construction with chains and parallel is already taken care of.
*
* Don't forget to register the new multiplexer sin CreateMultiplexMapperList()
* below. After that, the new mapper is available in the --led-multiplexing
* option.
*/
class StripeMultiplexMapper : public MultiplexMapperBase {
public:
StripeMultiplexMapper() : MultiplexMapperBase("Stripe", 2) {}
void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
const bool is_top_stripe = (y % (panel_rows_/2)) < panel_rows_/4;
*matrix_x = is_top_stripe ? x + panel_cols_ : x;
*matrix_y = ((y / (panel_rows_/2)) * (panel_rows_/4)
+ y % (panel_rows_/4));
}
};
class FlippedStripeMultiplexMapper : public MultiplexMapperBase {
public:
FlippedStripeMultiplexMapper() : MultiplexMapperBase("FlippedStripe", 2) {}
void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
const bool is_top_stripe = (y % (panel_rows_/2)) >= panel_rows_/4;
*matrix_x = is_top_stripe ? x + panel_cols_ : x;
*matrix_y = ((y / (panel_rows_/2)) * (panel_rows_/4)
+ y % (panel_rows_/4));
}
};
class CheckeredMultiplexMapper : public MultiplexMapperBase {
public:
CheckeredMultiplexMapper() : MultiplexMapperBase("Checkered", 2) {}
void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
const bool is_top_check = (y % (panel_rows_/2)) < panel_rows_/4;
const bool is_left_check = (x < panel_cols_/2);
if (is_top_check) {
*matrix_x = is_left_check ? x+panel_cols_/2 : x+panel_cols_;
} else {
*matrix_x = is_left_check ? x : x + panel_cols_/2;
}
*matrix_y = ((y / (panel_rows_/2)) * (panel_rows_/4)
+ y % (panel_rows_/4));
}
};
class SpiralMultiplexMapper : public MultiplexMapperBase {
public:
SpiralMultiplexMapper() : MultiplexMapperBase("Spiral", 2) {}
void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
const bool is_top_stripe = (y % (panel_rows_/2)) < panel_rows_/4;
const int panel_quarter = panel_cols_/4;
const int quarter = x / panel_quarter;
const int offset = x % panel_quarter;
*matrix_x = ((2*quarter*panel_quarter)
+ (is_top_stripe
? panel_quarter - 1 - offset
: panel_quarter + offset));
*matrix_y = ((y / (panel_rows_/2)) * (panel_rows_/4)
+ y % (panel_rows_/4));
}
};
class ZStripeMultiplexMapper : public MultiplexMapperBase {
public:
ZStripeMultiplexMapper(const char *name, int even_vblock_offset, int odd_vblock_offset)
: MultiplexMapperBase(name, 2),
even_vblock_offset_(even_vblock_offset),
odd_vblock_offset_(odd_vblock_offset) {}
void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
static const int tile_width = 8;
static const int tile_height = 4;
const int vert_block_is_odd = ((y / tile_height) % 2);
const int even_vblock_shift = (1 - vert_block_is_odd) * even_vblock_offset_;
const int odd_vblock_shitf = vert_block_is_odd * odd_vblock_offset_;
*matrix_x = x + ((x + even_vblock_shift) / tile_width) * tile_width + odd_vblock_shitf;
*matrix_y = (y % tile_height) + tile_height * (y / (tile_height * 2));
}
private:
const int even_vblock_offset_;
const int odd_vblock_offset_;
};
class CoremanMapper : public MultiplexMapperBase {
public:
CoremanMapper() : MultiplexMapperBase("coreman", 2) {}
void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
const bool is_left_check = (x < panel_cols_/2);
if ((y <= 7) || ((y >= 16) && (y <= 23))){
*matrix_x = ((x / (panel_cols_/2)) * panel_cols_) + (x % (panel_cols_/2));
if ((y & (panel_rows_/4)) == 0) {
*matrix_y = (y / (panel_rows_/2)) * (panel_rows_/4) + (y % (panel_rows_/4));
}
} else {
*matrix_x = is_left_check ? x + panel_cols_/2 : x + panel_cols_;
*matrix_y = (y / (panel_rows_/2)) * (panel_rows_/4) + y % (panel_rows_/4);
}
}
};
class Kaler2ScanMapper : public MultiplexMapperBase {
public:
Kaler2ScanMapper() : MultiplexMapperBase("Kaler2Scan", 4) {}
void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
// Now we have a 128x4 matrix
int offset = ((y%4)/2) == 0 ? -1 : 1;// Add o substract
int deltaOffset = offset < 0 ? 7:8;
int deltaColumn = ((y%8)/4)== 0 ? 64 : 0;
*matrix_y = (y%2+(y/8)*2);
*matrix_x = deltaColumn + (16 * (x/8)) + deltaOffset + ((x%8) * offset);
}
};
class P10MapperZ : public MultiplexMapperBase {
public:
P10MapperZ() : MultiplexMapperBase("P10-128x4-Z", 4) {}
// supports this panel: https://www.aliexpress.com/item/2017-Special-Offer-P10-Outdoor-Smd-Full-Color-Led-Display-Module-320x160mm-1-2-Scan-Outdoor/32809267439.html?spm=a2g0s.9042311.0.0.Ob0jEw
// with --led-row-addr-type=2 flag
void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
int yComp = 0;
if (y == 0 || y == 1 || y == 8 || y == 9) {
yComp = 127;
}
else if (y == 2 || y == 3 || y == 10 || y == 11) {
yComp = 112;
}
else if (y == 4 || y == 5 || y == 12 || y == 13) {
yComp = 111;
}
else if (y == 6 || y == 7 || y == 14 || y == 15) {
yComp = 96;
}
if (y == 0 || y == 1 || y == 4 || y == 5 ||
y == 8 || y == 9 || y == 12 || y == 13) {
*matrix_x = yComp - x;
*matrix_x -= (24 * ((int)(x / 8)));
}
else {
*matrix_x = yComp + x;
*matrix_x -= (40 * ((int)(x / 8)));
}
if (y == 0 || y == 2 || y == 4 || y == 6) {
*matrix_y = 3;
}
else if (y == 1 || y == 3 || y == 5 || y == 7) {
*matrix_y = 2;
}
else if (y == 8 || y == 10 || y == 12 || y == 14) {
*matrix_y = 1;
}
else if (y == 9 || y == 11 || y == 13 || y == 15) {
*matrix_y = 0;
}
}
};
class QiangLiQ8 : public MultiplexMapperBase {
public:
QiangLiQ8() : MultiplexMapperBase("QiangLiQ8", 2) {}
void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
const int column = x + (4+ 4*(x/4));
*matrix_x = column;
if ((y >= 15 && y <=19) || (y >= 5 && y <= 9)) {
const int reverseColumn = x + (4*(x/4));
*matrix_x = reverseColumn;
}
*matrix_y = y % 5 + (y/10) *5;
}
};
class InversedZStripe : public MultiplexMapperBase {
public:
InversedZStripe() : MultiplexMapperBase("InversedZStripe", 2) {}
void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
static const int tile_width = 8;
static const int tile_height = 4;
const int vert_block_is_odd = ((y / tile_height) % 2);
const int evenOffset[8] = {7, 5, 3, 1, -1, -3, -5, -7};
if (vert_block_is_odd) {
*matrix_x = x + (x / tile_width) * tile_width;
} else {
*matrix_x = x + (x / tile_width) * tile_width + 8 + evenOffset[x % 8];
}
*matrix_y = (y % tile_height) + tile_height * (y / (tile_height * 2));
}
};
/*
* Vairous P10 1R1G1B Outdoor implementations for 16x16 modules with separate
* RGB LEDs, e.g.:
* https://www.ledcontrollercard.com/english/p10-outdoor-rgb-led-module-160x160mm-dip.html
*
*/
class P10Outdoor1R1G1BMultiplexBase : public MultiplexMapperBase {
public:
P10Outdoor1R1G1BMultiplexBase(const char *name)
: MultiplexMapperBase(name, 2) {}
void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
const int vblock_is_odd = (y / tile_height_) % 2;
const int vblock_is_even = 1 - vblock_is_odd;
const int even_vblock_shift = vblock_is_even * even_vblock_offset_;
const int odd_vblock_shift = vblock_is_odd * odd_vblock_offset_;
MapPanel(x, y, matrix_x, matrix_y,
vblock_is_even, vblock_is_odd,
even_vblock_shift, odd_vblock_shift);
}
protected:
virtual void MapPanel(int x, int y, int *matrix_x, int *matrix_y,
int vblock_is_even, int vblock_is_odd,
int even_vblock_shift, int odd_vblock_shift) const = 0;
static const int tile_width_ = 8;
static const int tile_height_ = 4;
static const int even_vblock_offset_ = 0;
static const int odd_vblock_offset_ = 8;
};
class P10Outdoor1R1G1BMultiplexMapper1 : public P10Outdoor1R1G1BMultiplexBase {
public:
P10Outdoor1R1G1BMultiplexMapper1()
: P10Outdoor1R1G1BMultiplexBase("P10Outdoor1R1G1-1") {}
protected:
void MapPanel(int x, int y, int *matrix_x, int *matrix_y,
const int vblock_is_even, const int vblock_is_odd,
const int even_vblock_shift, const int odd_vblock_shift) const {
*matrix_x = tile_width_ * (1 + vblock_is_even + 2 * (x / tile_width_))
- (x % tile_width_) - 1;
*matrix_y = (y % tile_height_) + tile_height_ * (y / (tile_height_ * 2));
}
};
class P10Outdoor1R1G1BMultiplexMapper2 : public P10Outdoor1R1G1BMultiplexBase {
public:
P10Outdoor1R1G1BMultiplexMapper2()
: P10Outdoor1R1G1BMultiplexBase("P10Outdoor1R1G1-2") {}
protected:
void MapPanel(int x, int y, int *matrix_x, int *matrix_y,
const int vblock_is_even, const int vblock_is_odd,
const int even_vblock_shift, const int odd_vblock_shift) const {
*matrix_x = vblock_is_even
? tile_width_ * (1 + 2 * (x / tile_width_)) - (x % tile_width_) - 1
: x + ((x + even_vblock_shift) / tile_width_) * tile_width_ + odd_vblock_shift;
*matrix_y = (y % tile_height_) + tile_height_ * (y / (tile_height_ * 2));
}
};
class P10Outdoor1R1G1BMultiplexMapper3 : public P10Outdoor1R1G1BMultiplexBase {
public:
P10Outdoor1R1G1BMultiplexMapper3()
: P10Outdoor1R1G1BMultiplexBase("P10Outdoor1R1G1-3") {}
protected:
void MapPanel(int x, int y, int *matrix_x, int *matrix_y,
const int vblock_is_even, const int vblock_is_odd,
const int even_vblock_shift, const int odd_vblock_shift) const {
*matrix_x = vblock_is_odd
? tile_width_ * (2 + 2 * (x / tile_width_)) - (x % tile_width_) - 1
: x + ((x + even_vblock_shift) / tile_width_) * tile_width_ + odd_vblock_shift;
*matrix_y = (y % tile_height_) + tile_height_ * (y / (tile_height_ * 2));
}
};
class P10CoremanMapper : public MultiplexMapperBase {
public:
P10CoremanMapper() : MultiplexMapperBase("P10CoremanMapper", 4) {}
void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
//Row offset 8,8,8,8,0,0,0,0,8,8,8,8,0,0,0,0
int mulY = (y & 4) > 0 ? 0 : 8;
//Row offset 9,9,8,8,1,1,0,0,9,9,8,8,1,1,0,0
mulY += (y & 2) > 0 ? 0 : 1;
mulY += (x >> 2) & ~1; //Drop lsb
*matrix_x = (mulY << 3) + x % 8;
*matrix_y = (y & 1) + ((y >> 2) & ~1);
}
};
/*
* P8 1R1G1B Outdoor P8-5S-V3.2-HX 20x40
*/
class P8Outdoor1R1G1BMultiplexBase : public MultiplexMapperBase {
public:
P8Outdoor1R1G1BMultiplexBase(const char *name)
: MultiplexMapperBase(name, 2) {}
void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
const int vblock_is_odd = (y / tile_height_) % 2;
const int vblock_is_even = 1 - vblock_is_odd;
const int even_vblock_shift = vblock_is_even * even_vblock_offset_;
const int odd_vblock_shift = vblock_is_odd * odd_vblock_offset_;
MapPanel(x, y, matrix_x, matrix_y,
vblock_is_even, vblock_is_odd,
even_vblock_shift, odd_vblock_shift);
}
protected:
virtual void MapPanel(int x, int y, int *matrix_x, int *matrix_y,
int vblock_is_even, int vblock_is_odd,
int even_vblock_shift, int odd_vblock_shift) const = 0;
static const int tile_width_ = 8;
static const int tile_height_ = 5;
static const int even_vblock_offset_ = 0;
static const int odd_vblock_offset_ = 8;
};
class P8Outdoor1R1G1BMultiplexMapper : public P8Outdoor1R1G1BMultiplexBase {
public:
P8Outdoor1R1G1BMultiplexMapper()
: P8Outdoor1R1G1BMultiplexBase("P8Outdoor1R1G1") {}
protected:
void MapPanel(int x, int y, int *matrix_x, int *matrix_y,
const int vblock_is_even, const int vblock_is_odd,
const int even_vblock_shift, const int odd_vblock_shift) const {
*matrix_x = vblock_is_even
? tile_width_ * (1 + tile_width_ - 2 * (x / tile_width_)) + tile_width_ - (x % tile_width_) - 1
: tile_width_ * (1 + tile_width_ - 2 * (x / tile_width_)) - tile_width_ + (x % tile_width_);
*matrix_y = (tile_height_ - y % tile_height_) + tile_height_ * (1 - y / (tile_height_ * 2)) -1;
}
};
/*
* Here is where the registration happens.
* If you add an instance of the mapper here, it will automatically be
* made available in the --led-multiplexing commandline option.
*/
static MuxMapperList *CreateMultiplexMapperList() {
MuxMapperList *result = new MuxMapperList();
// Here, register all multiplex mappers from above.
result->push_back(new StripeMultiplexMapper());
result->push_back(new CheckeredMultiplexMapper());
result->push_back(new SpiralMultiplexMapper());
result->push_back(new ZStripeMultiplexMapper("ZStripe", 0, 8));
result->push_back(new ZStripeMultiplexMapper("ZnMirrorZStripe", 4, 4));
result->push_back(new CoremanMapper());
result->push_back(new Kaler2ScanMapper());
result->push_back(new ZStripeMultiplexMapper("ZStripeUneven", 8, 0));
result->push_back(new P10MapperZ());
result->push_back(new QiangLiQ8());
result->push_back(new InversedZStripe());
result->push_back(new P10Outdoor1R1G1BMultiplexMapper1());
result->push_back(new P10Outdoor1R1G1BMultiplexMapper2());
result->push_back(new P10Outdoor1R1G1BMultiplexMapper3());
result->push_back(new P10CoremanMapper());
result->push_back(new P8Outdoor1R1G1BMultiplexMapper());
result->push_back(new FlippedStripeMultiplexMapper());
return result;
}
const MuxMapperList &GetRegisteredMultiplexMappers() {
static const MuxMapperList *all_mappers = CreateMultiplexMapperList();
return *all_mappers;
}
} // namespace internal
} // namespace rgb_matrix

BIN
lib/multiplex-mappers.o Normal file

Binary file not shown.

461
lib/options-initialize.cc Normal file
View file

@ -0,0 +1,461 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2013, 2016 Henner Zeller <h.zeller@acm.org>
//
// 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 version 2.
//
// 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 <http://gnu.org/licenses/gpl-2.0.txt>
#include "led-matrix.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <grp.h>
#include <pwd.h>
#include <vector>
#include "multiplex-mappers-internal.h"
#include "framebuffer-internal.h"
#include "gpio.h"
namespace rgb_matrix {
RuntimeOptions::RuntimeOptions() :
#ifdef RGB_SLOWDOWN_GPIO
gpio_slowdown(RGB_SLOWDOWN_GPIO),
#else
gpio_slowdown(1),
#endif
daemon(0), // Don't become a daemon by default.
drop_privileges(1), // Encourage good practice: drop privileges by default.
do_gpio_init(true)
{
// Nothing to see here.
}
namespace {
typedef char** argv_iterator;
#define OPTION_PREFIX "--led-"
#define OPTION_PREFIX_LEN strlen(OPTION_PREFIX)
static bool ConsumeBoolFlag(const char *flag_name, const argv_iterator &pos,
bool *result_value) {
const char *option = *pos;
if (strncmp(option, OPTION_PREFIX, OPTION_PREFIX_LEN) != 0)
return false;
option += OPTION_PREFIX_LEN;
bool value_to_set = true;
if (strncmp(option, "no-", 3) == 0) {
value_to_set = false;
option += 3;
}
if (strcmp(option, flag_name) != 0)
return false; // not consumed.
*result_value = value_to_set;
return true;
}
static bool ConsumeIntFlag(const char *flag_name,
argv_iterator &pos, const argv_iterator end,
int *result_value, int *error) {
const char *option = *pos;
if (strncmp(option, OPTION_PREFIX, OPTION_PREFIX_LEN) != 0)
return false;
option += OPTION_PREFIX_LEN;
const size_t flag_len = strlen(flag_name);
if (strncmp(option, flag_name, flag_len) != 0)
return false; // not consumed.
const char *value;
if (option[flag_len] == '=') // --option=42 # value in same arg
value = option + flag_len + 1;
else if (pos + 1 < end) { // --option 42 # value in next arg
value = *(++pos);
} else {
fprintf(stderr, "Parameter expected after %s%s\n",
OPTION_PREFIX, flag_name);
++*error;
return true; // consumed, but error.
}
char *end_value = NULL;
int val = strtol(value, &end_value, 10);
if (!*value || *end_value) {
fprintf(stderr, "Couldn't parse parameter %s%s=%s "
"(Expected decimal number but '%s' looks funny)\n",
OPTION_PREFIX, flag_name, value, end_value);
++*error;
return true; // consumed, but error
}
*result_value = val;
return true; // consumed.
}
// The resulting value is allocated.
static bool ConsumeStringFlag(const char *flag_name,
argv_iterator &pos, const argv_iterator end,
const char **result_value, int *error) {
const char *option = *pos;
if (strncmp(option, OPTION_PREFIX, OPTION_PREFIX_LEN) != 0)
return false;
option += OPTION_PREFIX_LEN;
const size_t flag_len = strlen(flag_name);
if (strncmp(option, flag_name, flag_len) != 0)
return false; // not consumed.
const char *value;
if (option[flag_len] == '=') // --option=hello # value in same arg
value = option + flag_len + 1;
else if (pos + 1 < end) { // --option hello # value in next arg
value = *(++pos);
} else {
fprintf(stderr, "Parameter expected after %s%s\n",
OPTION_PREFIX, flag_name);
++*error;
*result_value = NULL;
return true; // consumed, but error.
}
*result_value = strdup(value); // This will leak, but no big deal.
return true;
}
static bool FlagInit(int &argc, char **&argv,
RGBMatrix::Options *mopts,
RuntimeOptions *ropts,
bool remove_consumed_options) {
argv_iterator it = &argv[0];
argv_iterator end = it + argc;
std::vector<char*> unused_options;
unused_options.push_back(*it++); // Not interested in program name
bool bool_scratch;
int err = 0;
bool posix_end_option_seen = false; // end of options '--'
for (/**/; it < end; ++it) {
posix_end_option_seen |= (strcmp(*it, "--") == 0);
if (!posix_end_option_seen) {
if (ConsumeStringFlag("gpio-mapping", it, end,
&mopts->hardware_mapping, &err))
continue;
if (ConsumeStringFlag("rgb-sequence", it, end,
&mopts->led_rgb_sequence, &err))
continue;
if (ConsumeStringFlag("pixel-mapper", it, end,
&mopts->pixel_mapper_config, &err))
continue;
if (ConsumeStringFlag("panel-type", it, end,
&mopts->panel_type, &err))
continue;
if (ConsumeIntFlag("rows", it, end, &mopts->rows, &err))
continue;
if (ConsumeIntFlag("cols", it, end, &mopts->cols, &err))
continue;
if (ConsumeIntFlag("chain", it, end, &mopts->chain_length, &err))
continue;
if (ConsumeIntFlag("parallel", it, end, &mopts->parallel, &err))
continue;
if (ConsumeIntFlag("multiplexing", it, end, &mopts->multiplexing, &err))
continue;
if (ConsumeIntFlag("brightness", it, end, &mopts->brightness, &err))
continue;
if (ConsumeIntFlag("scan-mode", it, end, &mopts->scan_mode, &err))
continue;
if (ConsumeIntFlag("pwm-bits", it, end, &mopts->pwm_bits, &err))
continue;
if (ConsumeIntFlag("pwm-lsb-nanoseconds", it, end,
&mopts->pwm_lsb_nanoseconds, &err))
continue;
if (ConsumeIntFlag("pwm-dither-bits", it, end,
&mopts->pwm_dither_bits, &err))
continue;
if (ConsumeIntFlag("row-addr-type", it, end,
&mopts->row_address_type, &err))
continue;
if (ConsumeIntFlag("limit-refresh", it, end,
&mopts->limit_refresh_rate_hz, &err))
continue;
if (ConsumeBoolFlag("show-refresh", it, &mopts->show_refresh_rate))
continue;
if (ConsumeBoolFlag("inverse", it, &mopts->inverse_colors))
continue;
// We don't have a swap_green_blue option anymore, but we simulate the
// flag for a while.
bool swap_green_blue;
if (ConsumeBoolFlag("swap-green-blue", it, &swap_green_blue)) {
if (strlen(mopts->led_rgb_sequence) == 3) {
char *new_sequence = strdup(mopts->led_rgb_sequence);
new_sequence[0] = mopts->led_rgb_sequence[0];
new_sequence[1] = mopts->led_rgb_sequence[2];
new_sequence[2] = mopts->led_rgb_sequence[1];
mopts->led_rgb_sequence = new_sequence; // leaking. Ignore.
}
continue;
}
bool allow_hardware_pulsing = !mopts->disable_hardware_pulsing;
if (ConsumeBoolFlag("hardware-pulse", it, &allow_hardware_pulsing)) {
mopts->disable_hardware_pulsing = !allow_hardware_pulsing;
continue;
}
bool request_help = false;
if (ConsumeBoolFlag("help", it, &request_help) && request_help) {
// In that case, we pretend to have failure in parsing, which will
// trigger printing the usage(). Typically :)
return false;
}
//-- Runtime options.
if (ConsumeIntFlag("slowdown-gpio", it, end, &ropts->gpio_slowdown, &err))
continue;
if (ropts->daemon >= 0 && ConsumeBoolFlag("daemon", it, &bool_scratch)) {
ropts->daemon = bool_scratch ? 1 : 0;
continue;
}
if (ropts->drop_privileges >= 0 &&
ConsumeBoolFlag("drop-privs", it, &bool_scratch)) {
ropts->drop_privileges = bool_scratch ? 1 : 0;
continue;
}
if (strncmp(*it, OPTION_PREFIX, OPTION_PREFIX_LEN) == 0) {
fprintf(stderr, "Option %s starts with %s but it is unknown. Typo?\n",
*it, OPTION_PREFIX);
}
}
unused_options.push_back(*it);
}
if (err > 0) {
return false;
}
if (remove_consumed_options) {
// Success. Re-arrange flags to only include the ones not consumed.
argc = (int) unused_options.size();
for (int i = 0; i < argc; ++i) {
argv[i] = unused_options[i];
}
}
return true;
}
} // anonymous namespace
bool ParseOptionsFromFlags(int *argc, char ***argv,
RGBMatrix::Options *m_opt_in,
RuntimeOptions *rt_opt_in,
bool remove_consumed_options) {
if (argc == NULL || argv == NULL) {
fprintf(stderr, "Called ParseOptionsFromFlags() without argc/argv\n");
return false;
}
// Replace NULL arguments with some scratch-space.
RGBMatrix::Options scratch_matrix;
RGBMatrix::Options *mopt = (m_opt_in != NULL) ? m_opt_in : &scratch_matrix;
RuntimeOptions scratch_rt;
RuntimeOptions *ropt = (rt_opt_in != NULL) ? rt_opt_in : &scratch_rt;
return FlagInit(*argc, *argv, mopt, ropt, remove_consumed_options);
}
static std::string CreateAvailableMultiplexString(
const internal::MuxMapperList &m) {
std::string result;
char buffer[256];
for (size_t i = 0; i < m.size(); ++i) {
if (i != 0) result.append("; ");
snprintf(buffer, sizeof(buffer), "%d=%s", (int) i+1, m[i]->GetName());
result.append(buffer);
}
return result;
}
void PrintMatrixFlags(FILE *out, const RGBMatrix::Options &d,
const RuntimeOptions &r) {
const internal::MuxMapperList &muxers
= internal::GetRegisteredMultiplexMappers();
std::vector<std::string> mapper_names = GetAvailablePixelMappers();
std::string available_mappers;
for (size_t i = 0; i < mapper_names.size(); ++i) {
if (i != 0) available_mappers.append(", ");
available_mappers.append("\"").append(mapper_names[i]).append("\"");
}
fprintf(out,
"\t--led-gpio-mapping=<name> : Name of GPIO mapping used. Default \"%s\"\n"
"\t--led-rows=<rows> : Panel rows. Typically 8, 16, 32 or 64."
" (Default: %d).\n"
"\t--led-cols=<cols> : Panel columns. Typically 32 or 64. "
"(Default: %d).\n"
"\t--led-chain=<chained> : Number of daisy-chained panels. "
"(Default: %d).\n"
"\t--led-parallel=<parallel> : Parallel chains. range=1..3 "
#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
"(6 for CM3) "
#endif
"(Default: %d).\n"
"\t--led-multiplexing=<0..%d> : Mux type: 0=direct; %s (Default: 0)\n"
"\t--led-pixel-mapper : Semicolon-separated list of pixel-mappers to arrange pixels.\n"
"\t Optional params after a colon e.g. \"U-mapper;Rotate:90\"\n"
"\t Available: %s. Default: \"\"\n"
"\t--led-pwm-bits=<1..%d> : PWM bits (Default: %d).\n"
"\t--led-brightness=<percent>: Brightness in percent (Default: %d).\n"
"\t--led-scan-mode=<0..1> : 0 = progressive; 1 = interlaced "
"(Default: %d).\n"
"\t--led-row-addr-type=<0..4>: 0 = default; 1 = AB-addressed panels; 2 = direct row select; 3 = ABC-addressed panels; 4 = ABC Shift + DE direct "
"(Default: 0).\n"
"\t--led-%sshow-refresh : %show refresh rate.\n"
"\t--led-limit-refresh=<Hz> : Limit refresh rate to this frequency in Hz. Useful to keep a\n"
"\t constant refresh rate on loaded system. 0=no limit. Default: %d\n"
"\t--led-%sinverse "
": Switch if your matrix has inverse colors %s.\n"
"\t--led-rgb-sequence : Switch if your matrix has led colors "
"swapped (Default: \"RGB\")\n"
"\t--led-pwm-lsb-nanoseconds : PWM Nanoseconds for LSB "
"(Default: %d)\n"
"\t--led-pwm-dither-bits=<0..2> : Time dithering of lower bits "
"(Default: 0)\n"
"\t--led-%shardware-pulse : %sse hardware pin-pulse generation.\n"
"\t--led-panel-type=<name> : Needed to initialize special panels. Supported: 'FM6126A', 'FM6127'\n",
d.hardware_mapping,
d.rows, d.cols, d.chain_length, d.parallel,
(int) muxers.size(), CreateAvailableMultiplexString(muxers).c_str(),
available_mappers.c_str(),
internal::Framebuffer::kBitPlanes, d.pwm_bits,
d.brightness, d.scan_mode,
d.show_refresh_rate ? "no-" : "", d.show_refresh_rate ? "Don't s" : "S",
d.limit_refresh_rate_hz,
d.inverse_colors ? "no-" : "", d.inverse_colors ? "off" : "on",
d.pwm_lsb_nanoseconds,
!d.disable_hardware_pulsing ? "no-" : "",
!d.disable_hardware_pulsing ? "Don't u" : "U");
fprintf(out, "\t--led-slowdown-gpio=<0..4>: "
"Slowdown GPIO. Needed for faster Pis/slower panels "
"(Default: %d).\n", r.gpio_slowdown);
if (r.daemon >= 0) {
const bool on = (r.daemon > 0);
fprintf(out,
"\t--led-%sdaemon : "
"%sake the process run in the background as daemon.\n",
on ? "no-" : "", on ? "Don't m" : "M");
}
if (r.drop_privileges >= 0) {
const bool on = (r.drop_privileges > 0);
fprintf(out,
"\t--led-%sdrop-privs : %srop privileges from 'root' "
"after initializing the hardware.\n",
on ? "no-" : "", on ? "Don't d" : "D");
}
}
bool RGBMatrix::Options::Validate(std::string *err_in) const {
std::string scratch;
std::string *err = err_in ? err_in : &scratch;
bool success = true;
if (rows < 8 || rows > 64 || rows % 2 != 0) {
err->append("Invalid number or rows per panel (--led-rows). "
"Should be in range of [8..64] and divisible by 2.\n");
success = false;
}
if (cols < 16) {
err->append("Invlid number of columns for panel (--led-cols). "
"Typically that is something like 32 or 64\n");
success = false;
}
if (chain_length < 1) {
err->append("Chain-length outside usable range.\n");
success = false;
}
const internal::MuxMapperList &muxers
= internal::GetRegisteredMultiplexMappers();
if (multiplexing < 0 || multiplexing > (int)muxers.size()) {
err->append("Multiplexing can only be one of 0=normal; ")
.append(CreateAvailableMultiplexString(muxers));
success = false;
}
if (row_address_type < 0 || row_address_type > 4) {
err->append("Row address type values can be 0 (default), 1 (AB addressing), 2 (direct row select), 3 (ABC address), 4 (ABC Shift + DE direct).\n");
success = false;
}
#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
const bool is_cm = (strcmp(hardware_mapping, "compute-module") == 0);
#else
const bool is_cm = false;
#endif
if (parallel < 1 || parallel > (is_cm ? 6 : 3)) {
err->append("Parallel outside usable range (1..3 allowed"
#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
", up to 6 only for CM3"
#endif
").\n");
success = false;
}
if (brightness < 1 || brightness > 100) {
err->append("Brightness outside usable range (Percent 1..100 allowed).\n");
success = false;
}
if (pwm_bits <= 0 || pwm_bits > internal::Framebuffer::kBitPlanes) {
char buffer[256];
snprintf(buffer, sizeof(buffer),
"Invalid range of pwm-bits (1..%d allowed).\n",
internal::Framebuffer::kBitPlanes);
err->append(buffer);
success = false;
}
if (scan_mode < 0 || scan_mode > 1) {
err->append("Invalid scan mode (0 or 1 allowed).\n");
success = false;
}
if (pwm_lsb_nanoseconds < 50 || pwm_lsb_nanoseconds > 3000) {
err->append("Invalid range of pwm-lsb-nanoseconds (50..3000 allowed).\n");
success = false;
}
if (pwm_dither_bits < 0 || pwm_dither_bits > 2) {
err->append("Inavlid range of pwm-dither-bits (0..2 allowed).\n");
success = false;
}
if (led_rgb_sequence == NULL || strlen(led_rgb_sequence) != 3) {
err->append("led-sequence needs to be three characters long.\n");
success = false;
} else {
if ((!strchr(led_rgb_sequence, 'R') && !strchr(led_rgb_sequence, 'r'))
|| (!strchr(led_rgb_sequence, 'G') && !strchr(led_rgb_sequence, 'g'))
|| (!strchr(led_rgb_sequence, 'B') && !strchr(led_rgb_sequence, 'b'))) {
err->append("led-sequence needs to contain all of letters 'R', 'G' "
"and 'B'\n");
success = false;
}
}
if (!success && !err_in) {
// If we didn't get a string to write to, we write things to stderr.
fprintf(stderr, "%s", err->c_str());
}
return success;
}
} // namespace rgb_matrix

BIN
lib/options-initialize.o Normal file

Binary file not shown.

338
lib/pixel-mapper.cc Normal file
View file

@ -0,0 +1,338 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2018 Henner Zeller <h.zeller@acm.org>
//
// 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 version 2.
//
// 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 <http://gnu.org/licenses/gpl-2.0.txt>
#include "pixel-mapper.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <map>
namespace rgb_matrix {
namespace {
class RotatePixelMapper : public PixelMapper {
public:
RotatePixelMapper() : angle_(0) {}
virtual const char *GetName() const { return "Rotate"; }
virtual bool SetParameters(int chain, int parallel, const char *param) {
if (param == NULL || strlen(param) == 0) {
angle_ = 0;
return true;
}
char *errpos;
const int angle = strtol(param, &errpos, 10);
if (*errpos != '\0') {
fprintf(stderr, "Invalid rotate parameter '%s'\n", param);
return false;
}
if (angle % 90 != 0) {
fprintf(stderr, "Rotation needs to be multiple of 90 degrees\n");
return false;
}
angle_ = (angle + 360) % 360;
return true;
}
virtual bool GetSizeMapping(int matrix_width, int matrix_height,
int *visible_width, int *visible_height)
const {
if (angle_ % 180 == 0) {
*visible_width = matrix_width;
*visible_height = matrix_height;
} else {
*visible_width = matrix_height;
*visible_height = matrix_width;
}
return true;
}
virtual void MapVisibleToMatrix(int matrix_width, int matrix_height,
int x, int y,
int *matrix_x, int *matrix_y) const {
switch (angle_) {
case 0:
*matrix_x = x;
*matrix_y = y;
break;
case 90:
*matrix_x = matrix_width - y - 1;
*matrix_y = x;
break;
case 180:
*matrix_x = matrix_width - x - 1;
*matrix_y = matrix_height - y - 1;
break;
case 270:
*matrix_x = y;
*matrix_y = matrix_height - x - 1;
break;
}
}
private:
int angle_;
};
class MirrorPixelMapper : public PixelMapper {
public:
MirrorPixelMapper() : horizontal_(true) {}
virtual const char *GetName() const { return "Mirror"; }
virtual bool SetParameters(int chain, int parallel, const char *param) {
if (param == NULL || strlen(param) == 0) {
horizontal_ = true;
return true;
}
if (strlen(param) != 1) {
fprintf(stderr, "Mirror parameter should be a single "
"character:'V' or 'H'\n");
}
switch (*param) {
case 'V':
case 'v':
horizontal_ = false;
break;
case 'H':
case 'h':
horizontal_ = true;
break;
default:
fprintf(stderr, "Mirror parameter should be either 'V' or 'H'\n");
return false;
}
return true;
}
virtual bool GetSizeMapping(int matrix_width, int matrix_height,
int *visible_width, int *visible_height)
const {
*visible_height = matrix_height;
*visible_width = matrix_width;
return true;
}
virtual void MapVisibleToMatrix(int matrix_width, int matrix_height,
int x, int y,
int *matrix_x, int *matrix_y) const {
if (horizontal_) {
*matrix_x = matrix_width - 1 - x;
*matrix_y = y;
} else {
*matrix_x = x;
*matrix_y = matrix_height - 1 - y;
}
}
private:
bool horizontal_;
};
// If we take a long chain of panels and arrange them in a U-shape, so
// that after half the panels we bend around and continue below. This way
// we have a panel that has double the height but only uses one chain.
// A single chain display with four 32x32 panels can then be arranged in this
// 64x64 display:
// [<][<][<][<] }- Raspbery Pi connector
//
// can be arranged in this U-shape
// [<][<] }----- Raspberry Pi connector
// [>][>]
//
// This works for more than one chain as well. Here an arrangement with
// two chains with 8 panels each
// [<][<][<][<] }-- Pi connector #1
// [>][>][>][>]
// [<][<][<][<] }--- Pi connector #2
// [>][>][>][>]
class UArrangementMapper : public PixelMapper {
public:
UArrangementMapper() : parallel_(1) {}
virtual const char *GetName() const { return "U-mapper"; }
virtual bool SetParameters(int chain, int parallel, const char *param) {
if (chain < 2) { // technically, a chain of 2 would work, but somewhat pointless
fprintf(stderr, "U-mapper: need at least --led-chain=4 for useful folding\n");
return false;
}
if (chain % 2 != 0) {
fprintf(stderr, "U-mapper: Chain (--led-chain) needs to be divisible by two\n");
return false;
}
parallel_ = parallel;
return true;
}
virtual bool GetSizeMapping(int matrix_width, int matrix_height,
int *visible_width, int *visible_height)
const {
*visible_width = (matrix_width / 64) * 32; // Div at 32px boundary
*visible_height = 2 * matrix_height;
if (matrix_height % parallel_ != 0) {
fprintf(stderr, "%s For parallel=%d we would expect the height=%d "
"to be divisible by %d ??\n",
GetName(), parallel_, matrix_height, parallel_);
return false;
}
return true;
}
virtual void MapVisibleToMatrix(int matrix_width, int matrix_height,
int x, int y,
int *matrix_x, int *matrix_y) const {
const int panel_height = matrix_height / parallel_;
const int visible_width = (matrix_width / 64) * 32;
const int slab_height = 2 * panel_height; // one folded u-shape
const int base_y = (y / slab_height) * panel_height;
y %= slab_height;
if (y < panel_height) {
x += matrix_width / 2;
} else {
x = visible_width - x - 1;
y = slab_height - y - 1;
}
*matrix_x = x;
*matrix_y = base_y + y;
}
private:
int parallel_;
};
class VerticalMapper : public PixelMapper {
public:
VerticalMapper() {}
virtual const char *GetName() const { return "V-mapper"; }
virtual bool SetParameters(int chain, int parallel, const char *param) {
chain_ = chain;
parallel_ = parallel;
// optional argument :Z allow for every other panel to be flipped
// upside down so that cabling can be shorter:
// [ O < I ] without Z [ O < I ]
// ,---^ <---- ^
// [ O < I ] [ I > O ]
// ,---^ with Z ^
// [ O < I ] ---> [ O < I ]
z_ = (param && strcasecmp(param, "Z") == 0);
return true;
}
virtual bool GetSizeMapping(int matrix_width, int matrix_height,
int *visible_width, int *visible_height)
const {
*visible_width = matrix_width * parallel_ / chain_;
*visible_height = matrix_height * chain_ / parallel_;
#if 0
fprintf(stderr, "%s: C:%d P:%d. Turning W:%d H:%d Physical "
"into W:%d H:%d Virtual\n",
GetName(), chain_, parallel_,
*visible_width, *visible_height, matrix_width, matrix_height);
#endif
return true;
}
virtual void MapVisibleToMatrix(int matrix_width, int matrix_height,
int x, int y,
int *matrix_x, int *matrix_y) const {
const int panel_width = matrix_width / chain_;
const int panel_height = matrix_height / parallel_;
const int x_panel_start = y / panel_height * panel_width;
const int y_panel_start = x / panel_width * panel_height;
const int x_within_panel = x % panel_width;
const int y_within_panel = y % panel_height;
const bool needs_flipping = z_ && (y / panel_height) % 2 == 1;
*matrix_x = x_panel_start + (needs_flipping
? panel_width - 1 - x_within_panel
: x_within_panel);
*matrix_y = y_panel_start + (needs_flipping
? panel_height - 1 - y_within_panel
: y_within_panel);
}
private:
bool z_;
int chain_;
int parallel_;
};
typedef std::map<std::string, PixelMapper*> MapperByName;
static void RegisterPixelMapperInternal(MapperByName *registry,
PixelMapper *mapper) {
assert(mapper != NULL);
std::string lower_name;
for (const char *n = mapper->GetName(); *n; n++)
lower_name.append(1, tolower(*n));
(*registry)[lower_name] = mapper;
}
static MapperByName *CreateMapperMap() {
MapperByName *result = new MapperByName();
// Register all the default PixelMappers here.
RegisterPixelMapperInternal(result, new RotatePixelMapper());
RegisterPixelMapperInternal(result, new UArrangementMapper());
RegisterPixelMapperInternal(result, new VerticalMapper());
RegisterPixelMapperInternal(result, new MirrorPixelMapper());
return result;
}
static MapperByName *GetMapperMap() {
static MapperByName *singleton_instance = CreateMapperMap();
return singleton_instance;
}
} // anonymous namespace
// Public API.
void RegisterPixelMapper(PixelMapper *mapper) {
RegisterPixelMapperInternal(GetMapperMap(), mapper);
}
std::vector<std::string> GetAvailablePixelMappers() {
std::vector<std::string> result;
MapperByName *m = GetMapperMap();
for (MapperByName::const_iterator it = m->begin(); it != m->end(); ++it) {
result.push_back(it->second->GetName());
}
return result;
}
const PixelMapper *FindPixelMapper(const char *name,
int chain, int parallel,
const char *parameter) {
std::string lower_name;
for (const char *n = name; *n; n++) lower_name.append(1, tolower(*n));
MapperByName::const_iterator found = GetMapperMap()->find(lower_name);
if (found == GetMapperMap()->end()) {
fprintf(stderr, "%s: no such mapper\n", name);
return NULL;
}
PixelMapper *mapper = found->second;
if (mapper == NULL) return NULL; // should not happen.
if (!mapper->SetParameters(chain, parallel, parameter))
return NULL; // Got parameter, but couldn't deal with it.
return mapper;
}
} // namespace rgb_matrix

BIN
lib/pixel-mapper.o Normal file

Binary file not shown.

100
lib/thread.cc Normal file
View file

@ -0,0 +1,100 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
//
// 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 version 2.
//
// 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 <http://gnu.org/licenses/gpl-2.0.txt>
#include "thread.h"
#include <assert.h>
#include <limits.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
namespace rgb_matrix {
void *Thread::PthreadCallRun(void *tobject) {
reinterpret_cast<Thread*>(tobject)->Run();
return NULL;
}
Thread::Thread() : started_(false) {}
Thread::~Thread() {
WaitStopped();
}
void Thread::WaitStopped() {
if (!started_) return;
int result = pthread_join(thread_, NULL);
if (result != 0) {
perror("Issue joining thread");
}
started_ = false;
}
void Thread::Start(int priority, uint32_t affinity_mask) {
assert(!started_); // Did you call WaitStopped() ?
pthread_create(&thread_, NULL, &PthreadCallRun, this);
int err;
if (priority > 0) {
struct sched_param p;
p.sched_priority = priority;
if ((err = pthread_setschedparam(thread_, SCHED_FIFO, &p))) {
char buffer[PATH_MAX];
const char *bin = realpath("/proc/self/exe", buffer); // Linux specific.
fprintf(stderr, "Can't set realtime thread priority=%d: %s.\n"
"\tYou are probably not running as root ?\n"
"\tThis will seriously mess with color stability and flicker\n"
"\tof the matrix. Please run as `root` (e.g. by invoking this\n"
"\tprogram with `sudo`), or setting the capability on this\n"
"\tbinary by calling\n"
"\tsudo setcap 'cap_sys_nice=eip' %s\n",
p.sched_priority, strerror(err), bin ? bin : "<this binary>");
}
}
if (affinity_mask != 0) {
cpu_set_t cpu_mask;
CPU_ZERO(&cpu_mask);
for (int i = 0; i < 32; ++i) {
if ((affinity_mask & (1<<i)) != 0) {
CPU_SET(i, &cpu_mask);
}
}
if ((err=pthread_setaffinity_np(thread_, sizeof(cpu_mask), &cpu_mask))) {
// On a Pi1, this won't work as there is only one core. Don't worry in
// that case.
}
}
started_ = true;
}
bool Mutex::WaitOn(pthread_cond_t *cond, long timeout_ms) {
if (timeout_ms < 0) {
pthread_cond_wait(cond, &mutex_);
return true;
}
else {
struct timespec t;
clock_gettime(CLOCK_REALTIME, &t);
t.tv_sec += timeout_ms / 1000;
t.tv_nsec += (timeout_ms % 1000) * 1000000;
t.tv_sec += t.tv_nsec / 1000000000;
t.tv_nsec %= 1000000000;
// TODO(hzeller): It doesn't seem we return with EINTR on signal. We should.
return pthread_cond_timedwait(cond, &mutex_, &t) == 0;
}
}
} // namespace rgb_matrix

BIN
lib/thread.o Normal file

Binary file not shown.

57
lib/utf8-internal.h Normal file
View file

@ -0,0 +1,57 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
//
// 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 version 2.
//
// 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 <http://gnu.org/licenses/gpl-2.0.txt>
#ifndef RPI_GRAPHICS_UTF8_H
#define RPI_GRAPHICS_UTF8_H
#include <stdint.h>
// Utility function that reads UTF-8 encoded codepoints from byte iterator.
// No error checking, we assume string is UTF-8 clean.
template <typename byte_iterator>
uint32_t utf8_next_codepoint(byte_iterator &it) {
uint32_t cp = *it++;
if (cp < 0x80) {
return cp; // iterator already incremented.
}
else if ((cp & 0xE0) == 0xC0) {
cp = ((cp & 0x1F) << 6) + (*it & 0x3F);
}
else if ((cp & 0xF0) == 0xE0) {
cp = ((cp & 0x0F) << 12) + ((*it & 0x3F) << 6);
cp += (*++it & 0x3F);
}
else if ((cp & 0xF8) == 0xF0) {
cp = ((cp & 0x07) << 18) + ((*it & 0x3F) << 12);
cp += (*++it & 0x3F) << 6;
cp += (*++it & 0x3F);
}
else if ((cp & 0xFC) == 0xF8) {
cp = ((cp & 0x03) << 24) + ((*it & 0x3F) << 18);
cp += (*++it & 0x3F) << 12;
cp += (*++it & 0x3F) << 6;
cp += (*++it & 0x3F);
}
else if ((cp & 0xFE) == 0xFC) {
cp = ((cp & 0x01) << 30) + ((*it & 0x3F) << 24);
cp += (*++it & 0x3F) << 18;
cp += (*++it & 0x3F) << 12;
cp += (*++it & 0x3F) << 6;
cp += (*++it & 0x3F);
}
++it;
return cp;
}
#endif // RPI_GRAPHICS_UTF8_H

BIN
librgbmatrix.a Normal file

Binary file not shown.

253
net.c Normal file
View file

@ -0,0 +1,253 @@
#include <netinet/in.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <event2/event.h>
#include <event2/buffer.h>
#include <event2/thread.h>
#include <event2/bufferevent.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <err.h>
#include "net.h"
// Lines longer than this are considered an error.
#define NET_MAX_LINE 1024
// The server buffers up to NET_READ_BUFFER bytes per client connection.
// Lower values allow lots of clients to draw at the same time, each with a fair share.
// Higher values increase throughput but fast clients might be able to draw large batches at once.
#define NET_MAX_BUFFER 10240
typedef struct NetClient {
int sock_fd;
struct bufferevent *buf_ev;
int state;
void *user;
} NetClient;
#define NET_CSTATE_OPEN 0
#define NET_CSTATE_CLOSING 1
static inline int min(int a, int b) {
return a < b ? a : b;
}
// global state
static struct event_base *base;
static char * line_buffer;
// User defined callbacks
static net_on_connect netcb_on_connect = NULL;
static net_on_read netcb_on_read = NULL;
static net_on_close netcb_on_close = NULL;
// libevent callbacks
// Like evbuffer_readln, but read into an existing string instead of allocating a new one.
// Return 1 if a line ending was found and the line was read completely.
// Return 2 if a line ending was found but the line was read only partially (longer than maxread-1).
// Return 0 if no line ending not found within the buffer.
int net_evbuffer_readln(struct evbuffer *buffer, char *line, size_t maxread, size_t * read_out, enum evbuffer_eol_style eol_style) {
struct evbuffer_ptr it;
size_t read_len=0, eol_len=0;
it = evbuffer_search_eol(buffer, NULL, &eol_len, eol_style);
if (it.pos < 0)
return 0;
read_len = min(maxread-1, it.pos);
if(read_out)
*read_out = read_len;
evbuffer_remove(buffer, line, read_len);
line[read_len] = '\0';
// Eat EOL if we have read all bytes before it
if(read_len == it.pos) {
evbuffer_drain(buffer, eol_len);
return 1;
} else {
return 2;
}
}
static void netev_on_read(struct bufferevent *bev, void *ctx) {
struct evbuffer *input;
NetClient *client = ctx;
int r = 0;
input = bufferevent_get_input(bev);
// Change while->if for less throughput but more fair pixel distribution across client connections.
while ((r = net_evbuffer_readln(input, line_buffer, NET_MAX_LINE, NULL, EVBUFFER_EOL_LF)) == 1) {
(*netcb_on_read)(client, line_buffer);
}
if (r == 2) {
net_err(client, "Line to long");
}
}
static void netev_on_write(struct bufferevent *bev, void *arg) {
NetClient *client = arg;
if (client->state == NET_CSTATE_CLOSING
&& evbuffer_get_length(bufferevent_get_output(bev)) == 0) {
if (netcb_on_close)
(*netcb_on_close)(client, 0);
bufferevent_free(bev);
free(client);
}
}
static void netev_on_error(struct bufferevent *bev, short error, void *arg) {
NetClient *client = arg;
// TODO: Some logging?
if (error & BEV_EVENT_EOF) {
} else if (error & BEV_EVENT_ERROR) {
} else if (error & BEV_EVENT_TIMEOUT) {
}
client->state = NET_CSTATE_CLOSING;
if (netcb_on_close)
(*netcb_on_close)(client, error);
bufferevent_free(bev);
free(client);
}
static void on_accept(evutil_socket_t listener, short event, void *arg) {
struct event_base *base = arg;
struct sockaddr_storage ss;
socklen_t slen = sizeof(ss);
int fd = accept(listener, (struct sockaddr*) &ss, &slen);
if (fd < 0) {
perror("accept failed");
} else if (fd > FD_SETSIZE) {
close(fd); // TODO: verify if this is needed. Only for select()? But libevent uses poll/kqueue?
} else {
NetClient *client = calloc(1, sizeof(NetClient));
if (client == NULL) {
perror("client malloc failed");
close(fd);
return;
}
client->sock_fd = fd;
client->buf_ev = bufferevent_socket_new(base, fd,
BEV_OPT_CLOSE_ON_FREE);
evutil_make_socket_nonblocking(fd);
bufferevent_setcb(client->buf_ev, netev_on_read, netev_on_write,
netev_on_error, client);
bufferevent_setwatermark(client->buf_ev, EV_READ, 0, NET_MAX_BUFFER);
if (netcb_on_connect)
(*netcb_on_connect)(client);
if (client->state == NET_CSTATE_OPEN)
bufferevent_enable(client->buf_ev, EV_READ | EV_WRITE);
}
}
// Public functions
void net_start(int port, net_on_connect on_connect, net_on_read on_read,
net_on_close on_close) {
evutil_socket_t listener;
struct sockaddr_in sin;
// struct sockaddr_in6 sin;
struct event *listener_event;
line_buffer = malloc(sizeof(char)*NET_MAX_LINE);
evthread_use_pthreads();
//setvbuf(stdout, NULL, _IONBF, 0);
netcb_on_connect = on_connect;
netcb_on_read = on_read;
netcb_on_close = on_close;
base = event_base_new();
if (!base)
err(1, "Failed to create event_base");
//evthread_make_base_notifiable(base);
sin.sin_family = AF_INET;
// sin.sin6_family = AF_INET6;
sin.sin_addr.s_addr = 0;
// sin.sin6_addr = in6addr_any;
sin.sin_port = htons(1337);
// sin.sin6_port = htons(1337);
listener = socket(AF_INET, SOCK_STREAM, 0);
// listener = socket(AF_INET6, SOCK_STREAM, 0);
evutil_make_socket_nonblocking(listener);
evutil_make_listen_socket_reuseable(listener);
if (bind(listener, (struct sockaddr*) &sin, sizeof(sin)) < 0) {
err(1, "bind failed");
}
if (listen(listener, 16) < 0) {
err(1, "listen failed");
}
listener_event = event_new(base, listener, EV_READ | EV_PERSIST, on_accept,
(void*) base);
event_add(listener_event, NULL);
event_base_dispatch(base);
}
void net_stop() {
event_base_loopbreak(base);
if(line_buffer) {
free(line_buffer);
line_buffer = NULL;
}
}
void net_send(NetClient *client, const char * msg) {
struct evbuffer *output = bufferevent_get_output(client->buf_ev);
evbuffer_add(output, msg, strlen(msg));
evbuffer_add(output, "\n", 1);
}
void net_close(NetClient *client) {
if (client->state == NET_CSTATE_OPEN) {
client->state = NET_CSTATE_CLOSING;
bufferevent_disable(client->buf_ev, EV_READ);
}
}
void net_err(NetClient *client, const char * msg) {
struct evbuffer *output = bufferevent_get_output(client->buf_ev);
evbuffer_add(output, "ERROR: ", 7);
evbuffer_add(output, msg, strlen(msg));
evbuffer_add(output, "\n", 1);
net_close(client);
}
void net_set_user(NetClient *client, void *user) {
client->user = user;
}
void net_get_user(NetClient *client, void **user) {
*user = client->user;
}

38
net.h Normal file
View file

@ -0,0 +1,38 @@
#ifndef NET_H_
#define NET_H_
typedef struct NetClient NetClient;
#define NET_CSTATE_OPEN 0
#define NET_CSTATE_CLOSING 1
// Callback called immediately after a client connects
typedef void (*net_on_connect)(NetClient *client);
// Callback called for each line of input.
// The char array is null-terminated and does not include the final line break.
// It is NOT owned by the callback and freed as soon as the callback returned.
typedef void (*net_on_read)(NetClient *client, char* line);
// Callback called after a client disconnects.
// The second parameter is 0 for a normal client-induced disconnect and != 0 on errors.
typedef void (*net_on_close)(NetClient *client, int error);
// Start the server and block until it is closed again.
void net_start(int port, net_on_connect on_connect, net_on_read on_read, net_on_close on_close);
// Stop the server as soon as possible
void net_stop();
// Send a string to the client. A newline is added automatically.
void net_send(NetClient *client, const char * msg);
// Stop reading from this clients socket, send all bytes still in the output buffer, then close the connection.
void net_close(NetClient *client);
// Send an error message to the client, then close the connection.
void net_err(NetClient *client, const char * msg);
// Get or set the user attachment, a pointer to an arbitrary data structure or NULL
void net_set_user(NetClient *client, void *user);
void net_get_user(NetClient *client, void **user);
#endif /* NET_H_ */

BIN
net.o Normal file

Binary file not shown.

110
pixel-mapper.h Normal file
View file

@ -0,0 +1,110 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2018 Henner Zeller <h.zeller@acm.org>
//
// 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 version 2.
//
// 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 <http://gnu.org/licenses/gpl-2.0.txt>
#ifndef RGBMATRIX_PIXEL_MAPPER
#define RGBMATRIX_PIXEL_MAPPER
#include <string>
#include <vector>
namespace rgb_matrix {
// A pixel mapper is a way for you to map pixels of LED matrixes to a different
// layout. If you have an implementation of a PixelMapper, you can give it
// to the RGBMatrix::ApplyPixelMapper(), which then presents you a canvas
// that has the new "visible_width", "visible_height".
class PixelMapper {
public:
virtual ~PixelMapper() {}
// Get the name of this PixelMapper. Each PixelMapper needs to have a name
// so that it can be referred to with command line flags.
virtual const char *GetName() const = 0;
// Pixel mappers receive the chain and parallel information and
// might receive optional user-parameters, e.g. from command line flags.
//
// This is a single string containing the parameters.
// You can be used from simple scalar parameters, such as the angle for
// the rotate transformer, or more complex parameters that describe a mapping
// of panels for instance.
// Keep it concise (as people will give parameters on the command line) and
// don't use semicolons in your string (as they are
// used to separate pixel mappers on the command line).
//
// For instance, the rotate transformer is invoked like this
// --led-pixel-mapper=rotate:90
// And the parameter that is passed to SetParameter() is "90".
//
// Returns 'true' if parameter was parsed successfully.
virtual bool SetParameters(int chain, int parallel,
const char *parameter_string) {
return true;
}
// Given a underlying matrix (width, height), returns the
// visible (width, height) after the mapping.
// E.g. a 90 degree rotation might map matrix=(64, 32) -> visible=(32, 64)
// Some multiplexing matrices will double the height and half the width.
//
// While not technically necessary, one would expect that the number of
// pixels stay the same, so
// matrix_width * matrix_height == (*visible_width) * (*visible_height);
//
// Returns boolean "true" if the mapping can be successfully done with this
// mapper.
virtual bool GetSizeMapping(int matrix_width, int matrix_height,
int *visible_width, int *visible_height)
const = 0;
// Map where a visible pixel (x,y) is mapped to the underlying matrix (x,y).
//
// To be convienently stateless, the first parameters are the full
// matrix width and height.
//
// So for many multiplexing methods this means to map a panel to a double
// length and half height panel (32x16 -> 64x8).
// The logic_x, logic_y are output parameters and guaranteed not to be
// nullptr.
virtual void MapVisibleToMatrix(int matrix_width, int matrix_height,
int visible_x, int visible_y,
int *matrix_x, int *matrix_y) const = 0;
};
// This is a place to register PixelMappers globally. If you register your
// PixelMapper before calling RGBMatrix::CreateFromFlags(), the named
// PixelMapper is available in the --led-pixel-mapper options.
//
// Note, you don't _have_ to register your mapper, you can always call
// RGBMatrix::ApplyPixelMapper() directly. Registering is for convenience and
// commandline-flag support.
//
// There are a few standard mappers registered by default.
void RegisterPixelMapper(PixelMapper *mapper);
// Get a list of the names of available pixel mappers.
std::vector<std::string> GetAvailablePixelMappers();
// Given a name (e.g. "rotate") and a parameter (e.g. "90"), return the
// parametrized PixelMapper with that name. Returns NULL if mapper
// can not be found or parameter is invalid.
// Ownership of the returned object is _NOT_ transferred to the caller.
// Current available mappers are "U-mapper" and "Rotate". The "Rotate"
// gets a parameter denoting the angle.
const PixelMapper *FindPixelMapper(const char *name,
int chain, int parallel,
const char *parameter = NULL);
} // namespace rgb_matrix
#endif // RGBMATRIX_PIXEL_MAPPER

BIN
pixelnuke Executable file

Binary file not shown.

235
pixelnuke.c Normal file
View file

@ -0,0 +1,235 @@
#include "net.h"
#include "canvaspixel.h"
#include "led-matrix-c.h"
#include <stdlib.h>
#include <errno.h>
#include <stdio.h> //sprintf
#include <string.h>
#include <unistd.h>
#include <pthread.h>
unsigned int px_width = 192;
unsigned int px_height = 128;
unsigned int px_pixelcount = 0;
unsigned int px_clientcount = 0;
struct RGBLedMatrix *matrix;
struct LedCanvas *offscreen_canvas;
// User sessions
typedef struct PxSession {
} PxSession;
// Helper functions
static inline int fast_str_startswith(const char* prefix, const char* str) {
char cp, cs;
while ((cp = *prefix++) == (cs = *str++)) {
if (cp == 0)
return 1;
}
return !cp;
}
// Decimal string to unsigned int. This variant does NOT consume +, - or whitespace.
// If **endptr is not NULL, it will point to the first non-decimal character, which
// may be \0 at the end of the string.
static inline uint32_t fast_strtoul10(const char *str, const char **endptr) {
uint32_t result = 0;
unsigned char c;
for (; (c = *str - '0') <= 9; str++)
result = result * 10 + c;
if (endptr)
*endptr = str;
return result;
}
// Same as fast_strtoul10, but for hex strings.
static inline uint32_t fast_strtoul16(const char *str, const char **endptr) {
uint32_t result = 0;
unsigned char c;
while ((c = *str - '0') <= 9 // 0-9
|| ((c -= 7) >= 10 && c <= 15) // A-F
|| ((c -= 32) >= 10 && c <= 15)) { // a-f
result = result * 16 + c;
str++;
}
if (endptr)
*endptr = str;
return result;
}
// server callbacks
void px_on_connect(NetClient *client) {
px_clientcount++;
}
void px_on_close(NetClient *client, int error) {
px_clientcount--;
}
void px_on_read(NetClient *client, char *line) {
if (fast_str_startswith("PX ", line)) {
const char * ptr = line + 3;
const char * endptr = ptr;
errno = 0;
uint32_t x = fast_strtoul10(ptr, &endptr);
if (endptr == ptr) {
net_err(client,
"Invalid command (expected decimal as first parameter)");
return;
}
if (*endptr == '\0') {
net_err(client, "Invalid command (second parameter required)");
return;
}
endptr++; // eat space (or whatever non-decimal is found here)
uint32_t y = fast_strtoul10((ptr = endptr), &endptr);
if (endptr == ptr) {
net_err(client,
"Invalid command (expected decimal as second parameter)");
return;
}
// PX <x> <y> -> Get RGB color at position (x,y) or '0x000000' for out-of-range queries
if (*endptr == '\0') {
uint32_t c;
//canvaspixel_get_px(x, y, &c);
char str[64];
sprintf(str, "PX %u %u %06X", x, y, (c >> 8));
net_send(client, str);
return;
}
endptr++; // eat space (or whatever non-decimal is found here)
// PX <x> <y> BB|RRGGBB|RRGGBBAA
uint32_t c = fast_strtoul16((ptr = endptr), &endptr);
if (endptr == ptr) {
net_err(client,
"Third parameter missing or invalid (should be hex color)");
return;
}
if (endptr - ptr == 6) {
// RGB -> RGBA (most common)
c = (c << 8) + 0xff;
} else if (endptr - ptr == 8) {
// done
} else if (endptr - ptr == 2) {
// WW -> RGBA
c = (c << 24) + (c << 16) + (c << 8) + 0xff;
} else {
net_err(client,
"Color hex code must be 2, 6 or 8 characters long (WW, RGB or RGBA)");
return;
}
px_pixelcount++;
//canvaspixel_set_px(x, y, c);
led_canvas_set_pixel(offscreen_canvas, x, y, c >>24&255 , c>>16&255, c>>8&255);
//offscreen_canvas = led_matrix_swap_on_vsync(matrix, offscreen_canvas);
} else if (fast_str_startswith("SIZE", line)) {
char str[64];
snprintf(str, 64, "SIZE %d %d", px_width, px_height);
net_send(client, str);
} else if (fast_str_startswith("STATS", line)) {
char str[128];
snprintf(str, 128, "STATS px:%u conn:%u", px_pixelcount,
px_clientcount);
net_send(client, str);
} else if (fast_str_startswith("HELP", line)) {
net_send(client,
"\
PX x y: Get color at position (x,y)\n\
PX x y rrggbb(aa): Draw a pixel (with optional alpha channel)\n\
SIZE: Get canvaspixel size\n\
STATS: Return statistics");
} else {
net_err(client, "Unknown command");
}
}
void px_on_key(int key, int scancode, int mods) {
printf("Key pressed: key:%d scancode:%d mods:%d\n", key, scancode, mods);
if (key == 301) { // F12
canvaspixel_fullscreen(canvaspixel_get_display() + 1);
} else if (key == 67) { // c
canvaspixel_fill(0x00000088);
} else if (key == 81 || key == 256) { // q or ESC
canvaspixel_close();
}
}
//void px_on_resize() {
// canvaspixel_get_size(&px_width, &px_height);
//}
void px_on_window_close() {
printf("Window closed\n");
net_stop();
//led_matrix_delete(matrix);
}
void *myThreadFun(/**struct RGBLedMatrix *matrix, struct LedCanvas *offscreen_canvas**/) {
while(true){
offscreen_canvas = led_matrix_swap_on_vsync(matrix, offscreen_canvas);
}
}
int main(int argc, char **argv) {
canvaspixel_setcb_key(&px_on_key);
struct RGBLedMatrixOptions options;
struct RGBLedRuntimeOptions rt_options;
//int width, height;
//int x, y, i;
memset(&options, 0, sizeof(options));
options.rows = 32;
options.cols = 64;
options.chain_length = 6;
options.parallel = 2;
options.hardware_mapping = "regular";
options.pixel_mapper_config = "U-mapper";
memset(&rt_options, 0, sizeof(rt_options));
rt_options.gpio_slowdown = 4;
options.chain_length = 6;
/* This supports all the led commandline options. Try --led-help */
matrix = led_matrix_create_from_options_and_rt_options(&options, &rt_options);
if (matrix == NULL)
return 1;
/* Let's do an example with double-buffering. We create one extra
* buffer onto which we draw, which is then swapped on each refresh.
* This is typically a good aproach for animations and such.
*/
offscreen_canvas = led_matrix_create_offscreen_canvas(matrix);
// canvaspixel_setcb_resize(&px_on_resize);
//canvaspixel_start(1024, &px_on_window_close);
pthread_t thread_id;
pthread_create(&thread_id, NULL, myThreadFun, NULL);
net_start(1337, &px_on_connect, &px_on_read, &px_on_close);
return 0;
}

BIN
pixelnuke.o Normal file

Binary file not shown.

86
thread.h Normal file
View file

@ -0,0 +1,86 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
//
// 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 version 2.
//
// 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 <http://gnu.org/licenses/gpl-2.0.txt>
#ifndef RPI_THREAD_H
#define RPI_THREAD_H
#include <stdint.h>
#include <pthread.h>
namespace rgb_matrix {
// Simple thread abstraction.
class Thread {
public:
Thread();
// The destructor waits for Run() to return so make sure it does.
virtual ~Thread();
// Wait for the Run() method to return.
void WaitStopped();
// Start thread. If realtime_priority is > 0, then this will be a
// thread with SCHED_FIFO and the given priority.
// If cpu_affinity is set !=, chooses the given bitmask of CPUs
// this thread should have an affinity to.
// On a Raspberry Pi 1, this doesn't matter, as there is only one core,
// Raspberry Pi 2 can has 4 cores, so any combination of (1<<0) .. (1<<3) is
// valid.
virtual void Start(int realtime_priority = 0, uint32_t cpu_affinity_mask = 0);
// Override this to do the work.
//
// This will be called in a thread once Start() has been called. You typically
// will have an endless loop doing stuff.
//
// It is a good idea to provide a way to communicate to the thread that
// it should stop (see ThreadedCanvasManipulator for an example)
virtual void Run() = 0;
private:
static void *PthreadCallRun(void *tobject);
bool started_;
pthread_t thread_;
};
// Non-recursive Mutex.
class Mutex {
public:
Mutex() { pthread_mutex_init(&mutex_, NULL); }
~Mutex() { pthread_mutex_destroy(&mutex_); }
void Lock() { pthread_mutex_lock(&mutex_); }
void Unlock() { pthread_mutex_unlock(&mutex_); }
// Wait on condition. If "timeout_ms" is < 0, it waits forever, otherwise
// until timeout is reached.
// Returns 'true' if condition is met, 'false', if wait timed out.
bool WaitOn(pthread_cond_t *cond, long timeout_ms = -1);
private:
pthread_mutex_t mutex_;
};
// Useful RAII wrapper around mutex.
class MutexLock {
public:
MutexLock(Mutex *m) : mutex_(m) { mutex_->Lock(); }
~MutexLock() { mutex_->Unlock(); }
private:
Mutex *const mutex_;
};
} // end namespace rgb_matrix
#endif // RPI_THREAD_H

View file

@ -0,0 +1,103 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2014 Henner Zeller <h.zeller@acm.org>
//
// 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 version 2.
//
// 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 <http://gnu.org/licenses/gpl-2.0.txt>
// Utility base class for continuously updating the canvas.
// Note: considering removing this, as real applications likely have something
// similar, but this might not be quite usable.
// Since it is just a few lines of code, it is probably better
// implemented in the application for readability.
//
// So for simplicity of the API, consider ThreadedCanvasManipulator deprecated.
#ifndef RPI_THREADED_CANVAS_MANIPULATOR_H
#define RPI_THREADED_CANVAS_MANIPULATOR_H
#include "thread.h"
#include "canvas.h"
namespace rgb_matrix {
//
// Typically, your programs will crate a canvas and then updating the image
// in a loop. If you want to do stuff in parallel, then this utility class
// helps you doing that. Also a demo for how to use the Thread class.
//
// Extend it, then just implement Run(). Example:
/*
class MyCrazyDemo : public ThreadedCanvasManipulator {
public:
MyCrazyDemo(Canvas *canvas) : ThreadedCanvasManipulator(canvas) {}
virtual void Run() {
unsigned char c;
while (running()) {
// Calculate the next frame.
c++;
for (int x = 0; x < canvas()->width(); ++x) {
for (int y = 0; y < canvas()->height(); ++y) {
canvas()->SetPixel(x, y, c, c, c);
}
}
usleep(15 * 1000);
}
}
};
// Later, in your main method.
RGBMatrix *matrix = RGBMatrix::CreateFromOptions(...);
MyCrazyDemo *demo = new MyCrazyDemo(matrix);
demo->Start(); // Start doing things.
// This now runs in the background, you can do other things here,
// e.g. aquiring new data or simply wait. But for waiting, you wouldn't
// need a thread in the first place.
demo->Stop();
delete demo;
*/
class ThreadedCanvasManipulator : public Thread {
public:
ThreadedCanvasManipulator(Canvas *m) : running_(false), canvas_(m) {}
virtual ~ThreadedCanvasManipulator() { Stop(); }
virtual void Start(int realtime_priority=0, uint32_t affinity_mask=0) {
{
MutexLock l(&mutex_);
running_ = true;
}
Thread::Start(realtime_priority, affinity_mask);
}
// Stop the thread at the next possible time Run() checks the running_ flag.
void Stop() {
MutexLock l(&mutex_);
running_ = false;
}
// Implement this and run while running() returns true.
virtual void Run() = 0;
protected:
inline Canvas *canvas() { return canvas_; }
inline bool running() {
MutexLock l(&mutex_);
return running_;
}
private:
Mutex mutex_;
bool running_;
Canvas *const canvas_;
};
} // namespace rgb_matrix
#endif // RPI_THREADED_CANVAS_MANIPULATOR_H