Arduino UNO と秋月 0.96 インチ OLEDで遊ぶ(OLED の描画が遅い問題の対応)

Arduino UNO も着々と触っています。

今は FPGA の方にも興味があり、他にも電気に関する基礎勉強も始めました。

改めて勉強してみると、難しいですね。楽しい(∩´∀`)∩

今回触ってみるのは秋月 0.96 インチ OLED です。Arduino UNO から動かしてみます。

実はある問題にめっちゃハマって時間を持っていかれたので、そのメモも兼ねています。

今回準備するもの

セットアップ

セットアップに関しては色々な方が書かれているので省略。ざっくり流れをまとめておくと下記のようになります。

初級者向け

  1. I2C アドレスの確認
  2. Adfruit SSD 1306 と Adfruit GFX Library を Arduino IDE にインストール
  3. ライブラリを利用したプログラムを書いて、動かす

中級者向け?

  1. I2C アドレスの確認
  2. Arduino 付属の Wire ライブラリを利用して OLED に対して書き込みを行うプログラムを書く

参考情報

Wire を使って生(なま)なやりとりをするのも楽しいのですが、まずはチュートリアルということで手っ取り早く初心者向けの方を試しています。(ライブラリ内部で同様に Wire 使ってそうだし)

接続図

はい、ものすごくシンプルですね。Arduino UNO に印字されている SCL を OLED の SCL に、SDA を OLED の SDA につなぎます。給電と GND は Arduino UNO のものを使います。

電源は秋月のホームページによると電源電圧が 3V 〜 5.5 V とのことなので、3.3 V の方に繋いでいます。

スケッチ(プログラム) ライブラリ利用編

早速自分のお好みのプログラムを書いてみます。(サンプルを動かす例は他のサイトを参考にしてください)

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

// DISPLAY SETTINGS
#define OLED_ADDRESS 0x3C
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

Adafruit_SSD1306 display(-1);

void setup() {
  display.begin(SSD1306_SWITCHCAPVCC,OLED_ADDRESS);

  display.setTextSize(1);
  display.setTextColor(WHITE);
}

void loop() {
  display.clearDisplay();

  display.setTextColor(WHITE);
  display.setCursor(0,0);

  display.print("Hello, World!");
  display.display();
}
0.96 インチ OLED 実行結果

ライブラリのおかげで、ものすごく簡単に表示ができました(∩´∀`)∩

調子に乗って、キャラをスクロールさせるコードを書いてみたのですが、これから大ハマリしたところを書いていきます。

ハマったところ(アニメーションが遅い問題)

まずは、ハマったコードを書いておきます。このコードは CharacterGfx のビットマップ(1 だと白く表示されて 0 だと非表示)を表示して左右に横スクロールするプログラムです。

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

// DISPLAY SETTINGS
#define OLED_ADDRESS 0x3C
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

// graphics
static const unsigned char CharacterGfx []={
  B00000000,B00000000,
  B00000000,B00000000,
  B00111111,B10000000,
  B01101110,B11000000,
  B11111111,B11100000,
  B10111111,B10100000,
  B10100000,B10100000,
  B00000000,B00000000
};

Adafruit_SSD1306 display(-1);
 
int XPos=0;
int direction = 1;
 
void setup() {
  display.begin(SSD1306_SWITCHCAPVCC,OLED_ADDRESS);
}
 
void loop() {
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.print("Hello, World");

  // XPos が loop のたびにズレるのでスクロールする
  display.drawBitmap(XPos, 20, CharacterGfx, 11, 8, WHITE);
  display.display();

  XPos+=direction;

  // はじっこまで行ったら反転
  if((XPos > (127 - 16)) || (XPos < 0))
    direction = -direction;
}
Arduino UNO + 秋月 0.96 OLED 遅いバージョン

めっちゃ遅い … (;^ω^)I2C って遅いのか … ? こんなもんなのか … ?

調査のためにライブラリのコードを集中的に読んだところ、初期化のところに問題がありそうだ、という事がわかりました。具体的には下記のコードの書き方が問題でした。

Adafruit_SSD1306 display(-1);

ここなんですが、Adfruit の該当しているソースコード(https://github.com/adafruit/Adafruit_SSD1306/blob/master/Adafruit_SSD1306.cpp#L305-L323A)を見ると、下記のようになっています。

/*!
    @brief  DEPRECATED constructor for I2C SSD1306 displays. Provided for
            older code to maintain compatibility with the current library.
            Screen size is determined by enabling one of the SSD1306_* size
            defines in Adafruit_SSD1306.h. New code should NOT use this.
            Only the primary I2C bus is supported.
    @param  rst_pin
            Reset pin (using Arduino pin numbering), or -1 if not used
            (some displays might be wired to share the microcontroller's
            reset pin).
    @return Adafruit_SSD1306 object.
    @note   Call the object's begin() function before use -- buffer
            allocation is performed there!
*/
Adafruit_SSD1306::Adafruit_SSD1306(int8_t rst_pin) :
  Adafruit_GFX(SSD1306_LCDWIDTH, SSD1306_LCDHEIGHT), spi(NULL), wire(&Wire),
  buffer(NULL), mosiPin(-1), clkPin(-1), dcPin(-1), csPin(-1),
  rstPin(rst_pin) {
}

DEPRECATED なのかよ!古いコンストラクタ使っているじゃん!( •̀ㅁ•́;)

というわけで新しい形式の方のコンストラクタを使います。( https://github.com/adafruit/Adafruit_SSD1306/blob/master/Adafruit_SSD1306.cpp#L133-L171 )

/*!
    @brief  Constructor for I2C-interfaced SSD1306 displays.
    @param  w
            Display width in pixels
    @param  h
            Display height in pixels
    @param  twi
            Pointer to an existing TwoWire instance (e.g. &Wire, the
            microcontroller's primary I2C bus).
    @param  rst_pin
            Reset pin (using Arduino pin numbering), or -1 if not used
            (some displays might be wired to share the microcontroller's
            reset pin).
    @param  clkDuring
            Speed (in Hz) for Wire transmissions in SSD1306 library calls.
            Defaults to 400000 (400 KHz), a known 'safe' value for most
            microcontrollers, and meets the SSD1306 datasheet spec.
            Some systems can operate I2C faster (800 KHz for ESP32, 1 MHz
            for many other 32-bit MCUs), and some (perhaps not all)
            SSD1306's can work with this -- so it's optionally be specified
            here and is not a default behavior. (Ignored if using pre-1.5.7
            Arduino software, which operates I2C at a fixed 100 KHz.)
    @param  clkAfter
            Speed (in Hz) for Wire transmissions following SSD1306 library
            calls. Defaults to 100000 (100 KHz), the default Arduino Wire
            speed. This is done rather than leaving it at the 'during' speed
            because other devices on the I2C bus might not be compatible
            with the faster rate. (Ignored if using pre-1.5.7 Arduino
            software, which operates I2C at a fixed 100 KHz.)
    @return Adafruit_SSD1306 object.
    @note   Call the object's begin() function before use -- buffer
            allocation is performed there!
*/
Adafruit_SSD1306::Adafruit_SSD1306(uint8_t w, uint8_t h, TwoWire *twi,
  int8_t rst_pin, uint32_t clkDuring, uint32_t clkAfter) :
  Adafruit_GFX(w, h), spi(NULL), wire(twi ? twi : &Wire), buffer(NULL),
  mosiPin(-1), clkPin(-1), dcPin(-1), csPin(-1), rstPin(rst_pin),
  wireClk(clkDuring), restoreClk(clkAfter) {
}

こちらは先程のコードとは違い、wireClk の設定などもされています。全ては辿りませんが、Arduino の標準にある Wire ライブラリの setClock() を叩いていそうなコードなので、Arduino – OLED 間のやりとりが速くなりそうな予感がします。

ですので、下記のようにコードを修正しました。

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

 
// DISPLAY SETTINGS
#define OLED_ADDRESS 0x3C
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

// graphics
static const unsigned char CharacterGfx []={
  B00000000,B00000000,
  B00000000,B00000000,
  B00111111,B10000000,
  B01101110,B11000000,
  B11111111,B11100000,
  B10111111,B10100000,
  B10100000,B10100000,
  B00000000,B00000000
};

// 修正したところ
// 新しい形式のコンストラクタを利用する。
// ディスプレイサイズの設定、Wire の参照を渡し、RST pin は変わらず -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
 
int XPos=0;
int direction = 1;
 
void setup() {
  display.begin(SSD1306_SWITCHCAPVCC,OLED_ADDRESS);
}
 
void loop() {
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.print("Hello, World");

  // XPos が loop のたびにズレるのでスクロールする
  display.drawBitmap(XPos, 20, CharacterGfx, 11, 8, WHITE);
  display.display();

  XPos+=direction;

  // はじっこまで行ったら反転
  if((XPos > (127 - 16)) || (XPos < 0))
    direction = -direction;
}

結果快適なスクロールになりました(∩´∀`)∩

Arduino UNO + 秋月 0.96 速い(なめらか) バージョン

お勉強タイム

I2C とは

またまた、wikipedia から引用します。(他にも書籍と合わせて)

I2C(アイ・スクエアド・シー、アイ・アイ・シー)はフィリップス社で開発されたシリアルバスである。低速な周辺機器をマザーボードへ接続したり、組み込みシステム携帯電話などで使われている。

https://ja.wikipedia.org/wiki/I2C

これに対して、SPI というのもあるそうです。

シリアル・ペリフェラル・インタフェース(Serial Peripheral Interface, SPI)は、コンピュータ内部で使われるデバイス同士を接続するバスである。パラレルバスに比べて接続端子数が少なくて済むシリアルバスの一種で、比較的低速なデータ転送を行うデバイスに利用される。

https://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%AB%E3%83%BB%E3%83%9A%E3%83%AA%E3%83%95%E3%82%A7%E3%83%A9%E3%83%AB%E3%83%BB%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9

SPI と比較して利用する信号線も少なく、速度よりも製造コストを抑えるため、と。

またこちらのサイトも参考にさせていただきました。ありがとうございます。

調べれば調べるほど奥が深い(; ・`д・´) Arduino の Wire ライブラリ自体の仕組みも気になりますし、Wire をいい感じに wrap してくれる adfruit のライブラリの中身も気になります。

どんな感じで信号を生成するのかじっくり学んで行きたいと思います。

最後に

「こんなに簡単に OLED を制御できるなんて、お手軽だなぁ」と思う反面、仕組みについて調べてみると膨大な量の知識が求められているような気がしています。そもそも自分はマイコンに馴染みがなかったので、 Arduino 自体はお手軽にこういったこともさせてもらえる、という意味では助かっていますが、相当な量が隠蔽されているんだなぁ、と素人ながら考えるところがありました。

これからも Arduino の色々な機能を駆使して、動かすところから始めていこうと思います。

とりあえず、OLED が制御できるようになったし、ゲームでも作ってみようかなぁ。