70歳の挑戦... マンデルブロー集合の描画をより高速化しました

【25/05/28編集】さらに「51秒」まで高速になりました。
久しぶりにSPI接続のTFT-LCDモジュールを使おうとしたところ、壊れていたりコンパイルできなかったりとトラブル続きでした。それで8ビットパラレル接続のモジュールを使ってみたら、これも謎のエラーが出てホント困りました。最初の4行だけですが、こんな感じです。
```
c:/users/gero/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s3-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s3-elf/12.2.0/../../../../xtensa-esp32s3-elf/bin/ld.exe: C:\Users\gero\AppData\Local\arduino\sketches\732FC2FCC1874DA54E03F0BCD3666CFF\libraries\LovyanGFX\lgfx\utility\miniz.c.o: in function `tdefl_get_png_row_default':
c:\Users\gero\AppData\Local\arduino\MySketch\libraries\LovyanGFX\src\lgfx\utility/miniz.c:2815: multiple definition of `tdefl_get_png_row_default'; C:\Users\gero\AppData\Local\arduino\sketches\732FC2FCC1874DA54E03F0BCD3666CFF\libraries\LovyanGFX\lgfx\utility\lgfx_miniz.c.o:c:\Users\gero\AppData\Local\arduino\MySketch\libraries\LovyanGFX\src\lgfx\utility/lgfx_miniz.c:2815: first defined here
c:/users/gero/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s3-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s3-elf/12.2.0/../../../../xtensa-esp32s3-elf/bin/ld.exe: C:\Users\gero\AppData\Local\arduino\sketches\732FC2FCC1874DA54E03F0BCD3666CFF\libraries\LovyanGFX\lgfx\utility\miniz.c.o: in function `tdefl_compress':
c:\Users\gero\AppData\Local\arduino\MySketch\libraries\LovyanGFX\src\lgfx\utility/miniz.c:2653: multiple definition of `tdefl_compress'; C:\Users\gero\AppData\Local\arduino\sketches\732FC2FCC1874DA54E03F0BCD3666CFF\libraries\LovyanGFX\lgfx\utility\lgfx_miniz.c.o:c:\Users\gero\AppData\Local\arduino\MySketch\libraries\LovyanGFX\src\lgfx\utility/lgfx_miniz.c:2653: first defined here
```
エイヤ！っと
```
c:\Users\(USER)\AppData\Local\arduino\MySketch\libraries\LovyanGFX\src\lgfx\utility/miniz.c
c:\Users\(USER)\AppData\Local\arduino\MySketch\libraries\LovyanGFX\src\lgfx\utility/miniz.h
```
を削除したところコンパイルでき動作しました。良かった。なお、ツールの設定はこのようにしました。

![S3_Compile_Tools.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3189926/3ed185e2-68c9-4514-834b-761a7107c1c1.jpeg)


--------------------------------------------------------------------
【24/09/14編集】藤田氏から頂いたアドバイスに従いコードの見直しをしたところ、実行速度が「72秒」まで改善しました。ありがとうございました。改善版は、そこに至る途中経過も残したいので、冗長にはなりますが最後に付け加えることにします。

----------------------------------------------------------------
 マンデルブロー集合の描画が、以前の<a href="https://qiita.com/Mobu_Kyoto/items/47f432549e073ad77187">「マンデルブロー集合が成長してるかのように。えっ、なんか動いた？変化してるよ💦」</a>より高速にできましたので投稿します。
　今回の構成は以下です。

    １．Arduino IDE 2.3.6 (ただし、esp32 by Espressif Systems のバージョンは最新ではない 3.0.0)
    ２．ESP32-S3-DevKitC-1、Flash size 16MB（秋月電子から購入）
    ３．3.95” TFT LCD Shield (320x480、ILI9488、8ビットパラレル接続、アマゾンで購入)

![ESP32S3_8Para_Mandelbrot.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3189926/637d0d0e-2d70-1a85-4072-330e1289564d.jpeg)

前回投稿の構成のあと、普通の ESP32ボードと8BitParallel の組み合わせで速くなりました。もっと高速化できないかと今回 ESP32S3 で試しました。描画写真の左下に小さく表示されてますが、設定を変えた20枚のマンデルブロー集合を描くのに92秒かかっています。この違いを並べてみます。

|ESP32 + SPI|ESP32 + 8bitParallel|ESP32S3+8bitParallel(9/9)|コード改善後(25/05/28)|
|------------|----------------|-|-|
|132秒|100秒|92秒|51秒|

ディスプレイモジュールの接続をSPIから8ビットパラレルに変えて100秒に、さらにESP32からESP32S3に変えてさらに92(->51)秒にまで速くなりました。

ありがたいことにコンパイルオプションを変更できるということなので、<a href="https://lang-ship.com/blog/work/esp32-compiler-option/#toc3">ESP32コンパイルオプションチューニング</a>に従って、

     Os → O2 (O3ではコンパイルエラー)
にしたところ更なる高速化ができました。

　ESP32-S3ボードとディスプレイシールドとの接続を次に示します。

|TFT LCD Shield|ESP32-S3 GPIO(24/09/09)|ESP32-S3 GPIO(25/05/28)|
|---------|-------------|---|
|RST|10|RST(-1)|
|CS|11|4|
|RS(DC)|8|5|
|WR|9|6|
|RD|12|7|
|D0|18|以下同|
|D1|37||
|D2|17||
|D3|38||
|D4|16||
|D5|39||
|D6|15||
|D7|40||

:::note
普通のESP32ボードと8ビットパラレル接続する場合を知りたい方は以前の投稿の
<a href="https://qiita.com/Mobu_Kyoto/items/cc381e3ac14b7a2c2426">70歳の挑戦... パラレル接続のTFTモジュールを使ってみた（その２）</a>
をご覧ください。
:::
　LovyanGFX使用の際に、次の myLovyanGFX.hpp をスケッチと同じフォルダーに保存します。

```html:myLovyanGFX.hpp
#pragma once

#define LGFX_USE_V1

#include <LovyanGFX.hpp>

class LGFX : public lgfx::LGFX_Device 
{
  lgfx::Panel_ILI9488   _panel_instance;
  lgfx::Bus_Parallel8   _bus_instance;  // 8ビットパラレルバスのインスタンス (ESP32のみ)
  
public:
  LGFX(void) {   
    {                     // バス制御の設定を行います。
      auto cfg = _bus_instance.config();  // バス設定用の構造体を取得します。
      // 8ビットパラレルバスの設定
      //cfg.i2s_port = I2S_NUM_0;   // 使用するI2Sポートを選択 (I2S_NUM_0 or I2S_NUM_1) (ESP32のI2S LCDモードを使用します)
      cfg.freq_write = 20000000;  // 送信クロック (最大20MHz, 80MHzを整数で割った値に丸められます)
      cfg.pin_wr = 6;//11;            // WR を接続しているピン番号
      cfg.pin_rd = 7;//12;            // RD を接続しているピン番号
      cfg.pin_rs = 5;//10;            // RS(D/C)を接続しているピン番号
      cfg.pin_d0 = 18;            // D0を接続しているピン番号
      cfg.pin_d1 = 37;            // D1を接続しているピン番号
      cfg.pin_d2 = 17;            // D2を接続しているピン番号
      cfg.pin_d3 = 38;            // D3を接続しているピン番号
      cfg.pin_d4 = 16;             // D4を接続しているピン番号
      cfg.pin_d5 = 39;             // D5を接続しているピン番号
      cfg.pin_d6 = 15;             // D6を接続しているピン番号
      cfg.pin_d7 = 40;            // D7を接続しているピン番号
      _bus_instance.config(cfg);               // 設定値をバスに反映します。
      _panel_instance.setBus(&_bus_instance);  // バスをパネルにセットします。
    }
    {                                       // 表示パネル制御の設定を行います。
      auto cfg = _panel_instance.config();  // 表示パネル設定用の構造体を取得します。
      cfg.pin_cs = 4;//9;    // 27 CSが接続されているピン番号 (-1 = disable)
      cfg.pin_rst = -1;//8;   // 33 RSTが接続されているピン番号  (-1 = disable)
      cfg.pin_busy = -1;  // BUSYが接続されているピン番号 (-1 = disable)
      // ※ 以下の設定値はパネル毎に一般的な初期値が設定されていますので、不明な項目はコメントアウトして試してみてください。
      cfg.memory_width = 320;                // ドライバICがサポートしている最大の幅
      cfg.memory_height = 480;               // ドライバICがサポートしている最大の高さ
      cfg.panel_width = 320;     // 実際に表示可能な幅
      cfg.panel_height = 480;    // 実際に表示可能な高さ
      cfg.offset_x = 0;          // パネルのX方向オフセット量
      cfg.offset_y = 0;          // パネルのY方向オフセット量
      cfg.offset_rotation = 0;   // 回転方向の値のオフセット 0~7 (4~7は上下反転)
      cfg.dummy_read_pixel = 8;  // ピクセル読出し前のダミーリードのビット数
      cfg.dummy_read_bits = 1;   // ピクセル以外のデータ読出し前のダミーリードのビット数
      cfg.readable = false;       // データ読出しが可能な場合 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等でバス制御を行います)
      _panel_instance.config(cfg);
    }

    setPanel(&_panel_instance);  // 使用するパネルをセットします。
  }

};
```

　次にスケッチ本体です。
```c:ESP32_S3_Mandelbrot_8Para.ino
#include "myLovyanGFX.hpp"

static LGFX tft;                 // LGFXのインスタンスを作成。

static uint16_t col[]{
  0x0000,  //0
  0x222A,  //1
  0x867E,  //2LightSkyBlue
  0xF81F,  //3Magenta
  0x480F,  //4Indigo
  0xFF1B,  //5MistyRose
  0xAEBC,  //6LightBlue
  0xFFFB,  //7LightYellow
  0x07FF,  //8Cyan
  0xF800,  //9Red
  0x07E0,  //10Green
  0x001F,  //11Blue
  0xFCEE,  //12LightSalmon
  0xEF31,  //13Khaki
  0xDFFF,  //14LightCyan
  0xFFE0   //15Yellow
};

int X0 = 240;// tft.width()/2
int Y0 = 160;// tft.height()/2
float view;             // viewport width
float di;               // real/imaginary calculation step
float CtoX, CtoY;       // 現在の表示（view）の中心
int repeat, k;          // 反復上限、回数
int re, gr, bl;         // color 0-255
float x, y, zr, zi, R, I;//複素計算変数
float absZ = 4.0;       // 発散判定
int page, dp;
float toRad = PI / 180.0;//度をラジアンに変換
//PI=3.1415926535897932384626433832795 defined in Libraly

void setup() {
  Serial.begin(115200);
  Serial.println("begin");
  tft.init();
  tft.setRotation(1);
  tft.fillScreen(TFT_BLACK);
  //tft.setTextColor(TFT_WHITE);

  // default Mandelbrot Set -----------------------------------------
  //view=2.4; CtoX = 0.0; CtoY = 0.0; repeat=200; page=1; dp=0;

  // view = 0.00024; // 10000倍に拡大
  // Viewport width; Viewport Center x;y; 反復上限; 画像枚数; 反復上限増分
  // 他の風景の良い場所
  view=0.00120; CtoX = -1.19998; CtoY = -0.15507; repeat=120; page=20; dp=2;
  //view=0.00024; CtoX = -1.24997; CtoY = -0.00877; repeat=190; page=28; dp=2;
  //view = 0.00024; CtoX=-1.401;CtoY=-0.000178; repeat=300; page=19; dp=2;

  di = view / 480.0;    // 1 pixel in Real and Imaginary number

  unsigned long now=0;
  unsigned long starttime;
  starttime=millis();
  for (int i = 1; i <= page; i++) {// 反復数上限を増やしていく
    Mandelbrot();

    now = millis();
    tft.setCursor(0,312);
    tft.printf("%2dpage:%3ds",i,now/1000);

    repeat += dp*i;
  }
  grid();
  tft.drawString("END",0,0);
}

void Mandelbrot(void) {
  for (int j = 320- Y0; j >= - Y0; j--) {
    y = j * di + CtoY;
    for (int i = - X0; i < 480 - X0; i++) {
      //if (pixel = tft.readPixel(X0 + i, 320 - Y0 - j) == 0) { // まだ発散してない点のみ計算
        x = i * di + CtoX;
        zr = 0.0;
        zi = 0.0;
        k = -1;
        do {
          k++;
          R = zr * zr - zi * zi + x;
          I = 2 * zr * zi + y;
          zr = R;
          zi = I;
          if (k >= repeat) break;
        } while ((R * R + I * I) < absZ);
        if (k == repeat) tft.drawPixel(X0 + i, 320 - Y0 - j, TFT_BLACK);
        else tft.drawPixel(X0 + i, 320 - Y0 - j, ColorSel(k));
      //}
    }
  }
}

void grid(void) {
  tft.drawLine(X0 + 100, 0, X0 + 100, tft.height(), 0xFFFF);
  tft.drawLine(X0 - 100, 0, X0 - 100, tft.height(), 0xFFFF);
  tft.drawLine(X0 + 200, 0, X0 + 200, tft.height(), 0xFFFF);
  tft.drawLine(X0 - 200, 0, X0 - 200, tft.height(), 0xFFFF);
  tft.drawLine(0, Y0 + 100, tft.width(), Y0 + 100, 0xFFFF);
  tft.drawLine(0, Y0 - 100, tft.width(), Y0 - 100, 0xFFFF);
  tft.drawLine(X0, 0, X0, tft.height(), TFT_RED);  // 赤で垂直の線を描画
  tft.drawLine(0, Y0, tft.width(), Y0, TFT_RED);
  tft.drawString("1.0",X0+201,0);
  tft.drawString("0",X0+1,0);
  tft.drawString("-1.0",X0-199,0);
}

uint16_t ColorSel(int c) {
  if (c < 16) {
    return col[c];
  } else {
    re = 128 + 126 * cos(c * toRad);
    gr = 128 + 126 * cos((c + 120) * toRad);
    bl = 128 + 126 * cos((c + 240) * toRad);
    return tft.color565(re, gr, bl);
  }
}

void loop(void) {
}
```
　最近、別のスケッチの作成時に不具合が起こったので「esp32 by Espressif Systems」 のバージョンは最新ではない 3.0.2 にしてあります。

------------------------------------------------------------------
【24/9/14 コード改善版】
ArduinoIDE のToolsオプションは次のようにしました。

![ESPS3_Tools.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3189926/8aa8d360-63f7-d3b5-cea9-edffb8670769.jpeg)

ESP32S3ボードのピン配置に合わせて結線の単純化も行いました。

```html:myLovyanGFX.hpp
#pragma once

#define LGFX_USE_V1

#include <LovyanGFX.hpp>

class LGFX : public lgfx::LGFX_Device 
{
  lgfx::Panel_ILI9488   _panel_instance;
  lgfx::Bus_Parallel8   _bus_instance;  // 8ビットパラレルバスのインスタンス (ESP32のみ)
  
public:
  LGFX(void) {   
    {                                   // バス制御の設定を行います。
      auto cfg = _bus_instance.config();// バス設定用の構造体を取得します。
      // 8ビットパラレルバスの設定
      cfg.freq_write = 20000000;  // 送信クロック (最大20MHz, 80MHzを整数で割った値に丸められます)
      cfg.pin_wr = 11;            // WR を接続しているピン番号
      cfg.pin_rd = 12; // must    // RD を接続しているピン番号
      cfg.pin_rs = 10;            // RS(D/C)を接続しているピン番号
      cfg.pin_d0 = 18;            // D0を接続しているピン番号
      cfg.pin_d1 = 37;            // D1を接続しているピン番号
      cfg.pin_d2 = 17;            // D2を接続しているピン番号
      cfg.pin_d3 = 38;            // D3を接続しているピン番号
      cfg.pin_d4 = 16;            // D4を接続しているピン番号
      cfg.pin_d5 = 39;            // D5を接続しているピン番号
      cfg.pin_d6 = 15;            // D6を接続しているピン番号
      cfg.pin_d7 = 40;            // D7を接続しているピン番号
      _bus_instance.config(cfg);               // 設定値をバスに反映します。
      _panel_instance.setBus(&_bus_instance);  // バスをパネルにセットします。
    }

    {                                       // 表示パネル制御の設定を行います。
      auto cfg = _panel_instance.config();  // 表示パネル設定用の構造体を取得します。
      cfg.pin_cs  =  9;    // 27 CSが接続されているピン番号 (-1 = disable)
      cfg.pin_rst =  8;   // 33 RSTが接続されているピン番号  (-1 = disable)
      cfg.pin_busy = -1;  // BUSYが接続されているピン番号 (-1 = disable)
      cfg.memory_width = 320;                // ドライバICがサポートしている最大の幅
      cfg.memory_height = 480;               // ドライバICがサポートしている最大の高さ
      cfg.panel_width = 320;     // 実際に表示可能な幅
      cfg.panel_height = 480;    // 実際に表示可能な高さ
      cfg.offset_x = 0;          // パネルのX方向オフセット量
      cfg.offset_y = 0;          // パネルのY方向オフセット量
      cfg.offset_rotation = 0;   // 回転方向の値のオフセット 0~7 (4~7は上下反転)
      cfg.dummy_read_pixel = 8;  // ピクセル読出し前のダミーリードのビット数
      cfg.dummy_read_bits = 1;   // ピクセル以外のデータ読出し前のダミーリードのビット数
      cfg.readable = false;       // データ読出しが可能な場合 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等でバス制御を行います)
      _panel_instance.config(cfg);
    }

    setPanel(&_panel_instance);  // 使用するパネルをセットします。
  }

};
```
```c:ESP32_S3_Mandelbrot_8Para_1.ino
#include "myLovyanGFX.hpp"

static LGFX tft;                 // LGFXのインスタンスを作成。

static uint16_t col[]{
  0x0000,  //0
  0x222A,  //1
  0x867E,  //2LightSkyBlue
  0xF81F,  //3Magenta
  0x480F,  //4Indigo
  0xFF1B,  //5MistyRose
  0xAEBC,  //6LightBlue
  0xFFFB,  //7LightYellow
  0x07FF,  //8Cyan
  0xF800,  //9Red
  0x07E0,  //10Green
  0x001F,  //11Blue
  0xFCEE,  //12LightSalmon
  0xEF31,  //13Khaki
  0xDFFF,  //14LightCyan
  0xFFE0   //15Yellow
};

//PI=3.1415926535897932384626433832795 defined in Libraly
const float toRad = PI / 180.0;//度をラジアンに変換
const float absZ = 4.0;       // 発散判定
const uint16_t W = 480;
const uint16_t H = 320;
const uint16_t X0 = 240;//W/2;// tft.width()/2
const uint16_t Y0 = 160;//H/2;// tft.height()/2
const uint16_t page = 20;
const uint16_t dp = 2;
const uint16_t YE = 320 - Y0;
const uint16_t XE = 480 - X0;

// default Mandelbrot Set -----------------------------------------
// view=2.4; CtoX = 0.0; CtoY = 0.0;
// repeat=200; page=1; dp=0;
// ----------------------------------------------------------------

// view = 0.00024; // 10000倍に拡大
// Viewport width; Viewport Center x;y; 反復上限; 画像枚数; 反復上限増分

// 風景の良い場所例

// example 1
// view = 0.00024; CtoX = -1.24997; CtoY = -0.00877;
// repeat=190; page=28; dp=2;

// example 2
// view = 0.00024; CtoX=-1.401;CtoY=-0.000178;
// repeat=300; page=19; dp=2;

const float view = 0.00120;   // viewport width(window width)
const float di = view / 480.0;// real/imaginary calculation step
                              // 1 pixel in Real and Imaginary number
const float CtoX = -1.19998;  // 現在の表示（view）の中心
const float CtoY = -0.15507; 

uint16_t repeat = 120;
uint16_t k;     // 反復上限.反復回数

float x, y, zr, zi, R, I;//複素計算変数
uint16_t re, gr, bl;     // color 0-255
uint16_t C[1000];// = 0x001F;

unsigned long now=0;
unsigned long starttime;

void setup() {
  Serial.begin(115200);
  Serial.println("begin");
  tft.init();
  tft.setRotation(1);
  tft.startWrite(); // 高速化に効果ありかも
  tft.fillScreen(TFT_BLACK);

  for( k=0;k<800;k++){
    C[k] = ColorSel(k);
  }

  starttime=millis();
  for (int i = 1; i <= page; i++) {// 反復数上限を増やしていく
    Mandelbrot();

    now = millis();
    tft.setCursor(0,0);
    tft.printf("%2dpage:%3ds",i,now/1000);

    repeat += dp*i;
  }
  // this takes 72 seconds. 
  grid();
}

void Mandelbrot(void) {
  for (int j = - Y0; j < YE; j++) {
    y = j * di + CtoY;
    for (int i = - X0; i < XE; i++) {
        x = i * di + CtoX;
        zr = 0.0;
        zi = 0.0;
        k = -1;
        do {
          k++;
          R = zr * zr - zi * zi + x;
          I = 2 * zr * zi + y;
          zr = R;
          zi = I;
          if (k >= repeat) break;
        } while ((R * R + I * I) < absZ);
        if (k == repeat) tft.drawPixel(X0 + i, YE - j, TFT_BLACK);
        else tft.drawPixel(X0 + i, YE - j, C[k]);//+k*2);//ColorSel(k));
    }
  }
}

void grid(void) {
  tft.drawLine(X0 + 100, 0, X0 + 100, tft.height(), 0xFFFF);
  tft.drawLine(X0 - 100, 0, X0 - 100, tft.height(), 0xFFFF);
  tft.drawLine(X0 + 200, 0, X0 + 200, tft.height(), 0xFFFF);
  tft.drawLine(X0 - 200, 0, X0 - 200, tft.height(), 0xFFFF);
  tft.drawLine(0, Y0 + 100, tft.width(), Y0 + 100, 0xFFFF);
  tft.drawLine(0, Y0 - 100, tft.width(), Y0 - 100, 0xFFFF);
  tft.drawLine(X0, 0, X0, tft.height(), TFT_RED);  // 赤で垂直の線を描画
  tft.drawLine(0, Y0, tft.width(), Y0, TFT_RED);
  tft.drawString("1.0",X0+192,H-8);
  tft.drawString("0",X0-2,H-8);
  tft.drawString("-1.0",X0-213,H-8);
  tft.drawString("End",0,H-8);
}

uint16_t ColorSel(uint16_t c) {
  if (c < 16) {
    return col[c];
  } else {
    re = 128 + 126 * cos(c * toRad);
    gr = 128 + 126 * cos((c + 120) * toRad);
    bl = 128 + 126 * cos((c + 240) * toRad);
    return tft.color565(re, gr, bl);
  }
}

void loop(void) {
}
```
　最後まで見ていただきありがとうございました。