Skip to content
Permalink
Browse files

Provide initial support for displays with AB line. Works for LED display

I am testing here, but might need confirmation/minor tweaks from other owners
of such panels.

New option --led-row-addr-type=1

Fixes #120 (?)
  • Loading branch information...
hzeller committed Jan 20, 2018
1 parent 6f098eb commit 829d40e9ff00dd315b8315e5647088fbd4d74a7f
Showing with 132 additions and 21 deletions.
  1. +1 −0 README.md
  2. +6 −0 include/led-matrix-c.h
  3. +5 −0 include/led-matrix.h
  4. +4 −1 lib/framebuffer-internal.h
  5. +104 −17 lib/framebuffer.cc
  6. +3 −1 lib/led-matrix-c.cc
  7. +4 −2 lib/led-matrix.cc
  8. +5 −0 lib/options-initialize.cc
@@ -65,6 +65,7 @@ They vary in the way the multiplexing is happening.
Type | Scan Multiplexing | Program Option | Remark
-----:|:-----------------:|:-----------------------------|-------
64x64 | 1:32 | --led-rows=64 --led-chain=2 | For displays with E line.
64x64 | 1:32 | --led-rows=64 --led-chain=2 --led-row-addr-type=1 | for displays with AB lines.
32x32 | 1:16 | --led-rows=32 |
32x64 | 1:16 | --led-rows=32 --led-chain=2 | internally two chained 32x32
16x32 | 1:8 | --led-rows=16 |
@@ -101,6 +101,12 @@ struct RGBLedMatrixOptions {
*/
const char *led_rgb_sequence; /* Corresponding flag: --led-rgb-sequence */

/* Default row address type is 0, corresponding to direct setting of the
* row, while row address type 1 is used for panels that only have A/B,
* typically some 64x64 panels
*/
int row_address_type; /* Corresponding flag: --led-row-addr-type */

/** The following are boolean flags, all off by default **/

/* Allow to use the hardware subsystem to create pulses. This won't do
@@ -118,6 +118,11 @@ class RGBMatrix : public Canvas {
// In case the internal sequence of mapping is not "RGB", this contains the
// real mapping. Some panels mix up these colors.
const char *led_rgb_sequence; // Flag: --led-rgb-sequence

// Default row address type is 0, corresponding to direct setting of the
// row, while row address type 1 is used for panels that only have A/B,
// typically some 64x64 panels
int row_address_type; // Flag --led-row-addr-type
};

// Create an RGBMatrix.
@@ -24,6 +24,7 @@ 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.
@@ -70,7 +71,8 @@ class Framebuffer {
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 pwm_lsb_nanoseconds,
int row_address_type);

// 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.
@@ -105,6 +107,7 @@ class Framebuffer {

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"
@@ -60,7 +60,95 @@ PixelMapper::~PixelMapper() {
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 >= 32) row_mask_ |= h.e;
if (double_rows >= 16) row_mask_ |= h.d;
if (double_rows >= 8) row_mask_ |= h.c;
if (double_rows >= 4) 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_;
};

// This is mostly experimental at this point. It works with the one panel I have
// seen that does AB, but might need smallish tweaks to work with all panels
// that do this.
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_;
};

}

const struct HardwareMapping *Framebuffer::hardware_mapping_ = NULL;
RowAddressSetter *Framebuffer::row_setter_ = NULL;

Framebuffer::Framebuffer(int rows, int columns, int parallel,
int scan_mode,
@@ -155,7 +243,8 @@ Framebuffer::~Framebuffer() {

/* static */ void Framebuffer::InitGPIO(GPIO *io, int rows, int parallel,
bool allow_hardware_pulsing,
int pwm_lsb_nanoseconds) {
int pwm_lsb_nanoseconds,
int row_address_type) {
if (sOutputEnablePulser != NULL)
return; // already initialized.

@@ -174,11 +263,18 @@ Framebuffer::~Framebuffer() {
}

const int double_rows = rows / SUB_PANELS_;
if (double_rows >= 32) all_used_bits |= h.e;
if (double_rows >= 16) all_used_bits |= h.d;
if (double_rows >= 8) all_used_bits |= h.c;
if (double_rows >= 4) all_used_bits |= h.b;
all_used_bits |= h.a;
switch (row_address_type) {
case 0:
row_setter_ = new DirectRowAddressSetter(double_rows, h);
break;
case 1:
row_setter_ = new ShiftRegisterRowAddressSetter(double_rows, h);
break;
default:
assert(0); // unexpected type.
}

all_used_bits |= row_setter_->need_bits();

// Initialize outputs, make sure that all of these are supported bits.
const uint32_t result = io->InitOutputs(all_used_bits);
@@ -415,10 +511,6 @@ void Framebuffer::DumpToMatrix(GPIO *io) {

color_clk_mask |= h.clock;

const gpio_bits_t row_mask = h.a | h.b | h.c | h.d | h.e;

gpio_bits_t row_address;

// info needed for interlace mode.
uint8_t rot_bits = 0;
switch (double_rows_) {
@@ -441,12 +533,6 @@ void Framebuffer::DumpToMatrix(GPIO *io) {
d_row = ((row_loop << 1) | (row_loop >> rot_bits)) & row_mask_;
}

row_address = (d_row & 0x01) ? h.a : 0;
row_address |= (d_row & 0x02) ? h.b : 0;
row_address |= (d_row & 0x04) ? h.c : 0;
row_address |= (d_row & 0x08) ? h.d : 0;
row_address |= (d_row & 0x10) ? h.e : 0;

// Rows can't be switched very quickly without ghosting, so we do the
// full PWM of one row before switching rows.
for (int b = kBitPlanes - pwm_to_show; b < kBitPlanes; ++b) {
@@ -464,7 +550,8 @@ void Framebuffer::DumpToMatrix(GPIO *io) {
sOutputEnablePulser->WaitPulseFinished();

// Setting address and strobing needs to happen in dark time.
io->WriteMaskedBits(row_address, row_mask); // Set row address
row_setter_->SetRowAddress(io, d_row);

io->SetBits(h.strobe); // Strobe in the previously clocked in row.
io->ClearBits(h.strobe);

@@ -76,6 +76,7 @@ struct RGBLedMatrix *led_matrix_create_from_options(
OPT_COPY_IF_SET(show_refresh_rate);
OPT_COPY_IF_SET(led_rgb_sequence);
OPT_COPY_IF_SET(inverse_colors);
OPT_COPY_IF_SET(row_address_type);
#undef OPT_COPY_IF_SET
}

@@ -101,6 +102,7 @@ struct RGBLedMatrix *led_matrix_create_from_options(
ACTUAL_VALUE_BACK_TO_OPT(show_refresh_rate);
ACTUAL_VALUE_BACK_TO_OPT(led_rgb_sequence);
ACTUAL_VALUE_BACK_TO_OPT(inverse_colors);
ACTUAL_VALUE_BACK_TO_OPT(row_address_type);
#undef ACTUAL_VALUE_BACK_TO_OPT
}

@@ -215,4 +217,4 @@ void draw_circle(struct LedCanvas *c, int xx, int y, int radius, uint8_t r, uint
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);
}
}
@@ -169,7 +169,8 @@ RGBMatrix::Options::Options() :
#else
inverse_colors(false),
#endif
led_rgb_sequence("RGB")
led_rgb_sequence("RGB"),
row_address_type(0)
{
// Nothing to see here.
}
@@ -218,7 +219,8 @@ void RGBMatrix::SetGPIO(GPIO *io, bool start_thread) {
io_ = io;
internal::Framebuffer::InitGPIO(io_, params_.rows, params_.parallel,
!params_.disable_hardware_pulsing,
params_.pwm_lsb_nanoseconds);
params_.pwm_lsb_nanoseconds,
params_.row_address_type);
}
if (start_thread) {
StartRefresh();
@@ -160,6 +160,9 @@ static bool FlagInit(int &argc, char **&argv,
if (ConsumeIntFlag("pwm-lsb-nanoseconds", it, end,
&mopts->pwm_lsb_nanoseconds, &err))
continue;
if (ConsumeIntFlag("row-addr-type", it, end,
&mopts->row_address_type, &err))
continue;
if (ConsumeBoolFlag("show-refresh", it, &mopts->show_refresh_rate))
continue;
if (ConsumeBoolFlag("inverse", it, &mopts->inverse_colors))
@@ -345,6 +348,8 @@ void PrintMatrixFlags(FILE *out, const RGBMatrix::Options &d,
"\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..1>: 0 = default; 1=AB-addressed panels "
"(Default: 0).\n"
"\t--led-%sshow-refresh : %show refresh rate.\n"
"\t--led-%sinverse "
": Switch if your matrix has inverse colors %s.\n"

0 comments on commit 829d40e

Please sign in to comment.
You can’t perform that action at this time.