diff --git a/examples/AnimatedGIFPanel_SD/AnimatedGIFPanel_SD.ino b/examples/AnimatedGIFPanel_SD/AnimatedGIFPanel_SD.ino new file mode 100644 index 0000000..9612244 --- /dev/null +++ b/examples/AnimatedGIFPanel_SD/AnimatedGIFPanel_SD.ino @@ -0,0 +1,268 @@ +/********************************************************************* + * AnimatedGif LED Matrix Panel example where the GIFs are + * stored on a SD card connected to the ESP32 using the + * standard GPIO pins used for SD card acces via. SPI. + * + * Put the gifs into a directory called 'gifs' (case sensitive) on + * a FAT32 formatted SDcard. + ********************************************************************/ +#include "FS.h" +#include "SD.h" +#include "SPI.h" +#include +#include + +/******************************************************************** + * Pin mapping below is for LOLIN D32 (ESP 32) + * + * Default pin mapping used by this library is NOT compatable with the use of the + * ESP32-Arduino 'SD' card library (there is overlap). As such, some of the pins + * used for the HUB75 panel need to be shifted. + * + * 'SD' card library requires GPIO 23, 18 and 19 + * https://github.com/espressif/arduino-esp32/tree/master/libraries/SD + * + */ + +/* + * Connect the SD card to the following pins: + * + * SD Card | ESP32 + * D2 - + * D3 SS + * CMD MOSI + * VSS GND + * VDD 3.3V + * CLK SCK + * VSS GND + * D0 MISO + * D1 - + */ + +/**** SD Card GPIO mappings ****/ +#define SS_PIN 5 +//#define MOSI_PIN 23 +//#define MISO_PIN 19 +//#define CLK_PIN 18 + + +/**** HUB75 GPIO mapping ****/ +// GPIO 34+ are on the ESP32 are input only!! +// https://randomnerdtutorials.com/esp32-pinout-reference-gpios/ + +#define A_PIN 33 // remap esp32 library default from 23 to 33 +#define B_PIN 32 // remap esp32 library default from 19 to 32 +#define C_PIN 22 // remap esp32 library defaultfrom 5 to 22 + +//#define R1_PIN 25 // library default for the esp32, unchanged +//#define G1_PIN 26 // library default for the esp32, unchanged +//#define B1_PIN 27 // library default for the esp32, unchanged +//#define R2_PIN 14 // library default for the esp32, unchanged +//#define G2_PIN 12 // library default for the esp32, unchanged +//#define B2_PIN 13 // library default for the esp32, unchanged +//#define D_PIN 17 // library default for the esp32, unchanged +//#define E_PIN -1 // IMPORTANT: Change to a valid pin if using a 64x64px panel. + +//#define LAT_PIN 4 // library default for the esp32, unchanged +//#define OE_PIN 15 // library default for the esp32, unchanged +//#define CLK_PIN 16 // library default for the esp32, unchanged + +/*************************************************************** + * HUB 75 LED DMA Matrix 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 PANEL_CHAIN 1 // Total number of panels chained one to another + +/**************************************************************/ + +AnimatedGIF gif; +MatrixPanel_I2S_DMA *dma_display = nullptr; + +static int totalFiles = 0; // GIF files count + +static File FSGifFile; // temp gif file holder +static File GifRootFolder; // directory listing + +std::vector GifFiles; // GIF files path + +const int maxGifDuration = 30000; // ms, max GIF duration + +#include "gif_functions.hpp" +#include "sdcard_functions.hpp" + + +/**************************************************************/ +void draw_test_patterns(); +int gifPlay( const char* gifPath ) +{ // 0=infinite + + if( ! gif.open( gifPath, GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile, GIFDraw ) ) { + log_n("Could not open gif %s", gifPath ); + } + + Serial.print("Playing: "); Serial.println(gifPath); + + int frameDelay = 0; // store delay for the last frame + int then = 0; // store overall delay + + while (gif.playFrame(true, &frameDelay)) { + + then += frameDelay; + if( then > maxGifDuration ) { // avoid being trapped in infinite GIF's + //log_w("Broke the GIF loop, max duration exceeded"); + break; + } + } + + gif.close(); + + return then; +} + + +void setup() +{ + Serial.begin(115200); + + // **************************** Setup SD Card access via SPI **************************** + if(!SD.begin(SS_PIN)){ + // bool begin(uint8_t ssPin=SS, SPIClass &spi=SPI, uint32_t frequency=4000000, const char * mountpoint="/sd", uint8_t max_files=5, bool format_if_empty=false); + Serial.println("Card Mount Failed"); + return; + } + uint8_t cardType = SD.cardType(); + + if(cardType == CARD_NONE){ + Serial.println("No SD card attached"); + return; + } + + Serial.print("SD Card Type: "); + if(cardType == CARD_MMC){ + Serial.println("MMC"); + } else if(cardType == CARD_SD){ + Serial.println("SDSC"); + } else if(cardType == CARD_SDHC){ + Serial.println("SDHC"); + } else { + Serial.println("UNKNOWN"); + } + + uint64_t cardSize = SD.cardSize() / (1024 * 1024); + Serial.printf("SD Card Size: %lluMB\n", cardSize); + + //listDir(SD, "/", 1, false); + + Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024)); + Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024)); + + + + // **************************** Setup DMA Matrix **************************** + HUB75_I2S_CFG mxconfig( + PANEL_RES_X, // module width + PANEL_RES_Y, // module height + PANEL_CHAIN // Chain length + ); + + // Need to remap these HUB75 DMA pins because the SPI SDCard is using them. + // Otherwise the SD Card will not work. + mxconfig.gpio.a = A_PIN; + mxconfig.gpio.b = B_PIN; + mxconfig.gpio.c = C_PIN; + // mxconfig.gpio.d = D_PIN; + + //mxconfig.clkphase = false; + //mxconfig.driver = HUB75_I2S_CFG::FM6126A; + + // Display Setup + dma_display = new MatrixPanel_I2S_DMA(mxconfig); + + // Allocate memory and start DMA display + if( not dma_display->begin() ) + Serial.println("****** !KABOOM! HUB75 memory allocation failed ***********"); + + dma_display->setBrightness8(128); //0-255 + dma_display->clearScreen(); + + + // **************************** Setup Sketch **************************** + Serial.println("Starting AnimatedGIFs Sketch"); + + // SD CARD STOPS WORKING WITH DMA DISPLAY ENABLED>... + + File root = SD.open("/gifs"); + if(!root){ + Serial.println("Failed to open directory"); + return; + } + + File file = root.openNextFile(); + while(file){ + if(!file.isDirectory()) + { + Serial.print(" FILE: "); + Serial.print(file.name()); + Serial.print(" SIZE: "); + Serial.println(file.size()); + + std::string filename = "/gifs/" + std::string(file.name()); + Serial.println(filename.c_str()); + + GifFiles.push_back( filename ); + // Serial.println("Adding to gif list:" + String(filename)); + totalFiles++; + + } + file = root.openNextFile(); + } + + file.close(); + Serial.printf("Found %d GIFs to play.", totalFiles); + //totalFiles = getGifInventory("/gifs"); + + + + // This is important - Set the right endianness. + gif.begin(LITTLE_ENDIAN_PIXELS); + +} + +void loop(){ + + // Iterate over a vector using range based for loop + for(auto & elem : GifFiles) + { + gifPlay( elem.c_str() ); + gif.reset(); + delay(500); + } + +} + +void draw_test_patterns() +{ + // fix the screen with green + dma_display->fillRect(0, 0, dma_display->width(), dma_display->height(), dma_display->color444(0, 15, 0)); + delay(500); + + // draw a box in yellow + dma_display->drawRect(0, 0, dma_display->width(), dma_display->height(), dma_display->color444(15, 15, 0)); + delay(500); + + // draw an 'X' in red + dma_display->drawLine(0, 0, dma_display->width()-1, dma_display->height()-1, dma_display->color444(15, 0, 0)); + dma_display->drawLine(dma_display->width()-1, 0, 0, dma_display->height()-1, dma_display->color444(15, 0, 0)); + delay(500); + + // draw a blue circle + dma_display->drawCircle(10, 10, 10, dma_display->color444(0, 0, 15)); + delay(500); + + // fill a violet circle + dma_display->fillCircle(40, 21, 10, dma_display->color444(15, 0, 15)); + delay(500); + delay(1000); + +} diff --git a/examples/AnimatedGIFPanel_SD/Readme.md b/examples/AnimatedGIFPanel_SD/Readme.md new file mode 100644 index 0000000..6f9f06a --- /dev/null +++ b/examples/AnimatedGIFPanel_SD/Readme.md @@ -0,0 +1,15 @@ +# ESP32-HUB75-MatrixPanel-DMA SDCard example + +A very basic example using the 'Animated GIF' library by Larry Bank + the SD / File system library provided for Arduino by Espressif. + +Some default HUB75 pins need to be remapped to accomodate for the SD Card. + +![image](esp32_sdcard.jpg) + +## How to use it? + +1. Format a SD Card with FAT32 file system (default setting) +2. Create a directory called 'gifs' +3. Drop your gifs in there. The resolution of the GIFS must match that of the display. + + diff --git a/examples/AnimatedGIFPanel_SD/esp32_sdcard.jpg b/examples/AnimatedGIFPanel_SD/esp32_sdcard.jpg new file mode 100644 index 0000000..4a4441d Binary files /dev/null and b/examples/AnimatedGIFPanel_SD/esp32_sdcard.jpg differ diff --git a/examples/AnimatedGIFPanel_SD/gif_functions.hpp b/examples/AnimatedGIFPanel_SD/gif_functions.hpp new file mode 100644 index 0000000..7a16a63 --- /dev/null +++ b/examples/AnimatedGIFPanel_SD/gif_functions.hpp @@ -0,0 +1,132 @@ + +// Code copied from AnimatedGIF examples + +#ifndef M5STACK_SD + // for custom ESP32 builds + #define M5STACK_SD SD +#endif + + +static void * GIFOpenFile(const char *fname, int32_t *pSize) +{ + //log_d("GIFOpenFile( %s )\n", fname ); + FSGifFile = M5STACK_SD.open(fname); + if (FSGifFile) { + *pSize = FSGifFile.size(); + return (void *)&FSGifFile; + } + return NULL; +} + + +static void GIFCloseFile(void *pHandle) +{ + File *f = static_cast(pHandle); + if (f != NULL) + f->close(); +} + + +static int32_t GIFReadFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) +{ + int32_t iBytesRead; + iBytesRead = iLen; + File *f = static_cast(pFile->fHandle); + // Note: If you read a file all the way to the last byte, seek() stops working + if ((pFile->iSize - pFile->iPos) < iLen) + iBytesRead = pFile->iSize - pFile->iPos - 1; // <-- ugly work-around + if (iBytesRead <= 0) + return 0; + iBytesRead = (int32_t)f->read(pBuf, iBytesRead); + pFile->iPos = f->position(); + return iBytesRead; +} + + +static int32_t GIFSeekFile(GIFFILE *pFile, int32_t iPosition) +{ + int i = micros(); + File *f = static_cast(pFile->fHandle); + f->seek(iPosition); + pFile->iPos = (int32_t)f->position(); + i = micros() - i; + //log_d("Seek time = %d us\n", i); + return pFile->iPos; +} + + +// Draw a line of image directly on the LCD +void GIFDraw(GIFDRAW *pDraw) +{ + uint8_t *s; + uint16_t *d, *usPalette, usTemp[320]; + int x, y, iWidth; + + iWidth = pDraw->iWidth; + if (iWidth > PANEL_RES_X) + iWidth = PANEL_RES_X; + usPalette = pDraw->pPalette; + y = pDraw->iY + pDraw->y; // current line + + s = pDraw->pPixels; + if (pDraw->ucDisposalMethod == 2) {// restore to background color + for (x=0; xucTransparent) + s[x] = pDraw->ucBackground; + } + pDraw->ucHasTransparency = 0; + } + // Apply the new pixels to the main image + if (pDraw->ucHasTransparency) { // if transparency used + uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent; + int x, iCount; + pEnd = s + iWidth; + x = 0; + iCount = 0; // count non-transparent pixels + while(x < iWidth) { + c = ucTransparent-1; + d = usTemp; + while (c != ucTransparent && s < pEnd) { + c = *s++; + if (c == ucTransparent) { // done, stop + s--; // back up to treat it like transparent + } else { // opaque + *d++ = usPalette[c]; + iCount++; + } + } // while looking for opaque pixels + if (iCount) { // any opaque pixels? + for(int xOffset = 0; xOffset < iCount; xOffset++ ){ + dma_display->drawPixel(x + xOffset, y, usTemp[xOffset]); // 565 Color Format + } + x += iCount; + iCount = 0; + } + // no, look for a run of transparent pixels + c = ucTransparent; + while (c == ucTransparent && s < pEnd) { + c = *s++; + if (c == ucTransparent) + iCount++; + else + s--; + } + if (iCount) { + x += iCount; // skip these + iCount = 0; + } + } + } else { + s = pDraw->pPixels; + // Translate the 8-bit pixels through the RGB565 palette (already byte reversed) + for (x=0; xdrawPixel(x, y, usPalette[*s++]); // color 565 + /* + usTemp[x] = usPalette[*s++]; + + for (x=0; xiWidth; x++) { + dma_display->drawPixel(x, y, usTemp[*s++]); // color 565 + } */ + + } +} /* GIFDraw() */ diff --git a/examples/AnimatedGIFPanel_SD/gifs/cartoon.gif b/examples/AnimatedGIFPanel_SD/gifs/cartoon.gif new file mode 100644 index 0000000..32a0e25 Binary files /dev/null and b/examples/AnimatedGIFPanel_SD/gifs/cartoon.gif differ diff --git a/examples/AnimatedGIFPanel_SD/gifs/ezgif.com-pacmn.gif b/examples/AnimatedGIFPanel_SD/gifs/ezgif.com-pacmn.gif new file mode 100644 index 0000000..0a219a4 Binary files /dev/null and b/examples/AnimatedGIFPanel_SD/gifs/ezgif.com-pacmn.gif differ diff --git a/examples/AnimatedGIFPanel_SD/gifs/loading.io-64x32px.gif b/examples/AnimatedGIFPanel_SD/gifs/loading.io-64x32px.gif new file mode 100644 index 0000000..342f8ae Binary files /dev/null and b/examples/AnimatedGIFPanel_SD/gifs/loading.io-64x32px.gif differ diff --git a/examples/AnimatedGIFPanel_SD/gifs/matrix-spin.gif b/examples/AnimatedGIFPanel_SD/gifs/matrix-spin.gif new file mode 100644 index 0000000..7925d68 Binary files /dev/null and b/examples/AnimatedGIFPanel_SD/gifs/matrix-spin.gif differ diff --git a/examples/AnimatedGIFPanel_SD/gifs/parasite1.gif b/examples/AnimatedGIFPanel_SD/gifs/parasite1.gif new file mode 100644 index 0000000..8b8b67a Binary files /dev/null and b/examples/AnimatedGIFPanel_SD/gifs/parasite1.gif differ diff --git a/examples/AnimatedGIFPanel_SD/gifs/parasite2.gif b/examples/AnimatedGIFPanel_SD/gifs/parasite2.gif new file mode 100644 index 0000000..60d03c7 Binary files /dev/null and b/examples/AnimatedGIFPanel_SD/gifs/parasite2.gif differ diff --git a/examples/AnimatedGIFPanel_SD/gifs/shock-gs.gif b/examples/AnimatedGIFPanel_SD/gifs/shock-gs.gif new file mode 100644 index 0000000..1d023d9 Binary files /dev/null and b/examples/AnimatedGIFPanel_SD/gifs/shock-gs.gif differ diff --git a/examples/AnimatedGIFPanel_SD/sdcard_functions.hpp b/examples/AnimatedGIFPanel_SD/sdcard_functions.hpp new file mode 100644 index 0000000..51ff5b1 --- /dev/null +++ b/examples/AnimatedGIFPanel_SD/sdcard_functions.hpp @@ -0,0 +1,102 @@ +/************************ SD Card Code ************************/ +// As per: https://github.com/espressif/arduino-esp32/tree/master/libraries/SD/examples/SD_Test + + + +void listDir(fs::FS &fs, const char * dirname, uint8_t levels, bool add_to_gif_list = false){ + Serial.printf("Listing directory: %s\n", dirname); + + File root = fs.open(dirname); + if(!root){ + Serial.println("Failed to open directory"); + return; + } + if(!root.isDirectory()){ + Serial.println("Not a directory"); + return; + } + + File file = root.openNextFile(); + while(file){ + if(file.isDirectory()){ + Serial.print(" DIR : "); + Serial.println(file.name()); + if(levels){ + listDir(fs, file.path(), levels -1, false); + } + } else { + Serial.print(" FILE: "); + Serial.print(file.name()); + Serial.print(" SIZE: "); + Serial.println(file.size()); + + if (add_to_gif_list && levels == 0) + { + GifFiles.push_back( std::string(dirname) + file.name() ); + Serial.println("Adding to gif list:" + String(dirname) +"/" + file.name()); + totalFiles++; + } + } + file = root.openNextFile(); + } + + file.close(); +} + +void readFile(fs::FS &fs, const char * path){ + Serial.printf("Reading file: %s\n", path); + + File file = fs.open(path); + if(!file){ + Serial.println("Failed to open file for reading"); + return; + } + + Serial.print("Read from file: "); + while(file.available()){ + Serial.write(file.read()); + } + file.close(); +} + +void testFileIO(fs::FS &fs, const char * path){ + File file = fs.open(path); + static uint8_t buf[512]; + size_t len = 0; + uint32_t start = millis(); + uint32_t end = start; + if(file){ + len = file.size(); + size_t flen = len; + start = millis(); + while(len){ + size_t toRead = len; + if(toRead > 512){ + toRead = 512; + } + file.read(buf, toRead); + len -= toRead; + } + end = millis() - start; + Serial.printf("%u bytes read for %u ms\n", flen, end); + file.close(); + } else { + Serial.println("Failed to open file for reading"); + } + + + file = fs.open(path, FILE_WRITE); + if(!file){ + Serial.println("Failed to open file for writing"); + return; + } + + size_t i; + start = millis(); + for(i=0; i<2048; i++){ + file.write(buf, 512); + } + end = millis() - start; + Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end); + file.close(); +} \ No newline at end of file