6-3 OLEDディスプレイ(I2C)

ディスプレイと一口に言っても、その仕組みや表示できるものの違い等、非常に幅が広いです。

ディスプレイの種類

電子工作用途で使われるディスプレイでは「LCD」と「OLED」が一般的です。 
あと最近では「電子ペーパー」なんてのもありますが、講師非所持のため説明は割愛します。


上段:クソデカLCDディスプレイ
下段:左2つはLCD、右端はOLED

LCD

「LCD」は「液晶」とも呼ばれ、スマートフォン・携帯ゲーム機・電卓・腕時計といった機器の画面等、ありとあらゆるところで使われている非常に身近なディスプレイです。
「LCD」自身は発光しない為、ディスプレイの内側にバックライトを搭載し、液晶の背面から光を当てることで表示を映し出しているのが特徴です。
電子工作用のLCDは、種類が豊富で比較的安価な特長があります。

OLED

「OLED」は「有機EL」とも呼ばれ、LCDと同じく各種機器のディスプレイとして、LCDに代わって近年採用されることが多くなりました。
OLEDは電流を流すと自らが発光する素子であるため、バックライトがないのが特徴です。
視野角が非常に広く視認性がよく、低電力なのが特長ですが、LCDに比べ寿命が短いデメリットもあります。
電子工作用のOLEDは単色表示のものとフルカラーのものがあるので、用途に合わせて選んでください。

ディスプレイに表示できるもの

キャラクタ

「キャラクタ」ディスプレイは、文字表示専用のディスプレイです。


文字や数字をのみを表示できるLCDキャラクタディスプレイ。
工作物に組み込み、情報表示用途に使われます。

まつはちさんからのアドバイス

余談ですが、キャラクタディスプレイの「キャラクタ」は変数char型のキャラと同義です。
characterが文字集合の意味なんですよね。

グラフィック

一方、「グラフィック」はドットの集合体という形で文字や絵を表示可能なディスプレイです。
情報表示や画像の描画など、用途に合わせた幅広い製品があります。


単色のグラフィックを描画できるOLEDディスプレイ。本項で使います。
  

フルカラーのグラフィック描画ができるLCDディスプレイ
上の方の図の「クソデカLCDディスプレイ」と書かれているやつです。
  

フルカラーのグラフィック描画ができるOLEDディスプレイ(を2枚使って作った電子目)
  


今回使うのは、単色表示のグラフィックOLEDディスプレイモジュールです。

このモジュールにはSSD1306という制御ICが搭載されており、対応するライブラリを使うことで、OLEDを簡便に制御することができます。
単色表示・超ミニサイズなのできぐるみの電子目には不向きですが、暗い場所且つディスプレイに角度があっても視認しやすい利点から、ヘッド内に搭載し、他のギミックの状態表示や、RTC(リアルタイムクロック)モジュールと組み合わせることで時刻や経過時間の表示などに役立てられると思います。
  
  

  
(この動画は6-3-2と6-3-1を繋げています。0:58くらいから文字表示の様子が見られるよ)
  

UARTに続く二つめのシリアル通信、I2Cの登場です!

I2Cとは

「I2C」はInter-Integrated Circuitの略で、「アイ・スクエアド・シー」と呼びます。
「アイ・アイ・シー」「アイ・ツー・シー」とも呼ばれます。アイスクエアドシーの方がかっこいいのに…

I2Cの最大の特徴は、2本1組の信号線で複数のI2C機器を操れるという点です。
(信号線が2本1組という点ではUARTと同じですが、UARTは1組の信号線で操れる機器は1つだけでしたね!)

I2Cの特徴は以下の4つです。

  • 「SCL」と「SDA」の2本の信号線を使う
  • 部品ごとに「I2Cアドレス」を持っており、「I2Cアドレス」を指定し部品を動かす
  • 「コントローラ(マスタ)」と「ターゲット(スレーブ)」を分け、コントローラが全ての制御を行う
  • クロック信号に同期させてデータ通信を行う

順番にひとつずつ解説します。

信号線「SCL」と「SDA」

「SCL」(「SCK」の場合も有)はSerial Clockの略で、通信の基準となるクロック信号を送信します。
「SDA」はSerial Dataの略で、こちらの線でデータのやり取りを行います。

注意点として、I2Cの2本の信号線は、必ずプルアップが必要となります。
親切なI2C機器では、部品側に既にプルアップ抵抗が備え付けられていることもあるので、使用前に仕様書を読みプルアップ抵抗の有無を確認する必要があります。

部品ごとに持っている「I2Cアドレス」

「I2Cアドレス」は、その電子部品の“住所”みたいなものです。
I2Cでは、この「I2Cアドレス」を指定して、その部品を動かすための命令を送ります。
この仕組みがあることで、I2CではArduino のSCL/SDAに複数個のI2C機器を繋ぎ、それぞれの機器に異なる命令を個別に送る…なんてことができます。
(前項のUARTでは、複数のUART機器を使いたい時には複数のRX/TXソケットを用意し、複数のハードウェアシリアルやソフトウェアシリアルを使う必要がありました)

「コントローラ」と「ターゲット」の関係

I2C通信では繋がれた機器のうち、制御する側を「コントローラ(マスタ)」、される側を「ターゲット(スレーブ)」と呼びます。
(コントローラ/ターゲットの名称は、以前はマスタ/スレーブでした。近年はポリコレ的観点から読み替えが進められていますが、過去に書かれた書籍や情報サイトでは旧表記となっていますので、適宜読み替えてください
(ちなみに今回のコントローラ/ターゲット呼称はI2Cの開発元よる改訂に基づくものですが、将来的に他の呼称で定着する可能性もある点に留意してください))

I2Cでは、制御の指示を出す「コントローラ」は必ず一つだけです。それに対し「ターゲット」は複数繋ぐことができます。
例えば本項での「コントローラ」はArduinoです。そこにOLEDディスプレイを「ターゲット」として繋ぎます。
「ターゲット」として複数のディスプレイや他のI2C機器を繋ぐことも可能です。

まつはちさんからのアドバイス

ESP32のように、複数のI2Cポートを持っているマイコンもあります。
そういったマイコンであれば、I2C機器を繋ぎすぎたりする場合などに、複数のI2Cコントローラを用意して分割して繋ぐ手も使えます。

クロック信号で同期を取る

I2Cでの通信の仕組みを超ざっくり説明します。
「コントローラ」の「SCL」は、「ターゲット」に対してクロック信号を送ります。
このクロック信号に同期する形で、「コントローラ」から「ターゲット」に対しデータの送信や受信の指示がされます。そのやり取りを、「SDA」で行います。
  

I2C機器をプログラムで動かすにあたって、その機器の「I2Cアドレス」を調べる必要があります。
I2Cアドレスはデータシートにだいたい記載してありますが、

  • そもそもデータシートがない商品(AmazonやAliExpressで売ってるあやしい中華製パーツとか)
  • 仕様変更に対しデータシートの更新が追い付いていない商品
  • 初期不良等の理由でI2Cアドレスが通常と異なる商品
  • I2Cアドレスを変更可能な部品で、変更後のI2Cアドレスがわからなくなった部品

こういった部品を扱う際には、自分でI2Cアドレスを調べる必要があります。
いつかそんなシーンに遭遇した時のために、I2Cアドレスの調べ方を掲載しておきます。
  

i2c_scannerでI2Cアドレスを調べる

I2Cアドレスを確認するには、i2c_scannerというスケッチを使用します。

 i2c_scanner(Arduino Playground):
 https://playground.arduino.cc/Main/I2cScanner/

i2c_scannerのスケッチはサンプルコード6-3-1のフォルダ内にあるi2c_scannerフォルダの中に
ご用意しております。(もちろん上記サイトから新規スケッチにコピペしてもOKです)
  
やり方としては、アドレスを知りたいI2C機器をArduinoに繋ぎ、i2c_scanner.inoをArduinoに書き込むと、接続されている機器のI2Cアドレスがシリアルモニタに表示されます。
(i2c_scannerのコード内容は何も触らず、そのまま書き込んでください)

上の図は講師のOLEDモジュールのスキャン結果です。

I2C device found at address 0x3C !

とありますので、講師のOLEDモジュールのI2Cアドレスは「0x3C」というのがわかります。
  
※i2c_scannerのコード解説は、本項の本筋とは関係ないため割愛させていただきます。
  

サンプルコード6-3-1では、Wireライブラリ、AdafruitGFXライブラリ、AdafruitSSD1306ライブラリの3つのライブラリを使用しています。

Wireライブラリ

WireライブラリはI2Cを使うために必要なライブラリで、Arduinoに標準でインストールされています。
特段の処理は不要です。

AdafruitGFXライブラリ・AdafruitSSD1306ライブラリ

AdafruitGFXライブラリとAdafruitSSD1306ライブラリはインストールが必要なライブラリです。

ArduinoIDEでライブラリマネージャを立ち上げます。
下記の検索ワードを打ち込み、2つのライブラリをインストールしてください。

  • “Adafruit GFX”で検索をすると出てくる、作者名が「by Adafruit」の「AdafruitGFX Library」
  • “Adafruit SSD1306”で検索すると出てくる作者名が「by Adafruit」の「AdafruitSSD1306」

ちなみに~GFXはディスプレイ用のグラフィックスライブラリ、~SSD1306はSSD1306ドライバを搭載した電子部品を使用するためのライブラリです。

  


  
ディスプレイのVDDはArduinoの3.3Vに繋ぎます。
I2Cの信号線SCLとSDAには必ずプルアップ抵抗が必要ですが、今回使うモジュールには予めプルアップ抵抗が搭載されています。

(2024/3現在、第1章に掲示のURLの商品については搭載されています。
が、今後仕様変更になる可能性がゼロではないのでご注意ください。
また他のサイトや店舗で販売している似たようなディスプレイは、個別に確認が必要です。
ネット情報ではプルアップ抵抗のない商品も存在するようです)

使う部品リスト

部品 個数
OLEDディスプレイ 1個

  

サンプルコード6-3-1_OLED_I2C.inoを開いてください。

#include <Wire.h>//I2C用のライブラリ
#include <Adafruit_GFX.h>//ディスプレイのグラフィックス用ライブラリ
#include <Adafruit_SSD1306.h>//SSD1306ドライバ用ライブラリ

#define SCREEN_WIDTH 128 //ディスプレイの解像度の"幅"を指定します
#define SCREEN_HEIGHT 64 //ディスプレイの解像度の"高さ"を指定します
#define OLED_RESET    -1 //使用しないので-1を設定します
#define SCREEN_ADDRESS 0x3C //繋げるディスプレイのI2Cアドレスを入れます

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
//インスタンス"display"を生成します。

void setup() {
  display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);//ディスプレイ初期化
}

void loop() {
  display.clearDisplay();//ディスプレイの表示をクリアします
  display.setTextSize(2);//テキストサイズを2に(1が最小)
  display.setTextColor(WHITE);//テキスト色を白に(変えられませんが省略不可)
  display.setCursor(15, 20);//1番目のテキストの位置を指定
  display.println("MORISOBA");//1番目に表示するテキスト
  display.setCursor(10, 40);//2番目のテキストの位置を指定
  display.println("YAMAMORI!");//2番目に表示するテキスト

  display.display();//ディスプレイにテキストを表示
  delay(1000);
}

今回はディスプレイに文字を表示してみます。

1~3行目
#include <Wire.h>//I2C用のライブラリ
#include <Adafruit_GFX.h>//ディスプレイのグラフィックス用ライブラリ
#include <Adafruit_SSD1306.h>//SSD1306ドライバ用ライブラリ

必要なライブラリ3つをインクルードします。

5~6行目
#define SCREEN_WIDTH 128 //ディスプレイの解像度の"幅"を指定します
#define SCREEN_HEIGHT 64 //ディスプレイの解像度の"高さ"を指定します

#defineは定数を宣言する構文です。
#defineは色々な意味で強力なので、定数を宣言する場合は、(特別理由がない限り)constを使ったほうがベターです。
今回はAdafruitSSD1306のサンプルコードで#defineが使われていた為、それに倣っています。なお#defineの末尾に「;」はつけません。

#defineで設定している定数はSCREEN_WIDTHとSCREEN_HEIGHTです。
これはディスプレイの解像度を設定する定数です。
SCREEN_WIDTHでディスプレイの幅、SCREEN_HEIGHTでディスプレイの高さの数値を入力しておきます。
今回使うディスプレイは幅128ピクセル、高さ64ピクセルの解像度なので、その数値が入ります。

7行目
#define OLED_RESET    -1 //使用しないので-1を設定します

OLEDにリセットピンがある場合はピンの番号を指定するのですが、今回はないので-1を入れます。

8行目
#define SCREEN_ADDRESS 0x3C //繋げるディスプレイのI2Cアドレスを入れます

ディスプレイのI2Cアドレスを指定します!
サンプルコードには講師が使ったディスプレイのI2Cアドレス0x3Cが入っていますが、もしみなさんのディスプレイのI2Cアドレスと違う場合は、ここを書き換えてください。

10行目
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

インスタンスdisplayを生成します。
引数は4つありますが、左から順番に(ディスプレイの解像度(幅), 同左(高さ), I2Cのピン番号, リセットピンの番号)となっています。

14行目
display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);//ディスプレイ初期化

displayを初期化します。
第一引数はディスプレイに供給する電源の電圧によって変える必要があります。
今回のように3.3Vに挿した場合はSSD1306_SWITCHCAPVCCを、3.3V以上の電圧の場合はSSD1306_EXTERNALVCCを指定するようです。
第二引数にはI2Cアドレスを指定します。

18行目
display.clearDisplay();//ディスプレイの表示をクリアします

clearDisplayでディスプレイの表示されているものを一旦すべてクリアします。

19行目
display.setTextSize(2);//テキストサイズを2に(1が最小)

setTextSizeはディスプレイに表示する文字のサイズを指定します。1が最小です。

20行目
display.setTextColor(WHITE);//テキスト色を白に(変えられませんが省略不可)

setTextColorは文字の色指定ですが、今回は単色表示ディスプレイなので色は変えられません。
変えられませんがこの記述を削除することはできないらしいので、無難な形で放っておきます。

21行目
display.setCursor(15, 20);//1番目のテキストの位置を指定

setCursorは文字を表示する位置を指定します。画面の左上を基準点として、引数の数(単位は
ピクセル)ぶんだけ右方向と下方向に進んだ位置が文字の表示開始位置となります。
今回は(15, 20)となっているため、一番目に表示する文字は画面の左上から右に15ピクセル、下に20ピクセル進んだ位置に文字が配置されます。

22行目
display.println("MORISOBA");//1番目に表示するテキスト

表示したい文字を入力します。シリアルモニタに表示する時のように、改行なしのprintか、改行ありのprintlnが使えます。今回はprintlnなので改行します。
ちなみに今回のディスプレイでは、日本語(というか2バイト文字)はこの方法では表示できません。

23行目
display.setCursor(10, 40);//2番目のテキストの位置を指定

二番目に表示する文字の位置を指定します。
条件は一番目の文字とまったく同様で、画面の左上が基準点となります。

26行目
display.display();//ディスプレイにテキストを表示

19~25行目までの処理は、ディスプレイに文字を表示するためのセッティングの処理です。
セッティング止まりなので25行目の時点では、まだディスプレイには何も表示されていません。
それらのセッティングをもとに実際にディスプレイに描画させるための命令が、26行目のdisplayです。
  
  

今回のOLEDディスプレイは表示面が非常に小さいため、図形の描画用途として使うのは若干現実的ではないかもしれません。
もちろんそれがまったく不可能であるというわけではありません!
Adafruit SSD1306ライブラリに同梱されているサンプルスケッチ「ssd1306_128x64_i2c」には、ディスプレイに線や図形を描画したり、文字をスクロールさせたりする関数が多数収録されています。

全く同じコードを、サンプルコード「6-3-2_OLED_I2C_from_sample.ino」として収録してありますので、興味のある方はぜひ実行してみてください。
  


  
直前の6-3-1で作った回路そのままでOKです!

使う部品リスト

部品 個数
OLEDディスプレイ 1個

  

サンプルコード6-3-2_OLED_I2C_from_sample.inoを開いてください。

/**************************************************************************
 This is an example for our Monochrome OLEDs based on SSD1306 drivers

 Pick one up today in the adafruit shop!
 ------> http://www.adafruit.com/category/63_98

 This example is for a 128x64 pixel display using I2C to communicate
 3 pins are required to interface (two I2C and one reset).

 Adafruit invests time and resources providing this open
 source code, please support Adafruit and open-source
 hardware by purchasing products from Adafruit!

 Written by Limor Fried/Ladyada for Adafruit Industries,
 with contributions from the open source community.
 BSD license, check license.txt for more information
 All text above, and the splash screen below must be
 included in any redistribution.
 **************************************************************************/

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

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// The pins for I2C are defined by the Wire-library. 
// On an arduino UNO:       A4(SDA), A5(SCL)
// On an arduino MEGA 2560: 20(SDA), 21(SCL)
// On an arduino LEONARDO:   2(SDA),  3(SCL), ...
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)

//ここのI2Cアドレスを、みなさんのディスプレイのI2Cアドレスに書き換えてください!
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32


Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define NUMFLAKES     10 // Number of snowflakes in the animation example

#define LOGO_HEIGHT   16
#define LOGO_WIDTH    16
static const unsigned char PROGMEM logo_bmp[] =
{ 0b00000000, 0b11000000,
  0b00000001, 0b11000000,
  0b00000001, 0b11000000,
  0b00000011, 0b11100000,
  0b11110011, 0b11100000,
  0b11111110, 0b11111000,
  0b01111110, 0b11111111,
  0b00110011, 0b10011111,
  0b00011111, 0b11111100,
  0b00001101, 0b01110000,
  0b00011011, 0b10100000,
  0b00111111, 0b11100000,
  0b00111111, 0b11110000,
  0b01111100, 0b11110000,
  0b01110000, 0b01110000,
  0b00000000, 0b00110000 };

void setup() {
  Serial.begin(9600);

  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

  // Show initial display buffer contents on the screen --
  // the library initializes this with an Adafruit splash screen.
  display.display();
  delay(2000); // Pause for 2 seconds

  // Clear the buffer
  display.clearDisplay();

  // Draw a single pixel in white
  display.drawPixel(10, 10, SSD1306_WHITE);

  // Show the display buffer on the screen. You MUST call display() after
  // drawing commands to make them visible on screen!
  display.display();
  delay(2000);
  // display.display() is NOT necessary after every single drawing command,
  // unless that's what you want...rather, you can batch up a bunch of
  // drawing operations and then update the screen all at once by calling
  // display.display(). These examples demonstrate both approaches...

  testdrawline();      // Draw many lines

  testdrawrect();      // Draw rectangles (outlines)

  testfillrect();      // Draw rectangles (filled)

  testdrawcircle();    // Draw circles (outlines)

  testfillcircle();    // Draw circles (filled)

  testdrawroundrect(); // Draw rounded rectangles (outlines)

  testfillroundrect(); // Draw rounded rectangles (filled)

  testdrawtriangle();  // Draw triangles (outlines)

  testfilltriangle();  // Draw triangles (filled)

  testdrawchar();      // Draw characters of the default font

  testdrawstyles();    // Draw 'stylized' characters

  testscrolltext();    // Draw scrolling text

  testdrawbitmap();    // Draw a small bitmap image

  // Invert and restore display, pausing in-between
  display.invertDisplay(true);
  delay(1000);
  display.invertDisplay(false);
  delay(1000);

  testanimate(logo_bmp, LOGO_WIDTH, LOGO_HEIGHT); // Animate bitmaps
}

void loop() {
}

void testdrawline() {
  int16_t i;

  display.clearDisplay(); // Clear display buffer

  for(i=0; i<display.width(); i+=4) {
    display.drawLine(0, 0, i, display.height()-1, SSD1306_WHITE);
    display.display(); // Update screen with each newly-drawn line
    delay(1);
  }
  for(i=0; i<display.height(); i+=4) {
    display.drawLine(0, 0, display.width()-1, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();

  for(i=0; i<display.width(); i+=4) {
    display.drawLine(0, display.height()-1, i, 0, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for(i=display.height()-1; i>=0; i-=4) {
    display.drawLine(0, display.height()-1, display.width()-1, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();

  for(i=display.width()-1; i>=0; i-=4) {
    display.drawLine(display.width()-1, display.height()-1, i, 0, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for(i=display.height()-1; i>=0; i-=4) {
    display.drawLine(display.width()-1, display.height()-1, 0, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();

  for(i=0; i<display.height(); i+=4) {
    display.drawLine(display.width()-1, 0, 0, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for(i=0; i<display.width(); i+=4) {
    display.drawLine(display.width()-1, 0, i, display.height()-1, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000); // Pause for 2 seconds
}

void testdrawrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2; i+=2) {
    display.drawRect(i, i, display.width()-2*i, display.height()-2*i, SSD1306_WHITE);
    display.display(); // Update screen with each newly-drawn rectangle
    delay(1);
  }

  delay(2000);
}

void testfillrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2; i+=3) {
    // The INVERSE color is used so rectangles alternate white/black
    display.fillRect(i, i, display.width()-i*2, display.height()-i*2, SSD1306_INVERSE);
    display.display(); // Update screen with each newly-drawn rectangle
    delay(1);
  }

  delay(2000);
}

void testdrawcircle(void) {
  display.clearDisplay();

  for(int16_t i=0; i<max(display.width(),display.height())/2; i+=2) {
    display.drawCircle(display.width()/2, display.height()/2, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testfillcircle(void) {
  display.clearDisplay();

  for(int16_t i=max(display.width(),display.height())/2; i>0; i-=3) {
    // The INVERSE color is used so circles alternate white/black
    display.fillCircle(display.width() / 2, display.height() / 2, i, SSD1306_INVERSE);
    display.display(); // Update screen with each newly-drawn circle
    delay(1);
  }

  delay(2000);
}

void testdrawroundrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2-2; i+=2) {
    display.drawRoundRect(i, i, display.width()-2*i, display.height()-2*i,
      display.height()/4, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testfillroundrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2-2; i+=2) {
    // The INVERSE color is used so round-rects alternate white/black
    display.fillRoundRect(i, i, display.width()-2*i, display.height()-2*i,
      display.height()/4, SSD1306_INVERSE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testdrawtriangle(void) {
  display.clearDisplay();

  for(int16_t i=0; i<max(display.width(),display.height())/2; i+=5) {
    display.drawTriangle(
      display.width()/2  , display.height()/2-i,
      display.width()/2-i, display.height()/2+i,
      display.width()/2+i, display.height()/2+i, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testfilltriangle(void) {
  display.clearDisplay();

  for(int16_t i=max(display.width(),display.height())/2; i>0; i-=5) {
    // The INVERSE color is used so triangles alternate white/black
    display.fillTriangle(
      display.width()/2  , display.height()/2-i,
      display.width()/2-i, display.height()/2+i,
      display.width()/2+i, display.height()/2+i, SSD1306_INVERSE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testdrawchar(void) {
  display.clearDisplay();

  display.setTextSize(1);      // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE); // Draw white text
  display.setCursor(0, 0);     // Start at top-left corner
  display.cp437(true);         // Use full 256 char 'Code Page 437' font

  // Not all the characters will fit on the display. This is normal.
  // Library will draw what it can and the rest will be clipped.
  for(int16_t i=0; i<256; i++) {
    if(i == '\n') display.write(' ');
    else          display.write(i);
  }

  display.display();
  delay(2000);
}

void testdrawstyles(void) {
  display.clearDisplay();

  display.setTextSize(1);             // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(0,0);             // Start at top-left corner
  display.println(F("Hello, world!"));

  display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // Draw 'inverse' text
  display.println(3.141592);

  display.setTextSize(2);             // Draw 2X-scale text
  display.setTextColor(SSD1306_WHITE);
  display.print(F("0x")); display.println(0xDEADBEEF, HEX);

  display.display();
  delay(2000);
}

void testscrolltext(void) {
  display.clearDisplay();

  display.setTextSize(2); // Draw 2X-scale text
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(10, 0);
  display.println(F("scroll"));
  display.display();      // Show initial text
  delay(100);

  // Scroll in various directions, pausing in-between:
  display.startscrollright(0x00, 0x0F);
  delay(2000);
  display.stopscroll();
  delay(1000);
  display.startscrollleft(0x00, 0x0F);
  delay(2000);
  display.stopscroll();
  delay(1000);
  display.startscrolldiagright(0x00, 0x07);
  delay(2000);
  display.startscrolldiagleft(0x00, 0x07);
  delay(2000);
  display.stopscroll();
  delay(1000);
}

void testdrawbitmap(void) {
  display.clearDisplay();

  display.drawBitmap(
    (display.width()  - LOGO_WIDTH ) / 2,
    (display.height() - LOGO_HEIGHT) / 2,
    logo_bmp, LOGO_WIDTH, LOGO_HEIGHT, 1);
  display.display();
  delay(1000);
}

#define XPOS   0 // Indexes into the 'icons' array in function below
#define YPOS   1
#define DELTAY 2

void testanimate(const uint8_t *bitmap, uint8_t w, uint8_t h) {
  int8_t f, icons[NUMFLAKES][3];

  // Initialize 'snowflake' positions
  for(f=0; f< NUMFLAKES; f++) {
    icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
    icons[f][YPOS]   = -LOGO_HEIGHT;
    icons[f][DELTAY] = random(1, 6);
    Serial.print(F("x: "));
    Serial.print(icons[f][XPOS], DEC);
    Serial.print(F(" y: "));
    Serial.print(icons[f][YPOS], DEC);
    Serial.print(F(" dy: "));
    Serial.println(icons[f][DELTAY], DEC);
  }

  for(;;) { // Loop forever...
    display.clearDisplay(); // Clear the display buffer

    // Draw each snowflake:
    for(f=0; f< NUMFLAKES; f++) {
      display.drawBitmap(icons[f][XPOS], icons[f][YPOS], bitmap, w, h, SSD1306_WHITE);
    }

    display.display(); // Show the display buffer on the screen
    delay(200);        // Pause for 1/10 second

    // Then update coordinates of each flake...
    for(f=0; f< NUMFLAKES; f++) {
      icons[f][YPOS] += icons[f][DELTAY];
      // If snowflake is off the bottom of the screen...
      if (icons[f][YPOS] >= display.height()) {
        // Reinitialize to a random position, just off the top
        icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
        icons[f][YPOS]   = -LOGO_HEIGHT;
        icons[f][DELTAY] = random(1, 6);
      }
    }
  }
}

37行目のI2CアドレスをみなさんのディスプレイのI2Cアドレスに書き換えてArduinoに書き込んでください。

※今回のコードはライブラリ付属のサンプルスケッチそのまんまですので、解説は致しません!