Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

83+ FPS Update Without DMA #3650

Closed
KaliAssistant opened this issue Feb 7, 2025 · 1 comment
Closed

83+ FPS Update Without DMA #3650

KaliAssistant opened this issue Feb 7, 2025 · 1 comment

Comments

@KaliAssistant
Copy link

I am using SPI_FREQUENCY 80E6 and SPI_READ_FREQUENCY 60E6 on a recently purchased Waveshare ESP32S3-GEEK board, which has an ST7789 240x135 RGB565 TFT LCD.

By leveraging FreeRTOS, I created a task that continuously refreshes the TFT LCD at a stable 85 Hz. Due to vTask timing precision being limited to 1 ms, the actual refresh rate becomes 83.33 Hz.

Additionally, I implemented an SD card streaming system that reads an RGB565 video file at 60 FPS. The video was generated using OpenCV, where I converted each frame to PNG, then transformed them into RGB565 format and merged them into a single video.bin file.

Surprisingly, I am able to consistently achieve this high frame rate without using DMA. I am unsure why the performance remains so stable.

I will include code snippets and a video demonstration for reference.

Would love to hear thoughts or explanations on this!

IMG_5774-2.mp4

code

Setup303_WaveShare_ESP32S3_GEEK.h

// ST7789 135 x 240 display

#define USER_SETUP_ID 303

#ifdef ILI9341_DRIVER
#undef ILI9341_DRIVER
#endif
#define ST7789_DRIVER // Configure all registers

#define TFT_WIDTH  135
#define TFT_HEIGHT 240

#define CGRAM_OFFSET      // Library will add offsets required

#define TFT_RGB_ORDER TFT_BGR  // Colour order Blue-Green-Red

#define TFT_INVERSION_ON

// Generic ESP32 setup
#define TFT_MOSI 11
#define TFT_SCLK 12
#define TFT_CS    10
#define TFT_DC    8
#define TFT_RST   9
//#define TFT_BL    7


#define LOAD_GLCD   // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH
#define LOAD_FONT2  // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters
#define LOAD_FONT4  // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters
#define LOAD_FONT6  // Font 6. Large 48 pixel font, needs ~2666 bytes in FLASH, only characters 1234567890:-.apm
#define LOAD_FONT7  // Font 7. 7 segment 48 pixel font, needs ~2438 bytes in FLASH, only characters 1234567890:.
#define LOAD_FONT8  // Font 8. Large 75 pixel font needs ~3256 bytes in FLASH, only characters 1234567890:-.
//#define LOAD_FONT8N // Font 8. Alternative to Font 8 above, slightly narrower, so 3 digits fit a 160 pixel TFT
#define LOAD_GFXFF  // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts

#define SMOOTH_FONT

#define USE_HSPI_PORT
//#define ESP32_DMA

 //#define SPI_FREQUENCY  27000000
#define SPI_FREQUENCY  (int)80E6
#define SPI_READ_FREQUENCY  (int)60E6

libfb.cpp


//#define USE_DMA

// .........

void libfb::fb_xinit()
{
    if (!fb_is_init)
    {

        xTaskCreatePinnedToCore([](void *param) { auto *fbInstance = static_cast<libfb*>(param); fbInstance->_fbUpdate(param);}, "FB_UPDATE", 8192, this, 1, &_fb_update_handel, 0);
        fb_is_init=true;
    }
    else
    {
        log_e("Fr4M3 BuFf3R 1s 4lR3aDy 1N1t !");
    }

}

// ........

void libfb::fb_disp_image(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t* ptr, uint16_t* data)
{
    if (x < 0 || y < 0 || (x + w) > TFT__WIDTH || (y + h) > TFT__HEIHT) 
    {
        return; // Exit if the area is out of bounds
    }
    int32_t data_pos = 0;
    for (int32_t i = 0; i < h; i++) 
    {
        for (int32_t j = 0; j < w; j++) 
        {
            // Calculate the 1D position in the image_data buffer
            int32_t pos = (y + i) * TFT__WIDTH + (x + j);
            ptr[pos] = data[data_pos++];
        }
    }
}

//......

void libfb::_fbUpdate(void* parameter)
{
    if (this == nullptr) 
    {
        log_e("this->nullptr!!!");
        return;
    }
    int64_t prevTime = esp_timer_get_time();  // Get initial timestamp (µs)
    const int TARGET_FPS = 85;  // Target frame rate
    int64_t FRAME_TIME_US = (1000000.0 / TARGET_FPS);  // Target frame time (µs)
    for(;;)
    {
        int64_t currentTime = esp_timer_get_time();
        int64_t elapsedTime = currentTime - prevTime;
        if (elapsedTime >= FRAME_TIME_US)
        {
            prevTime = currentTime;
            fb_update_frame_time = elapsedTime;
            if (rel_frameBuffer != nullptr && frameBuffer != nullptr) 
            {
               
                #ifdef USE_DMA
                uint32_t frameStart = millis();  // Start frame timer
                LCD.startWrite();
                //LCD.dmaWait();
                //LCD.setSwapBytes(true);
                LCD.pushImageDMA(0, 0, 240, 135, frameBuffer); // Initiate DMA - blocking only if last DMA is not complete
                LCD.endWrite();
                uint32_t frameDuration = millis() - frameStart;
                frame_delay_time = frameDuration;
                // The DMA transfer of image block to the TFT is now in progress...
                #else
                // Non-DMA blocking alternative
                uint32_t frameStart = millis();  // Start frame timer
                LCD.pushImage(0, 0, 240, 135, frameBuffer);  // Blocking, so only returns when image block is drawn
                uint32_t frameDuration = millis() - frameStart;
                frame_delay_time = frameDuration;
                #endif
            }
        }
        vTaskDelay(1);
    }
}

main.cpp

TaskHandle_t TEST_FB_VIDEO_TESK1_Handle = NULL;
// ......
 void setup() {
    fb.fb_xinit();
    xTaskCreate(TEST_FB_VIDEO_TESK1, "TEST_TASK_1", 66600, NULL, 1, &TEST_FB_VIDEO_TESK1_Handle);

//....
}

void TEST_FB_VIDEO_TESK1(void* parameter)
{
  int64_t prevTime = esp_timer_get_time();  // Get initial timestamp (µs)
  const int TARGET_FPS = 60;  // Target frame rate
  int64_t FRAME_TIME_US = (1000 / TARGET_FPS)*1000;  // Target frame time (µs)
  //const int TARGET_FPS = 60;  // Target frame rate
  //const int FRAME_TIME_MS = 1000 / TARGET_FPS;  // Target frame time (ms)

  // Allocate buffer once to avoid malloc/free overhead
  
  uint16_t *buffer = (uint16_t*) heap_caps_malloc(TFT__WIDTH * TFT__HEIHT * sizeof(uint16_t), MALLOC_CAP_SPIRAM);
  if (!buffer) {
    SerialUSB.println("Memory allocation failed!");
    vTaskDelete(NULL);
    return;
  }
  for(;;)
  {
    
    failSD:
    // Wait for SD card insertion
    HotPlugSD.checkCardStatus();
    while (!HotPlugSD.isCardInserted()) {
      SerialUSB.println("SD card missing. Halting...");
      vTaskDelay(pdMS_TO_TICKS(500));  // Check every 500ms
    }

    //uint32_t prevTime = millis();  // Get initial timestamp (µs)

    fs::File video_file = HotPlugSD.open("/video.bin", "r");
    if (!video_file) 
    {
      SerialUSB.println("Failed to open video file. Retrying...");
      vTaskDelay(pdMS_TO_TICKS(500));  // Retry after delay
      continue;
    }
    int i=0;
      
    while (video_file.available())
    {
      /* code */
      int64_t currentTime = esp_timer_get_time();
      int64_t elapsedTime = currentTime - prevTime;
      if (elapsedTime >= FRAME_TIME_US)
      {
        prevTime = currentTime;
        int bytesRead = video_file.read((uint8_t *)buffer, TFT__WIDTH * TFT__HEIHT * sizeof(uint16_t));  // Fast read
        if (bytesRead != TFT__WIDTH * TFT__HEIHT * sizeof(uint16_t)) goto failSD;
        fb.fb_disp_image(0, 0, 240, 135, fb.frameBuffer, buffer);  // Display frame

        // Measure and print actual FPS every 10 frames
        if (i % 10 == 0) 
        {
          SerialUSB.printf("Actual SOFT FPS: %lf\t TFTLCD PUSH TIME: %u ms\t TFTLCD UPDATE TIME: %lld us\t TFTLCD ACTUAL HZ: %lf\n", 1000000.0 / elapsedTime, fb.frame_delay_time, fb.fb_update_frame_time, 1000000.0 / fb.fb_update_frame_time);
        }

        // Check if SD card was removed mid-playback
        if (!HotPlugSD.isCardInserted()) {
          SerialUSB.println("SD card removed. Halting playback.");
          video_file.close();
          while (!HotPlugSD.isCardInserted()) 
          {
            SerialUSB.println("Waiting for SD card...");
            vTaskDelay(pdMS_TO_TICKS(500));
          }

          if (!reinitializeSD()) 
          {
            SerialUSB.println("Failed to reinitialize SD card.");
            continue;  // Restart loop
          }
        }
        i++;

      }
      vTaskDelay(1);
        //ets_delay_us(1);
    }
    video_file.close();
    vTaskDelay(pdMS_TO_TICKS(10)); 
  }
  heap_caps_free(buffer);
  
  vTaskDelete(NULL);
}

@KaliAssistant
Copy link
Author

move to -> discussions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant