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

ブレッドボード上の Arduino NANO に0.96インチ OLED を簡単に接続するためのアダプタ

最近 Arduino にI2Cインターフェイスの 0.96インチ OLED を接続したものを作ることが多いのですが、ブレッドボードの配線がコンパクトに出来なくて残念に思っていました。

Arduino NANO
ブレッドボードにArduino UNO と 0.96インチのOLEDを置いた状態です。これだけでブレッドボードのほぼ全面を使ってしまっているので、他の部品を置く場所はほとんどありません。

今回、そういう問題を解決するアダプタを作ったので紹介します。

▼OLEDのピン接続
0.96インチOLEDのピン配置
私が持っているOLEDのピン配置は全部これで、GND, VCC, SCL, SDA の順に並んでいます。なお、I2Cインターフェイスです。

これを Arduino NANO にコンパクトに接続したいのですが、なかなかうまい手がありません。

▼まずはこんな案
ダメな接続
NANOにはA6とA7ピンがあるので、これを出力にアサインしてHIGHとLOWを出力して電源供給をやってしまおうという案です。2ピン無駄になりますが、この二つはUNOには無いピンなのでほとんど使われておらず、大きな問題にはならないはずです。

これはグッドアイディアと思ってやってみたのですが、ダメでした。A6、A7ピンはアナログ入力専用でデジタル出力にはアサイン出来ないようです。つまりこの方法では電源が供給出来ませんでした。

と言うことでズルは出来なかったので、真面目に接続アダプタを作ることにしました。

追記)よく考えると、どうせ使わないアナログ入力ピンなら、ブレッドボードのジャンパーでVCCとGNDを配線しちゃう手がありました。

▼接続アダプタ
Atduino NANO と0.96インチOLED接続アダプタ
Arduino のシールドを作る場合に使うロングピンのピンソケットを加工して作りました。

8ピン用だったのを7ピンに幅を切り詰め。不要なピンを切断して除去し、電源線を配線しました。ブレッドボードには裸のリード線の部品が取り付けられるので、それらとのショートを防止するために被覆線を使った方が良いでしょう。

▼接続アダプタの回路図
アダプタの回路図
電源を本来のピンから持ってくるようにしただけだけです。

▼使っている様子
使用後
OLED を NANOの基板の上に置くことで、まとまった空き地が出来て遊び場が増えました。

別アングル
アダプタをこんなふうに挿して使います。

▼128x32画素のOLEDでも使えます
128x32画素のOLEDでも使えます
ピン配置が同じなのでこんなふうに使えます。

◆まとめ
なかなか使い易いアダプタが出来たと思います。もっと前から作っておくべきでした。唯一の難点はNANOのリセットボタンが押し難くなったことですが、全く押せないわけでは無いので妥協します。

実はもう少し大きなOLEDが使いたくて、1.3インチのOLEDを現在AliExpressに注文出しています。この1.3インチのOLEDのピン配置は0.96インチの物とは違う(電源ピンが逆)ようなのでこのアダプタは使えないようです。残念!

同じ目的で Arduino UNO 用のアダプタも作っています。こっちはもう少し複雑な仕組みになっているのですが、次回の記事で紹介予定です。

Arduino よもやま話-18(未使用ピンをまとめて設定する関数を作った)

◆未使用ピン
Arduinoでは未使用ピンは何もしないで放置しておくのが普通ですが、そうやると少し問題があることが判りました。特に気になるのが電源の消費電流の増加で、何も対策しないと約3mAも消費電流が増えてしまいます。そのあたりの話はよもやま話に書いてきましたが、重要と思われる記事へのリンクを以下に貼っていきます。
Arduino よもやま話-15 (未使用ピンの電圧変化の観察)
Arduino よもやま話-16 (未使用ピンの電圧と電源の消費電流の関係)

◆プログラムからの対策
この問題の対策は、未使用ピンを入力プルアップや出力LOWの状態に設定して電圧を確定してしまえば良く、居酒屋ガレージ日記さんのArduino やっぱり気になる放置ポートという記事で詳しく解説されています。
ここまでやれば完璧ですが、これまで pinMode( )なんちゃらとやって 1ピンずつ設定してきたので、いきなりこの書き方、つまりポート制御レジスタを直接いじる方式に変えるのは大変です。

◆未定義ピンを設定するプログラム
未定義のピンだけ空き端子処理することが出来れば良いので、そこに特化した関数を作ってみました。
// Arduino の空き端子処理関数 20200530uuPinSetup.ino
// uuPinOutputLow, uuPinOutputHigh, uupinInputPullup, uuPinInput
// 2020/05/30:割り込み対策追加 (居酒屋ガレージ日記さんご指摘)*1
// ラジオペンチ

void setup() {
// Serial.begin(115200);
// pinMode(13, OUTPUT);
uuPinOutputLow(0b01111111111100, 0b111111); // 未使用ピンをLOW出力に設定
// uuPinOutputHigh(0b01111111111100, 0b111111); // 未使用ピンをHIGH出力に設定
// uuPinInputPullup(0b01111111111100, 0b111111); // 未使用ピンを入力プルアップに設定
// uuPinInput(0b01111111111100, 0b111111); // 未使用ピンを入力に設定(デフォルトに戻す)
}

void loop() {
}

void uuPinOutputLow(unsigned int d, unsigned int a) { // 指定ピンを出力/LOWに設定(un used Pin set to Output Low)
// ビットパターンで該当ピンを指定(1で有効)。d:D13-D0, a:A5-A0
// PORTx=0, DDRx=1
unsigned int x;
uint8_t oldSREG = SREG; // ステータスレジスタ保存(割込状態保存)*1
cli(); // 割込み禁止*1
x = d & 0x00FF ; PORTD &= ~x; DDRD |= x; // D0-7
x = (d >> 8) & 0x3F; PORTB &= ~x; DDRB |= x; // D8-13
x = a & 0x003F ; PORTC &= ~x; DDRC |= x; // A0-5
SREG = oldSREG; // ステータスレジスタ復元(割込状態復元)*1
}

void uuPinOutputHigh(unsigned int d, unsigned int a) { // 未使用ピンを出力/HIGHに設定(un used Pin set to Output High)
// ビットパターンで該当ピンを指定(1で有効)。d:D13-D0, a:A5-A0
// PORTx=1, DDRx=1
unsigned int x;
uint8_t oldSREG = SREG; // ステータスレジスタ保存(割込状態保存)*1
cli(); // 割込み禁止*1
x = d & 0x00FF ; PORTD |= x; DDRD |= x; // D0-7
x = (d >> 8) & 0x3F; PORTB |= x; DDRB |= x; // D8-13
x = a & 0x003F ; PORTC |= x; DDRC |= x; // A0-5
SREG = oldSREG; // ステータスレジスタ復元(割込状態復元)*1
}

void uuPinInputPullup(unsigned int d, unsigned int a) { // 未使用ピンを入力/PULLUPに設定(un used Pin set to Input Pullup)
// ビットパターンで該当ピンを指定(1で有効)。d:D13-D0, a:A5-A0
// PORTx=1, DDRx=0
unsigned int x;
uint8_t oldSREG = SREG; // ステータスレジスタ保存(割込状態保存)*1
cli(); // 割込み禁止*1
x = d & 0x00FF ; PORTD |= x; DDRD &= ~x; // D0-7
x = (d >> 8) & 0x3F; PORTB |= x; DDRB &= ~x; // D8-13
x = a & 0x003F ; PORTC |= x; DDRC &= ~x; // A0-5
SREG = oldSREG; // ステータスレジスタ復元(割込状態復元)*1
}
void uuPinInput(unsigned int d, unsigned int a) { // 未使用ピンを入力(Hi-Z)に設定(un used Pin set to Input)
// ビットパターンで該当ピンを指定(1で有効)。d:D13-D0, a:A5-A0
// PORTx=0, DDRx=0
unsigned int x;
uint8_t oldSREG = SREG; // ステータスレジスタ保存(割込状態保存)*1
cli(); // 割込み禁止*1
x = d & 0x00FF ; PORTD &= ~x; DDRD &= ~x; // D0-7
x = (d >> 8) & 0x3F; PORTB &= ~x; DDRB &= ~x; // D8-13
x = a & 0x003F ; PORTC &= ~x; DDRC &= ~x; // A0-5
SREG = oldSREG; // ステータスレジスタ復元(割込状態復元)*1
}

居酒屋ガレージ日記さんから、「IOピンのレジスタ操作中は割り込みを禁止した方が良い」とのアドバイスを頂いたので修正しました。
なお旧版はこちら → 0528版(割り込み対策無し版)

つまり、空きピンの処理用に下記の関数を作った訳です。
・出力でLOW     uuPinOutputLow(d, a);
・出力でHIGH     uuPinOutputHigh(d, a);
・入力でプルアップ  uupinInputPullup(d, a);
・入力         uuPinInput(d, a);

空き端子を行いたいピンを引数のビットで指定する方式で、引数の d が D0~D13、引数の a が A0~A5に対応しています。 1 が指定されていればそのポートの空き端子処理を行い、0 だったら、元の設定のままで何もしないという動作を行います。

pinModeの設定が終わったあたりで未使用ピンに対してこの関数を使って空き端子処理を行えばよいと思います。あるいは最初にこの関数で全ピンの空き端子処理を行い、後でpinModeで変更するという手順でもいけると思います。どちらの場合でもグリッジが出る可能性があります。

どの空き端子処理を選択するかは好みに合わせて使えば良いと思いますが、私は居酒屋ガレージ日記さんの作法にならって、LOWを出力、つまり uuPinOutputLow を使っていこうかと思います。なお、入力 (uuPinInput) は無くても良いのですが、コードの対称性が悪くなるので作っておきました。設定を途中でにデフォルトに戻したい場合などで、あると便利かも知れません。

なお、必要なコードだけソースに書けば良く、使っていないコードは削除しても大丈夫です。(書いてあっても使っていなければコンパイラの段階で無かったことになるので悪影響はありません。)

◆効果
上記のプログラムを動かしたときのCPUの消費電流は約11mAでした。一方で、未使用ピンの処理無し、つまり uuPinOutputLow() をコメントアウトした状態の消費電流は 14mA、つまり消費電流を 3mA減らす効果が確認出来ました。
他の関数を使っても消費電流の低減効果は同じでした(デフォルト状態と同じuuPinInput() は除く)

◆電流波形
以上の確認はDMMによる測定と共に、オシロで電流波形も見たものです。オシロの波形で面白い結果が出ているので以下に示します。なお、この波形はCPUの電源ピンに入れた1Ωのシャント抵抗の電圧波形、つまり1mVは1mAに相当しています。

空き端子処理無しの状態
CPU電源電流波形の測定
約14mA流れています。

空き端子処理(OutputLow)ありの場合
出力LOWに設定
消費電流が10.5mAまで下がっています。

再び空き端子処理無しの状態
ACラインノイズの影響
I/Oコネクタに指を近付けると電源電流がACライン波形で変調されました。
空きピンがACラインの影響を受けて電位が変化し、そのために消費電流が変化しているのだと思います。空き端子処理をしていない場合の消費電流の値がフラフラするのが気になっていたのですが、どうもこういう現象が起きていて値が安定しなかったようです。

◆プログラム書き込み電流と同時観察
せっかくなのでCPUのフラッシュメモリーに書き込む様子と合わせて電流波形を観察してみました。

・空き端子処理無しの場合
プログラム書き込み、デフォルト
25mAくらいのスパイク電流がフラッシュに書き込みを行っているタイミングだと思います。その電流が6mAくらいまで減った後で、ユーザープログラムに制御が移り、消費電流14mAの状態で動き始めるのだと思います。小さな周期的な波は、AC電源からの誘導があるためだと思います。

・空き端子処理あり(OUTPUT LOW)
プログラム書き込み、OUTPUT LOW
こちらはユーザープログラムが動き出した後は消費電流が約11mAに減っています。これは前記した話と同じですが、さらにユーザープログラムが起動した後の波形は平坦になっています。これは空き端子処理を行ったためにノイズの影響を受け難くなった効果が表れているのだと思います。

◆まとめ
好みの問題はあるでしょうが、空き端子処理が必要な場合は、今回の記事の関数を使っていきたいと思います。何か問題があったらその時に修正したいと思います。

マイコンはデジタル処理の典型的なデバイスですが、そこにAC電源からの誘導、つまりハムが乗るとは油断が出来ないです。

Arduino NANO にはA6,A7ピンがありますが、この2つのピンにはデジタル入出力機能が無いので初期化は不要なはずです。

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回目でその様子を見ることが出来ます。
カレンダー
05 | 2020/06 | 07
- 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 - - - -
プロフィール

ラジオペンチ

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

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