0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

71歳の... えーっ、「RGB TFT-Lcd」では画像表示できないの? 良かった、できました。

Posted at

070_Jpg2Bmp.jpg

 最近購入した「7"ディスプレイ付きESP32-S3開発ボード」
https://qiita.com/Mobu_Kyoto/items/11d7a5eb328af64a79a8
を試しています。ふと、JPGやBMP画像を表示させようとしたら全然できません。組込関数を探してみたら、無い?そう無かったのです。これまで、

Arduino IDE
ESP32(S3)
LovyanGFX
16ビットパラレル接続ディスプレイモジュール

の組み合わせでは「tft.drawJpg」「lcd.drawBmp」関数を用いて簡単にできてたのですが。せっかくの大画面がもったいない。落胆しつつも、暇にまかせてネットを検索しました。やっと見つけました。
 JPGファイルは

 BMPファイルは

を参考にさせていただき出来ました。BMPについては他にもありましたが、「ペイント」で作成したBMP本来の色が出ず悩みました。どこが違うかはわかりませんでしたが、こちらの方法で表示でき良しとします。
 環境など細かなことは、今記事頭の投稿をご覧ください。Arduino IDE2.3.6でのスケッチは以下です。インクルードしているMatouch7.hは「LovyanGFX.hpp」の個別設定ファイルです。

Matouch7.h
#define LGFX_USE_V1
#include <LovyanGFX.hpp>
#include <Arduino_GFX_Library.h>
#include <lgfx/v1/platforms/esp32s3/Panel_RGB.hpp>
#include <lgfx/v1/platforms/esp32s3/Bus_RGB.hpp>
#include <driver/i2c.h>

class LGFX : public lgfx::LGFX_Device
{
public:
  lgfx::Bus_RGB     _bus_instance;
  lgfx::Panel_RGB   _panel_instance;

  LGFX(void) {
    {
      auto cfg = _panel_instance.config();
      cfg.memory_width  = 800;
      cfg.memory_height = 480;
      cfg.panel_width   = 800;
      cfg.panel_height  = 480;
      cfg.offset_x      = 0;
      cfg.offset_y      = 0;
      _panel_instance.config(cfg);
    }
    {
      auto cfg = _panel_instance.config_detail();
      cfg.use_psram = 1;
      _panel_instance.config_detail(cfg);
    }

    // Configure the RGB bus.
    {
      auto cfg = _bus_instance.config();

      cfg.panel = &_panel_instance;

      // Configure data pins.
      cfg.pin_d0  = GPIO_NUM_15;  // B0
      cfg.pin_d1  = GPIO_NUM_7;  // B1
      cfg.pin_d2  = GPIO_NUM_6; // B2
      cfg.pin_d3  = GPIO_NUM_5;  // B3
      cfg.pin_d4  = GPIO_NUM_4;  // B4
      
      cfg.pin_d5  = GPIO_NUM_9;  // G0
      cfg.pin_d6  = GPIO_NUM_46;  // G1
      cfg.pin_d7  = GPIO_NUM_3;  // G2
      cfg.pin_d8  = GPIO_NUM_8;  // G3
      cfg.pin_d9  = GPIO_NUM_16; // G4
      cfg.pin_d10 = GPIO_NUM_1; // G5
      
      cfg.pin_d11 = GPIO_NUM_14; // R0
      cfg.pin_d12 = GPIO_NUM_21; // R1
      cfg.pin_d13 = GPIO_NUM_47; // R2
      cfg.pin_d14 = GPIO_NUM_48; // R3
      cfg.pin_d15 = GPIO_NUM_45; // R4

      // Configure sync and clock pins.
      cfg.pin_henable = GPIO_NUM_41;
      cfg.pin_vsync   = GPIO_NUM_40;
      cfg.pin_hsync   = GPIO_NUM_39;
      cfg.pin_pclk    = GPIO_NUM_42;
      cfg.freq_write  = 16000000;

      // Configure timing parameters for horizontal and vertical sync.
      cfg.hsync_polarity    = 0;
      cfg.hsync_front_porch = 40;//80;//20;//
      cfg.hsync_pulse_width = 48;//4;//30;//
      cfg.hsync_back_porch  = 40;//16;//
      
      cfg.vsync_polarity    = 0;
      cfg.vsync_front_porch =  8;//22;//
      cfg.vsync_pulse_width = 31;//13;//
      cfg.vsync_back_porch  = 13;//10;//

      // Configure polarity for clock and data transmission.
      cfg.pclk_active_neg   = 1;
      cfg.de_idle_high      = 0;
      cfg.pclk_idle_high    = 0;//1;

      // Apply configuration to the RGB bus instance.
      _bus_instance.config(cfg);
    }
    // Set the RGB bus and panel instances.
    _panel_instance.setBus(&_bus_instance);
/*
    {
      auto cfg = _light_instance.config();
      cfg.pin_bl = GPIO_NUM_2;
      _light_instance.config(cfg);
    }
    _panel_instance.light(&_light_instance);
*/
    setPanel(&_panel_instance);
  }
};

なお、パネルの明るさは変更可能ですが、常時210に固定しています。

070_Jpeg2Bmp.ino
#include "Matouch7.h"
#include "SD.h"         // SD Card library, usually part of the standard install
#include <rom/tjpgd.h>  //TJpgDec (C)ChaN, 2012 in Arduino Library

LGFX lcd;
#define TFT_BL 2

#define cs 10  // SD Card chip select
#define SPI_MOSI 11
#define SPI_MISO 13
#define SPI_SCK 12

/* User defined device identifier */
typedef struct {
  const void *fp; /* File pointer for input function */
  BYTE *fbuf;     /* Pointer to the frame buffer for output function */
  UINT wfbuf;     /* Width of the frame buffer [pix] */
} IODEV;

void *work;  /* Pointer to the decompressor work area */
JDEC jdec;   /* Decompression object */
JRESULT res; /* Result code of TJpgDec API */
IODEV devid; /* User defined device identifier */

void setup() {
  Serial.begin(115200);
  analogWrite(TFT_BL, 210);  // TFT on
  lcd.begin();
  lcd.setRotation(0);
  lcd.fillScreen(NAVY);

  SD.begin(cs, SPI, 24000000, "/sd");

  lcd.setColorDepth(16);
  Disp_Jpg(180, 0, "/IMG_0720.jpg");
  Disp_Jpg(210, 30, "/abi.jpg");
  bmpDraw("/PIC/NYUTA.bmp", 400, 350);
}

void loop() { }

//**********************************************************
void Disp_Jpg(uint16_t x, uint16_t y, const char *path) {
  File file = SD.open(path);
  Serial.printf("%s file size = %d\r\n", path, file.size());
  Serial.printf("1.Free Heap Size=%d\r\n", esp_get_free_heap_size());
  devid.fp = &file;

  /* Allocate a work area for TJpgDec */
  work = malloc(3100);
  /* Prepare to decompress */
  res = jd_prepare(&jdec, in_func, work, 3100, &devid);
  if (res == JDR_OK) {
    /* Ready to dcompress. Image info is available here. */
    Serial.printf("Image dimensions: %d x %d. %u bytes used.\r\n", jdec.width, jdec.height, 3100 - jdec.sz_pool);

    devid.fbuf = (BYTE *)malloc(2 * jdec.width * jdec.height);  //RGB565
    devid.wfbuf = jdec.width;

    res = jd_decomp(&jdec, out_func, 0); /* Start to decompress with 1/1 scaling */
    if (res == JDR_OK) {
      /* Decompression succeeded. You have the decompressed image in the frame buffer here. */
      Serial.printf("\rOK  \n");
    } else {
      Serial.printf("Failed to decompress: rc=%d\n", res);
    }
  } else {
    Serial.printf("Failed to prepare: rc=%d\n", res);
  }

  free(work); /* Discard work area */
  Serial.printf("2.Free Heap Size=%d\r\n", esp_get_free_heap_size());
  delay(500);  //これはシリアルモニタへ正常に表示させるために必要。

  uint16_t width_bmp = jdec.width;
  uint16_t height_bmp = jdec.height;
  uint16_t X0 = x, Y0 = y;
  lcd.pushImage((uint16_t)X0, (uint16_t)Y0, (uint16_t)width_bmp, (uint16_t)height_bmp, (uint16_t *)devid.fbuf);

  if (devid.fbuf) {
    free(devid.fbuf);
    devid.fbuf = NULL;
    Serial.println("Free devid.fbuf OK!");
  } else {
    Serial.println("Failed free devid.fbuf.");
  }
  Serial.printf("3.Free Heap Size=%d\r\n", esp_get_free_heap_size());
}
UINT in_func(JDEC *jd, BYTE *buff, UINT nbyte) {
  UINT ret = 0;
  IODEV *dev = (IODEV *)jd->device; /* Device identifier for the session (5th argument of jd_prepare function) */
  File *f2 = (File *)dev->fp;
  if (buff) {
    return (UINT)f2->read(buff, nbyte);
  } else {
    /* Remove bytes from input stream */
    if (f2->seek(nbyte, SeekCur)) {  //seek mode:SeekCur=1(FS.h)
      ret = nbyte;
    } else {
      ret = 0;
    }
  }
  return ret;
}
UINT out_func(JDEC *jd, void *bitmap, JRECT *rect) {
  IODEV *dev = (IODEV *)jd->device;
  uint8_t *src, *dst;
  uint16_t x, y, bwd;

  if (rect->left == 0) {
    Serial.printf("%lu%%\r\n", (rect->top << jd->scale) * 100UL / jd->height);
  }
  src = (uint8_t *)bitmap;
  //use RGB565
  dst = dev->fbuf + 2 * (rect->top * dev->wfbuf + rect->left);
  //bws = 2 * (rect->right - rect->left + 1);
  bwd = 2 * dev->wfbuf;
  uint8_t msb, lsb, tmp1, tmp2;
  int16_t xcnt;
  for (y = rect->top; y <= rect->bottom; y++) {
    xcnt = 0;
    for (x = rect->left; x <= rect->right; x++) {
      msb = *(src++) & 0b11111000;
      tmp1 = *(src++) & 0b11111100;
      msb = msb | (tmp1 >> 5);
      tmp2 = *(src++) & 0b11111000;
      lsb = (tmp1 << 3) | (tmp2 >> 3);
      *(dst + xcnt++) = msb;
      *(dst + xcnt++) = lsb;
    }
    dst += bwd;
  }
  return 1; /* Continue to decompress */
}
//-------------------------------------------------------
// BMPファイル取得,LCD表示(SDカード)
#define BUFFPIXEL 20
void bmpDraw(char *filename, uint16_t x, uint16_t y) {
  File bmpFile;
  int bmpWidth, bmpHeight;             // W+H in pixels
  uint8_t bmpDepth;                    // Bit depth (currently must be 24)
  uint32_t bmpImageoffset;             // Start of image data in file
  uint32_t rowSize;                    // Not always = bmpWidth; may have padding
  uint8_t sdbuffer[3 * BUFFPIXEL];     // pixel buffer (R+G+B per pixel)
  uint8_t buffidx = sizeof(sdbuffer);  // Current position in sdbuffer
  boolean goodBmp = false;             // Set to true on valid header parse
  boolean flip = true;                 // BMP is stored bottom-to-top
  int w, h, row, col;
  uint8_t r, g, b;
  uint32_t pos = 0, startTime = millis();

  if ((x >= lcd.width()) || (y >= lcd.height())) return;

  Serial.println();
  Serial.print(F("Loading image '"));
  Serial.println(filename);

  // Open requested file on SD card
  if ((bmpFile = SD.open(filename)) == NULL) {
    Serial.print(F("File not found"));
    return;
  }

  // Parse BMP header
  if (read16(bmpFile) == 0x4D42) {  // BMP signature
    Serial.print(F("File size: "));
    Serial.println(read32(bmpFile));
    (void)read32(bmpFile);             // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile);  // Start of image data
    Serial.print(F("Image Offset: "));
    Serial.println(bmpImageoffset, DEC);
    // Read DIB header
    Serial.print(F("Header size: "));
    Serial.println(read32(bmpFile));
    bmpWidth = read32(bmpFile);
    bmpHeight = read32(bmpFile);
    if (read16(bmpFile) == 1) {    // # planes -- must be '1'
      bmpDepth = read16(bmpFile);  // bits per pixel
      Serial.print(F("Bit Depth: "));
      Serial.println(bmpDepth);
      if ((bmpDepth == 24) && (read32(bmpFile) == 0)) {  // 0 = uncompressed
        goodBmp = true;                                  //Supported BMP format -- proceed!
        w = bmpWidth;
        h = bmpHeight;
        Serial.print(F("Image size: "));
        Serial.print(w);
        Serial.print('x');
        Serial.println(h);

        // BMP rows are padded (if needed) to 4-byte boundary
        rowSize = (bmpWidth * 3 + 3) & ~3;

        // If bmpHeight is negative, image is in top-down order.
        // This is not canon but has been observed in the wild.
        if (bmpHeight < 0) {
          h = -h;
          flip = false;
        }

        // Crop area to be loaded.
        if ((x + w - 1) >= lcd.width()) w = lcd.width() - x;  // Cut off
        if ((y + h - 1) >= lcd.height()) h = lcd.height() - y;
        // Set TFT address window to clipped image bounds
        lcd.startWrite();
        lcd.setAddrWindow(x, y, w, h);

        Serial.print(F("sizeof(sdbuffer): "));
        Serial.println(sizeof(sdbuffer));

        for (row = 0; row < h; row++) {  // For each scanline...
          // Seek to start of scan line.  It might seem labor-
          // intensive to be doing this on every line, but this
          // method covers a lot of gritty details like cropping
          // and scanline padding.  Also, the seek only takes
          // place if the file position actually needs to change
          // (avoids a lot of cluster math in SD library).
          if (flip)  // Bitmap is stored bottom-to-top order (normal BMP)
            pos = bmpImageoffset + (h - 1 - row) * rowSize;
          else  // Bitmap is stored top-to-bottom
            pos = bmpImageoffset + row * rowSize;

          if (bmpFile.position() != pos) {  // Need seek?
            lcd.endWrite();
            bmpFile.seek(pos);
            buffidx = sizeof(sdbuffer);  // Force buffer reload
            lcd.startWrite();
          }

          for (col = 0; col < w; col++) {  // For each pixel...
            // Time to read more pixel data?
            if (buffidx >= sizeof(sdbuffer)) {  // Indeed
              lcd.endWrite();
              bmpFile.read(sdbuffer, sizeof(sdbuffer));
              buffidx = 0;  // Set index to beginning
              lcd.startWrite();
            }

            // Convert pixel from BMP to TFT format, push to display
            b = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            r = sdbuffer[buffidx++];
            lcd.pushColor(lcd.color565(r, g, b));
          }  // end pixel
        }    // end scanline
        lcd.endWrite();
        Serial.print(F("Loaded in "));
        Serial.print(millis() - startTime);
        Serial.println(" ms");
      }  // end goodBmp
    }
  }
  bmpFile.close();
  if (!goodBmp) Serial.println(F("BMP format not recognized."));
}
// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.
uint16_t read16(File &f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read();  // LSB
  ((uint8_t *)&result)[1] = f.read();  // MSB
  return result;
}
uint32_t read32(File &f) {
  uint32_t result;
  ((uint8_t *)&result)[0] = f.read();  // LSB
  ((uint8_t *)&result)[1] = f.read();
  ((uint8_t *)&result)[2] = f.read();
  ((uint8_t *)&result)[3] = f.read();  // MSB
  return result;
}

 最後まで見ていただきありがとうございました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?