bitbank2 / AnimatedGIF

An optimized GIF decoder suitable for microcontrollers and PCs
Apache License 2.0
359 stars 51 forks source link

Example TFT_eSPI_memory crash on ESP8266 #47

Closed ifengchao closed 2 years ago

ifengchao commented 2 years ago

Hi, nice work first !

I think it crashed here in git.playFrame(true,NULL);

Changes i made: gif.begin(LITTLE_ENDIAN_PIXELS);// little endian for esp8266, board esp-12e void GIFDraw(GIFDRAW *pDraw) copyed from GIFDRAW.ino

define GIF_IMAGE ucBadgers // No DMA 63 fps, DMA: 71fps // tested this gif image

TFT Screen is st7789 240x240 size.

Logs printed on serial console: Successfully opened GIF; Canvas size = 160 x 120 tft size = 240 x 240

--------------- CUT HERE FOR EXCEPTION DECODER --------------- ...

Any help will be appreciated, thanks.

bitbank2 commented 2 years ago

I need a bit more detail to see why it's crashing. I can tell you already that the 8266 is troublesome because of the way it uses RAM to execute code and can have an acute lack of RAM at inopportune times. If you could, I would try the same code on something with more RAM, like the ESP32. If the ESP8266 is all you've got, then try by a process of elimination such as having an empty GIFDraw() callback as your first test. If that works, then you know it's in your code that the problem lies.

ifengchao commented 2 years ago

Thanks for the quick reply. Here is the main.cpp in my PlatformIO project.

// TFT_eSPI_memory // // Example sketch which shows how to display an // animated GIF image stored in FLASH memory // // written by Larry Bank // bitbank@pobox.com // // Adapted by Bodmer for the TFT_eSPI Arduino library: // https://github.com/Bodmer/TFT_eSPI // // To display a GIF from memory, a single callback function // must be provided - GIFDRAW // This function is called after each scan line is decoded // and is passed the 8-bit pixels, RGB565 palette and info // about how and where to display the line. The palette entries // can be in little-endian or big-endian order; this is specified // in the begin() method. // // The AnimatedGIF class doesn't allocate or free any memory, but the // instance data occupies about 22.5K of RAM.

//#define USE_DMA // ESP32 ~1.25x single frame rendering performance boost for badgers.h // Note: Do not use SPI DMA if reading GIF images from SPI SD card on same bus as TFT

define NORMAL_SPEED // Comment out for rame rate for render speed test

// Load GIF library

include

AnimatedGIF gif;

// Example AnimatedGIF library images

include "../test_images/badgers.h"

//#include "../test_images/homer.h" //#include "../test_images/homer_tiny.h" //#include "../test_images/pattern.h"

// ESP32 40MHz SPI single frame rendering performance // Note: no DMA performance gain on smaller images or transparent pixel GIFs

define GIF_IMAGE ucBadgers // No DMA 63 fps, DMA: 71fps

//#define GIF_IMAGE ucHomer // No DMA 162 fps, DMA: 141 fps //#define GIF_IMAGE homer_tiny // No DMA 564 fps, DMA: 481 fps //#define GIF_IMAGE ucPattern // No DMA 90 fps, DMA: 78 fps

include

include

TFT_eSPI tft = TFT_eSPI();

void setup() { Serial.begin(115200);

tft.begin();

ifdef USE_DMA

tft.initDMA();

endif

tft.setRotation(1); tft.fillScreen(TFT_BLACK);

gif.begin(LITTLE_ENDIAN_PIXELS); }

// GIFDraw is called by AnimatedGIF library frame to screen

define DISPLAY_WIDTH tft.width()

define DISPLAY_HEIGHT tft.height()

define BUFFER_SIZE 64 // Optimum is >= GIF width or integral division of width

ifdef USE_DMA

uint16_t usTemp[2][BUFFER_SIZE]; // Global to support DMA use

else

uint16_t usTemp[1][BUFFER_SIZE]; // Global to support DMA use

endif

bool dmaBuf = 0;

// Draw a line of image directly on the LCD void GIFDraw(GIFDRAW pDraw) { return ; uint8_t s; uint16_t d, usPalette; int x, y, iWidth, iCount;

// Displ;ay bounds chech and cropping iWidth = pDraw->iWidth; if (iWidth + pDraw->iX > DISPLAY_WIDTH) iWidth = DISPLAY_WIDTH - pDraw->iX; usPalette = pDraw->pPalette; y = pDraw->iY + pDraw->y; // current line if (y >= DISPLAY_HEIGHT || pDraw->iX >= DISPLAY_WIDTH || iWidth < 1) return;

// Old image disposal 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; pEnd = s + iWidth; x = 0; iCount = 0; // count non-transparent pixels while (x < iWidth) { c = ucTransparent - 1; d = &usTemp[0][0]; while (c != ucTransparent && s < pEnd && iCount < BUFFER_SIZE ) { 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? { // DMA would degrtade performance here due to short line segments tft.setAddrWindow(pDraw->iX + x, y, iCount, 1); tft.pushPixels(usTemp, iCount); x += iCount; iCount = 0; } // no, look for a run of transparent pixels c = ucTransparent; while (c == ucTransparent && s < pEnd) { c = s++; if (c == ucTransparent) x++; else s--; } } } else { s = pDraw->pPixels;

// Unroll the first pass to boost DMA performance
// Translate the 8-bit pixels through the RGB565 palette (already byte reversed)
if (iWidth <= BUFFER_SIZE)
  for (iCount = 0; iCount < iWidth; iCount++) usTemp[dmaBuf][iCount] = usPalette[*s++];
else
  for (iCount = 0; iCount < BUFFER_SIZE; iCount++) usTemp[dmaBuf][iCount] = usPalette[*s++];

ifdef USE_DMA // 71.6 fps (ST7796 84.5 fps)

tft.dmaWait();
tft.setAddrWindow(pDraw->iX, y, iWidth, 1);
tft.pushPixelsDMA(&usTemp[dmaBuf][0], iCount);
dmaBuf = !dmaBuf;

else // 57.0 fps

tft.setAddrWindow(pDraw->iX, y, iWidth, 1);
tft.pushPixels(&usTemp[0][0], iCount);

endif

iWidth -= iCount;
// Loop if pixel buffer smaller than width
while (iWidth > 0)
{
  // Translate the 8-bit pixels through the RGB565 palette (already byte reversed)
  if (iWidth <= BUFFER_SIZE)
    for (iCount = 0; iCount < iWidth; iCount++) usTemp[dmaBuf][iCount] = usPalette[*s++];
  else
    for (iCount = 0; iCount < BUFFER_SIZE; iCount++) usTemp[dmaBuf][iCount] = usPalette[*s++];

ifdef USE_DMA

  tft.dmaWait();
  tft.pushPixelsDMA(&usTemp[dmaBuf][0], iCount);
  dmaBuf = !dmaBuf;

else

  tft.pushPixels(&usTemp[0][0], iCount);

endif

  iWidth -= iCount;
}

} } / GIFDraw() /

ifdef NORMAL_SPEED // Render at rate that is GIF controlled

void loop() { // Put your main code here, to run repeatedly: if (gif.open((uint8_t *)GIF_IMAGE, sizeof(GIF_IMAGE), GIFDraw)) { Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight()); tft.startWrite(); // The TFT chip slect is locked low Serial.printf(" tft size = %d x %d\n",tft.width(), tft.height()); while (gif.playFrame(true, NULL)) { Serial.printf(" yield\n"); yield(); } gif.close(); tft.endWrite(); // Release TFT chip select for other SPI devices } }

else // Test maximum rendering speed

void loop() { long lTime = micros(); int iFrames = 0;

if (gif.open((uint8_t *)GIF_IMAGE, sizeof(GIF_IMAGE), GIFDraw)) { tft.startWrite(); // For DMA the TFT chip slect is locked low while (gif.playFrame(false, NULL)) { // Each loop renders one frame iFrames++; yield(); } gif.close(); tft.endWrite(); // Release TFT chip select for other SPI devices lTime = micros() - lTime; Serial.print(iFrames / (lTime / 1000000.0)); Serial.println(" fps"); } }

endif

Empty GIFDraw() will still crash, but if i comment "while (gif.playFrame(false, NULL))" in loop(),crash stopped.

bitbank2 commented 2 years ago

I don't use platformio, so I don't know if that's affecting things, but I'm still not seeing anything that could help me diagnose the problem. If you can zip up the whole project including the GIF files you're using, I can take a look this weekend. Email it to bitbank@pobox.com

ifengchao commented 2 years ago

I don't use platformio, so I don't know if that's affecting things, but I'm still not seeing anything that could help me diagnose the problem. If you can zip up the whole project including the GIF files you're using, I can take a look this weekend. Email it to bitbank@pobox.com

Actually main.cpp could be renamed to main.ino (almost the same with your example code in TFT_eSPI_memory.ino) and open in Arduino IDE, the gif image i'm using is defined in #include "../test_images/badgers.h". I tried in Arduino, but also crashed. Do you happen to have arduino enviroment and an esp8266 board, it's easy to reproduce the crash.

ifengchao commented 2 years ago

I hope this will not take you too much time, if it could not be reproduced in your enviroment, i may move to esp32 few days later, need to do some screen soldering work. :)

ifengchao commented 2 years ago

I don't use platformio, so I don't know if that's affecting things, but I'm still not seeing anything that could help me diagnose the problem. If you can zip up the whole project including the GIF files you're using, I can take a look this weekend. Email it to bitbank@pobox.com

Another thing i forgot to clarify is i made some changes to User_Setup.h in TFT_eSPI library to make the st7789 spi screen work, and it worked with test code without AnimatedGif library. So i think this part may not be the reason caused crash.

bitbank2 commented 2 years ago

I do have an 8266 in my collection of parts; I'll give it a try later today. FYI - I have investigated these kinds of problems before across various github repos that I've released and 99.9999999% of the time, the problem is not with my code. In this case, my best guess is that you're running out of RAM somewhere; maybe even a stack overrun.

bitbank2 commented 2 years ago

Found the issue... ESP8266 is a Harvard architecture and my generic open function treats the source memory like RAM. In my JPEG decoder I added a specific function to deal with FLASH reads from a different address space. A quick fix for you is to edit gif.inl and change the memcpy() in readMem() to be memcpy_P(). I'll have to add a new function called "openFLASH()" to deal with the 8266 specifically.

ifengchao commented 2 years ago

Crash fix confirmed, now it can display small smooth gif animations. Great job, i'll do more test. Thanks Larry IMG_20211218_165317 .

bitbank2 commented 2 years ago

I did a new release (1.4.5) which adds the openFLASH method