Grove Beginner KitでのArduino入門 その8 OLEDディスプレイ

概要

前回はアナログ入力のマイクを使いました。今回はI2C接続のOLEDディスプレイになります。

I2Cとは?

これでは信号線が1本のみの通信でしたが、I2Cは2本つかって通信をするプロトコルです。クロック用の線とデータ用の線があります。特徴として、複数のI2Cデバイスを同時に接続することが可能です。Arduino UNOがI2Cホストとなり、複数のI2Cデバイスに対して通信をする形になります。I2Cホストは1つだけですが、I2Cデバイスは複数接続可能です。ただし、I2Cデバイスはアドレスで管理されており、同じアドレスのI2Cデバイスは同時に利用することができません。

I2Cデバイスのアドレスはセンサーごとに異なっており、同じ種類のセンサーを同時に使うと同じアドレスなのでバッティングしてしまいます。違うセンサーでもたまに同じアドレスの場合があるので注意して利用してください。I2Cデバイスによっては、アドレスの変更が可能なものがありバッティングした場合には他のアドレスに移動することで同時に使える場合もあります。

OLEDディスプレイとは?

液晶に似ているのですが、原理的にはちょっと違います。液晶は画面のドット自体は発光しませんので、バックライトで明るくすることで色がわかります。OLEDはLEDなので画面のドット自体が発行しています。小さなLEDが集まったような構造になっています。

ただし、使う上ではあまり気にする必要はありません。小さい画面はOLEDが多く、ある程度大きくなると液晶が増えてきます。若干扱い方に差はありますが、ライブラリが吸収してくれますので同じように扱うことが可能です。

OLEDライブラリのインストール

OLEDを操作する場合、非常に面倒な初期化や処理が必要になります。そのため通常はOLED用のライブラリを利用して操作を行います。Arduino IDEの「ツール」→「ライブラリを管理」を開きます。

ライブラリマネージャが開きますので「U8g2」で検索をかけます。

複数のライブラリがでてきますが、「U8g2」を選択してから右下にある「インストール」を押します。U8g2は非常に有名なライブラリで、モノクロ液晶はたいていサポートしています。逆にカラーには対応していませんので注意してください。

スケッチ例(テキストモード)

#include <Arduino.h>
#include <U8x8lib.h>

U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(/* reset=*/ U8X8_PIN_NONE);

void setup(void) {
  u8x8.begin();
  u8x8.setFlipMode(1);
}

void loop(void) {
  u8x8.setFont(u8x8_font_chroma48medium8_r);
  u8x8.setCursor(0, 0);
  u8x8.print("Hello World!");
}

上記がスケッチ例になります。利用している液晶はSSD1315ですが、SSD1306として動かしているようです。u8x8というクラスを定義して、動かしています。u8x8は文字だけの対応で、u8g2はグラフィックもサポートしているライブラリのようです。このスケッチは文字だけみたいですね。

実行結果

こんな感じで表示されました。

初期化

u8x8.begin();

引数なしで、begin()を呼び出すことで初期化するようです。

画面の向き

u8x8.setFlipMode(1);

上記で上下を反転するモードに設定しています。0で普通で、1で180度ひっくり返ります。画面を表示する向き次第なので、使う方向に合わせて使います。画像が使える画面の場合には横方向にも回転できる場合がありますが、テキスト用の画面は上下のみのようですね。

u8x8.setFlipMode(0)で初期化すると、上記のように逆さに表示されます。

フォントの指定

  u8x8.setFont(u8x8_font_chroma48medium8_r);

フォントを指定しています。

u8x8.setFont(u8x8_font_px437wyse700a_2x2_r)と違うフォントを指定した場合です。横幅が入らなかったのでHello\nWorldを表示しています。

使えるフォントは非常にたくさんあり、列挙することもできないぐらいです。上記のファイルにあるU8X8_FONT_SECTION(“u8x8_font_amstrad_cpc_extended_f”)の中の文字列がフォント名になります。現状で131種類ありました。

カーソルの移動

u8x8.setCursor(0, 0);

ドット単位での表示場所を指定します。テキスト描画をすると表示した文字の分だけカーソルがずれていくはずです。ただし、横幅に入らないぐらい大きな文字を表示した場合はおかしな表示になっていました。

テキスト描画

u8x8.print("Hello World!");

printで文字列を描画できます。ただし、printfは使えません。

  char str[128];
  snprintf(str, sizeof(str), "1 + 1 = %d\n", 1 + 1);
  u8x8.print(str);

printfを使いたい場合には、一度snprintfなどを使って文字列を作成してから表示してあげる必要があります。

スケッチ例(グラフィックモード)

#include <Arduino.h>
#include <U8g2lib.h>

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);

void setup(void) {
  u8g2.begin();
  u8g2.setFlipMode(1);
}

void loop(void) {
  u8g2.clearBuffer();                    // clear the internal memory
  u8g2.setFont(u8g2_font_ncenB08_tr);    // choose a suitable font
  u8g2.drawStr(0, 10, "Hello World!");   // write something to the internal memory
  u8g2.drawLine(0, 16, 128, 64);         // Draw line
  u8g2.sendBuffer();                     // transfer internal memory to the display
  delay(1000);
}

こんな感じで対応するu8g2クラスを使うことができました。こちらはLineなどの画像を使うことも可能です。

テキストだけであればu8x8の方が楽だと思いますが、細かい表示を考えるとu8g2の方がいいのかもしれません。ただしu8g2はちょっとサイズが大きいので、処理を作り込むとメモリが足りなくなる可能性があります。

最大32256バイトのフラッシュメモリのうち、スケッチが9330バイト(28%)を使っています。
最大2048バイトのRAMのうち、グローバル変数が1549バイト(75%)を使っていて、ローカル変数で499バイト使うことができます。
スケッチが使用できるメモリが少なくなっています。動作が不安定になる可能性があります。

上記のコードで、すでに75%のメモリを利用されています。

最大32256バイトのフラッシュメモリのうち、スケッチが6086バイト(18%)を使っています。
最大2048バイトのRAMのうち、グローバル変数が446バイト(21%)を使っていて、ローカル変数で1602バイト使うことができます。

一番最初のスケッチ例は上記の結果でした。U8X8の方がいいのかな?

I2Cスキャナ

#include <Wire.h>

void setup() {
  Wire.begin();

  Serial.begin(115200);
  while (!Serial);
  Serial.println("\nI2C Scanner");
}

void loop() {
  int nDevices = 0;

  Serial.println("Scanning...");

  for (byte address = 1; address < 127; ++address) {
    Wire.beginTransmission(address);
    byte error = Wire.endTransmission();

    if (error == 0) {
      Serial.print("I2C device found at address 0x");
      if (address < 16) {
        Serial.print("0");
      }
      Serial.print(address, HEX);
      Serial.println("  !");

      ++nDevices;
    } else if (error == 4) {
      Serial.print("Unknown error at address 0x");
      if (address < 16) {
        Serial.print("0");
      }
      Serial.println(address, HEX);
    }
  }

  if (nDevices == 0) {
    Serial.println("No I2C devices found\n");
  } else {
    Serial.println("done\n");
  }
  delay(5000);
}

I2Cを利用する場合に、まずは利用するのがI2Cスキャナです。Arduino IDEのスケッチ例だと「Wire」→「i2c_scanner」になります。シリアルモニタの数字だけ変更して、コメントを消したのが上記です。

#include <Wire.h>

void setup() {
  Wire.begin();

I2CはWireクラスで動いています。起動時にWire.begin()を呼び出して初期化をしています。本来はI2Cは信号線が2本あるので、そのピン番号を指定して初期化する必要があります。ただしArduino UNOだとSDAとSCLと書かれているピンを標準で利用するので、未指定でも初期化可能です。

  Serial.begin(115200);
  while (!Serial);
  Serial.println("\nI2C Scanner");

シリアルの初期化です。whileループでシリアルの初期化が終わるまで待機しています。初期化されるとSerialクラスの戻り値がtrueになります。この初期化待ちはボードにより方法が異なり、ESP32などの場合にはボードからシリアルの初期化がいつ終わったかわからないので、適当なdelayを入れて必要があったりします。Arduino UNOの場合はシリアルチップが統合されているので、このような処理で初期化が終わったかを確認できます。

  for (byte address = 1; address < 127; ++address) {
    Wire.beginTransmission(address);
    byte error = Wire.endTransmission();

I2Cのアドレスは1から126までになります。すべてのアドレスに対して通信をしてみて、反応があったアドレスを列挙する処理がI2Cスキャナになります。

Scanning...
I2C device found at address 0x19  !
I2C device found at address 0x3C  !
I2C device found at address 0x77  !
done

上記が実行結果でした。このキットには3種類のI2Cデバイスが接続されています。

I2Cアドレスデバイス
0x193-Axis Digital Accelerometer (LIS3DHTR)
0x3COLED(SSD1315)
0x77気圧センサ(BMP280)

調べたところ、上記の3つでした。このキットは最初から接続されているので問題がないのですが、ケーブルなどで接続する場合にはまちがって接続する場合があるので、まずはI2Cスキャナを使って正しく接続できているかを確認する癖を付けたほうがいいと思います。

まとめ

モノクロですが、画面が使えるようになるとかなり便利ですね。I2Cはかなり便利でいろいろ接続ができます。ただしたくさん接続すると問題が起こりやすく、ロジックアナライザやオシロスコープを使って解析をしないと原因がわからなかったりもします。最初からたくさん接続するよりは、確実に動いてから次のものを接続して確認していくことをおすすめします。

コメント

タイトルとURLをコピーしました