From 7ba611f81f904e13c07ec7713ddf5296b8460348 Mon Sep 17 00:00:00 2001 From: mrfaptastic <12006953+mrfaptastic@users.noreply.github.com> Date: Thu, 23 Dec 2021 01:03:24 +0000 Subject: [PATCH] Implement FastLED pixel buffer example An extreme memory hog, but not too different to what is used with the Aurora demo's. https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/discussions/225 --- .../ChainedPanelsScreenBuffer.ino | 156 ++++++++++++++++++ .../FastLED_Pixel_Buffer.cpp | 116 +++++++++++++ .../FastLED_Pixel_Buffer.h | 51 ++++++ 3 files changed, 323 insertions(+) create mode 100644 examples/ChainedPanelsScreenBuffer/ChainedPanelsScreenBuffer.ino create mode 100644 examples/ChainedPanelsScreenBuffer/FastLED_Pixel_Buffer.cpp create mode 100644 examples/ChainedPanelsScreenBuffer/FastLED_Pixel_Buffer.h diff --git a/examples/ChainedPanelsScreenBuffer/ChainedPanelsScreenBuffer.ino b/examples/ChainedPanelsScreenBuffer/ChainedPanelsScreenBuffer.ino new file mode 100644 index 0000000..63bf6c2 --- /dev/null +++ b/examples/ChainedPanelsScreenBuffer/ChainedPanelsScreenBuffer.ino @@ -0,0 +1,156 @@ +/************************************************************************* + * IMPORANT PLEASE READ THE INFORMATION BELOW! + * + * This example implements a 'pixel buffer' which is essentally an + * off-screen copy of what is intended to be sent to output (LED panels) + * + * This essentially means DOUBLE THE AMOUNT OF MEMORY is required to + * to store the off-screen image/pixel/display buffer WITH a similar + * amount of memory used for the DMA output buffer for the physical panels. + * + * This means the practical resolution you will be able to output with the + * ESP32 will be CUT IN HALF. Do not try to run huge chains of + * LED Matrix Panels using this buffer, you will run out of memory. + * + * Please DO NOT raise issues @ github about running out of memory, + * we can't do anything about it. It's an ESP32, not a Raspberry Pi! + * + *************************************************************************/ + +/* Use the FastLED_Pixel_Buffer class to handle panel chaining + * (it's based on the VirtualMatrixPanel class) AND also create an + * off-screen CRGB FastLED pixel buffer. + */ +#include "FastLED_Pixel_Buffer.h" + + // Panel configuration + #define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module. + #define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module. + + #define NUM_ROWS 1 // Number of rows of chained INDIVIDUAL PANELS + #define NUM_COLS 2 // Number of INDIVIDUAL PANELS per ROW + + // ^^^ NOTE: DEFAULT EXAMPLE SETUP IS FOR A CHAIN OF TWO x 1/8 SCAN PANELS + + // Change this to your needs, for details on VirtualPanel pls read the PDF! + #define SERPENT true + #define TOPDOWN false + + // placeholder for the matrix object + MatrixPanel_I2S_DMA *dma_display = nullptr; + + // placeholder for the virtual display object + VirtualMatrixPanel_FastLED_Pixel_Buffer *FastLED_Pixel_Buff = nullptr; + + /****************************************************************************** + * Setup! + ******************************************************************************/ + void setup() + { + delay(250); + + Serial.begin(115200); + Serial.println(""); Serial.println(""); Serial.println(""); + Serial.println("*****************************************************"); + Serial.println("* FastLED Pixel BufferDemonstration *"); + Serial.println("*****************************************************"); + +/* + // 62x32 1/8 Scan Panels don't have a D and E pin! + + HUB75_I2S_CFG::i2s_pins _pins = { + R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, + A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, + LAT_PIN, OE_PIN, CLK_PIN + }; +*/ + HUB75_I2S_CFG mxconfig( + PANEL_RES_X, // DO NOT CHANGE THIS + PANEL_RES_Y, // DO NOT CHANGE THIS + NUM_ROWS*NUM_COLS // DO NOT CHANGE THIS + //,_pins // Uncomment to enable custom pins + ); + + mxconfig.clkphase = false; // Change this if you see pixels showing up shifted wrongly by one column the left or right. + //mxconfig.driver = HUB75_I2S_CFG::FM6126A; // in case that we use panels based on FM6126A chip, we can set it here before creating MatrixPanel_I2S_DMA object + + // Do NOT use mxconfig.double_buffer when using this pixel buffer. + + // OK, now we can create our matrix object + dma_display = new MatrixPanel_I2S_DMA(mxconfig); + + // let's adjust default physical panel brightness to about 75% + dma_display->setBrightness8(96); // range is 0-255, 0 - 0%, 255 - 100% + + // Allocate memory and start DMA electrical output to physical panels + if( not dma_display->begin() ) + Serial.println("****** !KABOOM! I2S memory allocation failed ***********"); + + dma_display->clearScreen(); + delay(500); + + // NOW, create the 'Virtual Matrix Panel' class with a FastLED Pixel Buffer! + + // create FastLED_Pixel_Bufflay object based on our newly created dma_display object + FastLED_Pixel_Buff = new VirtualMatrixPanel_FastLED_Pixel_Buffer((*dma_display), NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, SERPENT, TOPDOWN); + + if( not FastLED_Pixel_Buff->allocateMemory() ) + Serial.println("****** !KABOOM! Unable to find enough memory for pixel buffer! ***********"); + + + dma_display->fillScreen(dma_display->color444(8, 0, 0)); + delay(2000); + //FastLED_Pixel_Buff->setPhysicalPanelScanRate(ONE_EIGHT); // If using with 1/8 Scan Panel Hardware + } + + + // Borrowed from the SimpleTextShapes example. + uint16_t colorWheel(uint8_t pos) { + if(pos < 85) { + return dma_display->color565(pos * 3, 255 - pos * 3, 0); + } else if(pos < 170) { + pos -= 85; + return dma_display->color565(255 - pos * 3, 0, pos * 3); + } else { + pos -= 170; + return dma_display->color565(0, pos * 3, 255 - pos * 3); + } + } + + /* A crap demonstration of using the pixel buffer. + * 1) Draw text at an incrementing (going down) y coordinate + * 2) Move down a pixel rows + * 3) Draw the text again, fade the 'old' pixels. Using the pixel buffer to update all pixels on screen. + * 4) 'show' (send) the pixel buffer to the DMA output. + * 5) LOOP + */ + + uint8_t y_coord = 0; + uint8_t wheel = 0; + + void loop() + { + + // draw text with a rotating colour + FastLED_Pixel_Buff->dimAll(200); // Dim all pixels by 250/255 + + FastLED_Pixel_Buff->fillCircle(40, 21, 10, dma_display->color444(15, 0, 15)); + + FastLED_Pixel_Buff->setTextSize(1); // size 1 == 8 pixels high + FastLED_Pixel_Buff->setTextWrap(false); // Don't wrap at end of line - will do ourselves + + FastLED_Pixel_Buff->setCursor(FastLED_Pixel_Buff->width()/4, y_coord); // start at top left, with 8 pixel of spacing + FastLED_Pixel_Buff->setTextColor(colorWheel(wheel++)); + + FastLED_Pixel_Buff->print("MythicalForce"); + + FastLED_Pixel_Buff->show(); // IMPORTANT -> SEND Pixel Buffer to DMA / Panel Output! + + y_coord++; + + if ( y_coord >= FastLED_Pixel_Buff->height()) + y_coord = 0; + + delay(35); + + } // end loop \ No newline at end of file diff --git a/examples/ChainedPanelsScreenBuffer/FastLED_Pixel_Buffer.cpp b/examples/ChainedPanelsScreenBuffer/FastLED_Pixel_Buffer.cpp new file mode 100644 index 0000000..79e19c9 --- /dev/null +++ b/examples/ChainedPanelsScreenBuffer/FastLED_Pixel_Buffer.cpp @@ -0,0 +1,116 @@ +/** + * Experimental layer class to do play with pixel in an off-screen buffer before painting to the DMA + * + * Requires FastLED + * + * Faptastic 2020-2021 + **/ + +#include "FastLED_Pixel_Buffer.h" + +/** + * The one for 256+ matrices + * otherwise this: + * for (uint8_t i = 0; i < MATRIX_WIDTH; i++) {} + * turns into an infinite loop + */ +inline uint16_t VirtualMatrixPanel_FastLED_Pixel_Buffer::XY16( uint16_t x, uint16_t y) { + + if (x >= virtualResX) return 0; + if (y >= virtualResY) return 0; + + return (y * virtualResX) + x + 1; // everything offset by one to compute out of bounds stuff - never displayed by ShowFrame() +} + +// For adafruit +void VirtualMatrixPanel_FastLED_Pixel_Buffer::drawPixel(int16_t x, int16_t y, uint16_t color) { + + //Serial.println("calling our drawpixel!"); + + // 565 color conversion + uint8_t r = ((((color >> 11) & 0x1F) * 527) + 23) >> 6; + uint8_t g = ((((color >> 5) & 0x3F) * 259) + 33) >> 6; + uint8_t b = (((color & 0x1F) * 527) + 23) >> 6; + + this->drawPixel(x, y, CRGB(r,g,b)); +} + +void VirtualMatrixPanel_FastLED_Pixel_Buffer::drawPixel(int16_t x, int16_t y, int r, int g, int b) { + this->drawPixel(x, y, CRGB(r,g,b)); +} + +// We actually just draw to ourselves... to our buffer +void VirtualMatrixPanel_FastLED_Pixel_Buffer::drawPixel(int16_t x, int16_t y, CRGB color) +{ + //Serial.printf("updated x y : %d %d", x, y); + buffer[XY16(x,y)] = color; +} + +CRGB VirtualMatrixPanel_FastLED_Pixel_Buffer::getPixel(int16_t x, int16_t y) +{ + return buffer[XY16(x,y)]; +} + +/** + * Dim all the pixels on the layer. + */ +void VirtualMatrixPanel_FastLED_Pixel_Buffer::dimAll(byte value) { + + //Serial.println("performing dimall"); + // nscale8 max value is 255, or it'll flip back to 0 + // (documentation is wrong when it says x/256), it's actually x/255 + /* + for (int y = 0; y < LAYER_HEIGHT; y++) { + for (int x = 0; x < LAYER_WIDTH; x++) { + pixels->data[y][x].nscale8(value); + }} + */ + dimRect(0,0, virtualResX, virtualResY, value); +} + +/** + * Dim all the pixels in a rectangular option of the layer the layer. + */ +void VirtualMatrixPanel_FastLED_Pixel_Buffer::dimRect(int16_t x, int16_t y, int16_t w, int16_t h, byte value) { + for (int16_t i = x; i < x + w; i++) + { + for (int16_t j = y; j < y + h; j++) + { + buffer[XY16(i,j)].nscale8(value); + } + } +} + +void VirtualMatrixPanel_FastLED_Pixel_Buffer::clear() { + memset(buffer, CRGB(0,0,0), (virtualResX * virtualResY) ); +} + +/** + * Actually Send the CRGB FastLED buffer to the DMA engine / Physical Panels! + * Do this via the underlying 'VirtualMatrixPanel' that does all the pixel-remapping for + * all sorts of chained panels, and panel scan types. + */ +void VirtualMatrixPanel_FastLED_Pixel_Buffer::show() { + + //Serial.println("Doing Show"); + + CRGB _pixel = 0; + for (int16_t y = 0; y < virtualResY; y++) { + for (int16_t x = 0; x < virtualResX; x++) + { + //VirtualMatrixPanel::getCoords(x, y); // call to base to update coords for chaining approach + _pixel = buffer[XY16(x,y)]; + drawPixelRGB888( x, y, _pixel.r, _pixel.g, _pixel.b); // call VirtualMatrixPanel::drawPixelRGB888(...) + //drawPixelRGB888( x, y, 0, 0, 128); // call VirtualMatrixPanel::drawPixelRGB888(...) + } // end loop to copy fast led to the dma matrix + } + +} // show + +/** + * Cleanup should we delete this buffer class. Unlikely during runtime. + */ +VirtualMatrixPanel_FastLED_Pixel_Buffer::~VirtualMatrixPanel_FastLED_Pixel_Buffer(void) +{ + delete(buffer); +} \ No newline at end of file diff --git a/examples/ChainedPanelsScreenBuffer/FastLED_Pixel_Buffer.h b/examples/ChainedPanelsScreenBuffer/FastLED_Pixel_Buffer.h new file mode 100644 index 0000000..28db7d5 --- /dev/null +++ b/examples/ChainedPanelsScreenBuffer/FastLED_Pixel_Buffer.h @@ -0,0 +1,51 @@ +#ifndef VIRTUAL_MATRIX_PANEL_FASTLED_LAYER +#define VIRTUAL_MATRIX_PANEL_FASTLED_LAYER + +#include +#include + +class VirtualMatrixPanel_FastLED_Pixel_Buffer : public VirtualMatrixPanel +{ + public: + using VirtualMatrixPanel::VirtualMatrixPanel; // perform VirtualMatrixPanel class constructor + + bool allocateMemory() // allocate memory + { + // https://www.geeksforgeeks.org/how-to-declare-a-2d-array-dynamically-in-c-using-new-operator/ + buffer = new CRGB[virtualResX * virtualResY]; // These are defined in the underliny + + if (!buffer) { return false; } + + Serial.printf("Allocated %d bytes of memory for pixel buffer.\r\n", sizeof(CRGB)*((virtualResX * virtualResY)+1)); + this->clear(); + + return true; + + } // end Buffer + + virtual void drawPixel(int16_t x, int16_t y, uint16_t color); // overwrite adafruit implementation + void drawPixel(int16_t x, int16_t y, int r, int g, int b); // Buffer implementation + void drawPixel(int16_t x, int16_t y, CRGB color); // Buffer implementation + CRGB getPixel(int16_t x, int16_t y); // Returns a pixel value from the buffer. + + + void dimAll(byte value); + void dimRect(int16_t x, int16_t y, int16_t w, int16_t h, byte value); + + void clear(); + + void show(); // Send buffer to physical hardware / DMA engine. + + // Release Memory + ~VirtualMatrixPanel_FastLED_Pixel_Buffer(void); + + protected: + uint16_t XY16( uint16_t x, uint16_t y); + + private: + CRGB* buffer = nullptr; +}; + + + +#endif \ No newline at end of file