OLEDディスプレイ(有機EL) 0.96inch をI2C接続で使ってみる

Arduino/ESP320.96inch,ESP32,GM009605,I2C,OLED,SSD1306

ここの所LCDのドライバを動かしたりしましたので、勢いで手持ちのLCDをもう一つ確認しておきます。

0.96インチ OLED 2色(黄/青)LCDモジュール

lcd_oledtest01

ピンのシルク印刷には、「GND VCC SCK SDA」とあります。

パッケージにはこの様に書いてあります。

TZT GME 12864-54 0.94″ 4P 双色/Y-B
新款 (NEW)
批次:91225

批次はロットナンバーらしぃ

読めないンゴねぇ・・・

lcd_oledtest02

裏面をみると、ピンヘッダが既にはんだ付けされていて手間が無くて良いですね。

しかし小さいですな

あんまり小さいので比較写真をば

lcd_oledtest03

こんなものに文字を表示しても、老眼が進んで来たワタクシに読めるの? 的な。

信号線がシリアルの2本しかないけど調光はどうするんだろう?なんて思ったりしましたが、、

OLEDってバックライトとか無くて自ら発光するんでしたっけか?

って、OrganicなLight Emitting Diode か。

OLEDなんて言うからわかりづらい。

馴染みの言葉でいうと有機ELってやつデシタ

オーガニックなLEDって事で発光するんですわね;

まぁとりあえず使ってみようと思います。

I2C(IIC)接続はどうしたらいいのん

はてさて、ついに出てきました、I2Cデバイス。

ええ、予備知識はサッパリありません。

なんか少ない結線でシリアルで接続するんだっけ?くらい。

とりあえずざっとgoogle先生に聞いてみると、こんな感じらしい。

・Inter Integrated Circuit(IIC / I2C) というらしい
・2本の接続で1Masterに対し複数のスレーブを構成できる
・通信速度はバージョンにより 100k ~ 3.4M bpsくらいまである
・単方向のみで5MbpsのUltra Fast-modeというのもある
・つなぎ方は並列
・デバイスに7bit or 10bitのI2Cアドレスを持たせる
・信号線SCK,SDAはプルアップかプルダウンしておく

プロトコルはマイコンのライブラリに全てお任せでしょうから、ちょっと置いておきます。

で、電源とGND、I2Cバスにそれぞれ結線して他のLCDの様にライブラリで好きな様に料理して!という感じだと思うます。

たぶん

OLEDディスプレイを接続する

とりあえず結線します。

結線しないと何も始まらないんですはぃ

さて我らがESP32 NodeMCU-32SさんのI2Cピンは、んー、、、

デフォルトでI2C用に設定されているのは、GPIO21とGPIO22の様子。

MUXするのも面倒ですので、GPIO 21(SDA)とGPIO 22(SCK)をそのまま使い、単純にSCLとSDAをガツガツと繋ぎ、電源とGNDを接続してやります。

そしてI2CではSCKとSDAをプルアップしておくべきとの事なんですが、、

どのくらいの抵抗を繋いでおけばいいのかサッパリわからない。

一応計算式はあるらしいのですが、大体1k~10kΩでプルアップするんだとか。

大体、、、?

まぁ困ったので

秘奥義、内部プルアップ。

ESP32では内臓プルアップ抵抗がありますので、もう抵抗とか(ノ・ω・)ノポイー

ほら、抵抗とかシートから外したらバラッとなって面倒じゃないっスか

だから良いんですコレで。

lcd_oledtest04

というわけでGPIO21,GPIO22の内臓プルアップを有効にしておきます。

setup()にはこんな感じで書いておけばOKっスたぶん。

pinMode(21, INPUT_PULLUP);	//	SDA
pinMode(22, INPUT_PULLUP);	//	SCL

尚、プルアップ抵抗は、小さいほど高速通信が出来るらしいですね。

抵抗が小さいという事は沢山の電流が流れるという事で、電流が流れている方がノイズが入りづらいので正確な通信が可能になる、と。

へぇ・・・(ぇ

しかしあんまり抵抗を小さくすると電源に負担がかかりますので、電力が足りなくならない様に「適度に」という事らしい。

今回はLCDの駆動ですから、ノイズなんてガンガン入ってくれても構わないっス

ESP32の内臓プルアップ抵抗の値は45kΩらしく、少し大きめですが構わないっス

たぶん。

LCDを使う前の下調べ

さてLCDですから、LCDコントローラに何が使われているのかという点と、I2CですからこのデバイスのI2Cアドレスが必要だと思うん、、でスガ

どれだ、、LCDコントローラ名は

GM009605v4 ってのが怪しいのでググってみると、SSD1306らしい。

それでもってI2Cアドレスはi2c_scannerというサンプルスケッチで調べられるらしい。

という訳でひとまずは大丈夫か・・・?

不安を抱えつつも、とりあえず接続します。

lcd_oledtest05

見てっ、見てっ!

綺麗な虹よ!💛

カオス・・・

ではI2Cのアドレスを取得してみます。

arduinoのページにI2Cアドレスをスキャンするサンプルがありますので、これを見てみます。

ざっと見ると、7ビットアドレスを総なめに Wire.beginTransmission を実行してリザルトをすべて取得している様ですね。

10ビットだったら少しややこしそうなので割愛しますが、現在は殆ど使われていないそうですのでキニシナイ。

ざっくりとこんな感じで動かしてみます。

#include <Arduino.h>
#include <Wire.h>
 
void setup() {
  Wire.begin();
  Serial.begin(115200);
  Serial.println("\nI2C Scanner");

	// I2C GPIO 内部プルアップを使用
	pinMode(21, INPUT_PULLUP);	//	SDA
	pinMode(22, INPUT_PULLUP);	//	SCL
}
 
void loop() {
  byte error, address;
  int nDevices;
  Serial.println("Scanning...");
  nDevices = 0;
  for(address = 1; address < 127; address++ ) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    if (error == 0) {
      Serial.print("I2C device found at address 0x");
      if (address<16) {
        Serial.print("0");
      }
      Serial.println(address,HEX);
      nDevices++;
    }
    else if (error==4) {
      Serial.print("Unknow 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);
}

内部プルアップを使って面倒なSCL SDAラインのプルアップ抵抗を省いてしまいます。

でちょっと実行をば

lcd_oledtest06

なんでやねん

なんでやねん

大事な事なので2回言いました。

と、いいますかね

じゃあI2Cを使うときはどうしてんねん、、、と思ってライブラリを漁ってみると、「esp32-hal-i2c.c」に以下の様な記述が・・・

i2c_err_t i2cAttachSCL(i2c_t * i2c, int8_t scl)
{
    if(i2c == NULL) {
        return I2C_ERROR_DEV;
    }
    digitalWrite(scl, HIGH);
    pinMode(scl, OPEN_DRAIN | PULLUP | INPUT | OUTPUT);
    pinMatrixOutAttach(scl, I2C_SCL_IDX(i2c->num), false, false);
    pinMatrixInAttach(scl, I2C_SCL_IDX(i2c->num), false);
    return I2C_ERROR_OK;
}

i2c_err_t i2cDetachSCL(i2c_t * i2c, int8_t scl)
{
    if(i2c == NULL) {
        return I2C_ERROR_DEV;
    }
    pinMatrixOutDetach(scl, false, false);
    pinMatrixInDetach(I2C_SCL_IDX(i2c->num), false, false);
    pinMode(scl, INPUT | PULLUP);
    return I2C_ERROR_OK;
}

i2c_err_t i2cAttachSDA(i2c_t * i2c, int8_t sda)
{
    if(i2c == NULL) {
        return I2C_ERROR_DEV;
    }
    digitalWrite(sda, HIGH);
    pinMode(sda, OPEN_DRAIN | PULLUP | INPUT | OUTPUT );
    pinMatrixOutAttach(sda, I2C_SDA_IDX(i2c->num), false, false);
    pinMatrixInAttach(sda, I2C_SDA_IDX(i2c->num), false);
    return I2C_ERROR_OK;
}

i2c_err_t i2cDetachSDA(i2c_t * i2c, int8_t sda)
{
    if(i2c == NULL) {
        return I2C_ERROR_DEV;
    }
    pinMatrixOutDetach(sda, false, false);
    pinMatrixInDetach(I2C_SDA_IDX(i2c->num), false, false);
    pinMode(sda, INPUT | PULLUP);
    return I2C_ERROR_OK;
}

なんとまぁ

I2C関連ライブラリを使おうとしたら、勝手にpinModeも設定する様になっているじゃぁ、ありませんか。

GPIOをアタッチする際、SCL,SDAともに、「OPEN_DRAIN | PULLUP | INPUT | OUTPUT」 にしてますね。

そしてデタッチする際には、INPUT | PULLUP に戻している。

という事はですよ

特にI2C関連ライブラリを呼んでおらずWireの特定メソッドしか使っていないI2C Scannerでは、pinModeを「OPEN_DRAIN | PULLUP | INPUT | OUTPUT」にすれば良い、と。

早速スキャナーを修正しまして・・・

#include <Arduino.h>
#include <Wire.h>
 
void setup() {
  Wire.begin();
  Serial.begin(115200);
  Serial.println("\nI2C Scanner");

	// I2C GPIO 内部プルアップを使用
//	pinMode(21, INPUT_PULLUP);	//	SDA
//	pinMode(22, INPUT_PULLUP);	//	SCL
	pinMode(21, OPEN_DRAIN|PULLUP|INPUT|OUTPUT);	//	SDA
	pinMode(22, OPEN_DRAIN|PULLUP|INPUT|OUTPUT);	//	SCL
}
 
void loop() {
  byte error, address;
  int nDevices;
  Serial.println("Scanning...");
  nDevices = 0;
  for(address = 1; address < 127; address++ ) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    if (error == 0) {
      Serial.print("I2C device found at address 0x");
      if (address<16) {
        Serial.print("0");
      }
      Serial.println(address,HEX);
      nDevices++;
    }
    else if (error==4) {
      Serial.print("Unknow 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);
}

これでいかが・・・

lcd_oledtest07

で・き・た!

俺が、、ガン〇ムだ・・・

何とか無事にこのOLEDのI2Cアドレスが「0x3C」である事がわかりますた。

よく考えれば、シリアルとはいえ一方通行の結線をしている訳でもないので、pinModeをINPUTに固定しちゃダメよんって事に気が付くべきでした・・・

尚、ちゃんとI2Cアドレスがボード上にシルク印刷されている様なモジュールはI2Cアドレスをちょこっと変更できるみたいですが、このボードは変更したらどうなるのか謎過ぎて手が出ません。

一応ボード上に2か所あるんですよね、抵抗がジャンパになってるっぽい箇所が。

lcd_oledtest08

少し調べてみた感じでは、このR3とR4を使ってSPI通信とI2C通信を切り替える様な使い方がチラホラと出てきましたが、、

他にI2Cでアドレスが被る様なデバイスは持ってないので、ひとまずはこのまま使ってみようと思います。

ひとまずまとめると、、

このボードをESP32で使う場合には、ライブラリに任せて余計なpinModeを書かないか、若しくは「OPEN_DRAIN|PULLUP|INPUT|OUTPUT」としましょうって事で。

	// I2C GPIO 内部プルアップを使用
//	pinMode(21, INPUT_PULLUP);	//	SDA
//	pinMode(22, INPUT_PULLUP);	//	SCL
	pinMode(21, OPEN_DRAIN|PULLUP|INPUT|OUTPUT);	//	SDA
	pinMode(22, OPEN_DRAIN|PULLUP|INPUT|OUTPUT);	//	SCL

もしpinModeを記述するなら、ここの部分をお直ししてください。

LCDライブラリ( adafruit/Adafruit_SSD1306)を使って表示させてみる

今回利用させて頂くのは、これまたPlatformIOで検索した時いっぱい使われてそうなやつ(ぇ

adafruit/Adafruit_SSD1306 を拝借いたします。

ささっとPlatformIOでインストールしまして、、

exampleの「ssd1306_128x64_i2c」をもってきます。(examples\ssd1306_128x64_i2c\ssd1306_128x64_i2c.ino)

プロトタイプ書くのも面倒なので、setupとloopを一番下に移動(ぇ

パラメータを一部修正しまして

// リセットピンが無いので・・・
//#define OLED_RESET 4 // Reset pin # (or -1 if sharing Arduino reset pin)
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)

// セットアップに
//	Serial.begin(9600);
	Serial.begin(115200);

とりあえずサンプルを動かしてみます。

評判の悪い2カラーですが、確かに全画面使用しようとすると、継ぎ目の線が気になりますね・・・

それからなんかモッサリしてますねぇ

何も設定していないので100kbpsなんでしょうか。

というわけでちょっとヘッダーファイルを覗いてみると、コンストラクタでI2Cの動作クロックを設定出来るみたいです。

Adafruit SSD1306\Adafruit_SSD1306.h

/*!
    @brief  Class that stores state and functions for interacting with
            SSD1306 OLED displays.
*/
class Adafruit_SSD1306 : public Adafruit_GFX {
public:
  // NEW CONSTRUCTORS -- recommended for new projects
  Adafruit_SSD1306(uint8_t w, uint8_t h, TwoWire *twi = &Wire,
                   int8_t rst_pin = -1, uint32_t clkDuring = 400000UL,
                   uint32_t clkAfter = 100000UL);
  Adafruit_SSD1306(uint8_t w, uint8_t h, int8_t mosi_pin, int8_t sclk_pin,
                   int8_t dc_pin, int8_t rst_pin, int8_t cs_pin);
  Adafruit_SSD1306(uint8_t w, uint8_t h, SPIClass *spi, int8_t dc_pin,
                   int8_t rst_pin, int8_t cs_pin, uint32_t bitrate = 8000000UL);

  // DEPRECATED CONSTRUCTORS - for back compatibility, avoid in new projects
  Adafruit_SSD1306(int8_t mosi_pin, int8_t sclk_pin, int8_t dc_pin,
                   int8_t rst_pin, int8_t cs_pin);
  Adafruit_SSD1306(int8_t dc_pin, int8_t rst_pin, int8_t cs_pin);
  Adafruit_SSD1306(int8_t rst_pin = -1);

  ~Adafruit_SSD1306(void);

何も指定しないと100kbpsになる様で。

という訳でここは400kbpsなんてケチくさい事を言わず、4Mbpsとかにしてみます(ぇ

// コンストラクタにI2Cクロックを指定
//Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET, 4000000UL, 100000UL);

てな感じで流してみると

プログラムの方で随所にディレイが入っている為あまり速くなった感じはしませんが、比べてみると結構早くなっているのが判りますね

明るさの調整はSSD1306ドライバICの方の説明を見ると、256階調で値を突っ込める様な事が書いてありました。

1 GENERAL DESCRIPTION SSD1306 is a single-chip CMOS OLED/PLED driver with controller for organic / polymer light emitting diode dot-matrix graphic display system. It consists of 128 segments and 64commons. This IC is designed for Common Cathode type OLED panel.

The SSD1306 embeds with contrast control, display RAM and oscillator, which reduces the number of external components and power consumption. It has 256-step brightness control. Data/Commands are sent from general MCU through the hardware selectable 6800/8000 series compatible Parallel Interface, I2C interface or Serial Peripheral Interface. It is suitable for many compact portable applications, such as mobile phone sub-display, MP3 player and calculator, etc.

https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf

10.1.7 Set Contrast Control for BANK0 (81h)

This command sets the Contrast Setting of the display. The chip has 256 contrast steps from 00h to FFh. The segment output current increases as the contrast step value increases.

https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf

ライブラリの方を見ると Adafruit_SSD1306::dim(bool dim) というメソッドがありましたので、これでコントラストを調整できる様です。

void Adafruit_SSD1306::dim(bool dim) {
  // the range of contrast to too small to be really useful
  // it is useful to dim the display
  TRANSACTION_START
  ssd1306_command1(SSD1306_SETCONTRAST);
  ssd1306_command1(dim ? 0 : contrast);
  TRANSACTION_END
}

ひとまず動作の確認はできました。

このLCDモジュールも、わりとすんなりと使い始める事が出来て何よりです。

すんなりと言っても、なんか結構面倒だった気がしないでもないですが・・・

キモはLCDドライバICですね

これが判らないとどうしようも無い様ですので、LCDモジュールを購入される際にはそこの辺りをよく調べてお買い求めください。

しかしあの上下で分割される謎の描画できない謎スペース、どうにかならないものですかね・・・( ノД`)