LSI Jiu-Jitsu

電子工作とブラジリアン柔術

OLED 0.96インチで遊ぶ (1) - SSD1306

f:id:mohran:20170902171226j:plain

aitendoで0.96インチのOLED(有機EL)モジュール「M096P4W(BL)」を購入しました。
128x64ドットの領域に絵を表示したりして楽しく遊べています。
Raspberry PiからPerlを通して動作させたので使い方を纏めてみました。
主にコントローラーIC「SSD1306」の制御方法についてです。

初めに、基板の端子は下の写真の向きで左から
「SDA(I2C)」「SCK(I2C)」「GND」「VCC」
の並びになっています。

f:id:mohran:20170902171238j:plain

これをRaspberry Piの各Pinと接続します。
Pin1(3.3V) - VCC
Pin3(SDA) - SDA
Pin5(SCL) - SCL
Pin9(GND) - GND

f:id:mohran:20171117003327j:plain

写真は秋月電子のブレッドボード接続キットを通して接続しています。

f:id:mohran:20170902171246j:plain


「SSD1306」のデータシートは、下記のサイトよりpdfをダウンロードしました。
https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf

f:id:mohran:20170902002802j:plain

以下、データシードから抜粋しながら記載していきます。

■ I2Cフォーマット

「SSD1306」にアクセスするI2C通信のフォーマットは下記のようになっています。

f:id:mohran:20170902002920j:plain

スレーブアドレス(0x3c)の後に、コントロールバイト、データバイトの順に送信します。
コントロールバイトには「Co (Continuation bit)」と「D/C# (Data / Command Selection bit)」の2ビットが含まれています。
「Co=1」で送信した時は、データバイトの次に再度コントロールバイトを送る必要がありますが「Co=0」で送信した時は、データバイトのみを連続して送ることができます。
「D/C#」は、データバイトがコマンドかデータかの区別で、コマンドは設定レジスタ、データは画像データと考えて良いと思います。

Raspberry PiからI2C通信を行うには「Raspberry Piの設定」の「I2C」を有効にして再起動しておきます。

f:id:mohran:20170830130757p:plain

「I2C」が有効になっているとターミナルから「i2cset」コマンドでI2C通信を行うことが可能になります。

$ i2cset -y 1 0x3c \
        [コントロールバイト] \
        [データバイト] \
        [コントロールバイト] \
        [データバイト] \
        i

最後の「i」を入力するまでを「SSD1306」に送信してくれます。

例えば、下記のコマンド表は、画面の明るさを調整するコマンドですが、これを設定してみます。

f:id:mohran:20170902012151j:plain


「Co=1」 で送信すると、データバイトの次に再度コントロールバイトを送信する必要があります。

#!/usr/bin/perl -w

$cmd  = 'i2cset -y 1 0x3c ';
$cmd .= '0x80 ';  # コントロールバイト Co=1, D/C#=0
$cmd .= '0x81 ';  # データバイト 明るさ設定
$cmd .= '0x80 ';  # コントロールバイト Co=1, D/C#=0
$cmd .= '0xff ';  # データバイト 明るさ最大値
$cmd .= 'i';

system($cmd);


「Co=0」で送信すると、データバイトを連続して送信することができます。

#!/usr/bin/perl -w

$cmd  = 'i2cset -y 1 0x3c';
$cmd .= '0x00 ';  # コントロールバイト Co=0, D/C#=0
$cmd .= '0x81 ';  # データバイト 明るさ設定
$cmd .= '0xff ';  # データバイト 明るさ最大値
$cmd .= 'i'

system($cmd);

特に理由が無ければ常に「Co=0」で送信していて問題無いと思います。

■ 画面構成

画面の構成ですが、「SSD1306」は縦8画素x横128画素を1ページとして、8ページ構成になっています。

f:id:mohran:20170902182600j:plain

ページ(縦位置)とカラム(横位置)を指定してから、コントロールバイトの「D/C#」へ「1」をセットして画像データを送信することで、指定した座標から画像が表示されます。
縦8画素はちょうど1Byteなので、1ByteのうちLSBが画面上側、MSBが画面下側となっています。

f:id:mohran:20170902182613j:plain

■ アドレッシングモード

画像データを送信して、表示する方法として3種類のモード(アドレッシングモード)があり、コマンド「0x20」の後に送る値で指定します。

f:id:mohran:20170902182628j:plain

「0x00」Horizontal Addressing Mode
f:id:mohran:20170902182642j:plain
右方向に表示が進み、指定したカラム位置まで進むと次のページへ進みます。

「0x01」Vertical Addressing Mode
f:id:mohran:20170902183020j:plain
縦方向に表示が進み、指定した最終ページまで進むとカラム位置が右に1画素進みます。

「0x02」Page Addressing Mode
f:id:mohran:20170902183236j:plain
Horizontalと同じく右方向に進みますが、次のページには進まずに同じページ内でループします。

HorizontalモードとVerticalモードでは、カラム位置の指定はコマンド「0x21」で指定し、ページ位置は「0x22」で指定します。

「0x21」の後に送る1Byte目で開始カラム位置、次の1Byteで最終カラム位置を0~127の範囲で指定します。
f:id:mohran:20170902192612j:plain

「0x22」の後に送る1Byte目で開始ページ位置、次の1Byteで最終ページ位置を0~7の範囲で指定します。
f:id:mohran:20170902192626j:plain

Horizontalモードで、0x21 0x02 0x7d 0x22 0x01 0x06 と送ることで、このような順序で表示されます。
f:id:mohran:20170902193541j:plain


Pageモードでは、座標指定の方法が変わります。
「0xB0」~「0xB7」の1コマンドでページ位置を指定します。
f:id:mohran:20170902194546j:plain

カラム座標は、開始位置のみの指定となります。
「0x00」~「0x0F」で開始位置の下位4Bitを指定し、「0x10」~「0x1F」で上位4Bitを指定します。
(カラムは0~127なので、7Bitで良いはずなのですが、データシートは8Bit指定になっています)
f:id:mohran:20170902194554j:plain

以上がアドレッシングモードについてですが、座標指定に関してデータシート上はPageモードとHorizontal/Verticalモードで個別の指定方法が記載されていますが、実使用としてはモードに関係無くどちらの座標指定でも可能なようです(笑)
個人的にはHorizontalモードが一番使いやすかったのでこれだけで十分かなぁ、と思っています。

■ コンフィグレーション

pdfの下の方にあるアプリケーションノートにコンフィグレーションのフローが載っていました。

f:id:mohran:20170922230755j:plain

パネルの設定などを行っていますが、このモジュールはデフォルトの状態で使用できるので下2つの「チャージポンプ設定」と「表示On」のみ設定すれば画面が表示されます。

チャージポンプの設定はアプリケーションノートに記載されています。

f:id:mohran:20170922231631j:plain


下記のコマンドで、チャージポンプと画面表示を有効にしてみます。

#!/usr/bin/perl -w

$cmd  = 'i2cset -y 1 0x3c ';
$cmd .= '0x00 ';  # コントロールバイト Co=0, D/C#=0
$cmd .= '0x8d ';  # チャージポンプ設定
$cmd .= '0x14 ';  # チャージポンプOn
$cmd .= '0xaf ';  # 画面表示On
$cmd .= 'i';

system($cmd);

SSD1306内の画像データRAM(GDDRAM)が初期化されていないためノイズのような画面が表示されました。

これをHorizontalモードで初期化してみます。

#!/usr/bin/perl -w

$cmd  = 'i2cset -y 1 0x3c ';
$cmd .= '0x00 ';  # コントロールバイト Co=0, D/C#=0
$cmd .= '0x20 ';  # アドレッシングモード指定
$cmd .= '0x00 ';  #   Horizontalモード
$cmd .= '0x21 ';  # カラム指定
$cmd .= '0x00 ';  #   開始位置(0)
$cmd .= '0x7f ';  #   終了位置(127)
$cmd .= '0x22 ';  # ページ指定
$cmd .= '0x00 ';  #   開始ページ(0)
$cmd .= '0x07 ';  #   終了ページ(7)
$cmd .= 'i';

system($cmd);


# ゼロで初期化
$cmd  = 'i2cset -y 1 0x3c ';
$cmd .= '0x40 ';  # コントロールバイト Co=0, D/C#=1
$cmd .= '0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 ';  # 8バイト
$cmd .= '0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 ';  # 8バイト
$cmd .= '0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 ';  # 8バイト
$cmd .= '0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 ';  # 8バイト 合計32バイト
$cmd .= 'i';

system($cmd);

画像データを32バイト送信したので左上から右に32画素クリアされました。

ちなみに1度の送信でデータバイトは32バイトまでしか受け付けてくれないようで、33バイト以上だと下記のエラーが発生しました。

Error: Too many arguments!
Usage: i2cset [-f] [-y] [-m MASK] [-r] I2CBUS CHIP-ADDRESS DATA-ADDRESS [VALUE] ... [MODE]

これは、i2cset コマンドの制限のようで、PICなどのマイコンから制御する際は33バイト以上でも問題なく送信できました。


32バイト送信を4回繰り返して1ページ初期化。

更に7回繰り返して1画面全体を初期化することができます。


次に、ある特定の位置に表示を行ってみます。

3ページ目の30カラムから40カラムにかけて塗りつぶしてみます。

#!/usr/bin/perl -w

$cmd  = 'i2cset -y 1 0x3c ';
$cmd .= '0x00 ';  # コントロールバイト Co=0, D/C#=0
$cmd .= '0x20 ';  # アドレッシングモード指定
$cmd .= '0x00 ';  # Horizontalモード
$cmd .= '0x21 ';  # カラム指定
$cmd .= '30 ';    #   開始位置(30)
$cmd .= '39 ';    #   終了位置(39)
$cmd .= '0x22 ';  # ページ指定
$cmd .= '3 ';     #   開始ページ(3)
$cmd .= '3 ';     #   終了ページ(3)
$cmd .= 'i';

system($cmd);


$cmd  = 'i2cset -y 1 0x3c ';
$cmd .= '0x40 ';  # コントロールバイト Co=0, D/C#=1
$cmd .= '0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff ';
$cmd .= '0xff 0xff ';
$cmd .= 'i';

system($cmd);

一部分だけ塗りつぶすことができました。

このように座標位置の送信→画像データの送信の繰り返しで表示を行います。