This commit is contained in:
3
depends/rpi-rgb-led-matrix/lib/.gitignore
vendored
Normal file
3
depends/rpi-rgb-led-matrix/lib/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
compiler-flags
|
||||
librgbmatrix.a
|
||||
librgbmatrix.so.1
|
||||
196
depends/rpi-rgb-led-matrix/lib/Makefile
Normal file
196
depends/rpi-rgb-led-matrix/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
|
||||
214
depends/rpi-rgb-led-matrix/lib/bdf-font.cc
Normal file
214
depends/rpi-rgb-led-matrix/lib/bdf-font.cc
Normal file
@@ -0,0 +1,214 @@
|
||||
// -*- 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>
|
||||
|
||||
#include <algorithm>
|
||||
#include <bitset>
|
||||
#include <vector>
|
||||
|
||||
// The little question-mark box "<22>" for unknown code.
|
||||
static const uint32_t kUnicodeReplacementCodepoint = 0xFFFD;
|
||||
|
||||
namespace rgb_matrix {
|
||||
// Bitmap for one row. This limits the number of available columns.
|
||||
// Make wider if running into trouble.
|
||||
static constexpr int kMaxFontWidth = 196;
|
||||
typedef std::bitset<kMaxFontWidth> rowbitmap_t;
|
||||
|
||||
struct Font::Glyph {
|
||||
int device_width, device_height;
|
||||
int width, height;
|
||||
int x_offset, y_offset;
|
||||
std::vector<rowbitmap_t> bitmap; // contains 'height' elements.
|
||||
};
|
||||
|
||||
static bool readNibble(char c, uint8_t* val) {
|
||||
if (c >= '0' && c <= '9') { *val = c - '0'; return true; }
|
||||
if (c >= 'a' && c <= 'f') { *val = c - 'a' + 0xa; return true; }
|
||||
if (c >= 'A' && c <= 'F') { *val = c - 'A' + 0xa; return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool parseBitmap(const char *buffer, rowbitmap_t* result) {
|
||||
// Read the bitmap left-aligned to our buffer.
|
||||
for (int pos = result->size() - 1; *buffer && pos >= 3; buffer+=1) {
|
||||
uint8_t val;
|
||||
if (!readNibble(*buffer, &val))
|
||||
break;
|
||||
(*result)[pos--] = val & 0x8;
|
||||
(*result)[pos--] = val & 0x4;
|
||||
(*result)[pos--] = val & 0x2;
|
||||
(*result)[pos--] = val & 0x1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Font::Font() : font_height_(-1), base_line_(0) {}
|
||||
Font::~Font() {
|
||||
for (CodepointGlyphMap::iterator it = glyphs_.begin();
|
||||
it != glyphs_.end(); ++it) {
|
||||
delete 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;
|
||||
|
||||
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) {
|
||||
// Limit to width we can actually display, limited by rowbitmap_t
|
||||
tmp.device_width = std::min(tmp.device_width, kMaxFontWidth);
|
||||
// parsed.
|
||||
}
|
||||
else if (sscanf(buffer, "BBX %d %d %d %d", &tmp.width, &tmp.height,
|
||||
&tmp.x_offset, &tmp.y_offset) == 4) {
|
||||
current_glyph = new Glyph();
|
||||
*current_glyph = tmp;
|
||||
current_glyph->bitmap.resize(tmp.height);
|
||||
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
|
||||
&& parseBitmap(buffer, ¤t_glyph->bitmap[row])) {
|
||||
row++;
|
||||
}
|
||||
else if (strncmp(buffer, "ENDCHAR", strlen("ENDCHAR")) == 0) {
|
||||
if (current_glyph && row == current_glyph->height) {
|
||||
delete 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;
|
||||
Glyph *const tmp_glyph = new Glyph();
|
||||
tmp_glyph->bitmap.resize(height);
|
||||
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.any(); m <<= 1, fill <<= 1) {
|
||||
if ((orig_bitmap & m).any()) {
|
||||
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;
|
||||
|
||||
if (x_pos + g->device_width < 0 || x_pos > c->width() ||
|
||||
y_pos + g->height < 0 || y_pos > c->height()) {
|
||||
return g->device_width; // Outside canvas border. Bail out early.
|
||||
}
|
||||
|
||||
for (int y = 0; y < g->height; ++y) {
|
||||
const rowbitmap_t& row = g->bitmap[y];
|
||||
for (int x = 0; x < g->device_width; ++x) {
|
||||
if (row.test(kMaxFontWidth - 1 - x)) {
|
||||
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
|
||||
203
depends/rpi-rgb-led-matrix/lib/content-streamer.cc
Normal file
203
depends/rpi-rgb-led-matrix/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
|
||||
177
depends/rpi-rgb-led-matrix/lib/framebuffer-internal.h
Normal file
177
depends/rpi-rgb-led-matrix/lib/framebuffer-internal.h
Normal file
@@ -0,0 +1,177 @@
|
||||
// -*- 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"
|
||||
#include "../include/graphics.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 SetPixels(int x, int y, int width, int height, Color *colors);
|
||||
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
|
||||
887
depends/rpi-rgb-led-matrix/lib/framebuffer.cc
Normal file
887
depends/rpi-rgb-led-matrix/lib/framebuffer.cc
Normal file
@@ -0,0 +1,887 @@
|
||||
// -*- 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"
|
||||
#include "../include/graphics.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_;
|
||||
}
|
||||
}
|
||||
|
||||
void Framebuffer::SetPixels(int x, int y, int width, int height, Color *colors) {
|
||||
for (int iy = 0; iy < height; ++iy) {
|
||||
for (int ix = 0; ix < width; ++ix) {
|
||||
SetPixel(x + ix, y + iy, colors->r, colors->g, colors->b);
|
||||
++colors;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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
|
||||
28
depends/rpi-rgb-led-matrix/lib/gpio-bits.h
Normal file
28
depends/rpi-rgb-led-matrix/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
|
||||
817
depends/rpi-rgb-led-matrix/lib/gpio.cc
Normal file
817
depends/rpi-rgb-led-matrix/lib/gpio.cc
Normal file
@@ -0,0 +1,817 @@
|
||||
// -*- 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 {
|
||||
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;
|
||||
}
|
||||
|
||||
#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_);
|
||||
|
||||
// We don't know exactly what GPIO pins are occupied by 1-wire (can we
|
||||
// easily do that ?), so let's complain only about the default GPIO.
|
||||
if ((outputs & GPIO_BIT(4))
|
||||
&& LinuxHasModuleLoaded("w1_gpio")) {
|
||||
fprintf(stderr, "This Raspberry Pi has the one-wire protocol enabled.\n"
|
||||
"This will mess with the display if GPIO pins overlap.\n"
|
||||
"Disable 1-wire in raspi-config (Interface Options).\n\n");
|
||||
}
|
||||
|
||||
#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) {
|
||||
buffer[0] = '\0';
|
||||
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 */
|
||||
case 0x12: /* Zero W 2 (behaves close to Pi 2) */
|
||||
return PI_MODEL_2;
|
||||
|
||||
case 0x11: /* Pi 4 */
|
||||
case 0x13: /* Pi 400 */
|
||||
case 0x14: /* CM4 */
|
||||
return PI_MODEL_4;
|
||||
|
||||
default: /* a bunch of versions representing 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;
|
||||
}
|
||||
|
||||
bool GPIO::IsPi4() {
|
||||
return GetPiModel() == PI_MODEL_4;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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_;
|
||||
};
|
||||
|
||||
// Check that 3 shows up in isolcpus
|
||||
static bool HasIsolCPUs() {
|
||||
char buf[256];
|
||||
ReadFileToBuffer(buf, sizeof(buf), "/sys/devices/system/cpu/isolated");
|
||||
return index(buf, '3') != NULL;
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
if (GetPiModel() != PI_MODEL_1 && !HasIsolCPUs()) {
|
||||
fprintf(stderr, "Suggestion: to slightly improve display update, add\n\tisolcpus=3\n"
|
||||
"at the end of /boot/cmdline.txt and reboot (see README.md)\n");
|
||||
}
|
||||
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
|
||||
149
depends/rpi-rgb-led-matrix/lib/gpio.h
Normal file
149
depends/rpi-rgb-led-matrix/lib/gpio.h
Normal file
@@ -0,0 +1,149 @@
|
||||
// -*- 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 slowdown);
|
||||
|
||||
// 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_; }
|
||||
|
||||
// Return if this is appears to be a Pi4
|
||||
static bool IsPi4();
|
||||
|
||||
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
|
||||
172
depends/rpi-rgb-led-matrix/lib/graphics.cc
Normal file
172
depends/rpi-rgb-led-matrix/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
|
||||
287
depends/rpi-rgb-led-matrix/lib/hardware-mapping.c
Normal file
287
depends/rpi-rgb-led-matrix/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
depends/rpi-rgb-led-matrix/lib/hardware-mapping.h
Normal file
60
depends/rpi-rgb-led-matrix/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
|
||||
316
depends/rpi-rgb-led-matrix/lib/led-matrix-c.cc
Normal file
316
depends/rpi-rgb-led-matrix/lib/led-matrix-c.cc
Normal file
@@ -0,0 +1,316 @@
|
||||
// -*- 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), "C and C++ out of sync");
|
||||
static_assert(sizeof(rgb_matrix::RuntimeOptions) == sizeof(RGBLedRuntimeOptions), "C and C++ out of sync");
|
||||
|
||||
// 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 rgb_matrix::Color* to_color(struct Color* color) {
|
||||
return reinterpret_cast<rgb_matrix::Color*>(color);
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
RT_OPT_COPY_IF_SET(drop_priv_user);
|
||||
RT_OPT_COPY_IF_SET(drop_priv_group);
|
||||
#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);
|
||||
ACTUAL_VALUE_BACK_TO_RT_OPT(drop_priv_user);
|
||||
ACTUAL_VALUE_BACK_TO_RT_OPT(drop_priv_group);
|
||||
#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_set_pixels(struct LedCanvas *canvas, int x, int y,
|
||||
int width, int height, struct Color *colors) {
|
||||
to_canvas(canvas)->SetPixels(x, y, width, height, to_color(colors));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
794
depends/rpi-rgb-led-matrix/lib/led-matrix.cc
Normal file
794
depends/rpi-rgb-led-matrix/lib/led-matrix.cc
Normal file
@@ -0,0 +1,794 @@
|
||||
// -*- 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);
|
||||
|
||||
if (created_frames_.size() % 500 == 0) {
|
||||
if (created_frames_.size() == 500) {
|
||||
fprintf(stderr, "CreateFrameCanvas() called %d times; Usually you only want to call it once (or at most a few times) for double-buffering. These frames will not be freed until the end of the program.\n"
|
||||
"Typical reasons: \n"
|
||||
" * Accidentally called CreateFrameCanvas() inside your inner loop (move outside the loop. Create offscreen-canvas once, then re-use. See SwapOnVSync() examples).\n"
|
||||
" * Used to pre-compute many frames (use led_matrix::StreamWriter instead for such use-case. See e.g. led-image-viewer)\n",
|
||||
(int)created_frames_.size());
|
||||
} else {
|
||||
fprintf(stderr, "FYI: CreateFrameCanvas() now called %d times.\n",
|
||||
(int)created_frames_.size());
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (priv_user == nullptr || priv_user[0] == 0) priv_user = "daemon";
|
||||
if (priv_group == nullptr || priv_group[0] == 0) priv_group = "daemon";
|
||||
|
||||
gid_t gid = atoi(priv_group); // Attempt to parse as GID first
|
||||
if (gid == 0) {
|
||||
struct group *g = getgrnam(priv_group);
|
||||
if (g == NULL) {
|
||||
perror("group lookup.");
|
||||
return false;
|
||||
}
|
||||
gid = g->gr_gid;
|
||||
}
|
||||
if (setresgid(gid, gid, gid) != 0) {
|
||||
perror("setresgid()");
|
||||
return false;
|
||||
}
|
||||
|
||||
uid_t uid = atoi(priv_user); // Attempt to parse as UID first.
|
||||
if (uid == 0) {
|
||||
struct passwd *p = getpwnam(priv_user);
|
||||
if (p == NULL) {
|
||||
perror("user lookup.");
|
||||
return false;
|
||||
}
|
||||
uid = p->pw_uid;
|
||||
}
|
||||
if (setresuid(uid, uid, 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(runtime_options.drop_priv_user,
|
||||
runtime_options.drop_priv_group);
|
||||
}
|
||||
|
||||
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::SetPixels(int x, int y, int width, int height,
|
||||
Color *colors) {
|
||||
frame_->SetPixels(x, y, width, height, colors);
|
||||
}
|
||||
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
|
||||
38
depends/rpi-rgb-led-matrix/lib/multiplex-mappers-internal.h
Normal file
38
depends/rpi-rgb-led-matrix/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
|
||||
492
depends/rpi-rgb-led-matrix/lib/multiplex-mappers.cc
Normal file
492
depends/rpi-rgb-led-matrix/lib/multiplex-mappers.cc
Normal file
@@ -0,0 +1,492 @@
|
||||
// -*- 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;
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
class P10Outdoor32x16HalfScanMapper : public MultiplexMapperBase {
|
||||
public:
|
||||
P10Outdoor32x16HalfScanMapper() : MultiplexMapperBase("P10Outdoor32x16HalfScan", 4) {}
|
||||
|
||||
void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
|
||||
int base = (x/8)*32;
|
||||
bool reverse = (y%4)/2 == 0;
|
||||
int offset = (3-((y%8)/2))*8;
|
||||
int dx = x%8;
|
||||
|
||||
*matrix_y = (y/8 == 0) ? ((y%2 == 0) ? 0:1) : ((y%2 == 0) ? 2:3);
|
||||
*matrix_x = base + (reverse ? offset + (7-dx) : offset + dx);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* 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());
|
||||
result->push_back(new P10Outdoor32x16HalfScanMapper());
|
||||
return result;
|
||||
}
|
||||
|
||||
const MuxMapperList &GetRegisteredMultiplexMappers() {
|
||||
static const MuxMapperList *all_mappers = CreateMultiplexMapperList();
|
||||
return *all_mappers;
|
||||
}
|
||||
} // namespace internal
|
||||
} // namespace rgb_matrix
|
||||
478
depends/rpi-rgb-led-matrix/lib/options-initialize.cc
Normal file
478
depends/rpi-rgb-led-matrix/lib/options-initialize.cc
Normal file
@@ -0,0 +1,478 @@
|
||||
// -*- 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(GPIO::IsPi4() ? 2 : 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),
|
||||
drop_priv_user("daemon"),
|
||||
drop_priv_group("daemon")
|
||||
{
|
||||
// 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 (ConsumeStringFlag("drop-priv-user", it, end,
|
||||
&ropts->drop_priv_user, &err)) {
|
||||
continue;
|
||||
}
|
||||
if (ConsumeStringFlag("drop-priv-group", it, end,
|
||||
&ropts->drop_priv_group, &err)) {
|
||||
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 (2 on Pi4, 1 other)).\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");
|
||||
fprintf(out, "\t--led-drop-priv-user : "
|
||||
"Drop privileges to this username or UID (Default: '%s')\n",
|
||||
r.drop_priv_user);
|
||||
fprintf(out, "\t--led-drop-priv-group : "
|
||||
"Drop privileges to this groupname or GID (Default: '%s')\n",
|
||||
r.drop_priv_group);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
338
depends/rpi-rgb-led-matrix/lib/pixel-mapper.cc
Normal file
338
depends/rpi-rgb-led-matrix/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
|
||||
100
depends/rpi-rgb-led-matrix/lib/thread.cc
Normal file
100
depends/rpi-rgb-led-matrix/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
|
||||
57
depends/rpi-rgb-led-matrix/lib/utf8-internal.h
Normal file
57
depends/rpi-rgb-led-matrix/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
|
||||
Reference in New Issue
Block a user