mirror of
https://github.com/Hopiu/rpi-rgb-led-matrix.git
synced 2026-05-19 12:31:08 +00:00
258 lines
7.1 KiB
C++
258 lines
7.1 KiB
C++
#include "thread.h"
|
|
#include "led-matrix.h"
|
|
|
|
#include <assert.h>
|
|
#include <unistd.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
// Base-class for a Thread that does something with a matrix.
|
|
class RGBMatrixManipulator : public Thread {
|
|
public:
|
|
RGBMatrixManipulator(RGBMatrix *m) : running_(true), matrix_(m) {}
|
|
virtual ~RGBMatrixManipulator() { running_ = false; }
|
|
|
|
// Run() implementation needs to check running_ regularly.
|
|
|
|
protected:
|
|
volatile bool running_; // TODO: use mutex, but this is good enough for now.
|
|
RGBMatrix *const matrix_;
|
|
};
|
|
|
|
// Pump pixels to screen. Needs to be high priority real-time because jitter
|
|
// here will make the PWM uneven.
|
|
class DisplayUpdater : public RGBMatrixManipulator {
|
|
public:
|
|
DisplayUpdater(RGBMatrix *m) : RGBMatrixManipulator(m) {}
|
|
|
|
void Run() {
|
|
while (running_) {
|
|
matrix_->UpdateScreen();
|
|
}
|
|
}
|
|
};
|
|
|
|
// -- The following are demo image generators.
|
|
|
|
// Simple generator that pulses through RGB and White.
|
|
class ColorPulseGenerator : public RGBMatrixManipulator {
|
|
public:
|
|
ColorPulseGenerator(RGBMatrix *m) : RGBMatrixManipulator(m) {}
|
|
void Run() {
|
|
uint32_t count = 0;
|
|
while (running_) {
|
|
usleep(5000);
|
|
++count;
|
|
int color = (count >> 9) % 4;
|
|
int value = count & 0xFF;
|
|
if (count & 0x100) value = 255 - value;
|
|
int r, g, b;
|
|
switch (color) {
|
|
case 0: r = value; g = b = 0; break;
|
|
case 1: g = value; r = b = 0; break;
|
|
case 2: b = value; r = g = 0; break;
|
|
default: r = g = b = value; break;
|
|
}
|
|
for (int x = 0; x < 32; ++x)
|
|
for (int y = 0; y < 32; ++y)
|
|
matrix_->SetPixel(x, y, r, g, b);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Simple class that generates a rotating block on the screen.
|
|
class RotatingBlockGenerator : public RGBMatrixManipulator {
|
|
public:
|
|
RotatingBlockGenerator(RGBMatrix *m) : RGBMatrixManipulator(m) {}
|
|
|
|
uint8_t scale_col(int val, int lo, int hi) {
|
|
if (val < lo) return 0;
|
|
if (val > hi) return 255;
|
|
return 255 * (val - lo) / (hi - lo);
|
|
}
|
|
|
|
void Run() {
|
|
const float deg_to_rad = 2 * 3.14159265 / 360;
|
|
int rotation = 0;
|
|
while (running_) {
|
|
++rotation;
|
|
usleep(15 * 1000);
|
|
rotation %= 360;
|
|
for (int x = -8; x < 40; ++x) {
|
|
for (int y = -8; y < 40; ++y) {
|
|
float disp_x, disp_y;
|
|
Rotate(x - 16, y - 16, deg_to_rad * rotation, &disp_x, &disp_y);
|
|
if (x >= 5 && x < 28 && y >= 5 && y < 28) {
|
|
matrix_->SetPixel(disp_x + 16, disp_y + 16,
|
|
scale_col(x, 5, 27),
|
|
255 - scale_col(y, 5, 27),
|
|
scale_col(y, 5, 27));
|
|
} else if (x == 16 && y == 3) {
|
|
matrix_->SetPixel(disp_x + 16, disp_y + 16, 255, 0, 0);
|
|
} else if (x == 3 && y == 16) {
|
|
matrix_->SetPixel(disp_x + 16, disp_y + 16, 0, 255, 0);
|
|
} else if (x == 29 && y == 16) {
|
|
matrix_->SetPixel(disp_x + 16, disp_y + 16, 0, 0, 255);
|
|
} else {
|
|
matrix_->SetPixel(disp_x + 16, disp_y + 16, 0, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
void Rotate(int x, int y, float angle,
|
|
float *new_x, float *new_y) {
|
|
*new_x = x * cosf(angle) - y * sinf(angle);
|
|
*new_y = x * sinf(angle) + y * cosf(angle);
|
|
}
|
|
};
|
|
|
|
class ImageScroller : public RGBMatrixManipulator {
|
|
public:
|
|
ImageScroller(RGBMatrix *m)
|
|
: RGBMatrixManipulator(m), image_(NULL), horizontal_position_(0) {
|
|
}
|
|
|
|
// _very_ simplified. Can only read binary P6 PPM. Expects newlines in headers
|
|
// Not really robust. Use at your own risk :)
|
|
bool LoadPPM(const char *filename) {
|
|
if (image_) {
|
|
delete [] image_;
|
|
image_ = NULL;
|
|
}
|
|
FILE *f = fopen(filename, "r");
|
|
if (f == NULL) return false;
|
|
char header_buf[256];
|
|
const char *line = ReadLine(f, header_buf, sizeof(header_buf));
|
|
#define EXIT_WITH_MSG(m) { fprintf(stderr, "%s: %s |%s", filename, m, line); \
|
|
fclose(f); return false; }
|
|
if (sscanf(line, "P6 ") == EOF)
|
|
EXIT_WITH_MSG("Can only handle P6 as PPM type.");
|
|
line = ReadLine(f, header_buf, sizeof(header_buf));
|
|
if (!line || sscanf(line, "%d %d ", &width_, &height_) != 2)
|
|
EXIT_WITH_MSG("Width/height expected");
|
|
int value;
|
|
line = ReadLine(f, header_buf, sizeof(header_buf));
|
|
if (!line || sscanf(line, "%d ", &value) != 1 || value != 255)
|
|
EXIT_WITH_MSG("Only 255 for maxval allowed.");
|
|
const size_t pixel_count = width_ * height_;
|
|
image_ = new Pixel [ pixel_count ];
|
|
assert(sizeof(Pixel) == 3); // we make that assumption.
|
|
if (fread(image_, sizeof(Pixel), pixel_count, f) != pixel_count) {
|
|
line = "";
|
|
EXIT_WITH_MSG("Not enough pixels read.");
|
|
}
|
|
#undef EXIT_WITH_MSG
|
|
fclose(f);
|
|
fprintf(stderr, "Read image with %dx%d\n", width_, height_);
|
|
horizontal_position_ = 0;
|
|
return true;
|
|
}
|
|
|
|
void Run() {
|
|
while (running_) {
|
|
if (image_ == NULL) {
|
|
usleep(100 * 1000);
|
|
continue;
|
|
}
|
|
usleep(30 * 1000);
|
|
for (int x = 0; x < 32; ++x) {
|
|
for (int y = 0; y < 32; ++y) {
|
|
const Pixel &p = getPixel((horizontal_position_ + x) % width_, y);
|
|
int disp_x = 31 - x; // Display upside down on my desk. Lets flip :)
|
|
int disp_y = 31 - y;
|
|
matrix_->SetPixel(disp_x, disp_y, p.red, p.green, p.blue);
|
|
}
|
|
}
|
|
++horizontal_position_;
|
|
}
|
|
}
|
|
|
|
private:
|
|
struct Pixel {
|
|
uint8_t red;
|
|
uint8_t green;
|
|
uint8_t blue;
|
|
};
|
|
|
|
// Read line, skip comments.
|
|
char *ReadLine(FILE *f, char *buffer, size_t len) {
|
|
char *result;
|
|
do {
|
|
result = fgets(buffer, len, f);
|
|
} while (result != NULL && result[0] == '#');
|
|
return result;
|
|
}
|
|
|
|
const Pixel &getPixel(int x, int y) {
|
|
static Pixel dummy;
|
|
if (x < 0 || x > width_ || y < 0 || y > height_) return dummy;
|
|
return image_[x + width_ * y];
|
|
}
|
|
|
|
int width_;
|
|
int height_;
|
|
Pixel *image_;
|
|
uint32_t horizontal_position_;
|
|
};
|
|
|
|
int main(int argc, char *argv[]) {
|
|
int demo = 0;
|
|
if (argc > 1) {
|
|
demo = atoi(argv[1]);
|
|
}
|
|
fprintf(stderr, "Using demo %d\n", demo);
|
|
|
|
GPIO io;
|
|
if (!io.Init())
|
|
return 1;
|
|
|
|
RGBMatrix m(&io);
|
|
|
|
RGBMatrixManipulator *image_gen = NULL;
|
|
switch (demo) {
|
|
case 0:
|
|
image_gen = new RotatingBlockGenerator(&m);
|
|
break;
|
|
case 1:
|
|
if (argc > 2) {
|
|
ImageScroller *scroller = new ImageScroller(&m);
|
|
if (!scroller->LoadPPM(argv[2]))
|
|
return 1;
|
|
image_gen = scroller;
|
|
} else {
|
|
fprintf(stderr, "Demo %d Requires PPM image as parameter", demo);
|
|
return 1;
|
|
}
|
|
break;
|
|
default:
|
|
image_gen = new ColorPulseGenerator(&m);
|
|
break;
|
|
}
|
|
|
|
if (image_gen == NULL)
|
|
return 1;
|
|
|
|
RGBMatrixManipulator *updater = new DisplayUpdater(&m);
|
|
updater->Start(10); // high priority
|
|
|
|
image_gen->Start();
|
|
|
|
// Things are set up. Just wait for <RETURN> to be pressed.
|
|
printf("Press <RETURN> to exit\n");
|
|
getchar();
|
|
|
|
// Stopping threads and wait for them to join.
|
|
delete image_gen;
|
|
delete updater;
|
|
|
|
// Final thing before exit: clear screen and update once, so that
|
|
// we don't have random pixels burn
|
|
m.ClearScreen();
|
|
m.UpdateScreen();
|
|
|
|
return 0;
|
|
}
|