ATtiny85で小型ディスプレイSSD1306を使おう(I2C通信)

こんにちは、くのへ@MasazaneKunoheです。

今日はATtiny85で小型ディスプレイSSD1306を使う方法を紹介します!
ArduinoだとシリアルモニタとSerial.Printを使ったデバッグ作業をすることが出来ますが、ATtiny85はパソコンとシリアル通信していないため、このデバック操作が出来ません。

そのため、小型ディスプレイをシリアルモニタ代わりに使うとちょっと分かりやすくなったりします。

ただ、この小型ディスプレイ、Arduinoが通常使うライブラリを使ってもATtiny85では動きません。
ATtiny85用のライブラリを使って動かします!

小型ディスプレイSSD1306について

SSD1306と言うのはこんな感じの約26mm×26mmの小さなディスプレイです。

SSD1306はOLED(Organic Light Emitting Diode)と呼ばれる有機ELディスプレイの1つです。
OLEDディスプレイは自己発光性を持っていて、内部にライトなどが要らないので小型化を実現できています。
小型ICチップであるATtiny85と組み合わせると小型のアイテムが作れるので、相性がすごく良いです。

2023年2月現在、このSSD1306は580円で秋月電子で販売されています。AliExpressなどで買うともっと安価に買えると思います。
(でもベストセラー商品なので微妙に仕様が違うパチもんがいっぱいあるのでお気をつけて!)

このSSD1306を買う時に気をつけねばならないのはピン数です。
この図のように4本ピンのものはI2C通信用のものです。

SSD1306にはここが7本ピンになっているものも販売されています。
こんなやつです。

この7本ピンのものは、I2C通信用ではなく、SPI通信用のSSD1306になります。

ATtiny85はSPI通信機能もあるのですが、SPI通信には4~5本のピンを使用するので、ピン数が少ないATtiny85とはすごく相性が悪いです。

このSPI通信用のSSD1306は裏面をハード的に改造するとI2C通信機に変えることが出来るみたいです(参考にさせて頂いたサイト様)。
ただ、抵抗や回路を繋ぎ変えたり(R1⇒R3、R8を結線)、電源を入れる時にRESETをLOWにしておく、などの制約が色々と出てくるみたいなのでかなり難しそうです。

ということで、ATtiny85でSSD1306ディスプレイを使う場合には4ピンタイプのものを買いましょう!

ATtiny85との接続回路について

ATtiny85はI2C接続用の端子があります。

上記図の通り、7ピン(PB2ピン)がSCL、5ピン(PB0ピン)がSDAです。
I2C通信、SCL、SDAって何?全く分から~ん!!!という方でも大丈夫です!
とにかく、こいつらをSSD1306のSCL端子とSDA端子に繋げばいいんです。
(詳しく知りたい方は、この記事の一番下にI2C通信について詳しく書いたので見てみて下さい)

と言うことで、こんな感じでATtiny85とSSD1306を接続してください。

実際にセットした写真がこちらです。(上の絵とブレッドボードのジャンパー線の色が違うのでご注意下さい)

これで回路の準備はばっちりです。

ATtiny85でSSD1306を動かすプログラム

ATtiny85でSSD1306を動かすためには、I2C通信を行うためのライブラリを読み込み、更にSSD1306を動かすライブラリを読み込みます。
つまりI2C通信用とSSD1306用の2つのライブラリを使います。

しかし、Arduinoでよく使われるI2Cライブラリ「Wire.h」ではATtiny85は動きません。
また、SSD1306ライブラリとしてよくインターネット上の記事で扱われているAdafruit社の「Adafruit_SSD1306.h」もATtiny85では上手く動きませんでした。

そこで、次のような別のライブラリを使って動かします。

  • I2C通信ライブラリとして、Wire.hの代わりにTinyWireM.h
  • 小型ディスプレイ「SSD1306」のライブラリとして、Adafruit_SSD1306.hの代わりにTinyOzOLED.h

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

まずはI2C通信ライブラリ用のTinyWireM.hライブラリを使えるようにしましょう。TinyWireM.hライブラリをダウンロードし、プログラムの最初に読み込む(#includeする)ことで、I2C通信を簡単に行うことが出来るようになります。

まず、ArduinoIDEで、ツール⇒ライブラリを管理を選択し、ライブラリマネージャを開きます。

そして、「tinywirem」と書いて検索するとTinyWireMライブラリが出てきます。私は1.1.1版をインストールしました。(2023年2月時点で最新版です)

これでTinyWireM.hのインストールはOKです。

次に、TinyOzOLEDライブラリのインストールを行いますが、こっちはTinyWireMライブラリとは違って、ライブラリマネージャで検索しても出てきません
GitHubで公開されているので、直接GitHubに行ってファイルをダウンロードし、Arduinoのlibrariesフォルダに直接保存する必要があります。

まず、このリンク先のGitHubを開きます。

ここで、「GitHub」と言う単語を初めて開く人は、「やだ何この英語のページ」って思うかもしれませんが、GitHubはすごく価値があるウェブサイトなので、この際にどんなサイトか覚えてしまいましょう!

GitHubと言うのは多くのソフトウェア開発者がコードなどを保存・更新・公開しているウェブサイトです。今回のTinyOzOLEDライブラリも海外のプログラマーにより作成されてGitHub上に公開されており、ダウンロード可能です。
GitHubについて詳しく知りたい方は、湊川 あい先生の「わかばちゃん本」がとても面白くて分かりやすく解説されているので是非読んでみて下さい。

さて、上記のGitHubのリンクを開いたら、こんなページに飛ぶはずです。

このページの緑の<>Codeと書いてあるボタンを押し、出てきたウィンドウの一番下のDownload ZIPをクリックすると、TinyOzOLEDライブラリをダウンロードできます。

これで、ZIPファイルがダウンロードされます。
ダウンロードされたZIPファイルを開けてみるとこんな感じになっています。

この「TinyOzOled-master」フォルダを丸ごとArduinoのlibrariesフォルダに入れます。

Arduinoのlibrariesフォルダの場所が分からない人は、ArduinoIDEを開いて、ファイル⇒環境設定を押してください。

この「スケッチブックの保管場所」に書いてあるフォルダに「libraries」フォルダがあります。

このlibrariesフォルダに、TinyOzOled-masterフォルダごとコピー保存してください。

これでライブラリのインストールは完了です!
ダウンロードしたファイルをlibrariesフォルダに保存するだけなので簡単ですね!

コーディング

ここまでライブラリをインストール出来たら、次のコードを書いて動かしてみましょう。

#include <TinyWireM.h>
#include <TinyOzOLED.h>

void setup(){
  OzOled.init(); 
  OzOled.clearDisplay();

  OzOled.setCursorXY(1,1);
  OzOled.printString("--------------");
  OzOled.setCursorXY(1,2);
  OzOled.printString("SSD1306 Sample");
  OzOled.setCursorXY(1,3);
  OzOled.printString("--------------");

}

void loop() {

}

これで次のような感じに表示されます!!

これでSSD1306をATtiny85で動かせました!!!ヨシ!!!

ちなみに、上下関係には気を付けてください。この画像のようにSSD1306のピンが下に来るように配置すればOKです。
SSD1306のピンに書いてある文字(:GNDとかVCCとか)は逆さになるため勘違いしやすいです。間違わないようにご注意下さい。

SSD1306の動かし方一覧(基本)

先ほどの章にてSSD1306を動かせましたので、そのコードの仕組みを解説します。

まず、TinyWireM.hライブラリを読み込み(#include <TinyWireM.h>)I2C通信を行うための機能を使えるようにします。
このTinyWireM.hライブラリは、次のTinyOzOLED.hライブラリが内部で使うので、読み込むだけでOKです。

次に、TinyOzOLED.hライブラリを読み込む(#include <TinyOzOLED.h>)ことで、「OzOled.命令」の形で命令を与えることが出来るようになります。

前章のサンプルコードに登場した各種の命令文は以下の通りです。
これを理解すれば、SSD1306を自由に操れるようになります!

OzOled.init();

SSD1306をスタンバイさせる命令です。SSD1306を使う時には、必ずこれをsetup()の中に入れます。
SSD1306を使うために唱える呪文と思ってOKです。

OzOled.clearDisplay();

SSD1306の表示をクリアする命令です。何も表示されていない状態にできます。
OzOled.init()に続いて記載して、いったん全てSSD1306の表示をクリアした方がいいと思います。

(補足)
ちなみに、コードを解析するとOzOled.init()を実行するとOzOled.clearDisplay()が一度 呼び出されているので、init()をした直後にもう一度OzOled.clearDisplay()を行う意味は実はありません。
でも、コードの可読性が良くなる気がするので、無駄だとしてもコードに入れたほうが私は良いと思います。好みの問題かな~。

OzOled.setCursorXY(X,Y);

TinyOzOLED.hライブラリを使うと、SSD1306ディスプレイに次のような16×8マスのXY座標の概念で文字や数字を表示させることが出来ます。

OzOled.setCursorXY(X,Y)は、XY座標にカーソルを合わせる命令で、サンプルコードのようにOzOled.setCursorXY(1,1)と書くと、↓ここにカーソルが移動します。
後述のOzOled.printStringで文字を書くと、ここを起点に文字が書かれるようにできます。

OzOled.printString(“xxxx”);

OzOled.printString(“xxxx”)は、SSD1306のディスプレイにxxxxと言う文字列を表示させる命令です。

なので、OzOled.printString(“SSD1306 Sample”)と書くと、ディスプレイに「SSD1306 Sample」と表示されます。

これらの各種命令でSSD1306を操ることが出来ます!ヨシ!!!

SSD1306の動かし方一覧(応用)

TinyOzOLED.hライブラリには、他にも色んな命令があります。

特に数字を表示させる命令は使えるようになっておくと、センサーで計測した値をSSD1306に表示する、などの応用ができるようになるので便利です。
ただ、一部の命令はちょっとクセがあるので注意が必要です。

OzOled.printNumber(数値, 小数点以下の桁数);

文字列を表示するのはprintStringでしたが、数字を表示するのはprintNumberです。
これを使えば数字をディスプレイに表示できます。

例)

tp=24.352

OzOled.printString("Temp:");
OzOled.printNumber(tp,2);

上記のコードは、SSG1306ディスプレイに

  Temp: 24.35

と表示させることが出来ます。

このサンプルコードを改造すれば、変数「tp」に温度センサーで取得した値を代入して、それを表示させることが出来ますね。

ただし、この命令はクセがあります。
それは、第1引数の数値はfloat型でなければならない、ということです。
int型の変数を入れると、エラーになります。
これを知らないと、なんでエラーが生じているのか分からずにハマり、クソが!!って気分になっちゃいますw

このような時は、次のコードのようにfloat( )で囲んでfloat型に変換してあげる必要があります。(setTimeというint型の変数を使った式を代入する例)

//setTimeというint型の数値を表示する例
OzOled.printNumber(float(setTime / 60),0);

ハマりやすいのは、このように直接数字を表示させたい時です。printStringもハマりやすいので、ハマりポイントを一緒に紹介します。(私はハマったw)

//これだと動かない(コンパイルエラー)
OzOled.printNumber(0,0);

//これだとバグった表示になる(|←こんな表示になる)
OzOled.printString(0);

これらを動かそうとしたら、前者は第1引数を「float」で囲んでfloat型にする必要があり、後者は「””」で囲んで文字列として扱う必要があります。(以下参照)

//これは動く(0が表示される)
OzOled.printNumber(float(0),0);

//これも動く(0が表示される)
OzOled.printString("0");

float型しかダメというところがポイントであり、ハマらないようにご注意下さい。

OzOled.printChar(数値);

文字列を表示するのはprintString、数字を表示するのはprintNumberでした。OzOled.printCharを使えば、記号を使えます。
例えば次のようなコードを書きます。

OzOled.printChar(1);

こう書くと、「*」が表示されます。(例では「1」をprintCharに代入していますが、実は31以下と128以上の数字は全て「*」になりますw)
詳しくはこのページの下の参考資料の欄に記載しておきますので参照してください。

OzOled.setInverseDisplay();

ディスプレイの白黒を反転します。

setupの中に次のコードを入れておくと、下の写真のようにベースが白で文字が黒で表示されます。

OzOled.setInverseDisplay();

OzOled.setPowerOFF():

OzOled.setPowerON();

OzOled.setPowerOFFとOzOled.setPowerOnは、画面表示を消したりつけたりする命令です。

このコードでこんな動きをします。

#include <TinyWireM.h>
#include <TinyOzOLED.h>

void setup(){
  OzOled.init(); 
  OzOled.clearDisplay();

  OzOled.setCursorXY(1,1);
  OzOled.printString("--------------");
  OzOled.setCursorXY(1,2);
  OzOled.printString("SSD1306 Sample");
  OzOled.setCursorXY(1,3);
  OzOled.printString("--------------");

  delay(1000);

//setPower:表示のON・OFF----------
  OzOled.setPowerOff();  
  delay(1000);

  OzOled.setPowerOn();
  delay(1000);

}

void loop() {

}

電子工作でボタンを設置して、そのボタンを押せば画面表示し、もう一度押すと消える、みたいなものを作るときに使えそうです。

OzOled.setBrightness(数値);

文字などの白い部分の明るさを変更できます。「数値」に入れる値が小さいほど暗く、大きいほど明るくなります。
「200」の明るさにするコードはこちらです。

OzOled.setBrightness(200);

実験してみたところ、「1」はかなり暗く、「100」でデフォルトの明るさくらい、「200」はデフォルトよりも明るい感じになりました。おそらく127くらいがデフォルトなのだと思われます。

コードを読んだところ、OzOled.setBrightness(数値)の「数値」は「byte型」でした。
ということで、0~255の値を代入すれば動作するはずなのですが、試しに256以上の数字を入力してもエラーにならずに動きました(デフォルトよりも明るかった)。
おそらく256以上の数字は255と同じ明るさになるのだと思われます。

OzOled.scrollRight(スタート,エンド,スピード);

OzOled.scrollLeft(スタート,エンド,スピード);

OzOled.scrollRightとOzOled.scrollLeftは表示を右または左に動かす命令です。

スタートとエンドには「行」を入れます。
例えば「スタート」に1,「エンド」に2を書くと、1行目から2行目までが動きます。
「スピード」はスクロールスピードです。100くらいでも結構早い感じです。

OzOled.scrollRight(1,2,100)を使う例(1行目から2行目を100で右に動かす)のコードと動画がこちらです。
3行目は動いていないことが分かりますね!

#include <TinyWireM.h>
#include <TinyOzOLED.h>

void setup(){
  OzOled.init(); 
  OzOled.clearDisplay();

//  OzOled.setCursorXY(1,1);
//  OzOled.printString("--------------");
//  OzOled.setCursorXY(1,2);
//  OzOled.printString("SSD1306 Sample");
//  OzOled.setCursorXY(1,3);
//  OzOled.printString("--------------");

  delay(1000);

//scroll:文字のスクロール----------
  OzOled.scrollRight(1,2,100);

}

void loop() {

}

OzOled.scrollDiagRight();

OzOled.scrollDiagLeft();

ソースコードを読むと、scrollDiagRight、scrollDiagLeftという命令文があります。
scrollDiagという名称なので、Diagonal scroll(斜め方向にスクロールすること)の機能を実装しようとしているのだと思われます。
しかし、これはまだ完全なコードではないのだと思われ、実際には下から上方向に向かうスクロールが動作します(scrollDiagRight、scrollDiagLeftともに下から上にスクロールする)。

しかも、scrollDiagRight(またはscrollDiagLeft)のコードよりも前にOzOled.scrollRight(またはOzOled.scrollLeft)が一度動いていないと、この命令文は動作しないっぽいです。ハマりやすい!

ということで、このコードで次の動画のような動作をします。

  OzOled.scrollRight(2,2,100); //ここは何でもいい。いったんスクロール系の命令を動かす
  OzOled.scrollDiagRight();

以上。


今日はSSD1306を紹介しました。
SSD1306ディスプレイを操れれば、センサーで取得した数値を色んな表現で表示できますね!!
視覚的に表現できるのはとても面白いですよ!

ではまた別の記事で会いましょう。
ではでは~

🦅バサバサ~

参考資料

I2C通信について

I2C通信というのはInter-Integrated Circuitの略で、2本のワイヤー(SCL、SDA)を使用してデータを送受信します。なんと、I2C通信は、複数のデバイスを最大127個まで接続することができます。

SCL (Serial Clock Line) は、基本的に電圧をHIGH・LOWの切り替えをずっとカチカチやってます。まさにクロックです。この1回の山が1信号になります。

SDA (Serial Data Line)が、その信号が「1」なのか「0」なのか決めています。

次の図のように、SCLが1山の信号を送るタイミングで、SDAがLOWだと0、HIGHだと1、というルールで信号が送られます。これがI2C通信です。

I2C通信には、マスターデバイスとスレーブデバイスの2つのタイプがあり、今回の例で言えばATtiny85がマスターで、SSD1306がスレーブになります。

どうして最大127個までデバイスを接続できるかと言うと、スレーブ側には個体を識別する機器番号のような「アドレス」と呼ばれる2バイトの数値がハード側に設定してあるためです。
マスターから「○○の信号を発信。0xXXさんキャッチして」と言う信号を発信するので、いったんすべてのスレーブがその信号をキャッチします。そして自分が0xXXでなければ無視する、という感じの仕組みで0xXXだけに信号を与えることが出来ます。つまりアドレスが違うデバイスなら別々に動かすことが出来ます。

じゃあ、たまたまSSD1306のアドレスである0x3Cと同じアドレスを持つセンサー等をSSD1306と一緒に使いたい場合はどうしましょう?

この場合、SSD1306の裏面にADDRESS SELECTと書いていある部分があるので、ここを物理的にいじります。普通は0x78の方が抵抗で連結されていると思います。0x78をビット表示した「0b01111000」を右に1つズラした「0b00111100」をもう一度バイト表示にした「0x3C」がSSD1306のスレーブアドレスになってます。(何でわざわざビットを1つズラした0x78と書いているのか、全く分からんwww)
もしもSSD1306と同じアドレスのセンサーをI2C通信で使いたい場合は、ここの抵抗のハンダを溶かして0x7A側に接続します。そして、上記のバイト⇒ビット⇒1つずらす⇒バイトに変換した「0x3D」のアドレスを使えばいい感じに使えるはずです。(未検証です)

この場合、TinyOzOLED.hライブラリのここの部分のアドレスの書き換えれば、動くはずです。はず。(未検証です)

ビット表記(0bXXXXXXXX)とバイト表記(0xXX)について

ビット(2進数)表記「0bXXXXXXXX」、バイト(16進数)表記「0xXX」を初めて見る方もおられるかもしれません。
頭に「0b」を付けた数字はビット表記、「0x」を付けるとバイト表記である、と昔の偉い人が決めたそうです(嘘です。偉いかどうかは知りません。)。
なので0x3Cと書いたら、Cは12を表すので、3×16+12=60、となり、0x3C=60となります。

printCharとアスキーコードについて

printChar(X)のXに数字を入れると記号を入力できます。この数時は、以下のアスキーコード表に対応しています。
表の「Dec」列に書かれた数値を入れると、「文字」列に書かれた文字が表示されます。
ただし、printCharは31以下または128以上の数値を入力すると、一律「*」が表示されます。

出典:こちらのサイト様