Hello, I want to control a P3 64x64 matrix through WiFi and Bluetooth. Arduino IDE will be used, only GIF will be displayed in the matrix, I want the gif file to be able to be uploaded through an Android app that I have seen App Inventor, the GIF would be saved in the internal flash of the ESP32 with SPIFFS. I have seen on the web that it can be done via OTA. So how can I implement the above? I see some tutorial but I not have idea to done that, like ESP32 OTA (Over-the-Air) Updates – AsyncElegantOTA using Arduino IDE, Bluetooth connect timeout when coexistance with WiFi (OTA), ESP32 OTA Updates over BLE from a React Web Application, ESP32 Web Bluetooth (BLE): Getting Started Guide The 4MB esp32 will have enough space to do that. I have no problem using an SD card instead of the internal flash but I don't know how to use it
To control the matrix use the esp32 hub75 led matrix panel_dma library and the following code, but I dont have idea how work the code Could you explain to me what each block is for?
// Example sketch which shows how to display a 64x64 animated GIF image stored in FLASH memory
// on a 64x64 LED matrix
//
// Credits: https://github.com/bitbank2/AnimatedGIF/tree/master/examples/ESP32_LEDMatrix_I2S
//
/* INSTRUCTIONS
*
* 1. First Run the 'ESP32 Sketch Data Upload Tool' in Arduino from the 'Tools' Menu.
* - If you don't know what this is or see it as an option, then read this:
* https://github.com/me-no-dev/arduino-esp32fs-plugin
* - This tool will upload the contents of the data/ directory in the sketch folder onto
* the ESP32 itself.
*
* 2. You can drop any animated GIF you want in there, but keep it to the resolution of the
* MATRIX you're displaying to. To resize a gif, use this online website: https://ezgif.com/
*
* 3. Have fun.
*/
#define FILESYSTEM SPIFFS
#include <SPIFFS.h>
#include <AnimatedGIF.h>
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
// ----------------------------
/*
* Below is an is the 'legacy' way of initialising the MatrixPanel_I2S_DMA class.
* i.e. MATRIX_WIDTH and MATRIX_HEIGHT are modified by compile-time directives.
* By default the library assumes a single 64x32 pixel panel is connected.
*
* Refer to the example '2_PatternPlasma' on the new / correct way to setup this library
* for different resolutions / panel chain lengths within the sketch 'setup()'.
*
*/
#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module.
#define PANEL_RES_Y 64 // Number of pixels tall of each INDIVIDUAL panel module.
#define PANEL_CHAIN 1 // Total number of panels chained one to another
//MatrixPanel_I2S_DMA dma_display;
MatrixPanel_I2S_DMA *dma_display = nullptr;
uint16_t myBLACK = dma_display->color565(0, 0, 0);
uint16_t myWHITE = dma_display->color565(255, 255, 255);
uint16_t myRED = dma_display->color565(255, 0, 0);
uint16_t myGREEN = dma_display->color565(0, 255, 0);
uint16_t myBLUE = dma_display->color565(0, 0, 255);
AnimatedGIF gif;
File f;
int x_offset, y_offset;
// Draw a line of image directly on the LED Matrix
void GIFDraw(GIFDRAW *pDraw)
{
uint8_t *s;
uint16_t *d, *usPalette, usTemp[320];
int x, y, iWidth;
iWidth = pDraw->iWidth;
if (iWidth > MATRIX_WIDTH)
iWidth = MATRIX_WIDTH;
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 + pDraw->iWidth;
x = 0;
iCount = 0; // count non-transparent pixels
while(x < pDraw->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 // does not have transparency
{
s = pDraw->pPixels;
// Translate the 8-bit pixels through the RGB565 palette (already byte reversed)
for (x=0; x<pDraw->iWidth; x++)
{
dma_display->drawPixel(x, y, usPalette[*s++]); // color 565
}
}
} /* GIFDraw() */
void * GIFOpenFile(const char *fname, int32_t *pSize)
{
Serial.print("Playing gif: ");
Serial.println(fname);
f = FILESYSTEM.open(fname);
if (f)
{
*pSize = f.size();
return (void *)&f;
}
return NULL;
} /* GIFOpenFile() */
void GIFCloseFile(void *pHandle)
{
File *f = static_cast<File *>(pHandle);
if (f != NULL)
f->close();
} /* GIFCloseFile() */
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;
} /* GIFReadFile() */
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;
// Serial.printf("Seek time = %d us\n", i);
return pFile->iPos;
} /* GIFSeekFile() */
unsigned long start_tick = 0;
void ShowGIF(char *name)
{
start_tick = millis();
if (gif.open(name, GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile, GIFDraw))
{
x_offset = (MATRIX_WIDTH - gif.getCanvasWidth())/2;
if (x_offset < 0) x_offset = 0;
y_offset = (MATRIX_HEIGHT - gif.getCanvasHeight())/2;
if (y_offset < 0) y_offset = 0;
Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight());
Serial.flush();
while (gif.playFrame(true, NULL))
{
if ( (millis() - start_tick) > 8000) { // we'll get bored after about 8 seconds of the same looping gif
break;
}
}
gif.close();
}
} /* ShowGIF() */
/************************* Arduino Sketch Setup and Loop() *******************************/
void setup() {
Serial.begin(115200);
delay(1000);
HUB75_I2S_CFG mxconfig(
PANEL_RES_X, // module width
PANEL_RES_Y, // module height
PANEL_CHAIN // Chain length
);
mxconfig.gpio.e = 32;
mxconfig.clkphase = false;
//mxconfig.driver = HUB75_I2S_CFG::FM6126A;
// Display Setup
dma_display = new MatrixPanel_I2S_DMA(mxconfig);
dma_display->begin();
dma_display->setBrightness8(128); //0-255
dma_display->clearScreen();
dma_display->fillScreen(myWHITE);
Serial.println("Starting AnimatedGIFs Sketch");
// Start filesystem
Serial.println(" * Loading SPIFFS");
if(!SPIFFS.begin()){
Serial.println("SPIFFS Mount Failed");
}
dma_display->begin();
/* all other pixel drawing functions can only be called after .begin() */
dma_display->fillScreen(dma_display->color565(0, 0, 0));
gif.begin(LITTLE_ENDIAN_PIXELS);
}
String gifDir = "/gifs"; // play all GIFs in this directory on the SD card
char filePath[256] = { 0 };
File root, gifFile;
void loop()
{
while (1) // run forever
{
root = FILESYSTEM.open(gifDir);
if (root)
{
gifFile = root.openNextFile();
while (gifFile)
{
if (!gifFile.isDirectory()) // play it
{
// C-strings... urghh...
memset(filePath, 0x0, sizeof(filePath));
strcpy(filePath, gifFile.path());
// Show it.
ShowGIF(filePath);
}
gifFile.close();
gifFile = root.openNextFile();
}
root.close();
} // root
delay(1000); // pause before restarting
} // while
}