Add SDCard AnimatedGIF example
268
examples/AnimatedGIFPanel_SD/AnimatedGIFPanel_SD.ino
Normal file
|
@ -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 <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
|
||||
#include <AnimatedGIF.h>
|
||||
|
||||
/********************************************************************
|
||||
* 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<std::string> 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);
|
||||
|
||||
}
|
15
examples/AnimatedGIFPanel_SD/Readme.md
Normal file
|
@ -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.
|
||||
|
||||
|
BIN
examples/AnimatedGIFPanel_SD/esp32_sdcard.jpg
Normal file
After Width: | Height: | Size: 150 KiB |
132
examples/AnimatedGIFPanel_SD/gif_functions.hpp
Normal file
|
@ -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<File *>(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<File *>(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<File *>(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; x<iWidth; x++) {
|
||||
if (s[x] == pDraw->ucTransparent)
|
||||
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; x<iWidth; x++)
|
||||
dma_display->drawPixel(x, y, usPalette[*s++]); // color 565
|
||||
/*
|
||||
usTemp[x] = usPalette[*s++];
|
||||
|
||||
for (x=0; x<pDraw->iWidth; x++) {
|
||||
dma_display->drawPixel(x, y, usTemp[*s++]); // color 565
|
||||
} */
|
||||
|
||||
}
|
||||
} /* GIFDraw() */
|
BIN
examples/AnimatedGIFPanel_SD/gifs/cartoon.gif
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
examples/AnimatedGIFPanel_SD/gifs/ezgif.com-pacmn.gif
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
examples/AnimatedGIFPanel_SD/gifs/loading.io-64x32px.gif
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
examples/AnimatedGIFPanel_SD/gifs/matrix-spin.gif
Normal file
After Width: | Height: | Size: 171 KiB |
BIN
examples/AnimatedGIFPanel_SD/gifs/parasite1.gif
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
examples/AnimatedGIFPanel_SD/gifs/parasite2.gif
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
examples/AnimatedGIFPanel_SD/gifs/shock-gs.gif
Normal file
After Width: | Height: | Size: 34 KiB |
102
examples/AnimatedGIFPanel_SD/sdcard_functions.hpp
Normal file
|
@ -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();
|
||||
}
|