Initial commit
This commit is contained in:
commit
b6231669cc
58 changed files with 9168 additions and 0 deletions
42
Makefile
Normal file
42
Makefile
Normal 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
52
canvas.h
Normal 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
408
canvaspixel.c
Normal 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
27
canvaspixel.h
Normal 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
BIN
canvaspixel.o
Normal file
Binary file not shown.
108
content-streamer.h
Normal file
108
content-streamer.h
Normal 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
144
graphics.h
Normal 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
52
include/canvas.h
Normal 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
108
include/content-streamer.h
Normal 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
144
include/graphics.h
Normal 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
394
include/led-matrix-c.h
Normal 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
110
include/pixel-mapper.h
Normal 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
86
include/thread.h
Normal 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
|
103
include/threaded-canvas-manipulator.h
Normal file
103
include/threaded-canvas-manipulator.h
Normal 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
394
led-matrix-c.h
Normal 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
504
led-matrix.h
Normal 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
3
lib/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
compiler-flags
|
||||||
|
librgbmatrix.a
|
||||||
|
librgbmatrix.so.1
|
196
lib/Makefile
Normal file
196
lib/Makefile
Normal 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
188
lib/bdf-font.cc
Normal 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, ¤t_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
BIN
lib/bdf-font.o
Normal file
Binary file not shown.
203
lib/content-streamer.cc
Normal file
203
lib/content-streamer.cc
Normal 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
BIN
lib/content-streamer.o
Normal file
Binary file not shown.
175
lib/framebuffer-internal.h
Normal file
175
lib/framebuffer-internal.h
Normal 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
878
lib/framebuffer.cc
Normal 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
BIN
lib/framebuffer.o
Normal file
Binary file not shown.
28
lib/gpio-bits.h
Normal file
28
lib/gpio-bits.h
Normal 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
787
lib/gpio.cc
Normal 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
153
lib/gpio.h
Normal 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
BIN
lib/gpio.o
Normal file
Binary file not shown.
172
lib/graphics.cc
Normal file
172
lib/graphics.cc
Normal 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
BIN
lib/graphics.o
Normal file
Binary file not shown.
287
lib/hardware-mapping.c
Normal file
287
lib/hardware-mapping.c
Normal 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
60
lib/hardware-mapping.h
Normal 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
BIN
lib/hardware-mapping.o
Normal file
Binary file not shown.
304
lib/led-matrix-c.cc
Normal file
304
lib/led-matrix-c.cc
Normal 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
BIN
lib/led-matrix-c.o
Normal file
Binary file not shown.
763
lib/led-matrix.cc
Normal file
763
lib/led-matrix.cc
Normal 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(¶ms_.cols, ¶ms_.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
BIN
lib/led-matrix.o
Normal file
Binary file not shown.
38
lib/multiplex-mappers-internal.h
Normal file
38
lib/multiplex-mappers-internal.h
Normal 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
476
lib/multiplex-mappers.cc
Normal 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
BIN
lib/multiplex-mappers.o
Normal file
Binary file not shown.
461
lib/options-initialize.cc
Normal file
461
lib/options-initialize.cc
Normal 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
BIN
lib/options-initialize.o
Normal file
Binary file not shown.
338
lib/pixel-mapper.cc
Normal file
338
lib/pixel-mapper.cc
Normal 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
BIN
lib/pixel-mapper.o
Normal file
Binary file not shown.
100
lib/thread.cc
Normal file
100
lib/thread.cc
Normal 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
BIN
lib/thread.o
Normal file
Binary file not shown.
57
lib/utf8-internal.h
Normal file
57
lib/utf8-internal.h
Normal 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
BIN
librgbmatrix.a
Normal file
Binary file not shown.
253
net.c
Normal file
253
net.c
Normal 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
38
net.h
Normal 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
BIN
net.o
Normal file
Binary file not shown.
110
pixel-mapper.h
Normal file
110
pixel-mapper.h
Normal 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
BIN
pixelnuke
Executable file
Binary file not shown.
235
pixelnuke.c
Normal file
235
pixelnuke.c
Normal 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
BIN
pixelnuke.o
Normal file
Binary file not shown.
86
thread.h
Normal file
86
thread.h
Normal 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
|
103
threaded-canvas-manipulator.h
Normal file
103
threaded-canvas-manipulator.h
Normal 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
|
Loading…
Reference in a new issue