ESP32 2432S028R (CYD)でLVGL - ILI9341 vs ST7789 速度比較
Arduino 環境で LVGL を試したくて、ディスプレイや SD カードの面倒な配線が不要な “黄色い基板”(以下 CYD)を購入しました。ただ最初に入手した品がハズレだったため、結果的に2種類の2.8インチ版を手にする事に…。
どうせならと言うことで、LVGL に深入りする前に CYD を使いこなす上での次の2つのお題を片付けておく事にしました。
- 2種類の LCD ドライバ IC(ILI9341、ST7789)の SPI クロック周波数と描画速度の比較
- 2本しか無い SPI バスで3つのデバイス(LCD、タッチパネル、SD カード)を扱う方法
今回はまず、前者に関する調査結果を報告したいと思います。
ILI9341 と ST7789 の SPI クロック周波数
CYD のように LCD を搭載したボード限定ですが、僕がお気に入りの LovyanGFX には、自動でボードの種類を識別し、パラメータ を設定してくれるとても便利な機能があります。
#define LGFX_AUTODETECT
#include <LovyanGFX.h>
この仕組みを利用し、設定されたパラメータをダンプするよう コード に手を加えて得られたのが次の2つです。1つ目は Micro-USB が1つのタイプ(ILI9341)、2つ目は Micro-USB と USB-C の2つを備えたタイプ(ST7789)です。
SPI のピン番号は、ボードタイプに ESP32-2432S028R CYD
を設定すると読み込まれる pins_arduino.h
の定義済みシンボルに置き換えています。
LGFX_ESP32_2432S028R_CYD_1USB.hpp (ILI9341)
#pragma once
#include <LovyanGFX.hpp>
// ESP32-2432S028R で LovyanGFX を独自設定で利用する場合の設定
// ファイル名例:LGFX_ESP32_2432S028R_CYD_1USB.hpp
//
// ピン設定は、以下を元にしています
// ボードタイプ:ESP32_2432S028R CYD
// https://github.com/espressif/arduino-esp32/blob/master/variants/jczn_2432s028r/pins_arduino.h
/// 独自の設定を行うクラスを、LGFX_Deviceから派生して作成します。
class LGFX : public lgfx::LGFX_Device
{
lgfx::Panel_ILI9341 _panel_instance; // 接続するパネルの型にあったインスタンスを用意します。
lgfx::Bus_SPI _bus_instance; // SPIバスのインスタンス
lgfx::Light_PWM _light_instance; // バックライト制御が可能な場合はインスタンスを用意します。
lgfx::Touch_XPT2046 _touch_instance; // タッチスクリーンの型にあったインスタンスを用意します。
public:
// コンストラクタを作成し、ここで各種設定を行います。
LGFX(void)
{
{ // バス制御の設定を行います。
auto cfg = _bus_instance.config(); // バス設定用の構造体を取得します。
cfg.spi_host = HSPI_HOST; // 使用するSPIを選択 ESP32-S2,C3 : SPI2_HOST or SPI3_HOST / ESP32 : VSPI_HOST or HSPI_HOST
cfg.spi_mode = 0; // SPI通信モードを設定 (0 ~ 3)
cfg.freq_write = 40000000; // 送信時のSPIクロック (最大80MHz, 80MHzを整数で割った値に丸められます)
cfg.freq_read = 16000000; // 受信時のSPIクロック
cfg.spi_3wire = false; // 受信をMOSIピンで行う場合はtrueを設定
cfg.use_lock = true; // トランザクションロックを使用する場合はtrueを設定
cfg.dma_channel = SPI_DMA_CH_AUTO; // 使用するDMAチャンネルを設定 (0=DMA不使用 / 1=1ch / 2=ch / SPI_DMA_CH_AUTO=自動設定)
cfg.pin_sclk = CYD_TFT_SCK; // SPIのSCLKピン番号を設定 (14)
cfg.pin_mosi = CYD_TFT_MOSI; // SPIのMOSIピン番号を設定 (13)
cfg.pin_miso = CYD_TFT_MISO; // SPIのMISOピン番号を設定 (12)
cfg.pin_dc = CYD_TFT_DC; // SPIのD/Cピン番号を設定 (2)
_bus_instance.config(cfg); // 設定値をバスに反映します。
_panel_instance.setBus(&_bus_instance); // バスをパネルにセットします。
}
{ // 表示パネル制御の設定を行います。
auto cfg = _panel_instance.config(); // 表示パネル設定用の構造体を取得します。
cfg.pin_cs = CYD_TFT_CS; // CSが接続されているピン番号 (15)
cfg.pin_rst = -1; // RSTが接続されているピン番号 (-1 = disable)
cfg.pin_busy = -1; // BUSYが接続されているピン番号 (-1 = disable)
cfg.panel_width = 240; // 実際に表示可能な幅
cfg.panel_height = 320; // 実際に表示可能な高さ
cfg.offset_x = 0; // パネルのX方向オフセット量
cfg.offset_y = 0; // パネルのY方向オフセット量
cfg.offset_rotation = 2; // 回転方向の値のオフセット 0~7 (4~7は上下反転)
cfg.dummy_read_pixel = 8; // ピクセル読出し前のダミーリードのビット数
cfg.dummy_read_bits = 1; // ピクセル以外のデータ読出し前のダミーリードのビット数
cfg.readable = true; // データ読出しが可能な場合 trueに設定
cfg.invert = false; // パネルの明暗が反転してしまう場合 trueに設定
cfg.rgb_order = false; // パネルの赤と青が入れ替わってしまう場合 trueに設定
cfg.dlen_16bit = false; // 16bitパラレルやSPIでデータ長を16bit単位で送信するパネルの場合 trueに設定
cfg.bus_shared = false; // SDカードとバスを共有している場合 trueに設定(drawJpgFile等でバス制御を行います)
cfg.memory_width = 240; // ドライバICがサポートしている最大の幅
cfg.memory_height = 320; // ドライバICがサポートしている最大の高さ
_panel_instance.config(cfg);
}
{ // バックライト制御の設定を行います。
auto cfg = _light_instance.config(); // バックライト設定用の構造体を取得します。
cfg.pin_bl = CYD_TFT_BL; // バックライトが接続されているピン番号 (21)
cfg.invert = false; // バックライトの輝度を反転させる場合 true
cfg.freq = 12000; // バックライトのPWM周波数
cfg.pwm_channel = 7; // 使用するPWMのチャンネル番号
_light_instance.config(cfg);
_panel_instance.setLight(&_light_instance); // バックライトをパネルにセットします。
}
{ // タッチスクリーン制御の設定を行います。
auto cfg = _touch_instance.config();
cfg.x_min = 240; // タッチスクリーンから得られる最小のX値(生の値)
cfg.x_max = 3800; // タッチスクリーンから得られる最大のX値(生の値)
cfg.y_min = 3700; // タッチスクリーンから得られる最小のY値(生の値)
cfg.y_max = 200; // タッチスクリーンから得られる最大のY値(生の値)
cfg.pin_int = CYD_TP_IRQ; // INTが接続されているピン番号 (36)
cfg.bus_shared = false; // 画面と共通のバスを使用している場合 trueを設定
cfg.offset_rotation = 0; // 表示とタッチの向きのが一致しない場合の調整 0~7の値で設定
cfg.spi_host = -1; // 使用するSPIを選択 (HSPI_HOST or VSPI_HOST)
cfg.freq = 1000000; // SPIクロックを設定
cfg.pin_sclk = CYD_TP_CLK; // SCLKが接続されているピン番号 (25)
cfg.pin_mosi = CYD_TP_MOSI; // MOSIが接続されているピン番号 (32)
cfg.pin_miso = CYD_TP_MISO; // MISOが接続されているピン番号 (39)
cfg.pin_cs = CYD_TP_CS; // CSが接続されているピン番号 (33)
_touch_instance.config(cfg);
_panel_instance.setTouch(&_touch_instance); // タッチスクリーンをパネルにセットします。
}
setPanel(&_panel_instance); // 使用するパネルをセットします。
}
};
LGFX_ESP32_2432S028R_CYD_2USB.hpp (ST7789)
#pragma once
#include <LovyanGFX.hpp>
// ESP32-2432S028R で LovyanGFX を独自設定で利用する場合の設定
// ファイル名例:LGFX_ESP32_2432S028R_CYD_1USB.hpp
//
// ピン設定は、以下を元にしています
// ボードタイプ:ESP32_2432S028R CYD
// https://github.com/espressif/arduino-esp32/blob/master/variants/jczn_2432s028r/pins_arduino.h
/// 独自の設定を行うクラスを、LGFX_Deviceから派生して作成します。
class LGFX : public lgfx::LGFX_Device
{
lgfx::Panel_ST7789 _panel_instance; // 接続するパネルの型にあったインスタンスを用意します。
lgfx::Bus_SPI _bus_instance; // SPIバスのインスタンス
lgfx::Light_PWM _light_instance; // バックライト制御が可能な場合はインスタンスを用意します。
lgfx::Touch_XPT2046 _touch_instance; // タッチスクリーンの型にあったインスタンスを用意します。
public:
// コンストラクタを作成し、ここで各種設定を行います。
LGFX(void)
{
{ // バス制御の設定を行います。
auto cfg = _bus_instance.config(); // バス設定用の構造体を取得します。
cfg.spi_host = HSPI_HOST; // 使用するSPIを選択 ESP32-S2,C3 : SPI2_HOST or SPI3_HOST / ESP32 : VSPI_HOST or HSPI_HOST
cfg.spi_mode = 0; // SPI通信モードを設定 (0 ~ 3)
cfg.freq_write = 80000000; // 送信時のSPIクロック (最大80MHz, 80MHzを整数で割った値に丸められます)
cfg.freq_read = 16000000; // 受信時のSPIクロック
cfg.spi_3wire = false; // 受信をMOSIピンで行う場合はtrueを設定
cfg.use_lock = true; // トランザクションロックを使用する場合はtrueを設定
cfg.dma_channel = SPI_DMA_CH_AUTO; // 使用するDMAチャンネルを設定 (0=DMA不使用 / 1=1ch / 2=ch / SPI_DMA_CH_AUTO=自動設定)
cfg.pin_sclk = CYD_TFT_SCK; // SPIのSCLKピン番号を設定 (14)
cfg.pin_mosi = CYD_TFT_MOSI; // SPIのMOSIピン番号を設定 (13)
cfg.pin_miso = CYD_TFT_MISO; // SPIのMISOピン番号を設定 (12)
cfg.pin_dc = CYD_TFT_DC; // SPIのD/Cピン番号を設定 (2)
_bus_instance.config(cfg); // 設定値をバスに反映します。
_panel_instance.setBus(&_bus_instance); // バスをパネルにセットします。
}
{ // 表示パネル制御の設定を行います。
auto cfg = _panel_instance.config(); // 表示パネル設定用の構造体を取得します。
cfg.pin_cs = CYD_TFT_CS; // CSが接続されているピン番号 (15)
cfg.pin_rst = -1; // RSTが接続されているピン番号 (-1 = disable)
cfg.pin_busy = -1; // BUSYが接続されているピン番号 (-1 = disable)
cfg.panel_width = 240; // 実際に表示可能な幅
cfg.panel_height = 320; // 実際に表示可能な高さ
cfg.offset_x = 0; // パネルのX方向オフセット量
cfg.offset_y = 0; // パネルのY方向オフセット量
cfg.offset_rotation = 0; // 回転方向の値のオフセット 0~7 (4~7は上下反転)
cfg.dummy_read_pixel = 16; // ピクセル読出し前のダミーリードのビット数
cfg.dummy_read_bits = 1; // ピクセル以外のデータ読出し前のダミーリードのビット数
cfg.readable = true; // データ読出しが可能な場合 trueに設定
cfg.invert = false; // パネルの明暗が反転してしまう場合 trueに設定
cfg.rgb_order = false; // パネルの赤と青が入れ替わってしまう場合 trueに設定
cfg.dlen_16bit = false; // 16bitパラレルやSPIでデータ長を16bit単位で送信するパネルの場合 trueに設定
cfg.bus_shared = false; // SDカードとバスを共有している場合 trueに設定(drawJpgFile等でバス制御を行います)
cfg.memory_width = 240; // ドライバICがサポートしている最大の幅
cfg.memory_height = 320; // ドライバICがサポートしている最大の高さ
_panel_instance.config(cfg);
}
{ // バックライト制御の設定を行います。
auto cfg = _light_instance.config(); // バックライト設定用の構造体を取得します。
cfg.pin_bl = CYD_TFT_BL; // バックライトが接続されているピン番号 (21)
cfg.invert = false; // バックライトの輝度を反転させる場合 true
cfg.freq = 12000; // バックライトのPWM周波数
cfg.pwm_channel = 7; // 使用するPWMのチャンネル番号
_light_instance.config(cfg);
_panel_instance.setLight(&_light_instance); // バックライトをパネルにセットします。
}
{ // タッチスクリーン制御の設定を行います。
auto cfg = _touch_instance.config();
cfg.x_min = 240; // タッチスクリーンから得られる最小のX値(生の値)
cfg.x_max = 3800; // タッチスクリーンから得られる最大のX値(生の値)
cfg.y_min = 3700; // タッチスクリーンから得られる最小のY値(生の値)
cfg.y_max = 200; // タッチスクリーンから得られる最大のY値(生の値)
cfg.pin_int = CYD_TP_IRQ; // INTが接続されているピン番号 (36)
cfg.bus_shared = false; // 画面と共通のバスを使用している場合 trueを設定
cfg.offset_rotation = 2; // 表示とタッチの向きのが一致しない場合の調整 0~7の値で設定
cfg.spi_host = -1; // 使用するSPIを選択 (HSPI_HOST or VSPI_HOST)
cfg.freq = 1000000; // SPIクロックを設定
cfg.pin_sclk = CYD_TP_CLK; // SCLKが接続されているピン番号 (25)
cfg.pin_mosi = CYD_TP_MOSI; // MOSIが接続されているピン番号 (32)
cfg.pin_miso = CYD_TP_MISO; // MISOが接続されているピン番号 (39)
cfg.pin_cs = CYD_TP_CS; // CSが接続されているピン番号 (33)
_touch_instance.config(cfg);
_panel_instance.setTouch(&_touch_instance); // タッチスクリーンをパネルにセットします。
}
setPanel(&_panel_instance); // 使用するパネルをセットします。
}
};
diff
コマンドで両者の差分を取ると、次のようになります(僕はこれらを1つのファイルにまとめ、ココ に掲載されたシンボル定義 DISPLAY_CYD_2USB
で 切り替えてます)。
% diff LGFX_ESP32_2432S028R_CYD_1USB.hpp LGFX_ESP32_2432S028R_CYD_2USB.hpp
17c17
< lgfx::Panel_ILI9341 _panel_instance; // 接続するパネルの型にあったインスタンスを用意します。
---
> lgfx::Panel_ST7789 _panel_instance; // 接続するパネルの型にあったインスタンスを用意します。
31c31
< cfg.freq_write = 40000000; // 送信時のSPIクロック (最大80MHz, 80MHzを整数で割った値に丸められます)
---
> cfg.freq_write = 80000000; // 送信時のSPIクロック (最大80MHz, 80MHzを整数で割った値に丸められます)
56,57c56,57
< cfg.offset_rotation = 2; // 回転方向の値のオフセット 0~7 (4~7は上下反転)
< cfg.dummy_read_pixel = 8; // ピクセル読出し前のダミーリードのビット数
---
> cfg.offset_rotation = 0; // 回転方向の値のオフセット 0~7 (4~7は上下反転)
> cfg.dummy_read_pixel = 16; // ピクセル読出し前のダミーリードのビット数
92c92
< cfg.offset_rotation = 0; // 表示とタッチの向きのが一致しない場合の調整 0~7の値で設定
---
> cfg.offset_rotation = 2; // 表示とタッチの向きのが一致しない場合の調整 0~7の値で設定
これらの設定が他の ILI9341 や ST7789 に適用可能とは限りません。僕の経験では、単体の ST7789 ディプレイで dummy_read_pixel
をデフォルトの 8
から 16
に変えたところ、スクリーンキャプチャ(=画素値の読み出し)時に色がシフトしてしまいました。
上記から分かる通り、LovyanGFX の自動設定では ILI9341 の SPI クロック周波数(40MHz)を ST7789(80MHz)の 1/2 に設定してます。各パネルのデータシートを見ると、書き込み時のクロック周期として ILI9341 が 100ns(10MHz)、ST7789 は 66ns(約 15MHz)が「最小値」として示されているだけで、最大値の規定がありません。


おそらく 40MHz とか 80MHz とかの値は、らびやんさん をはじめ多くの先人達の経験に基づく値と思われます。例えば witnessmenow/ESP32-Cheap-Yellow-Display では、TFT_eSPI 用の値として ILI9341 タイプ、 ST7789 タイプ ともに 55MHz
が設定されています。
手元の CYD で TFT_eSPI のベンチマーク用スケッチ を 55MHz に設定しても、40MHz と有意なさは見られませんでした。どこかで(調べろよ、オィ😅)40MHz の倍数で丸められているようです。
描画性能の実験
先人達の経験値を信じないわけではありませんが、鵜呑みにして「ILI9341 より ST7789 の方が速い!」と結論づけるのもナニなので、TFT_eSPI のベンチマーク用スケッチ を LovyanGFX でも動くようにして、SPI クロック周波数を変えてテストしてみました。
結果はご覧の通り、ILI9341 も 80MHz で動作し、ST7789 と同等のスコアを示しました。



念の為、各テスト結果を 24 ビットのビットマップ画像に保存し、40MHz と 80MHz で比較したところ、完全に一致しました(ただし ILI9341 と ST7789 とでは、幾つかの画像で目視ではほぼ区別がつかない程度の差(RGB 値で 1 ビット)が見られた)。
一方 LovyanGFX の例題スケッチ RotatedZoomSample では、ILI9341 の 80MHz で同期が乱れたような画像になっちゃいました。



この例題スケッチではスプライトが使われているので、メモリからパネルへのデータ転送時にタイミング的な不整合が生じたのでしょう。DMA は明示的に有効化してませんが、デフォルトで働いていたのかも知れません(調べろよ、オィ 😠)
これらの結果から、使用する関数次第で 40MHz が上限の場合もあれば、80MHz が可能な場合もある事が分かります。逆に言えば、ILI9341 であっても 40MHz に固執する必要はなく、条件次第で 80MHz で高速化を狙えるという事です。
ILI9341、オーバークロックして大丈夫?
どこからがオーバークロックなのかハッキリしませんが、TFT_graphicstest_PDQ の全ての待ち時間をゼロにして3時間連続稼働した、なんちゃって耐久試験(Alexa に聞いたら室温は22度でした)を行いました。



パネルの中のドライバ IC を直接測温することは出来ませんが、表も裏も特段に熱くなることもなく正常に機能しています。まぁ、ILI9341 も ST7789 もチップの作りとしては似たり寄ったりなのでしょう(よく知りもせず、いい加減なヤツ 🙄)
次回は…
もしかしたら LVGL+ILI9341 でも 80MHz で動かせるかも?という期待を持ちつつ、今回はココまでとします。
次回は、冒頭に挙げた2番目の気になる項目「2本しか無い SPI バスで3つの SPI デバイスを扱う方法」を掘り下げたいと思います。3つのうち2つしか使えないなんて、有り得ないですからネ 🤡