google-site-verification: google3bd66dd162ef54c7.html
FC2ブログ

Arduinoで 0.96インチ モノクロ OLED (128x64) にマンデルブロ集合を描く

◆まえがき
ここまで0.96インチのOLEDを使ってあれこれ遊んで来ました。このデバイスはグラフィック表示が可能なのでもっと相応しい画像を表示させてみたい、ということでマンデルブロ集合を描いてみました。

ちなみに、同じことを以前 320x24画素のカラー液晶を使ってやったことがあります。今回はその時より画面の画素数が少なくなり、しかもモノクロ画面なので表現力は数段落ちます。しかし情報量が減った分だけ高速化できるはずです。さて、どうなるでしょうか、

◆回路図
回路図
これは Arduino NANO を使った場合の回路図で、凄く簡単な回路です。なお、スイッチは省略してもプログラムは動きます。(途中で止められなくなるだけです)

◆実際の状態
Arduino NANO

UNOでやるなら
Arduino UNO
かなり強引ですが、オス/メスのジャンパー線を使ってこんなふうにやれば、OLEDをUNOに直結することが出来ます。

◆プログラム
動画を YouTube に投稿したので、コメントは日本語に英語を併記する形にしてみました。
/* 0.96インチOLEDにマンデルブロ集合を描く write mandelbrot set on 0.96inch OLED
2020/05/26 ラジオペンチ http://radiopench.blog96.fc2.com/
*/

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define zRatio 2.0 // ズーム比率 zoom up ratio

Adafruit_SSD1306 oled(128, 64, &Wire, -1); // device name is oled

struct body { // 条件表の構造 structure of table
int s_pN; // プログラムNo. prog. No.
float s_x; // X座標 x position
float s_y; // Y座標 y position
float s_mag; // 初期倍率(表示サイズ)initial plane size
int s_zN; // ズーム回数 zooming times
int s_iN; // イタレーション回数 iteration times
};

struct body recipe[10] = { // 表示レシピの設定表 spec. table for display
{ 1, -0.65, 0.0, 0.0625, 4, 100}, // 全体像 over view of mandelbot set
{ 2, -0.22255579, 1.119984429, 1.0, 19, 100}, // 本体上部のエンドレスなヒゲ endless whisker
{ 3, -1.2556, 0.380974, 1.0, 7, 100}, // 隠れた集合hiddon shape
{ 4, -1.73959677, 2.562320E-4, 100.0, 9, 100}, // ワーム warm
{ 5, -1.941239, 0.0, 0.5, 10, 100}, // 左端付近に隠れた集合 flower and mandelblot set
{ 6, -1.7489405, 0.0, 2000.0, 5, 100}, // 魚の骨と花 fish bone and flower(high mag)
{ 7, -1.26233923, 0.40812936, 2.0, 9, 100}, // 左の丸の上に隠れた集合 hidden set
{ 8, 0.269194959, 0.00440711, 1000.0, 5, 100}, // アンモナイトAmmonite
{ 9, 999.9, 0.0, 1.00, 1, 100} // 終了 end definition
};

void setup() {
pinMode(12, INPUT_PULLUP); // 一時停止スイッチ pause switch (pause when connect to GND)
pinMode(13, OUTPUT); // 計算中表示LED calculation status display LED
Serial.begin(115200);

oled.begin(SSD1306_SWITCHCAPVCC, 0x3C); // OLEDのI2Cアドレス(モジュールの設定に合わせる)set I2C address for the module
startScreen(); // 開始画面表示 display start screen
}

void loop() { // main
int px, py; // 画面の作画座標 screen address
float x0, y0; // マンデルブロ空間の中心位置 center address of screen
float mag, pitch; // サイズ display size variable
float x, y; // マンデルブロ判定座標 mandelbrot judgement position
int pN; // プログラム番号 program No.
int mResult; // マンデルブロ領域判定結果 mandelbrot judgement result
int zN; // ズーム回数 zooming Number
int col; // 表示色 dosplay color
int iN; // イタレーション回数 iteration number

for (int i = 0; i < 10; i++) { // 設定されたパターンを読み出して順に実行 execute the pattern in sequence
pN = recipe[i].s_pN; // プログラムNo. program nunber
x0 = recipe[i].s_x; // X座標 x position
if (abs(x0) >= 900) { // もし値が900以上なら if the value over 900
break; // 中止(最初に戻る) end this loop and return begginig
}
y0 = recipe[i].s_y; // Y y position
mag = recipe[i].s_mag; // 初期倍率(サイズ)initial magnification(size)
zN = recipe[i].s_zN; // ズーム回数 zooming number of times
iN = recipe[i].s_iN; // イタレーション回数 iteration numbers

oled.clearDisplay(); // 先頭で画面を全クリア clear display
writeBorder(); // 外形枠を描く write border graphic area

for (int z = 0; z <= zN; z++) { // 指定のズーム回数繰り返す execute specified timescount
pitch = 1.0 / (30.0 * mag); // y軸サイズ=±30画素(61画素)pitch value calc.
Serial.print(pN); Serial.print(", "); Serial.println(mag, 2);
paraDisp(pN, z, x0, y0, mag); // display numeric informations
oled.display();

for (py = 1; py <= 61; py++) { // Y軸画面サイズ(1-61) display position of x
for (px = 40; px <= 126; px++) { // X軸画面サイズ指定(40-126) display position of y
x = (83 - px) * pitch - x0; // calculate the position value
y = - (31 - py) * pitch - y0;
mResult = mandelbrot(x, y, iN); // マンデルブロ判定 Mandelbrot judgement
if (mResult == 0) { // 内側なら if inside of the area
col = BLACK; // 黒で塗る paint black
} else { // 外側なら if outside and,
if ((mResult % 2) == 0) { // 判定結果が偶数なら、if result is even number
col = WHITE; // 白!paint white
} else { // そうでなかったら、odd number, so
col = BLACK; // 黒!paint black
}
}
oled.drawPixel(px, py, col); // 画面にプロット plot result on OLED
} // x loop completed
oled.display(); // 実際の表示(1行ずつ更新)display execute (line by line)
}
delay(1000); // 次の面の開始までちょっと待つ wait
while (digitalRead(12) == LOW) { // ピン12がLOW(ONだったら)if halt switch is ON
} // wait
mag *= zRatio; // 拡大 zoom up next show
}
delay(2000); // プログラムの境で長めに待つ long wait between program
} // end of main
}

int mandelbrot(float a, float b, int maxN) { // マンデルブロ領域判定, Mandelbrot area judgment
float xM = 0.0, yM = 0.0; // xMemory,yMemory
float x1, y1; // temporary register for calc.
for (int n = 1; n < maxN; n++) { // 指定回数までチェック, check up to specified times
digitalWrite(13, HIGH);
x1 = xM * xM - yM * yM - a; // element of calc.
y1 = 2.0 * xM * yM - b;
digitalWrite(13, LOW);
if ( x1 * x1 + y1 * y1 > 4.0 ) { // 発散していたら, if it was diverging (over 4)
return n; // その回数を返す return the count
}
xM = x1; // 次のループ用に値を保存 Save value for next loop
yM = y1;
}
return 0; // 指定回数内で発散しなければ(収束したとみなす)Returns zero if it dooes't diverge
}

void paraDisp(int i, int z, float x0, float y0, float mag) {
oled.fillRect(0, 0, 37, 64, BLACK); // erase character display area
oled.setCursor(0, 0);
oled.setTextColor(WHITE);
oled.print("P:"); // disp prog. No.
oled.print(i); oled.print(","); oled.println(z);
oled.println("mag:"); // disp magnification
if (mag < 1) {
oled.println(mag, 2); // *.**
} else {
oled.println(mag, 0); // **
}
oled.println();
oled.println("x:"); // x poition
oled.println(x0, 3);
oled.println("y:"); // y position
oled.println(y0, 3);
}

void writeBorder() { // write graphic area border line
oled.drawFastHLine( 39, 0, 43, WHITE); // upper left hrizontal line
oled.drawFastHLine( 85, 0, 43, WHITE); // upper right
oled.drawFastHLine( 39, 62, 43, WHITE); // lower left
oled.drawFastHLine( 85, 62, 43, WHITE); // lower right
oled.drawFastHLine( 37, 31, 3, WHITE); // left center short mark (y0)
oled.drawFastHLine( 127, 31, 1, WHITE); // right center mark (y0)

oled.drawFastVLine( 39, 0, 30, WHITE); // left upper
oled.drawFastVLine( 39, 33, 30, WHITE); // left lower
oled.drawFastVLine(127, 0, 30, WHITE); // right upper
oled.drawFastVLine(127, 33, 30, WHITE); // right lower
oled.drawFastVLine( 83, 0, 1, WHITE); // upper center dot (x0)
oled.drawFastVLine( 83, 62, 2, WHITE); // lower center short line (x0)
}

void startScreen() { // 開始時表示画面
oled.clearDisplay();
oled.setTextColor(WHITE);
oled.println("Mandelbrot set");
oled.println("version 0.7"); // version No.
oled.println("by radiopench1"); //
oled.display();
delay(2000);
}

同じものをファイルでも置いておきます。20200526Mandelblot.ino (Shift-JIS エンコードになっています)

スイッチをONにしておくと、その画像の作画が終わった時点で一時停止します。スイッチOFFで再開。
作図条件はrecipi[] の構造体で定義していて、ここをいじれば表示内容を変更することが出来ます。

画面座標図 (クリックで別窓に拡大)
画面マップ

◆表示の例
プログラムを動かすと見えてくる画像の中から面白そうなものをいくつか紹介します。なお、画面の左側に作図条件などが書いてあります。

マンデルブロ集合の全体像
1-4全体像 

4-8

・花
5-10

・魚の骨
6,0 魚の骨

・隠れた集合
7,9

8,0

・アンモナイト
8,3

◆動画

マンデルブロ集合に接近するとプログラムのループ数が増えるため表示速度が遅くなります。そのあたりを含めて動画を見ると良いと思います。赤色の LED は計算中は光るようになっています。
動画の後半はもの凄いスピードで表示が進みますが、これは (iPhone で) タイムラプス撮影したためです。実際には18分かかるので、約36倍の早送りで見ていることになります。

◆まとめ
・非力なCPUと粗いディスプレイですが、それなりに楽しめる物が出来たと思います。

・もっと強力なCPUを使えば、より面白い物に進化させることが出来ると思います。どなたかやってみませんか?

・説明が後になってしまいましたが、モノクロディスプレイでも発散回数の偶数・奇数で色分けすることでマンデルブロ集合とその周辺分布の様子を楽しむことが出来ます。

・マンデルブロ集合の面白そうな場所をこの記事の仕掛けだけで探すのは大変です。そこで、タブレットのアプリを使って、事前にその座標を調べるというズルをやっています。

・拡大倍率がおよそ数十万倍以上で計算精度不足で画像がギザギザになる現象が発生します。これはArduinoの浮動小数点は32ビットで計算されているため、精度不足が現れているのだと思います。レシピ2の19回目でその様子を見ることが出来ます。
関連記事

コメントの投稿

管理者にだけ表示を許可する

カレンダー
06 | 2020/07 | 08
- - - 1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31 -
プロフィール

ラジオペンチ

Author:ラジオペンチ
電子工作を中心としたブログです。たまに近所(東京都稲城市)の話題など。60過ぎて視力や器用さの衰えを感じつつ日々挑戦!
コメントを入れる時にメールアドレスの記入は不要です。なお、非公開コメントは受け付けていません。

記事が気に入ったらクリックを!
最新記事
カテゴリ
最新コメント
リンク
FC2カウンター
検索フォーム
月別アーカイブ
RSSリンクの表示
QRコード
QRコード