目次
[1] プロローグ:大きいことはいいことだ
[2] 0.96インチと差し替えるだけでOK?
[3] ハード的には問題が無い。となると、ライブラリ?
[4] 0.96インチと1.3インチのOLED 2台同時表示の検討
[5] 2系統のI2Cのテスト
[6] 0.96インチと1.3インチのOLED 2台同時表示を実現!
[7]関連記事
[1] プロローグ:大きいことはいいことだ
現在IoT関連のOLEDディスプレイは、サイズが0.96インチで128×64ドットが主流のようです。0.96インチというサイズはシステムをコンパクトにまとめるという点で大きなメリットですが場合により、また人によってはもう少し大きい方が良いこともありそうです。
実際どーなの?という訳で1.3インチOLEDを入手。インタフェースは配線本数が少ないI2Cにしました。
早速テスト・・・毎度の事ながら、やっぱり今回もいくつかハマってしまいました。
[2] 0.96インチと差し替えるだけでOK?
説明書は無し。購入したサイトにI2Cアドレスなどの情報はありません。
ま、サイズが違うだけで、0.96インチのプログラムですんなり表示できるだろう・・・と思って以前Arduino IDE 上で作成した「ESP32 + BME680 + 0.96インチOLED」(詳細は最後の関連記事の項参照)のOLEDと差し替えてみました。
この配線図は0.96インチOLEDのものです。
[危険!] 0.96インチも1.3インチも 電源とGNDの端子の並び方が逆、つまり「VDD,GND,SCK,SDA」と「GND,VDD,SCK,SDA」というのがあるので要注意!です。
ありゃりゃ? なんだ、これ・・ う~ん・・・
しかし、めちゃくちゃな画像にしろ表示できているということは配線とI2CアドレスはOKということかな。念のため「I2Cスキャナ」をダウンロードして確認。
BME680モジュールを取り去ると0.96インチOLEDと同じ0x3Cだけが表示されます。
[3] ハード的には問題が無い。となると、ライブラリ?
今使用しているライブラリは SSD1306というチップ用だけど・・・
あらためて購入したOLEDのホームページをよ~くみると下記のように書いてありました。
OLED screen, internal drive chip: SH1106 (Operation and SSD1306 same)
括弧内の意味がよく分からないけど、似て非なるものということでしょうか?
とにかくSH1106のライブラリを探して試そう・・
ライブラリマネージャの検索ボックスにSH1106と入力して、Adafruit SH110Xを選択して「インストール」のボタンをクリックします。
サンプルプログラムは、ファイル > スケッチ例 > Adafruit SH1106 > OLED_QTPY_SH1106 > SH1106_128x64_i2c_QTPY にあります。
このスケッチ例を参考にして以前作成したBME680 + OLEDのプログラムを修正して、下記を作成しました。
/*
* ESP32 + BME680で温度・湿度・気圧・ガスを測定し、
* 1.3インチOLED(SH1106)に表示する
*
* ボード:ESP32 DEVKIT V1
* 2022-08-28
*
* (注1)BME680 I2Cアドレス=0x76
* Adafruit_BME680.h は、I2Cアドレス=0x77 なので要変更!
* (注2)ストロベリーリナックス社のBME680モジュールはSDA,SCLピンのプルアップが必要。
* (注3)1.3インチOLED: I2Cアドレス=0x3C, SH1106ライブラリ
*/
#include <Wire.h>
#include "Adafruit_BME680.h"
#include <Adafruit_GFX.h> // グラフィックのコア・ライブラリ
#include <Adafruit_SH110X.h>
Adafruit_BME680 bme680; // I2C
#define SEALEVELPRESSURE_HPA (1013.25)
#define OLED_I2C_ADRS 0x3C // I2C address of OLED
Adafruit_SH1106G OLED(128, 64, &Wire, 4);
//Adafruit_SH1106G OLED(128, 64, &Wire, -1);
// 引数 WIDTH, HEIGHT, &Wire, OLED_RESET
// 4つ目の引数はRESETピン -1はArduinoのRESETピンと共有
//--------------------------------------------------------------------------
void setup() {
Serial.begin(115200);
delay(250); // wait for the OLED to power up
if(!OLED.begin(OLED_I2C_ADRS, true)) {
Serial.println(F("SH1106 OLED allocation failed"));
for(;;); // Don't proceed, loop forever
}
// OLEDに初期表示バッファの内容を表示する
// ライブラリはこれをAdafruitスプラッシュ画面で初期化する
OLED.display();
delay(2000); // 2秒待つ
OLED.clearDisplay();
OLED.display();
OLED.setTextSize(1); // フォントサイズを1~で指定
OLED.setTextColor(SH110X_WHITE); // フォント色
if (!bme680.begin()) {
Serial.println("Could not find a valid BME680 sensor, check wiring!");
while (1) {
delay(0);
}
}
bme680.setTemperatureOversampling(BME680_OS_8X); // 温度のオーバーサンプリング
bme680.setHumidityOversampling(BME680_OS_2X); // 湿度のオーバーサンプリング
bme680.setPressureOversampling(BME680_OS_4X); // 気圧のオーバーサンプリング
bme680.setIIRFilterSize(BME680_FILTER_SIZE_3); // フィルタを初期化
bme680.setGasHeater(320, 150); // ガス測定用ヒーター 320℃ 150 ms
}
//--------------------------------------------------------------------------
void loop() {
OLED.setCursor(0,0);
OLED.clearDisplay();
if (! bme680.performReading()) {
Serial.println("Failed to perform reading :(");
return;
}
Serial.print("Temperature = "); Serial.print(bme680.temperature); Serial.print(" *C ");
Serial.print("Pressure = "); Serial.print(bme680.pressure/200.0); Serial.print(" hPa ");
Serial.print("Humidity = "); Serial.print(bme680.humidity); Serial.print(" % ");
Serial.print("Gas = "); Serial.print(bme680.gas_resistance/1000.0); Serial.println(" KOhms");
OLED.print("Temperature: "); OLED.print(bme680.temperature); OLED.println(" *C");
OLED.print("Pressure: "); OLED.print(bme680.pressure/200); OLED.println(" hPa");
OLED.print("Humidity: "); OLED.print(bme680.humidity); OLED.println(" %");
OLED.print("Gas: "); OLED.print(bme680.gas_resistance/1000.0); OLED.println(" KOhms");
OLED.display();
delay(2000);
}
[4] 0.96インチと1.3インチのOLED 2台同時表示の検討
1.3インチOLEDに表示できるようになったけど、さらに進めて0.96インチと2台同時に表示して比較したいなぁ、と思いました。問題は、どちらもI2Cアドレスが0x32であることです。どちらかを0x33に変更する必要があります。
しかし、1.3インチのOLEDはアドレス変更のジャンパ回路がありません!
4ピンコネクタの近くに0Ωのジャンパ抵抗が2つありますが、これはコネクタの1番ピンと2番ピンにVDD, GNDの何れを接続するかを選択するもので、現状は1ピン:VDD、2ピン:GNDです。
※ I2Cアドレスをジャンパで変更できる1.3インチOLEDもあります。中華サイトで販売されているのを見ました。
茶色の30ピンのフレキシブル・コネクタ(略称フレキ:業界用語)があります。この30ピン、それぞれどういう信号線なのでしょう? 仕様書は下記にありました(ディスプレイ部に関することだけで、SH1106やモジュールとしての説明はありません)。
SH1106の仕様書は下記にあります。
1つ目の資料を見ると15番ピン(D/C#)がI2Cアドレスの選択で、GNDに接続すると0x32(現状)、VDDに接続すると0x33になるようです。ここをパターンカットしてVDDに接続するという方法もあるけど⓵面倒、⓶強引なやり方でスマートではない、⓷ハードウェア改造は万人向けのやり方ではない、ということで却下。
じゃあ、0.96インチOLEDのアドレスを変更する? う~ん、これはチップ抵抗を1個場所を移動させるだけなので工作としては簡単だけど、これも同じ理由でやりたくないなぁ・・
[5] 2系統のI2Cのテスト
ESP32にはI2Cが2系統あるので、下図のようにすれば2つのOLEDを何れもアドレス0x32のままで接続できるはずです。
さらに、下記のようにすればOLEDを4つ接続することも可能かな。
ESP32の2系統のI2Cを同時に使用するにはどうするか?
下記サイトを参考にしました。簡潔な説明と短いコードが紹介されてますが必要にして十分な情報です。
上記リンクはスキップせずに一読することをお勧めしますが、2つ目のI2Cを使用するには、以下のようにするということです。
#define Wire1_SDA (33) // SDAのI/Oピン
#define Wire1_SCL (32) // SCLのI/Oピン
void setup()
{
Wire.begin(); // 1つ目のI2C
Wire1.begin(Wire1_SDA, Wire1_SCL); // 2つ目のI2C
ふむふむ、なるほど。1つ目は Wireオブジェクト、2つ目はピン番号を定義して Wire1オブジェクトを利用すればいいのか。な~んだ、拍子抜けするほど簡単じゃないか、と思ったのですが・・・
一度に色々なことをまとめてやろうとすると大概失敗する、もしくは死ぬほど苦労する という苦い教訓を生かし、まずは下図のように2つ目の I2C (Wire1) に1.3インチOLEDとBME680を接続して正常に動作したら、1つ目の I2C (Wire) に0.96インチOLEDを接続するという2段構えでいくことにしました。
現在動作しているSH1106 OLEDを2つ目のI2Cに変更することを検討します。
現時点でのI2Cに関するコードのみピックアップすると以下のようになってます。
#include <Wire.h> // I2Cのライブラリ
#include <Adafruit_GFX.h> // グラフィックのコア・ライブラリ
#include <Adafruit_SH110X.h> // SH1106のライブラリ
Adafruit_BME680 bme680; // I2C
#define OLED_I2C_ADRS 0x3C // OLEDのI2Cアドレス
Adafruit_SH1106G OLED(128, 64, &Wire, -1);
void setup() {
if(!OLED.begin(OLED_I2C_ADRS, true)) {
Serial.println(F("SH1106 OLED allocation failed"));
for(;;); // Don't proceed, loop forever
}
これを以下のように変更しました。
#include <Wire.h> // I2Cのライブラリ
#include <Adafruit_GFX.h> // グラフィックのコア・ライブラリ
#include <Adafruit_SH110X.h> // SH1106のライブラリ
Adafruit_BME680 bme680; // I2C
#define OLED_I2C_ADRS 0x3C // OLEDのI2Cアドレス
#define Wire1_SDA (33) // 2つ目のI2CのSDAのピン
#define Wire1_SCL (32) // 2つ目のI2CのSCLのピン
Adafruit_SH1106G OLED(128, 64, &Wire1, 4);
void setup() {
Wire1.begin(Wire1_SDA, Wire1_SCL);
if(!OLED.begin(OLED_I2C_ADRS, true)) {
Serial.println(F("SH1106 OLED allocation failed"));
for(;;); // Don't proceed, loop forever
}
ダウンロードが終わるとAdafruitのスプラッシュ画面が表示されました。
おおっ、表示された! いいぞ、いいぞ・・・
成功したかに見えた。しかし、その後画面は真っ暗。BME680の測定値が表示されない! えっ、ど、どーして? Why?
ここで長考、そして様々なトライ&エラー。 しかし、どれも上手くいかない。
OLED のI2Cのことばかり注目していたけど、BME680の測定値が表示されないということは、もしかしてBME680に問題あり?ということに気が付いた。で、シリアルモニタを見ると、
"Could not find a valid BME680 sensor, check wiring!"
なに、BME680をふぁいんど出来ませんでした、だと? なんで? どーして? おい、こら・・・ またもや長考。
SH1106 OLED は2つ目のI2Cを使うように変更したけど、BME680 は何もしてないことに気が付いた。そこで
//Adafruit_BME680 bme680; // I2C
Adafruit_BME680 bme680(&Wire1); // I2C
と修正してみました。
元は bme680; と ( ) は付いてないけどOLEDの見よう見まねで bme680(&Wire1); でいいのかな???
結果は? ⇒ ⇒ ⇒ ⇒ ⇒ ⇒ ⇒ ⇒ ⇒ ⇒ 正解。動きました!!
[6] 0.96インチと1.3インチのOLED 2台同時表示を実現!
ここまで長かった道のりもやっと最終段階にきました。あとは1つ目のI2Cで0.96インチのOLEDに表示させれば完了です。
回路構成と実体配線図、プログラムは以下の通りです。
[注1]5穴のブレッドボードに幅が広いESP32Sを載せると、使用できるのは1列だけです。
そのため追加のI2Cのピンは、(21,22)ピンと同じ側にある (2,4)ピンを使うようにしました。
[注2]BME680と1.3インチOLEDの画像は、Fritzingの正式な手順で作成したものではありません。
パワーポイントで作った図と写真を画像処理ソフトで貼り付けただけの手抜きです。
うん、やっぱり大きいと見易いです! いいね!
小さくまとめることが最優先のシステムでなければ、目に優しい1.3インチOLEDはお勧めです。
(0.96インチでも無理なくハッキリ見える、且つ自分しか使用しないのであれば必要ないかも)
最後にプログラムです。この後にベースになった過去の関連記事のリンクがあるので合わせてご覧下さい。
/*
* ESP32 + BME680で温度・湿度・気圧・ガスを測定し、
* 0.96インチOLED(SSD1306)と1.3インチOLED(SH1106) に表示する
*
* ボード:ESP32 DEVKIT V1
* 2022-08-28
*
* (注1)BME680 I2Cアドレス=0x76
* Adafruit_BME680.h は、I2Cアドレス=0x77 なので要変更!
* (注2)ストロベリーリナックス社のBME680モジュールはSDA,SCLピンのプルアップが必要。
* (注3)0.96インチOLED: I2Cアドレス=0x3C, SSD1306ライブラリ
* (注3)1.3インチOLED: I2Cアドレス=0x3C, SH1106ライブラリ
*/
#include <Wire.h> // I2C ライブラリ
#include "Adafruit_BME680.h"
#include <Adafruit_GFX.h> // グラフィックのコア・ライブラリ
#include <Adafruit_SSD1306.h> // 0.96インチ OLED
#include <Adafruit_SH110X.h> // 1.3インチ OLED
Adafruit_BME680 bme680(&Wire1); // I2C(2)
#define SEALEVELPRESSURE_HPA (1013.25)
#define OLED_I2C_ADRS 0x3C // I2C address of OLED
#define Wire1_SDA (4)
#define Wire1_SCL (2)
Adafruit_SSD1306 OLED(128, 64, &Wire, 4);
Adafruit_SH1106G OLED1(128, 64, &Wire1, -1);
// 引数 WIDTH, HEIGHT, &Wire, OLED_RESET
// 4つ目の引数はRESETピン -1はArduinoのRESETピンと共有
//--------------------------------------------------------------------------
void setup() {
Wire1.begin(Wire1_SDA, Wire1_SCL);
Serial.begin(115200);
delay(250); // wait for the OLED to power up
// SSD1306_SWITCHCAPVCC :表示用電圧を3.3Vから生成する
if(!OLED.begin(SSD1306_SWITCHCAPVCC, OLED_I2C_ADRS)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Don't proceed, loop forever
}
if(!OLED1.begin(OLED_I2C_ADRS, true)) {
Serial.println(F("SH1106 OLED allocation failed"));
for(;;); // Don't proceed, loop forever
}
// OLEDに初期表示バッファの内容を表示する
// ライブラリはこれをAdafruitスプラッシュ画面で初期化する
OLED.display();
OLED1.display();
delay(2000); // 2秒待つ
OLED.clearDisplay();
OLED1.clearDisplay();
OLED.display();
OLED1.display();
OLED.setTextSize(1); // フォントサイズを1~で指定
OLED1.setTextSize(1); // フォントサイズを1~で指定
OLED.setTextColor(SH110X_WHITE); // フォント色
OLED1.setTextColor(SH110X_WHITE); // フォント色
if (!bme680.begin()) {
Serial.println("Could not find a valid BME680 sensor, check wiring!");
while (1) {
delay(0);
}
}
bme680.setTemperatureOversampling(BME680_OS_8X); // 温度のオーバーサンプリング
bme680.setHumidityOversampling(BME680_OS_2X); // 湿度のオーバーサンプリング
bme680.setPressureOversampling(BME680_OS_4X); // 気圧のオーバーサンプリング
bme680.setIIRFilterSize(BME680_FILTER_SIZE_3); // フィルタを初期化
bme680.setGasHeater(320, 150); // ガス測定用ヒーター 320℃ 150 ms
}
//--------------------------------------------------------------------------
void loop() {
OLED.setCursor(0,0);
OLED1.setCursor(0,0);
OLED.clearDisplay();
OLED1.clearDisplay();
if (! bme680.performReading()) {
Serial.println("Failed to perform reading :(");
return;
}
Serial.print("Temperature = "); Serial.print(bme680.temperature); Serial.print(" *C ");
Serial.print("Pressure = "); Serial.print(bme680.pressure/200.0); Serial.print(" hPa ");
Serial.print("Humidity = "); Serial.print(bme680.humidity); Serial.print(" % ");
Serial.print("Gas = "); Serial.print(bme680.gas_resistance/1000.0); Serial.println(" KOhms");
OLED.println("0.96 inch OLED");
OLED.println("SSD1306");
OLED.println("");
OLED.print("Temperature: "); OLED.print(bme680.temperature); OLED.println(" *C");
OLED.print("Pressure: "); OLED.print(bme680.pressure/200); OLED.println(" hPa");
OLED.print("Humidity: "); OLED.print(bme680.humidity); OLED.println(" %");
OLED.print("Gas: "); OLED.print(bme680.gas_resistance/1000.0); OLED.println(" KOhm");
OLED.display();
OLED1.println("1.3 inch OLED");
OLED1.println("SH1106");
OLED1.println("");
OLED1.print("Temperature: "); OLED1.print(bme680.temperature); OLED1.println(" *C");
OLED1.print("Pressure: "); OLED1.print(bme680.pressure/200); OLED1.println(" hPa");
OLED1.print("Humidity: "); OLED1.print(bme680.humidity); OLED1.println(" %");
OLED1.print("Gas: "); OLED1.print(bme680.gas_resistance/1000.0); OLED1.println(" KOhm");
OLED1.display();
delay(2000);
}
//--------------------------------------------------------------------------
[7]関連記事