目次 [ Contents ]
u8glibライブラリを使いArduinoでOLEDを扱う方法を紹介してきましたが、活用するにあたって役立ちそうなことをまとめておきます。重複していることもありますが、ご了承ください。
配線
I2C接続のディスプレイと可変抵抗器を使います。SPIの場合は前回までの例にならって変更してください。
基本スケッチ
関数を説明するために下記のスケッチをベースにします。
#include <U8glib.h> //U8GLIB_SSD1306_128X64 u8g(4, 5, 6, 7); U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_DEV_0 | U8G_I2C_OPT_NO_ACK | U8G_I2C_OPT_FAST); // Fast I2C / TWI byte disp_x; byte disp_y; void setup() { disp_x = u8g.getWidth(); disp_y = u8g.getHeight(); u8g.setFont(u8g_font_helvR10r); //1.6KByte/17x22 u8g.setColorIndex(1); // pixel on pinMode(A0, INPUT); Serial.begin(9600); } int VOL_READ() { static int vol_val = 0; vol_val = 0.9 * vol_val + 0.1 * analogRead(A0); return vol_val; } void loop() { byte val; u8g.firstPage(); do { val = map(VOL_READ(), 0, 1013, 0, 14); } while (u8g.nextPage()); }
スクリーンのサイズを調べる
画面の横ピクセル数:u8g.getWidth()
画面の縦ピクセル数:u8g.getHeight()
接続されているスクリーンのピクセル数が取得できます。その数値を格納するためにdisp_x、disp_yという変数を宣言し代入しています。試しに、
void loop() { Serial.print("X:"); Serial.print(disp_x); Serial.print(" Y:"); Serial.print(disp_y); Serial.print(" "); Serial.println(val); }
とするとシリアルモニタにそれぞれの値が帰ってきます。
配列と同じで0からのカウントなので、このスクリーンで実際に扱う最大値は(128, 64)ではなく、(127, 63)になります。この記事のスケッチではそこら辺は適当ですが、厳密に位置決めしたい方はご注意ください。
図形の描画
まずは簡単な図形の描き方から
直線図形
点を描く:
u8g.drawPixel(x,y)
val = map(VOL_READ(), 0, 1013, 0, disp_x); u8g.drawPixel(val, disp_y / 2);
線を描く:
u8g.drawLine(start x, start y, end x, end y)
val = map(VOL_READ(), 0, 1013, 0, disp_x); u8g.drawLine(0, disp_y / 2, val, disp_y / 2);
それぞれ始点と終点の座標を指定するだけですが、さらに簡略化した関数もあります。
u8g.drawHline(start x, start y, length)
u8g.drawVline(start x, start y, length)
lengthで線の長さを指定します。Hlineは横線で描画は常に左から右、Vlineの場合は縦線で上から下です。
val = map(VOL_READ(), 0, 1013, 0, disp_x); u8g.drawVLine(0, disp_y / 2, val);
四角を描く:
u8g.drawBox(x, y, width, height)
u8g.drawFrame(x, y, width, height)
val = map(VOL_READ(), 0, 1013, 0, disp_x); u8g.drawBox(0, 0, val, val / 2);
Boxは塗りつぶしの四角、Frameは枠線の四角です。線を描くときと違い、引数の後半は長さと、高さであることに気をつけてください。
角丸の四角を描く:
u8g.drawRBox(x, y, width, height, radius)
u8g.drawRFrame(x, y, width, height, radius)
val = map(VOL_READ(), 0, 1013, 0, 14); u8g.drawRBox(disp_x / 4, disp_y / 4, disp_x / 2, disp_y / 2, val);
三角形を描く:
u8g.drawTriangle(ax, ay, bx, by, cx, cy)
val = map(VOL_READ(), 0, 1013, 0, 64); byte sx = disp_x / 2; byte sy = disp_y / 2; u8g.drawTriangle(sx, sy - val / 2, sx + val, sy + val / 2, sx - val, sy + val / 2);
円の描画
円枠線:drawCircle(x, y, 半径, option)
円塗り:drawDisc(x, y, 半径, option)
楕円枠線:drawEllipse(x, y, x半径, y半径, option)
楕円塗り:drawFilledEllipse(x, y, x半径, y半径, option)
val = map(VOL_READ(), 0, 1013, 0, disp_y); u8g.drawDisc(disp_x / 2, disp_y / 2, val);
optionは省略可能ですが、ここへ数値を入れるか、下記の変数を書き込むと弧を描けます。
option変数:
U8G_DRAW_UPPER_RIGHT
U8G_DRAW_UPPER_LEFT
U8G_DRAW_LOWER_LEFT
U8G_DRAW_LOWER_RIGHT
U8G_DRAW_ALL
これらはu8glib内でdefineされているもので、シリアルモニタで確認すると、
void loop() { Serial.print("UPPER_RIGHT:"); Serial.println(U8G_DRAW_UPPER_RIGHT); Serial.print("UPPER_LEFT:"); Serial.println(U8G_DRAW_UPPER_LEFT); Serial.print("LOWER_LEFT:"); Serial.println(U8G_DRAW_LOWER_LEFT); Serial.print("LOWER_RIGHT:"); Serial.println(U8G_DRAW_LOWER_RIGHT); Serial.print("DRAW_ALL:"); Serial.println(U8G_DRAW_ALL); delay(1000); Serial.println(); }
結果:
UPPER_RIGHT:1
UPPER_LEFT:2
LOWER_LEFT:4
LOWER_RIGHT:8
DRAW_ALL:15
つまりオプションは0-15の数値で入れても同様になります。
val = map(VOL_READ(), 0, 1013, 0, 15); u8g.drawDisc(disp_x / 2, disp_y / 2, disp_y / 2, val);
文字の描画
文字の表示は以前の記事で書いています。ただ、これより簡単に描画する方法もあります。というかありました。
位置の指定&文字表示
位置の指定:u8g.setPrintPos(x, y);
文字の描画:u8g.print(value);
u8g.setPrintPos(disp_x, disp_y); u8g.print(val);
これはSerial.printと同じように扱え、キャラ型も数値も関係ないので簡単です。
但し、前回のようにu8g.drawStr()を使う方が都合のいいことがあります。用途に合わせて使い分けるといいかもしれません
文字のサイズを調べる
実はdrawStrには返り値があり、文字の描画にかかった幅が得られます。これを利用すると、文字列の長さに沿って他の描画を調節できるようになります。
void loop() { char texts [5][8] = {"KIRIN", "Suntory", "Asahi", "Ebisu", "Sapporo"}; byte rnd = random(0, 4); u8g.firstPage(); do { byte str_sft = u8g.drawStr(0, 32, texts[rnd]); u8g.drawStr(str_sft + 10, 32, "Beer!"); } while (u8g.nextPage()); delay(1000); }
乱数で変わる文字列の返り値をstr_sftで受けて、余白の10pixelを足した座標で次のBeer!を表示しています。こんな感じに文字列の長さ、フォントの変更に臨機応変で対応出来るようになるので、非常に便利です。
また、表示する文字列の幅や高さを取得する関数もあります。
文字の幅を取得:u8g.getStrWidth(文字列);
文字の高さを取得:u8g.getFontAscent();
これによって文字列のセンタリングや枠線を描くのが簡単になります。
byte x = u8g.getStrWidth(texts[rnd]); byte y = u8g.getFontAscent(); u8g.drawStr(64 - x / 2, 32, texts[rnd]); u8g.drawFrame(64 - x / 2 - 5, 32 - y / 2 - 10, x + 10, y + 10);
ディスプレイの中心64から取得した文字列の長さの半分を引けば、64を中心に配置することになります。文字列の右合わせなんかもこの理屈で出来るので試してみてください。
高さに関しては厳密に言うとgetFontAscentは正確な高さではないですが、これだけでも大まかなサイズが分かります。詳しく知りたければこちらで調べてください。
文字の回転
drawStrなら文字を回転させて表示することもできます。ただし、これは各角度別の関数になっています。
u8g.drawStr (x, y, 文字列)
u8g.drawStr90 (x, y, 文字列)
u8g.drawStr180 (x, y, 文字列)
u8g.drawStr270 (x, y, 文字列)
文字の拡大
指定しているフォントを拡大表示することができます。
u8g.setScale2x2();
u8g.undoScale();
フォントの搭載容量を節約しつつ、表示にバラエティがつけられるので便利なんですが、いくつか注意点があります。
- 表示が大きくなるだけなので、ビットマップも荒くなる
- 座標の指定も倍になる(x座標5にすると10に描画される)
- 終了後にはundoScaleで戻さないと適用され続ける
u8g.drawStr(0, 15, "Small"); u8g.setScale2x2(); u8g.drawStr(0, (15 + u8g.getFontAscent() + 10) / 2, "Large"); u8g.undoScale();
画面の回転
文字の回転ではなく、画面全体を回転させることができます。
u8g.setRot90();
u8g.setRot180();
u8g.setRot270();
u8g.undoRotation();
u8g.drawStr(0, 15, "text1"); u8g.setRot90(); u8g.drawStr(0, 15, "text2"); u8g.setRot180(); u8g.drawStr(0, 15, "text3"); u8g.undoRotation();
こちらもundoRotationで戻してあげないと適用されたままになるのでご注意ください。
その他
u8glibの関数ではなく、使う上で役立つであろうことに触れます。
フレームレート
前の記事にも書いた通りu8glibはdo〜while文で描画しているので、本来させたい処理の間に組み込んでいると、足手まといになりやすいです。そこで、一定間隔で描画をリフレッシュするような仕組みを導入すると便利です。僕はmillis()関数で時間を計り、インターバルを作るようにしています。
void loop() { static unsigned long time_oled = millis(); int val = VOL_READ(); if ((millis() - time_oled) > 1000) { u8g.firstPage(); do { u8g.setPrintPos(disp_x / 2, 18); u8g.print(time_oled); u8g.setPrintPos(0, 55); u8g.print(val); } while (u8g.nextPage()); time_oled = millis(); } }
time_oled変数を用意して、(現在時間 – 前回時間time_oled)が一定時間(1000ミリ秒)を超えていたら、u8glibの描画に入ります。その描画終了後にtime_oledへ前回時間として今を記憶させます。これで、周期的に描画する処理にできます。
Grid画面
最後に自分が画面レイアウト時に使用していたGrid画面のスケッチを紹介します。u8glibのdo〜while文の中、必要に応じてGRID(1);で呼び出す形で使ってました。役に立つようであればお使い下さい。
#define DISPX u8g.getWidth() #define DISPY u8g.getHeight() void GRID(boolean det) { byte reso = 8; //main flame for (byte ii = 0 ; ii < 2 ; ii++) { for (byte i = 0 ; i < 4 ; i++) { u8g.drawFrame(i * (DISPX / 4), ii * (DISPY / 2), (DISPX / 4), (DISPY / 2)); } } if (det) { //dot grid for (byte ii = 0 ; ii < reso ; ii++) { byte lny = ii * (DISPY / reso); for (byte i = 0 ; i < (reso * 4) ; i++) { byte lnx = i * (DISPX / (reso * 4)); u8g.drawLine(lnx, lny, lnx, lny); } } //+ for (byte ii = 0 ; ii < (reso / 2) ; ii++) { byte lny = ii * (DISPY / (reso / 2)); for (byte i = 0 ; i < reso ; i++) { byte lnx = i * (DISPX / reso); u8g.drawLine(lnx - 2, lny, lnx + 2, lny); u8g.drawLine(lnx, lny - 2, lnx, lny + 2); } } } }