ESP32 Arduino2 LVGL9 HMIディスプレイボード駆動

AliExpressの「Sunton Store」「GUITION Official Store」で販売されている、ESP32とLCDが付いたHMI(Human Machine Interface)向けの開発ボードが存在します。

シリーズ名は無く、黄色の基板で安価なため、海外では「ESP32 Cheap Yellow Display Board / CYD Board」と言われているようです。

最近発売されたと思われる「JC~」型番のボードは「Guition」と表記がありますね。シリーズ名でしょうか。

私は最初、技適マークが無いと思っていたのであまり興味がなかったのですが、Twitter上で技適マーク付きとの報告があり購入した次第です。

所有しているものは今のところ全て技適マークが付いていますが、上記URL等を参考にすると技適マークが付いていないロットがあるようで要注意です。

今回はこのボードを使用してLVGLを動作させてみます。

 

ディスプレイボード型番一覧

型番 ESP型番 画面サイズ 接続 タッチ 概要
ESP32-1732S019N ESP32-S3-WROOM-1 1.9 (170x320) SPI (ST7789) 無し GPIO 20ピン有り
ESP32-2432S022N ESP-WROOM-32 2.2 (240x320) 8bit (ST7789) 無し GPIO無し
ESP32-2432S022C ESP-WROOM-32 2.2 (240x320) 8bit (ST7789) 静電容量 (CST820 I2C) GPIO無し
ESP32-2432S024N ESP-WROOM-32 2.4 (240x320) SPI (ILI9341) 無し PicoBlade 4P * 3(電源他)+ SP + BAT
ESP32-2432S024R ESP-WROOM-32 2.4 (240x320) SPI (ILI9341) 抵抗膜 (XPT2046 SPI) PicoBlade 4P * 3(電源他)+ SP + BAT
ESP32-2432S024C ESP-WROOM-32 2.4 (240x320) SPI (ILI9341) 静電容量 (CST820 I2C) PicoBlade 4P * 3(電源他)+ SP + BAT
ESP32-2432S028R ESP-WROOM-32 2.8 (240x320) SPI (ILI9341) 抵抗膜 (XPT2046 SPI) PicoBlade 4P * 3(電源他)+ SP USBがTypeCではない終売?
JC2432W328N ESP-WROOM-32 2.8 (240x320) SPI (ST7789) 無し PicoBlade 4P * 4(電源他)+ SP + BAT
JC2432W328R ESP-WROOM-32 2.8 (240x320) SPI (ST7789) 抵抗膜 (?) PicoBlade 4P * 4(電源他)+ SP + BAT
JC2432W328C ESP-WROOM-32 2.8 (240x320) SPI (ST7789) 静電容量 (?) PicoBlade 4P * 4(電源他)+ SP + BAT
ESP32-2432S032N ESP-WROOM-32 3.2 (240x320) IPS SPI (ST7789) 無し PicoBlade 4P * 3(電源他)+ SP + BAT
ESP32-2432S032R ESP-WROOM-32 3.2 (240x320) IPS SPI (ST7789) 抵抗膜 (XPT2046 SPI) PicoBlade 4P * 3(電源他)+ SP + BAT
ESP32-2432S032C ESP-WROOM-32 3.2 (240x320) IPS SPI (ST7789) 静電容量 (? I2C) PicoBlade 4P * 3(電源他)+ SP + BAT
ESP32-3248S035R ESP-WROOM-32 3.5 (320x480) SPI (ST7796) 抵抗膜 (XPT2046 SPI) PicoBlade 4P * 3(電源他)+ SP USBがTypeCではない終売?
ESP32-3248S035C ESP-WROOM-32 3.5 (320x480) SPI (ST7796) 静電容量 (? I2C) PicoBlade 4P * 3(電源他)+ SP USBがTypeCではない終売?
ESP32-4827S043N ESP32-S3-WROOM-1 4.3 (480x272) RGB565 (ILI9485) 無し PicoBlade 4P * 3(電源他)+ SH 4P * 1
ESP32-4827S043R ESP32-S3-WROOM-1 4.3 (480x272) RGB565 (ILI9485) 抵抗膜 (XPT2046 SPI) PicoBlade 4P * 3(電源他)+ SH 4P * 1
ESP32-4827S043C ESP32-S3-WROOM-1 4.3 (480x272) RGB565 (ILI9485) 静電容量 (? I2C) PicoBlade 4P * 3(電源他)+ SH 4P * 1
ESP32-8048S043C ESP32-S3-WROOM-1 4.3 (800x480) IPS RGB565 (ILI9485) 静電容量 (? I2C) PicoBlade 4P * 3(電源他)+ SH 4P * 1
JC4827W543N ESP32-S3-WROOM-1 4.3 (480x272) IPS SPI (ST3401A) 無し PicoBlade 4P * 4(電源他)+ SH 4P * 1 + SP + BAT
JC4827W543R ESP32-S3-WROOM-1 4.3 (480x272) IPS SPI (ST3401A) 抵抗膜 (XPT2046 SPI) PicoBlade 4P * 4(電源他)+ SH 4P * 1 + SP + BAT
JC4827W543C ESP32-S3-WROOM-1 4.3 (480x272) IPS SPI (ST3401A) 静電容量 (? I2C) PicoBlade 4P * 4(電源他)+ SH 4P * 1 + SP + BAT
ESP32-8048S050N ESP32-S3-WROOM-1 5.0 (800x480) IPS RGB565 (ST7262) 無し PicoBlade 4P * 4(電源他)+ SH 4P * 1 + SP
ESP32-8048S050C ESP32-S3-WROOM-1 5.0 (800x480) IPS RGB565 (ST7262) 静電容量 (? I2C) PicoBlade 4P * 4(電源他)+ SH 4P * 1 + SP
JC8048W550N ESP32-S3-WROOM-1 5.0 (800x480) IPS RGB565 (ST7262) 無し PicoBlade 4P * 4(電源他)+ SH 4P * 1 + SP + BAT
JC8048W550C ESP32-S3-WROOM-1 5.0 (800x480) IPS RGB565 (ST7262) 静電容量 (? I2C) PicoBlade 4P * 4(電源他)+ SH 4P * 1 + SP + BAT
ESP32-8048S070N ESP32-S3-WROOM-1 7.0 (800x480) RGB565 (EK9716) 無し PicoBlade 4P * 4(電源他)+ SH 4P * 1 + SP
ESP32-8048S070C ESP32-S3-WROOM-1 7.0 (800x480) RGB565 (EK9716) 静電容量 (? I2C) PicoBlade 4P * 4(電源他)+ SH 4P * 1 + SP

RGB565接続でも大丈夫なのか?と思いましたが、ESP32-S3のドキュメントを見るとパラレルRGB接続のLCDでも扱えるみたいですね。

大型LCDを備えたものではESP32-S3が必須のようです。

 

技適メモ一覧

日本国内で電波を出す機器には技適マークが必須です。

搭載されているESP32マイコンモジュール自体にWi-Fi/Bluetoothの電波を出す機能があるため、技適マークが無いものを使用するのは違法です。

型番 技適マーク 概要
ESP32-1732S019N
ESP32-2432S022N
ESP32-2432S022C
ESP32-2432S024N
ESP32-2432S024R
ESP32-2432S024C
ESP32-2432S028R
JC2432W328N
JC2432W328R
JC2432W328C あり(211-161007 2024/08購入分:Espressif?(メーカー刻印無し) ESP-WROOM-32 搭載確認
ESP32-2432S032N
ESP32-2432S032R
ESP32-2432S032C
ESP32-3248S035R
ESP32-3248S035C
ESP32-4827S043N あり(201-220052 2024/01購入分:Espressif ESP32-S3-WROOM-1-N16R8 搭載確認
ESP32-4827S043R あり(201-220052 2024/09購入分:Espressif ESP32-S3-WROOM-1-N16R8 搭載確認         
ESP32-4827S043C
ESP32-8048S043C
JC4827W543N
JC4827W543R
JC4827W543C 無し 2024/08購入分:Guitionロゴ表記カスタム品?ESP32-S3-WROOM N4R8
ESP32-8048S050N あり(201-220052 2024/01購入分:Espressif ESP32-S3-WROOM-1-N16R8 搭載確認
ESP32-8048S050C あり(201-220052 2024/09購入分:Espressif ESP32-S3-WROOM-1-N16R8 搭載確認
JC8048W550N
JC8048W550C 無し 2024/08購入分:Siinst ESP32-S3-WROOM N16R8
ESP32-8048S070N あり(201-220052 2024/01購入分:Espressif ESP32-S3-WROOM-1-N16R8 搭載確認
ESP32-8048S070C あり(201-220052 2024/09購入分:Espressif ESP32-S3-WROOM-1-N16R8 搭載確認

情報をお待ちしております。

技適マークが無いものに関しては、技適マークがあるものにリワークして貼り替え必須かと思います。

 

技適に対応するためのESP32リワーク

ホットエアーリワークステーション(Quick 861DW)を使用してリワークを行いました。

周りの部品に影響が出ないように手元にあった銅テープで熱を遮断しています。

液晶画面がすぐ裏にあるのであまり長く行うとダメージが多くなると思われます。

 

JC4827W543C

ESP32 Arduino2 LVGL9 HMI Display Board Rework JC4827W543C 001 ESP32 Arduino2 LVGL9 HMI Display Board Rework JC4827W543C 002

真ん中のGNDパッドは元々ハンダが乗っていませんでした。楽にリワークできました。

 

JC8048W550C

ESP32 Arduino2 LVGL9 HMI Display Board Rework JC8048W550C 001 ESP32 Arduino2 LVGL9 HMI Display Board Rework JC8048W550C 002

真ん中のGNDパッドは元々ハンダが乗っていませんでした。楽にリワークできました。

 

ボード上の接続コネクタ

Molex PicoBlade 4P 1.25mm

説明中には「JS-1.25」などと表記があるが、これはPicoBladeの互換品と思われる。

(中国では小さいコネクタを総称してJSTと言っているのではないか?)

種類 型番
コネクタハウジング 51021-0400
コンタクトピン(連鎖・AWG 26~28) 50079-8000
コンタクトピン(バラ・AWG 26~28) 50079-8100
コンタクトピン(連鎖・AWG 28~32) 50058-8000
コンタクトピン(バラ・AWG 28~32) 50058-8100
基板用スルーホールコネクタヘッダ 53047-0410
基板用スルーホールコネクタヘッダL字 53048-0410
基板用面実装コネクタヘッダ 53398-0471
基板用面実装コネクタヘッダL字 53261-0471
両側コネクタ付きハーネス (50mm) 15134-0400
両側コネクタ付きハーネス (100mm) 15134-0401
両側コネクタ付きハーネス (150mm) 15134-0402
両側コネクタ付きハーネス (300mm) 15134-0403
両側コネクタ付きハーネス (450mm) 15134-0405
両側コネクタ付きハーネス (600mm) 15134-0406
片側コネクタ付きハーネス (75mm) 218112-0400
片側コネクタ付きハーネス (150mm) 218112-0401
片側コネクタ付きハーネス (225mm) 218112-0402
片側コネクタ付きハーネス (300mm) 218112-0403
片側コネクタ付きハーネス (425mm) 218112-0404
延長コネクタ付きハーネス (75mm) 218113-0400
延長コネクタ付きハーネス (150mm) 218113-0401
延長コネクタ付きハーネス (225mm) 218113-0402
延長コネクタ付きハーネス (300mm) 218113-0403
延長コネクタ付きハーネス (425mm) 218113-0404

Molex型番は、ハイフンを入れない番号のみで検索すると出やすい場合があります。

なお、一部ボードに搭載されているスピーカー・バッテリーコネクタはPicoBladeの2Pです。

 

互換品ハウジング

互換品コンタクトピン

互換品が多くて探しきれません。

互換品コネクタヘッダ

互換品ケーブルハーネス

 

JST SH 4P 1.0mm

種類 型番
コネクタハウジング SHR-04V-S-B
コンタクトピン(バラ・AWG 28~32) SSH-003T-P0.2-H
基板用面実装コネクタヘッダ BM04B-SRSS-TB
基板用面実装コネクタヘッダL字 SM04B-SRSS-TB
圧着済みケーブル(150mm) ※1本 SH3-SH3-28150
圧着済みケーブル(300mm) ※1本 SH3-SH3-28300

秋月・千石に取り扱いあり!

SparkFun社のQwiicシステムと同等ですね。

 

互換品ハウジング

互換品コンタクトピン

互換品が多くて探しきれません。

互換品コネクタヘッダ

互換品ケーブルハーネス

 

USBシリアル

使用しているUSBシリアルチップは CH340C

最近のWindowsであれば接続するだけでドライバが当たるはずです。

なお、JC4827W543Cだけは例外でUSBシリアルチップは使用せずに、ESP32 S3のUSBポートに直接つながっています。

ArduinoのUSB Mode設定はUSB-OTGに変更し、初回書き込み時にはBOOTボタンを押しながらUSBを接続してDownload Bootモードにする必要があります。

初期状態では何も書き込まれていないためリセットを繰り返します。

 

搭載されているLCDについて

深圳市晶彩智能有限公司(Shenzhen Jingcai Intelligent Co., Ltd)製のよう。

 

4.3インチ 480 * 272(深圳市晶彩智能有限公司 JC4827B043N)

データシート上、ドットクロックは最小8MHz, 通常9MHz, 最大12MHz。

 

4.3インチ 800 * 480(深圳市晶彩智能有限公司?)

データシート見つからず。

 

5.0インチ 800 * 480(深圳市晶彩智能有限公司 JC8048B050N)

データシート上、ドットクロックは最小23MHz, 通常25MHz, 最大27MHz。

発色は比較的良いが、ドットクロックをデータシート通りに設定すると表示が盛大にズレる。

ESP32側の問題の可能性もあるが、ドットクロックを落とすと正常な位置に表示される。

同封サンプルでは12MHzやら16MHzの指定が見られる。私はデータシートの半分の値、12.5MHzを使用した。

クロックを落としている関係かわからないが、色表示がうねっている。特に端のほうが顕著。

 

7.0インチ 800 * 480(深圳市晶彩智能有限公司 JC8048B070N)

データシート上、ドットクロックは最小29MHz, 通常33MHz, 最大38MHz。

全体的に白く飛んでいる。

 

LVGL GUIデザイン

LVGLを使用する上で、GUIデザイン用のソフトを探しました。

LVGLは画面上に配置するウィジェットをコード中で座標指定して配置していく形なので、デザイン用のソフトが無いと効率が悪いです。

色々探したところ、最近になってLVGLに対応し始めた「EEZ Studio」が良さそうでした。

 

EEZ Studio 0.15.1 を使用したLVGL GUIデザイン

こちらからダウンロードすることができます。

今回使用したのは「EEZ-Studio-Setup-0.15.1.exe」です。

 

ESP32 Arduino2 LVGL9 HMI Display Board EEZ Studio 001 ESP32 Arduino2 LVGL9 HMI Display Board EEZ Studio 002

起動したら「LVGL」を選択してプロジェクトを作成します。

LVGLのバージョンは9.0を選択しておきます。

保存場所については、Arduinoのスケッチと同じ位置にすると良いです。

EEZ Studioで書き出しを行うと、プロジェクトファイルと同じ位置から「src/ui」というフォルダで書き出されます。

Arduinoはsrcという名前のフォルダのみ、別途ソースコードを含むことができますので、スケッチのあるフォルダにプロジェクトを保存すると都合が良いと思います。

デフォルトでは「Main」という名前のページが作成され、800*480の解像度設定になっています。

 

解像度設定

ESP32 Arduino2 LVGL9 HMI Display Board EEZ Studio 003 ESP32 Arduino2 LVGL9 HMI Display Board EEZ Studio 004

使用するボードの液晶解像度に合わせて設定してみましょう。480*272で設定する例です。

左ペインから「Main」ページを選択し、右ペインに表示される「Properties」から「Width」と「Height」を設定します。

左下の「Check」に警告が出ていますね。

これは、設定されているディスプレイの解像度とページの解像度が違うためです。

ディスプレイの解像度も合わせるために、メニューバー中の歯車マークをクリックし、「Settings」を表示させます。

「General」中の「Display width」と「Display height」がディスプレイ解像度になります。これも同様に変更します。

 

インクルードパス設定

ESP32 Arduino2 LVGL9 HMI Display Board EEZ Studio 005

生成されるファイルのインクルードパスも設定しておきましょう。

「Settings」中の「Build」を選択すると「LVGL include」が表示されます。

デフォルトでは「lvgl/lvgl.h」ですが、Arduino環境から使う場合には「lvgl.h」のほうが都合が良いので変更しておきます。

これで生成されるファイル内のインクルードパスが変更されます。

 

GUIデザインのビルド(LVGLコード書き出し)

ESP32 Arduino2 LVGL9 HMI Display Board EEZ Studio 006 ESP32 Arduino2 LVGL9 HMI Display Board EEZ Studio 007

GUIのデザインが終わったら「Save」で保存しておきましょう。

チェックマークのボタン「Check」で設定をチェックし、レンチマークのボタン「Build」で現在のGUIを書き出しします。

このようにファイル群が書き出しされます。

他の環境からはこれらをインクルードして使用します。

 

Arduino IDEでの動作テスト

各種ディスプレイで動作確認したコードです。

 

ESP32-4827S043N 動作テストコード

ESP32-S3向け、LCDがRGB565パラレルで接続されているものは流用が効くと思います。

Arduino core for the ESP32(ESP-IDF)のドライバをそのまま使用しているので、別途TFT_eSPIやLovyanGFX等のLCDライブラリは必要ありません。

しかし、パラレルLCDはESP32-S3特有のペリフェラルなので、無印ESP32では動作しません。

//#include "esp_log.h"
//static const char *TAG = "main";

#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_rgb.h"
#include "driver/ledc.h"
#include "lv_conf.h"
extern "C" {
#include "src/ui/ui.h"
}

#define LCD_H_RES              480
#define LCD_V_RES              272
#define LCD_PIXEL_CLOCK_HZ     (9 * 1000 * 1000)
#define LCD_HSYNC_PULSE_WIDTH  4
#define LCD_HSYNC_BACK_PORCH   43
#define LCD_HSYNC_FRONT_PORCH  8
#define LCD_VSYNC_PULSE_WIDTH  4
#define LCD_VSYNC_BACK_PORCH   12
#define LCD_VSYNC_FRONT_PORCH  8
#define LCD_PIN_NUM_HSYNC      GPIO_NUM_39
#define LCD_PIN_NUM_VSYNC      GPIO_NUM_41
#define LCD_PIN_NUM_DE         GPIO_NUM_40
#define LCD_PIN_NUM_PCLK       GPIO_NUM_42
#define LCD_PIN_NUM_DISP_EN    GPIO_NUM_NC
#define LCD_PIN_NUM_DATA0      GPIO_NUM_8   // B0
#define LCD_PIN_NUM_DATA1      GPIO_NUM_3   // B1
#define LCD_PIN_NUM_DATA2      GPIO_NUM_46  // B2
#define LCD_PIN_NUM_DATA3      GPIO_NUM_9   // B3
#define LCD_PIN_NUM_DATA4      GPIO_NUM_1   // B4
#define LCD_PIN_NUM_DATA5      GPIO_NUM_5   // G0
#define LCD_PIN_NUM_DATA6      GPIO_NUM_6   // G1
#define LCD_PIN_NUM_DATA7      GPIO_NUM_7   // G2
#define LCD_PIN_NUM_DATA8      GPIO_NUM_15  // G3
#define LCD_PIN_NUM_DATA9      GPIO_NUM_16  // G4
#define LCD_PIN_NUM_DATA10     GPIO_NUM_4   // G5
#define LCD_PIN_NUM_DATA11     GPIO_NUM_45  // R0
#define LCD_PIN_NUM_DATA12     GPIO_NUM_48  // R1
#define LCD_PIN_NUM_DATA13     GPIO_NUM_47  // R2
#define LCD_PIN_NUM_DATA14     GPIO_NUM_21  // R3
#define LCD_PIN_NUM_DATA15     GPIO_NUM_14  // R4

#define LCD_BL_PWM_FREQ        1000 
#define LCD_BL_PWM_RESOLUTION  LEDC_TIMER_8_BIT 
#define LCD_BL_LEDC_CHANNEL    LEDC_CHANNEL_0  
#define LCD_BL_LEDC_TIMER      LEDC_TIMER_0 
#define LCD_BL_LEDC_PIN_NUM    GPIO_NUM_2
#define LCD_BL_LEDC_INVERT     0

#define LVGL_TASK_MAX_DELAY_MS 500
#define LVGL_TASK_MIN_DELAY_MS 1
#define LVGL_TASK_STACK_SIZE   (8 * 1024)
#define LVGL_TASK_PRIORITY     2


SemaphoreHandle_t sem_vsync_end;
SemaphoreHandle_t sem_gui_ready;
SemaphoreHandle_t lvgl_mux;


void init_backlight(void) {
  ledc_channel_config_t ledc_channel = {
    .gpio_num = LCD_BL_LEDC_PIN_NUM,
    .speed_mode = LEDC_LOW_SPEED_MODE,
    .channel = LCD_BL_LEDC_CHANNEL,
    .intr_type = LEDC_INTR_DISABLE,
    .timer_sel = LCD_BL_LEDC_TIMER,
    .duty = 0,
    .hpoint = 0,
    .flags = {
      .output_invert = LCD_BL_LEDC_INVERT,
    },
  };
  ledc_timer_config_t ledc_timer = {
    .speed_mode = LEDC_LOW_SPEED_MODE,
    .duty_resolution = LCD_BL_PWM_RESOLUTION,
    .timer_num = LCD_BL_LEDC_TIMER,
    .freq_hz = LCD_BL_PWM_FREQ,
    .clk_cfg = LEDC_AUTO_CLK,
  };
  ESP_LOGI(TAG, "Initializing LCD backlight");
  ledc_channel_config(&ledc_channel);
  ledc_timer_config(&ledc_timer);
}


void set_backlight_brightness(uint8_t brightness) {
  ESP_LOGI(TAG, "Setting LCD backlight brightness to %d", ((0b1 << LCD_BL_PWM_RESOLUTION) - 1) * brightness / 100);
  ledc_set_duty(LEDC_LOW_SPEED_MODE, LCD_BL_LEDC_CHANNEL, ((0b1 << LCD_BL_PWM_RESOLUTION) - 1) * brightness / 100);
  ledc_update_duty(LEDC_LOW_SPEED_MODE, LCD_BL_LEDC_CHANNEL);
}


IRAM_ATTR static void lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *color_map) {
  esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t) lv_display_get_user_data(disp);
  xSemaphoreGive(sem_gui_ready);
  xSemaphoreTake(sem_vsync_end, portMAX_DELAY);
  esp_lcd_panel_draw_bitmap(panel_handle, area->x1, area->y1, area->x2 + 1, area->y2 + 1, color_map);
  lv_disp_flush_ready(disp);
}


IRAM_ATTR static bool on_vsync_event(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *event_data, void *user_data) {
  BaseType_t high_task_awoken = pdFALSE;
  if (xSemaphoreTakeFromISR(sem_gui_ready, &high_task_awoken) == pdTRUE) {
    xSemaphoreGiveFromISR(sem_vsync_end, &high_task_awoken);
  }
  return high_task_awoken == pdTRUE;
}


IRAM_ATTR static void lvgl_port_task(void *arg) {
  ESP_LOGI(TAG, "Starting LVGL task");
  uint32_t task_delay_ms = LVGL_TASK_MAX_DELAY_MS;
  while (1) {
    if (xSemaphoreTakeRecursive(lvgl_mux, portMAX_DELAY) == pdTRUE) {
      task_delay_ms = lv_task_handler();
      ui_tick();
      xSemaphoreGiveRecursive(lvgl_mux);
    }
    if (task_delay_ms > LVGL_TASK_MAX_DELAY_MS) {
      task_delay_ms = LVGL_TASK_MAX_DELAY_MS;
    } else if (task_delay_ms < LVGL_TASK_MIN_DELAY_MS) {
      task_delay_ms = LVGL_TASK_MIN_DELAY_MS;
    }
    vTaskDelay(pdMS_TO_TICKS(task_delay_ms));
  }
}


void init_lcd(esp_lcd_panel_handle_t *panel_handle) {
  ESP_LOGI(TAG, "Create LCD semaphores");
  sem_vsync_end = xSemaphoreCreateBinary();
  assert(sem_vsync_end);
  sem_gui_ready = xSemaphoreCreateBinary();
  assert(sem_gui_ready);
  esp_lcd_rgb_panel_config_t panel_config = {
    .clk_src = LCD_CLK_SRC_DEFAULT,
    .timings = {
      .pclk_hz = LCD_PIXEL_CLOCK_HZ,
      .h_res = LCD_H_RES,
      .v_res = LCD_V_RES,
      .hsync_pulse_width = LCD_HSYNC_PULSE_WIDTH,
      .hsync_back_porch = LCD_HSYNC_BACK_PORCH,
      .hsync_front_porch = LCD_HSYNC_FRONT_PORCH,
      .vsync_pulse_width = LCD_VSYNC_PULSE_WIDTH,
      .vsync_back_porch = LCD_VSYNC_BACK_PORCH,
      .vsync_front_porch = LCD_VSYNC_FRONT_PORCH,
      .flags = {
        .hsync_idle_low = false,
        .vsync_idle_low = false,
        .de_idle_high = false,
        .pclk_active_neg = true,
        .pclk_idle_high = false,
      },
    },
    .data_width = 16,
    .bits_per_pixel = 0,
    .num_fbs = 2,
    .bounce_buffer_size_px = 0,
    .sram_trans_align = 0,
    .psram_trans_align = 64,
    .hsync_gpio_num = LCD_PIN_NUM_HSYNC,
    .vsync_gpio_num = LCD_PIN_NUM_VSYNC,
    .de_gpio_num = LCD_PIN_NUM_DE,
    .pclk_gpio_num = LCD_PIN_NUM_PCLK,
    .disp_gpio_num = LCD_PIN_NUM_DISP_EN,
    .data_gpio_nums = {
      LCD_PIN_NUM_DATA0,
      LCD_PIN_NUM_DATA1,
      LCD_PIN_NUM_DATA2,
      LCD_PIN_NUM_DATA3,
      LCD_PIN_NUM_DATA4,
      LCD_PIN_NUM_DATA5,
      LCD_PIN_NUM_DATA6,
      LCD_PIN_NUM_DATA7,
      LCD_PIN_NUM_DATA8,
      LCD_PIN_NUM_DATA9,
      LCD_PIN_NUM_DATA10,
      LCD_PIN_NUM_DATA11,
      LCD_PIN_NUM_DATA12,
      LCD_PIN_NUM_DATA13,
      LCD_PIN_NUM_DATA14,
      LCD_PIN_NUM_DATA15,
    },
    .flags = {
      .disp_active_low = 0,
      .refresh_on_demand = 0,
      .fb_in_psram = true,
      .double_fb = true,
      .no_fb = 0,
      .bb_invalidate_cache = 0,
    },
  };
  ESP_LOGI(TAG, "Create RGB LCD panel");
  ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, panel_handle));
  ESP_LOGI(TAG, "Register event callbacks");
  esp_lcd_rgb_panel_event_callbacks_t cbs = {
    .on_vsync = on_vsync_event,
  };
  esp_lcd_rgb_panel_register_event_callbacks(*panel_handle, &cbs, NULL);
  ESP_LOGI(TAG, "Initialize RGB LCD panel");
  ESP_ERROR_CHECK(esp_lcd_panel_reset(*panel_handle));
  ESP_ERROR_CHECK(esp_lcd_panel_init(*panel_handle));
}


void init_lvgl(esp_lcd_panel_handle_t panel_handle) {
  ESP_LOGI(TAG, "Create LVGL semaphores");
  lvgl_mux = xSemaphoreCreateRecursiveMutex();
  assert(lvgl_mux);
  ESP_LOGI(TAG, "Initialize LVGL library");
  lv_init();
  ESP_LOGI(TAG, "Set system tick callback");
  lv_tick_set_cb(millis);
  ESP_LOGI(TAG, "Allocating LVGL buffers");
  void *buf1 = NULL;
  void *buf2 = NULL;
  ESP_ERROR_CHECK(esp_lcd_rgb_panel_get_frame_buffer(panel_handle, 2, &buf1, &buf2));
  static lv_display_t *disp;
  disp = lv_display_create(LCD_H_RES, LCD_V_RES);
  lv_display_set_buffers(disp, buf1, buf2, LCD_H_RES * LCD_V_RES * (LV_COLOR_DEPTH / 8), LV_DISPLAY_RENDER_MODE_DIRECT);
  lv_display_set_user_data(disp, panel_handle);
  lv_display_set_flush_cb(disp, lvgl_flush_cb);
}


void init_display(void) {
  esp_lcd_panel_handle_t lcd = NULL;
  init_backlight();
  init_lcd(&lcd);
  init_lvgl(lcd);
  if (xSemaphoreTakeRecursive(lvgl_mux, portMAX_DELAY) == pdTRUE) {
    ui_init();
    xSemaphoreGiveRecursive(lvgl_mux);
  }
  ESP_LOGI(TAG, "Create LVGL task");
  xTaskCreate(lvgl_port_task, "LVGL", LVGL_TASK_STACK_SIZE, NULL, LVGL_TASK_PRIORITY, NULL);
}


void setup() {
  Serial.begin(115200);
  ESP_LOGI(TAG, "Start");
  init_display();
  set_backlight_brightness(100);

}


void loop() {

  delay(1);
}

 

JC8048W550C(※ESP32-S3貼り替え済み) 動作テストコード

ESP32-S3向け、LCDがRGB565パラレルで接続されているものは流用が効くと思います。

上記、ESP32-4827S043N向けのコードに加えて静電容量式のタッチパネルの処理が入っています。

また画面解像度も異なります。

//#include "esp_log.h"
//static const char *TAG = "main";

#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_rgb.h"
#include "driver/ledc.h"
#include "driver/i2c.h"
#include "lv_conf.h"
extern "C" {
#include "src/lcd_touch/esp_lcd_touch_gt911.h"
#include "src/ui/ui.h"
}

#define LCD_H_RES              800
#define LCD_V_RES              480
#define LCD_PIXEL_CLOCK_HZ     (12.5 * 1000 * 1000)
#define LCD_HSYNC_PULSE_WIDTH  4
#define LCD_HSYNC_BACK_PORCH   8
#define LCD_HSYNC_FRONT_PORCH  8
#define LCD_VSYNC_PULSE_WIDTH  4
#define LCD_VSYNC_BACK_PORCH   8
#define LCD_VSYNC_FRONT_PORCH  8
#define LCD_PIN_NUM_HSYNC      GPIO_NUM_39
#define LCD_PIN_NUM_VSYNC      GPIO_NUM_41
#define LCD_PIN_NUM_DE         GPIO_NUM_40
#define LCD_PIN_NUM_PCLK       GPIO_NUM_42
#define LCD_PIN_NUM_DISP_EN    GPIO_NUM_NC
#define LCD_PIN_NUM_DATA0      GPIO_NUM_8   // B0
#define LCD_PIN_NUM_DATA1      GPIO_NUM_3   // B1
#define LCD_PIN_NUM_DATA2      GPIO_NUM_46  // B2
#define LCD_PIN_NUM_DATA3      GPIO_NUM_9   // B3
#define LCD_PIN_NUM_DATA4      GPIO_NUM_1   // B4
#define LCD_PIN_NUM_DATA5      GPIO_NUM_5   // G0
#define LCD_PIN_NUM_DATA6      GPIO_NUM_6   // G1
#define LCD_PIN_NUM_DATA7      GPIO_NUM_7   // G2
#define LCD_PIN_NUM_DATA8      GPIO_NUM_15  // G3
#define LCD_PIN_NUM_DATA9      GPIO_NUM_16  // G4
#define LCD_PIN_NUM_DATA10     GPIO_NUM_4   // G5
#define LCD_PIN_NUM_DATA11     GPIO_NUM_45  // R0
#define LCD_PIN_NUM_DATA12     GPIO_NUM_48  // R1
#define LCD_PIN_NUM_DATA13     GPIO_NUM_47  // R2
#define LCD_PIN_NUM_DATA14     GPIO_NUM_21  // R3
#define LCD_PIN_NUM_DATA15     GPIO_NUM_14  // R4

#define LCD_BL_PWM_FREQ        1000 
#define LCD_BL_PWM_RESOLUTION  LEDC_TIMER_8_BIT 
#define LCD_BL_LEDC_CHANNEL    LEDC_CHANNEL_0  
#define LCD_BL_LEDC_TIMER      LEDC_TIMER_0 
#define LCD_BL_LEDC_PIN_NUM    GPIO_NUM_2
#define LCD_BL_LEDC_INVERT     0

#define TOUCH_I2C_SCL          GPIO_NUM_20
#define TOUCH_I2C_SDA          GPIO_NUM_19
#define TOUCH_I2C_RST          GPIO_NUM_38
#define TOUCH_I2C_CLK_SPEED    400000
#define TOUCH_I2C_NUM          I2C_NUM_0

#define LVGL_TASK_MAX_DELAY_MS 500
#define LVGL_TASK_MIN_DELAY_MS 1
#define LVGL_TASK_STACK_SIZE   (8 * 1024)
#define LVGL_TASK_PRIORITY     2


SemaphoreHandle_t sem_vsync_end;
SemaphoreHandle_t sem_gui_ready;
SemaphoreHandle_t lvgl_mux;


void init_backlight(void) {
  ledc_channel_config_t ledc_channel = {
    .gpio_num = LCD_BL_LEDC_PIN_NUM,
    .speed_mode = LEDC_LOW_SPEED_MODE,
    .channel = LCD_BL_LEDC_CHANNEL,
    .intr_type = LEDC_INTR_DISABLE,
    .timer_sel = LCD_BL_LEDC_TIMER,
    .duty = 0,
    .hpoint = 0,
    .flags = {
      .output_invert = LCD_BL_LEDC_INVERT,
    },
  };
  ledc_timer_config_t ledc_timer = {
    .speed_mode = LEDC_LOW_SPEED_MODE,
    .duty_resolution = LCD_BL_PWM_RESOLUTION,
    .timer_num = LCD_BL_LEDC_TIMER,
    .freq_hz = LCD_BL_PWM_FREQ,
    .clk_cfg = LEDC_AUTO_CLK,
  };
  ESP_LOGI(TAG, "Initializing LCD backlight");
  ledc_channel_config(&ledc_channel);
  ledc_timer_config(&ledc_timer);
}


void set_backlight_brightness(uint8_t brightness) {
  ESP_LOGI(TAG, "Setting LCD backlight brightness to %d", ((0b1 << LCD_BL_PWM_RESOLUTION) - 1) * brightness / 100);
  ledc_set_duty(LEDC_LOW_SPEED_MODE, LCD_BL_LEDC_CHANNEL, ((0b1 << LCD_BL_PWM_RESOLUTION) - 1) * brightness / 100);
  ledc_update_duty(LEDC_LOW_SPEED_MODE, LCD_BL_LEDC_CHANNEL);
}


IRAM_ATTR static void lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *color_map) {
  esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t) lv_display_get_user_data(disp);
  xSemaphoreGive(sem_gui_ready);
  xSemaphoreTake(sem_vsync_end, portMAX_DELAY);
  esp_lcd_panel_draw_bitmap(panel_handle, area->x1, area->y1, area->x2 + 1, area->y2 + 1, color_map);
  lv_disp_flush_ready(disp);
}


IRAM_ATTR static bool on_vsync_event(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *event_data, void *user_data) {
  BaseType_t high_task_awoken = pdFALSE;
  if (xSemaphoreTakeFromISR(sem_gui_ready, &high_task_awoken) == pdTRUE) {
    xSemaphoreGiveFromISR(sem_vsync_end, &high_task_awoken);
  }
  return high_task_awoken == pdTRUE;
}


IRAM_ATTR static void lvgl_port_task(void *arg) {
  ESP_LOGI(TAG, "Starting LVGL task");
  uint32_t task_delay_ms = LVGL_TASK_MAX_DELAY_MS;
  while (1) {
    if (xSemaphoreTakeRecursive(lvgl_mux, portMAX_DELAY) == pdTRUE) {
      task_delay_ms = lv_task_handler();
      ui_tick();
      xSemaphoreGiveRecursive(lvgl_mux);
    }
    if (task_delay_ms > LVGL_TASK_MAX_DELAY_MS) {
      task_delay_ms = LVGL_TASK_MAX_DELAY_MS;
    } else if (task_delay_ms < LVGL_TASK_MIN_DELAY_MS) {
      task_delay_ms = LVGL_TASK_MIN_DELAY_MS;
    }
    vTaskDelay(pdMS_TO_TICKS(task_delay_ms));
  }
}


void init_lcd(esp_lcd_panel_handle_t *panel_handle) {
  ESP_LOGI(TAG, "Create LCD semaphores");
  sem_vsync_end = xSemaphoreCreateBinary();
  assert(sem_vsync_end);
  sem_gui_ready = xSemaphoreCreateBinary();
  assert(sem_gui_ready);
  esp_lcd_rgb_panel_config_t panel_config = {
    .clk_src = LCD_CLK_SRC_DEFAULT,
    .timings = {
      .pclk_hz = LCD_PIXEL_CLOCK_HZ,
      .h_res = LCD_H_RES,
      .v_res = LCD_V_RES,
      .hsync_pulse_width = LCD_HSYNC_PULSE_WIDTH,
      .hsync_back_porch = LCD_HSYNC_BACK_PORCH,
      .hsync_front_porch = LCD_HSYNC_FRONT_PORCH,
      .vsync_pulse_width = LCD_VSYNC_PULSE_WIDTH,
      .vsync_back_porch = LCD_VSYNC_BACK_PORCH,
      .vsync_front_porch = LCD_VSYNC_FRONT_PORCH,
      .flags = {
        .hsync_idle_low = false,
        .vsync_idle_low = false,
        .de_idle_high = false,
        .pclk_active_neg = true,
        .pclk_idle_high = false,
      },
    },
    .data_width = 16,
    .bits_per_pixel = 0,
    .num_fbs = 2,
    .bounce_buffer_size_px = 0,
    .sram_trans_align = 0,
    .psram_trans_align = 64,
    .hsync_gpio_num = LCD_PIN_NUM_HSYNC,
    .vsync_gpio_num = LCD_PIN_NUM_VSYNC,
    .de_gpio_num = LCD_PIN_NUM_DE,
    .pclk_gpio_num = LCD_PIN_NUM_PCLK,
    .disp_gpio_num = LCD_PIN_NUM_DISP_EN,
    .data_gpio_nums = {
      LCD_PIN_NUM_DATA0,
      LCD_PIN_NUM_DATA1,
      LCD_PIN_NUM_DATA2,
      LCD_PIN_NUM_DATA3,
      LCD_PIN_NUM_DATA4,
      LCD_PIN_NUM_DATA5,
      LCD_PIN_NUM_DATA6,
      LCD_PIN_NUM_DATA7,
      LCD_PIN_NUM_DATA8,
      LCD_PIN_NUM_DATA9,
      LCD_PIN_NUM_DATA10,
      LCD_PIN_NUM_DATA11,
      LCD_PIN_NUM_DATA12,
      LCD_PIN_NUM_DATA13,
      LCD_PIN_NUM_DATA14,
      LCD_PIN_NUM_DATA15,
    },
    .flags = {
      .disp_active_low = 0,
      .refresh_on_demand = 0,
      .fb_in_psram = true,
      .double_fb = true,
      .no_fb = 0,
      .bb_invalidate_cache = 0,
    },
  };
  ESP_LOGI(TAG, "Create RGB LCD panel");
  ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, panel_handle));
  ESP_LOGI(TAG, "Register event callbacks");
  esp_lcd_rgb_panel_event_callbacks_t cbs = {
    .on_vsync = on_vsync_event,
  };
  esp_lcd_rgb_panel_register_event_callbacks(*panel_handle, &cbs, NULL);
  ESP_LOGI(TAG, "Initialize RGB LCD panel");
  ESP_ERROR_CHECK(esp_lcd_panel_reset(*panel_handle));
  ESP_ERROR_CHECK(esp_lcd_panel_init(*panel_handle));
}


void init_lvgl(esp_lcd_panel_handle_t panel_handle, esp_lcd_touch_handle_t touch_handle) {
  ESP_LOGI(TAG, "Create LVGL semaphores");
  lvgl_mux = xSemaphoreCreateRecursiveMutex();
  assert(lvgl_mux);
  ESP_LOGI(TAG, "Initialize LVGL library");
  lv_init();
  ESP_LOGI(TAG, "Set system tick callback");
  lv_tick_set_cb(millis);
  ESP_LOGI(TAG, "Allocating LVGL buffers");
  void *buf1 = NULL;
  void *buf2 = NULL;
  ESP_ERROR_CHECK(esp_lcd_rgb_panel_get_frame_buffer(panel_handle, 2, &buf1, &buf2));
  static lv_display_t *disp;
  disp = lv_display_create(LCD_H_RES, LCD_V_RES);
  lv_display_set_buffers(disp, buf1, buf2, LCD_H_RES * LCD_V_RES * (LV_COLOR_DEPTH / 8), LV_DISPLAY_RENDER_MODE_DIRECT);
  lv_display_set_user_data(disp, panel_handle);
  lv_display_set_flush_cb(disp, lvgl_flush_cb);
  ESP_LOGI(TAG, "Register input device driver to LVGL");
  lv_indev_t *indev = lv_indev_create();
  lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
  lv_indev_set_user_data(indev, touch_handle);
  lv_indev_set_read_cb(indev, touchpad_read);
}


IRAM_ATTR static void touchpad_read(lv_indev_t *indev, lv_indev_data_t *data) {
  esp_lcd_touch_handle_t touch_handle = (esp_lcd_touch_handle_t)lv_indev_get_user_data(indev);
  uint16_t touchpad_x;
  uint16_t touchpad_y;
  uint16_t touch_strength;
  uint8_t touch_cnt = 0;
  data->state = LV_INDEV_STATE_REL;
  esp_lcd_touch_read_data(touch_handle);
  bool touchpad_pressed = esp_lcd_touch_get_coordinates(touch_handle, &touchpad_x, &touchpad_y, &touch_strength, &touch_cnt, 1);
  if (touchpad_pressed) {
    ESP_LOGI(TAG, "Touchpad_read %d %d", touchpad_x, touchpad_y);
    data->state = LV_INDEV_STATE_PR;
    data->point.x = touchpad_x;
    data->point.y = touchpad_y;
  }
}


void init_display(void) {
  esp_lcd_panel_handle_t lcd = NULL;
  esp_lcd_touch_handle_t tp = NULL;
  init_backlight();
  init_touch(&tp);
  init_lcd(&lcd);
  init_lvgl(lcd, tp);
  if (xSemaphoreTakeRecursive(lvgl_mux, portMAX_DELAY) == pdTRUE) {
    ui_init();
    xSemaphoreGiveRecursive(lvgl_mux);
  }
  ESP_LOGI(TAG, "Create LVGL task");
  xTaskCreate(lvgl_port_task, "LVGL", LVGL_TASK_STACK_SIZE, NULL, LVGL_TASK_PRIORITY, NULL);
}


void init_touch(esp_lcd_touch_handle_t *touch_handle) {
  ESP_LOGI(TAG, "Install Touch driver");
  const i2c_config_t i2c_conf = {
    .mode = I2C_MODE_MASTER,
    .sda_io_num = TOUCH_I2C_SDA,
    .scl_io_num = TOUCH_I2C_SCL,
    .sda_pullup_en = GPIO_PULLUP_DISABLE,
    .scl_pullup_en = GPIO_PULLUP_DISABLE,
    .master = {
      .clk_speed = TOUCH_I2C_CLK_SPEED,
    },
  };
  ESP_LOGI(TAG, "i2c_param_config");
  i2c_param_config(TOUCH_I2C_NUM, &i2c_conf);
  ESP_LOGI(TAG, "i2c_driver_install");
  i2c_driver_install(TOUCH_I2C_NUM, i2c_conf.mode, 0, 0, 0);
  const esp_lcd_touch_config_t tp_cfg = {
    .x_max = LCD_H_RES,
    .y_max = LCD_V_RES,
    .rst_gpio_num = TOUCH_I2C_RST, 
    .int_gpio_num = GPIO_NUM_NC,
    .levels = {
      .reset = 0,
      .interrupt = 0,
    },
    .flags = {
      .swap_xy = 0,
      .mirror_x = 0,
      .mirror_y = 0,
    },
  };
  esp_lcd_panel_io_handle_t tp_io_handle = NULL;
  const esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG();
  ESP_LOGI(TAG, "Create LCD panel IO handle");
  esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)TOUCH_I2C_NUM, &tp_io_config, &tp_io_handle);
  ESP_LOGI(TAG, "Create a new GT911 touch driver");
  esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, touch_handle);
}



void setup() {
  Serial.begin(115200);
  ESP_LOGI(TAG, "Start");
  init_display();
  set_backlight_brightness(100);


}


void loop() {

  delay(1);
}

 

スタックサイズについて

タスクを作成する際のスタックメモリ量指定が難しいです。

マイコンという限られたリソースのなか、タスクに割り当てるスタックのサイズは少ないとエラーが発生して停止しますし、多く割り当てるとその分無駄が大きくなります。

クラッシュして再起動を繰り返し「Debug exception reason: Stack canary watchpoint triggered (LVGL)」のようなメッセージがシリアルモニタに出ている場合は、サイズを増やしてみると良いと思われます。

Arduino core for the ESP32でのloop関数の設定値は8192なので、そのあたりの数値を基準にするとよいかと思われます。(1024単位が良さそう?)

 

ESP32上でのセマフォ・ミューテックスの必要性

LVGLはスレッドセーフではありません。

つまり、FreeRTOSが動作しているESP32上や、複数のコアが存在するマイコンで動作させるような場合、セマフォ・ミューテックスが必要になってきます。

例えばスレッドが2つ存在して、それぞれからLVGLで表示されている内容を書き換えるような処理があったとします。

それぞれのスレッドは並列に動きますから、片方のスレッドで書き換え処理をしている”最中”にもう片方のスレッドから書き換え処理を初めてしまうということが起こり得ます。

同時に複数箇所から書き換え処理が行われたLVGL側ではどっちの値を取ればいいのか混乱してしまい、動作がおかしくなってしまうなどといった現象が起こります。

そのため、セマフォやミューテックスを活用し、同時に複数から書き換えできないようにする必要があるのです。

ESP32(FreeRTOS)では、xSemaphoreTakeやxSemaphoreGiveといった関数があり、これを実行して排他制御を行います。

基本的にはTakeでセマフォを得て、処理を行い、Giveでセマフォを開放するという流れになります。

Takeでセマフォを得ると、続けて他からTakeが行われた場合にセマフォを得ることができません。

このような流れで複数箇所からの同時処理を防ぐのです。

 

LCDドライバ

Arduino core for the ESP32に収録されています。

そのままインクルードして使用できます。

LCDドライバ周りの記述は以下のパスに入っているはずです。

C:\Users\(ユーザー名)\AppData\Local\Arduino15\packages\esp32\tools\esp32-arduino-libs\idf-release_v5.1-b6b4727c58\esp32s3\include\esp_lcd\include\esp_lcd_panel_rgb.h

上記パラレルLCDドライバはESP32-S3でないと使用できません。(無印のESP32には機能が搭載されていません)

SPI液晶など他のバスを使用するLCDはどのESP32でも使えると思います。

 

LVGLの設定ファイル(lv_conf.h)の準備

必ず現在のバージョンのライブラリからコピーしてきます。

例えばLVGL v8の設定ファイルを流用すると、LVGL v9ではビルドが通らないなど不具合があるので、必ず現在のバージョンのライブラリからコピーして持ってくるようにしましょう。

C:\Users\(ユーザー名)\Documents\Arduino\libraries\lvgl\lv_conf_template.h

にあるファイルをスケッチと同じディレクトリにコピーし、lv_conf.hに名前変更しておきます。

#if 1 /*Set it to "1" to enable content*/

ファイル中15行目にある値を1に変更して、設定を有効にしましょう。

設定内容に関しては他にいじらなくて大丈夫です。

スケッチと同じフォルダに入れ、「#include "lv_conf.h"」でインクルードするのが一番良いとは思います。

万が一、うまく動作しない場合は、「#define LV_CONF_INCLUDE_SIMPLE」を入れるなどなにかしら対応する必要がありそうです。

※実際の設定読み込み処理に関しては以下のファイル中、40行目~あたりに書かれていますので参考まで。

C:\Users\(ユーザー名)\Documents\Arduino\libraries\lvgl\src\lv_conf_internal.h

 

ESP-BSPの準備

様々なところで例に上げられているタッチデバイスについては、ESP-BSPというボードサポート用のパッケージに入っています。

ESP-IDFをインストールした際には収録されているようですが、今回使用するArduino core for the ESP32には収録されていません。

タッチパネル周りのものだけ esp-bsp/components/lcd_touch/ 以下から必要なファイルをダウンロードして以下のように配置して使用しています。

  • src/lcd_touch/esp_lcd_touch.c
  • src/lcd_touch/esp_lcd_touch.h
  • src/lcd_touch/esp_lcd_touch_gt911.c
  • src/lcd_touch/esp_lcd_touch_gt911.h

必要なものだけを抽出した関係で、一部の定数(CONFIG_ESP_LCD_TOUCH_MAX_POINTS)が宣言されずにビルドが通りませんでした。

対処は以下のように行いました。

 

build_opt.hの準備

-DCONFIG_ESP_LCD_TOUCH_MAX_POINTS=5

build_opt.hというテキストファイルを用意し、上記の内容を書き込みます。

Arduino core for the ESP32では、スケッチと同じ位置にあるbuild_opt.hファイルを読み込むようです。

そのため、コンパイラの-DオプションでCONFIG_ESP_LCD_TOUCH_MAX_POINTS=5を宣言するようにしました。

#define CONFIG_ESP_LCD_TOUCH_MAX_POINTS 5

と同じ効果があります。

 

Arduino IDE上での設定

8MB以上のPSRAMを備えたものでは

  • PSRAM: OPI PSRAM

設定にすること。

でないと上記スケッチはフレームバッファ分のRAMが確保できないので動きません。

具体的には

  • ESP32-S3-WROOM-1-N4R8
  • ESP32-S3-WROOM-1-N8R8
  • ESP32-S3-WROOM-1-N16R8

のものは Octal SPI 接続のPSRAMになります。

 

環境について

- 環境 バージョン 概要
開発環境 Arduino 2.3.2 Arduino IDE使用
ボード定義 esp32 3.0.4 ESP32S3 Dev Module設定
ライブラリ LVGL 9.2.0 GUI用
GUIデザイン EEZ Studio 0.15.1 LVGL v9 GUIデザイン

 

参考リンク

こちらの記事も参考にどうぞ

 

各種情報