単色OLEDモジュールを使う(SSD1306 – I2C / SPI)


小型の単色OLEDモジュール SSD1306を使います。
I2Cで制御するモデルとSPIで制御するモデルがあり、それぞれの特徴を比べて記載します。
RaspberryPi PicoとESP32-WROOMほか、Arduino環境で使用できるマイコンを使って簡単な表示サンプルを作ります。

OLED SSD1306

簡単紹介

Arduino環境で手軽に使える小型の表示器です。

◆0.91inch I2C

◆0.96inch I2C

◆0.96inch SPI

◆関連記事

ピン配置

◆0.91inch I2C

◆0.96inch I2C

◆0.96inch SPI

外観

◆0.91inch I2c

◆0.96inch I2C

◆0.96inch SPI

使ってみた

◆開発環境

Arduino環境でRP2040搭載のRaspberry Pi PicoとESP32-WROOM32搭載のNodeMCUを使って制御しました。
ライブラリは「Adafruit SSD1306 by Adafruit」を使いました。
必要な準備はこちらです。

◆I2CとSPI

I2Cは電源、GND、通信線を含めて4線を使います。
SPIは電源、GND、通信線を含めた7線を使います。

I2Cは配線が少ないが描画速度が遅く、SPIは配線が多いが描画速度が速い特徴があります。
下の動画ではI2CとSPIにはっきりと差が見られます。

I2Cでは複数モジュールを使うときにI2Cアドレスを変更します。
チップ抵抗の実装位置を変更する作業が必要で高度な半田技術が必要です。
アドレスは2種類(3c(h), 3d(h))なので、I2Cでは2個まで同時に使用できます。

SPIはマイコン側のCSピンが使えるだけ使える(と思います)
ほとんどのマイコンはCSピンをどこにでも使えます。

I2CSPI
ワイヤ数(電源、GND込み)4
配線が楽
7
配線が面倒
描画速度遅い速い
同時に使えるモジュール数
(1通信ラインあたり)
1~21~2以上

◆サイズと分解能

今回0.96inchと0.91inchのOLEDモジュールを使いました。
この2つには表示面積と解像度の違いがあります。
0.96inchは(w, h) = (128, 64)、0.91inchは(w, h) = (128, 32)です。
0.91inchは0.96inchより横手方向に面積がありますが余白が多いだけです。

それぞれのモジュールに最適な分解能を設定する必要があります。
設定が違っても表示されますが、期待通りの表示はされません。

◆表示色

3色使ってみました。
単色で使えるOLED(SSD1306)は4パターンあり、青、白、黄の他、黄と青の組み合わせがあるようです。
黄と青の組み合わせはそれぞれのエリアの色が固定されていて変更はできないようです。

準備

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

ArduinoIDEでSSD1306を使用するために必要なライブラリをインストールします。
ライブラリは以下2つのライブラリを使用します。

I2C / SPI 共通です。
0.91inch / 0.96inch 共通です。

ライブラリ名検索動作確認Ver
Adafruit SSD1306 by AdafruitSSD13062.5.9
Adafruit GFX Library by AdafruitGFX1.11.9

マイコン側に必要なボードライブラリは使用するマイコンに対応したボードライブラリをインストールします。

OLEDのI2Cアドレス確認と変更

SSD1306モジュールのI2Cアドレスは3c(h)または3d(h)のどちらかを選択して使用します。
OLEDモジュールの裏面の「IIC ADDRESS SELECT」の抵抗の位置を確認します。
初期位置は3c(h)と思います。

なお、Adafruitライブラリを使用していますが、サンプルのアドレスは3d(h)です。モジュールの抵抗付け替えをしない場合はスケッチ側で修正します。

繊細な作業で半田作業には高度なスキルが必要です。
パターンの剥離、部品やパターンのショート、オープンに注意して作業します。
※モジュールの破損、故障するリスクがある作業です。

こちらはマイクロスコープを使って作業している様子です。

サンプル(I2C)

Raspberry Pi Pico I2Cを使った表示(0.96inch)

説明

Raspberry Pi PicoのI2C(I2C0)を使ってOLEDを表示制御します。

OLED画面には
“TAMANEGI”, “SSD1306”, “I2C”
と表示します。

SSD1306のサンプルコードはAdafruitサンプルを参考に修正しています。
メニュー File > Examples > Adafruit SSD1306 > ssd1306_128x64_i2c

配線

SSD1306 OLEDのI2Cアドレスは3c(h)です。

スケッチ

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

#define SCREEN_WIDTH (128)
#define SCREEN_HEIGHT (64)

#define OLED_SDA        (SDA)
#define OLED_SCL        (SCL)
#define OLED_RESET      (-1)
#define SCREEN_ADDRESS  (0x3C)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

void setup()
{ 
  // Wire.setSDA(OLED_SDA);
  // Wire.setSCL(OLED_SCL);

  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS))
  {
    for(;;);
  }

  display.clearDisplay();
  
  display.setTextSize(2);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(20, 10);
  display.print(F("TAMANEGI"));
  display.setCursor(25, 30);
  display.print(F("SSD1306"));
  display.setCursor(50, 50);
  display.print(F("I2C"));

  display.display();
}

void loop() {
}

結果

ESP32系 ESP32-WROOMを使った表示

I2C0を使ったOLEDの表示です。
画面に “TAMANEGI” と “I2C0 0x3c”と表示します。

I2Cに使用するピンをWireオブジェクトを使って変更します。
サンプルスケッチでは、ESP32-WROOM (NodeMCU)のデフォルトI2Cを使用するため、ピンの変更をしていませんが、以下のコメントを解除することで、任意のピンに変更することができます。

// Wire.setPins(PIN_SDA, PIN_SCL);

配線

スケッチ

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

#define SCREEN_WIDTH (128)              //解像度 128 x 64 で使用します。
#define SCREEN_HEIGHT (64)              //SCREEN_HEIGHTは 32 に設定することができます。

#define OLED_RESET     (-1)             //使用しないので -1を設定する。
#define SCREEN_ADDRESS (0x3C)           //I2Cアドレスは 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
                                        //表示制御にはAdafruit製 SSD1306を使用する。

//任意の位置にピン設定する場合の定義
#define PIN_SDA (3)
#define PIN_SCL (1)                                   

void setup()
{
//  任意のピンをI2Cに設定する場合コメントの解除
//  Wire.setPins(PIN_SDA, PIN_SCL);

  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    for(;;);
  }

  display.clearDisplay();               //何か表示されている場合に備えて表示クリア

  display.setTextSize(2);               //フォントサイズは2(番目に小さい)
  display.setTextColor(SSD1306_WHITE);  //色指定はできないが必要
  display.setCursor(20, 10);            //テキストの表示開始位置
  display.print(F("TAMANEGI"));         //表示文字列
  display.setCursor(10, 30);
  display.print(F("I2C0 0x3c"));

  display.display();                    //バッファ転送(表示)
}

void loop()
{
}

結果

Raspberry Pi Pico I2Cを使った表示(0.91inch)

説明

OLED SSD1306 0.91inchを使います。
0.96inchとの違いは画面解像度です。

0.91inch0.96inch
w, h = 128, 32w, h = 128, 64

Raspberry Pi Picoを使って画面に “TAMANEGI”と表示します。
スケッチで設定する解像度の違いで表示の仕方の変化を確認します。
解像度の確認はサンプルスケッチ6行目と7行目のコメントを入れ替えて実行します。

配線

SSD1306 OLEDのI2Cアドレスは3c(h)です。

スケッチ

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

#define SCREEN_WIDTH (128)
#define SCREEN_HEIGHT (32)
//#define SCREEN_HEIGHT (64)

#define OLED_SDA        (SDA)
#define OLED_SCL        (SCL)
#define OLED_RESET      (-1)
#define SCREEN_ADDRESS  (0x3C)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

void setup()
{ 
  // Wire.setSDA(OLED_SDA);
  // Wire.setSCL(OLED_SCL);

  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS))
  {
    for(;;);
  }

  display.clearDisplay();
  
  display.setTextSize(2);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(20, 10);
  display.print(F("TAMANEGI"));
  display.display();
}

void loop() {
}

結果

サンプル(I2C 応用)

SSD1306 OLEDの複数使用などのサンプルを掲載します。
サンプル数が多くなるので代表してRaspberryPi Picoで実装します。

I2C0を使ったコピーモニタ<非推奨>

I2C0ライン上に同じI2Cアドレスを持ったモジュールを配線することで、コピーモニタになります。
※I2Cではマスタからスレーブにデータを送信するときに、スレーブからACK/NACKを受け取ります。
同じアドレスが重複するとどちらの返答かわからなくて不具合の原因となるため、推奨できません

配線

SSD1306のI2Cアドレスは2つともに3c(h)。

スケッチ

「RP2040系 I2Cによる表示」と同じスケッチを使用します。

結果

期待通りの表示はできていますが、潜在的な不具合が発生する可能性があります。

I2Cアドレスを変更した使ったデュアルモニタ

同じI2C0 ライン上には異なるI2Cアドレスのモジュールを使用します。
スケッチにはそれぞれのアドレスに対して個別の表示ができます。

I2Cアドレス 3c(h)用のオブジェクト display1と、3d(h)用のオブジェクトdisplay2を使用します。
使用するI2Cラインはどちらも同じI2C0なので、Wireオブジェクトを使用します。

オブジェクトの制御開始関数 display1.beginの引数にI2C 3c(h)のアドレス、
display2.beginの引数にI2C 3d(h)アドレスを与えています。

配線

OLED SSD1306のI2Cアドレスは 3c(h)と3d(h)を使用します。
配線の並びは、3c(h)と3d(h)のどちらが先でも構いません。
配線は、「I2C0を使ったコピーモニタ<非推奨>」と同じです。

スケッチ

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

#define SCREEN_WIDTH (128)                //解像度 128 x 64 で使用します。
#define SCREEN_HEIGHT (64)                //SCREEN_HEIGHTは 32 に設定することができます。

#define OLED_RESET     (-1)               //使用しないので -1を設定する。
#define SCREEN_ADDRESS_M1 (0x3C)          //OLED 1のI2Cアドレスは0x3C
#define SCREEN_ADDRESS_M2 (0x3D)          //OLED 2のI2Cアドレスは0x3d

//それぞれを個別制御するためにそれぞれのオブジェクトを用意します。
Adafruit_SSD1306 display1(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Adafruit_SSD1306 display2(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);


void setup() {
  
  Wire.setSDA(0);                       //I2C0で使用するGPは SDA = 0, SCL = 1
  Wire.setSCL(1);

  if(!display1.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS_M1)) {
    for(;;);
  }
  if(!display2.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS_M2)) {
    for(;;);
  }

  //ここは OLED1の表示処理
  display1.clearDisplay();

  display1.setTextSize(2);
  display1.setTextColor(SSD1306_WHITE);
  display1.setCursor(20, 10);
  display1.print(F("TAMANEGI"));
  display1.setCursor(10, 30);
  display1.print(F("I2C0 0x3c"));

  display1.display();

  //ここから OLED2の表示処理
  display2.clearDisplay();

  display2.setTextSize(2);
  display2.setTextColor(SSD1306_WHITE);
  display2.setCursor(20, 10);
  display2.print(F("TAMANEGI"));
  display2.setCursor(10, 30);
  display2.print(F("I2C0 0x3d"));

  display2.display();

}

void loop() {
}

結果

I2Cを2ライン使ったデュアルモニタ

説明

I2Cが2ライン以上あるマイコンでは同じI2CアドレスのモジュールでもそれぞれのI2Cラインを使用することで個別に制御できます。

この例ではOLED SSD1306を2つ使用しますが、I2Cアドレスはどちらも3c(h)です。
片方はI2C0のWireオブジェクトを使用し、もう片方をI2C1のWire1オブジェクトを使用します。


配線

SSD1306のI2Cアドレスは2つともに3c(h)。

スケッチ

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

#define SCREEN_WIDTH (128)                //解像度 128 x 64 で使用します。
#define SCREEN_HEIGHT (64)                //SCREEN_HEIGHTは 32 に設定することができます。

#define OLED_RESET     (-1)               //使用しないので -1を設定する。
#define SCREEN_ADDRESS (0x3C)             //I2Cアドレスは0x3C

//それぞれを個別制御するためにそれぞれのオブジェクトを用意します。
Adafruit_SSD1306 display1(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Adafruit_SSD1306 display2(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire1, OLED_RESET);


void setup() {
  
  Wire.setSDA(0);                       //I2C0で使用するGPは SDA = 0, SCL = 1
  Wire.setSCL(1);

  Wire1.setSDA(2);                       //I2C1で使用するGPは SDA = 2, SCL = 3
  Wire1.setSCL(3);

  if(!display1.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    for(;;);
  }
  if(!display2.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    for(;;);
  }

  //ここは OLED1の表示処理
  display1.clearDisplay();

  display1.setTextSize(2);
  display1.setTextColor(SSD1306_WHITE);
  display1.setCursor(20, 10);
  display1.print(F("TAMANEGI"));
  display1.setCursor(10, 30);
  display1.print(F("I2C0 0x3c"));

  display1.display();

  //ここから OLED2の表示処理
  display2.clearDisplay();

  display2.setTextSize(2);
  display2.setTextColor(SSD1306_WHITE);
  display2.setCursor(20, 10);
  display2.print(F("TAMANEGI"));
  display2.setCursor(10, 30);
  display2.print(F("I2C1 0x3c"));

  display2.display();

}

void loop() {
}

結果

サンプル(SPI)

Raspberry Pi Pico SPIを使った表示

説明

Raspberry Pi PicoのSPI(SPI0)を使ってOLEDを表示制御します。

OLED画面には
“TAMANEGI”, “SSD1306”, “SPI”
と表示します。

SSD1306のサンプルコードはAdafruitサンプルを参考に修正しています。
メニュー File > Examples > Adafruit SSD1306 > ssd1306_128x64_spi

配線

スケッチ

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

#define SCREEN_WIDTH (128)
#define SCREEN_HEIGHT (64)

#define OLED_MOSI   (MOSI)
#define OLED_CLK    (SCK)
#define OLED_DC     (28)
#define OLED_CS     (SS)
#define OLED_RESET  (22)

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);
                                        
void setup() 
{
//  SPI.setMOSI(OLED_MOSI);
//  SPI.setSCK(OLED_CLK);
   
  if(!display.begin(SSD1306_SWITCHCAPVCC))
  {
    for(;;);
  }

  display.clearDisplay();
  
  display.setTextSize(2);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(20, 10);
  display.print(F("TAMANEGI"));
  display.setCursor(25, 30);
  display.print(F("SSD1306"));
  display.setCursor(50, 50);
  display.print(F("SPI"));

  display.display();
}

void loop() {
}

結果

SPIでのOLED表示ができました。
I2Cスケッチと比べると定義や初期化に違いがあり、描画部は同じスケッチを流用できます。

I2Cのモデルと比較すると配線の数が多いです。
静止状態ではSPIのメリットは感じません。
次の「実験(I2C vs SPI)」ではSPIに描画速度のメリットを感じます。

実験(I2C vs SPI)

Raspberry Pi Pico SPIを使った表示

説明

OLED(SSD1306)のI2C, SPIを使って描画速度の違いを確認します。
I2Cモデルと、SPIモデルの内部の描画速度の違いは判りませんが、I2CとSPIの通信速度が描画速度に与える影響を確認します。

表示制御はRaspberry Pi Picoを2つ使い同時に描画開始させます。
描画内容はドットを画面左上から右方向へ塗りつぶし、同じことを下方向へ繰り返します。
塗り始めから塗終わりまでの時間を計測し最後に時間を表示します。

配線

SSD1306 OLEDのI2Cアドレスは3c(h)です。

I2C側

SPI側

スケッチ

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

#define SCREEN_WIDTH (128)
#define SCREEN_HEIGHT (64)

#define OLED_RESET      (-1)
#define SCREEN_ADDRESS  (0x3C)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

void setup()
{ 
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS))
  {
    for(;;);
  }

  display.clearDisplay();
  display.display();
  
  display.setTextSize(2);
  display.setTextColor(SSD1306_BLACK);
}

void loop()
{
  unsigned long start_ms = millis();

  for(int y = 0; y < SCREEN_HEIGHT; y ++)
  {
    for(int x = 0; x < SCREEN_WIDTH; x ++)
    {
      display.drawPixel(x, y, SSD1306_WHITE);
      display.display();
    }
  }

  display.print(millis() - start_ms);
  display.display();

  while(1);
}
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH (128)
#define SCREEN_HEIGHT (64)

#define OLED_DC     (28)
#define OLED_CS     (SS)
#define OLED_RESET  (22)

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &SPI, OLED_DC, OLED_RESET, OLED_CS);
                                        
void setup() 
{
  if(!display.begin(SSD1306_SWITCHCAPVCC))
  {
    for(;;);
  }

  display.clearDisplay();
  display.display();

  display.setTextSize(2);
  display.setTextColor(SSD1306_BLACK);
}

void loop()
{
  unsigned long start_ms = millis();

  for(int y = 0; y < SCREEN_HEIGHT; y ++)
  {
    for(int x = 0; x < SCREEN_WIDTH; x ++)
    {
      display.drawPixel(x, y, SSD1306_WHITE);
      display.display();
    }
  }

  display.print(millis() - start_ms);
  display.display();

  while(1);
}

結果

塗りつぶしている様子です。
左下がI2C, 右上がSPI通信です。

SPIは塗り終わっていて時間が表示されています。
塗終わりまでの時間は
I2C : 231.964秒
SPI : 19.874秒
SPIが11倍程度早い結果でした。

当サイトロゴの3Dモデルで回転させたときのビジュアルの違いです。
ゆったり回転するI2Cと、きびきび回転するSPIの違いを感じられます。

コメント

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