マンデルブロー集合が成長してるかのように。えっ、なんか動いた？変化してるよ💦

　20数年前、当時のパソコン上でVisualBASIC6.0を使いフラクタルやカオスを描いていました。現在、ディスプレイモジュールも65Kカラーで画素数が480x320に達し、マイコンの演算も速くノートPCとさほど変わらぬ環境になったので、マンデルブロー集合をやってみようと思いました。使用したのは次。

:::note
・ESP32（TTGO LoRa32ｰOLED. Arduino IDEでのボード名)
・3.5" TFT SPI 480x320 V1.0 (IlI9488, XPT2046, with SD card slot)
・TFT_eSPI ライブラリ（タッチ機能で目標地点を拡大表示しようと当初は考えていた。LovyanGFXではタッチ機能がうまくセットアップできなかったのでこちらにした）
:::
![Mandelbrot1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3189926/27586bfc-73df-2c6b-50e4-377571e22bcc.jpeg)
ESP32とディスプレイモジュールのSPIでの接続は以下の通り。

|ESP32|ディスプレイ部|タッチパネル部(不使用ですが)|
|---|---|---|
|4||T_CS|
|5|CS||
|18|SCK|T_CLK|
|23|SDI(MOSI)|T_DIN|
|19||T_DO|
|27|DC/RS||
|32 or RST|RESET||
|3V3|LED||
|GND|GND||
|3V3|VCC||

　"RGB 65K color"が使えとても綺麗です。なので昔のように８色や16色ではなく、ほぼフルカラーかつグラデーションで色を付けることにします。さらに静止画を表示するだけではつまらないので、PCでも出来なかった（私のPCと技術では）動きを付けてみようと思いました。そこで、発散判定する反復上限回数を少しずつ増やすことを考えました。例えば

:::note
1回目・・・120回反復
　abs(z)＞4.0→発散→120回中何回目だったかでpixel色付け
　　　　　発散せず→pixelを黒
2回目・・・140回反復
　abs(z)＞4.0→発散→140回中何回目だったかでpixel色付け
　　　　　発散せず→pixelを黒
　・・・
:::
しかし問題なのは、画像１枚の表示に現状20秒ほど要します。少しでも高速化するにはどうしよう？　考えたのは、

:::note
既に発散判定されている座標(Z、pixel、黒以外の色で塗られている)は計算せず飛ばし、黒のpixel(座標、Z)のみ計算する
:::
ことにしました。これで少し速くなったような気が…(?)。しかし、どうも働いていないよう。次は、SPIの動作速度を上げる。つまり

:::note
```libraries/TFT_eSPI/User_Setup.h (about 360th line)
#define SPI_FREQUENCY  40000000
```
:::
としたら、１枚表示８秒程に改善され実用的になりました。
![Mandelbrot4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3189926/cffd883c-a58d-bf18-665c-93e38021a51d.jpeg)
反復回数120～500までの成長(?)するマンデルブロー集合を20枚表示させると132秒程になりました。（80MHzでも動作しましたが、速度的にほとんど変わらぬ上、画面が少し乱れました。）
　稚拙な作品ですが、ほぼ黒の画面の中にカラフルな画像が浮かび上がってくる様子に満足できました。
　なお、風景の美しい場所を探すのに手間が掛かりました。PC上の昔の自分のプログラムを使い、良さそうな場所をピックアップしました。他にも変化の美しい場所は探せば一杯あるはずです。
　またやる気が出たら、ジュリア集合などの他のフラクタル画像やカオス図形もやってみようかと思います。以下にArduinoIDE 2.1.1でのスケッチを示します。

【追記　投稿後に "tft.readPixel(int x,int y)" が使えることが解ったので記しておきます。SPI読込みの周波数を次のように20MHzから10MHzに落としたところ、Pixelの色を読取れるようになりました。しかし、そうすると読込みに時間を取られ反って動作速度が落ちたので実用的ではありませんでした。】

:::note
```libraries/TFT_eSPI/User_Setup.h (about 370th line)
// Optional reduced SPI frequency for reading TFT
#define SPI_READ_FREQUENCY  10000000 // 20000000 in default
```

:::

```MandelbrotGrows.ino
#include <TFT_eSPI.h> 
                // 必要なPin設定は libraries/TFT_eSPI/User_Setup.hで行う
                // SPI_Speed Up 20MHz(in default) to 40MHz
                // #define SPI_FREQUENCY  40000000 in about 360th line
TFT_eSPI tft = TFT_eSPI();// also work with RST pin -> board's RST

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.00096; 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) {
}
```