最近購入した「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」の個別設定ファイルです。
#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
に固定しています。
#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;
}
最後まで見ていただきありがとうございました。