2019年3月29日金曜日

MycroPythonとSSD1306 ~ 初期化まで ~

MicroPythonとI2Cシリアル通信の"お勉強"の続きです。

使用するのは、秋月電子通商さんの0.96インチ 128x64ドット有機ELディスプレイ(OLED)
SSD1306だけど、I2Cしか使わせないぞ!と言うわかりやすい構成は嫌いじゃないです。
表側は液晶パネルとピンヘッダの頭が見えるだけと到ってシンプル。

裏側は、チップ抵抗(394:390kΩ/103:10kΩ)、チップコンデンサ、電圧レギュレータ(662K)など表面実装部品が並んでいます。

理解出来た範囲内でピックアップすると、
  • 電圧レギュレータ(662K)により回路内の電圧を3.3Vに保持
  • C1P/C1N、C2P/C2Nにコンデンサが接続(内臓チャージポンプが使用可能状態)
  • SA0( D/C#)は、ジャンパーチップ抵抗でGNDに接続(0x78に設定済み)されている
  • IREFに390kΩ抵抗を接続して入力電流を12.5μAにしている
  • SCLはD0、SDAはD1、D2と、それぞれ10kΩ抵抗でプルアップ済み
などなど。他にもあった気がするけど、思い出したら書きます。
チャージポンプは、OLED発光のための電圧(7.5V)供給に用いられます。
こちらはコマンドからON/OFF設定出来るようになっています。
コンデンサの静電容量も時間が出来れば調べたいところです。

OLEDモジュールの予習

引き続き液晶パネルやSSD1306との配線について確認して行きます。以降では、データシート内と同じ表記で、この解説内では用途、意味合いの異なるワードがあるので混同を避けるため事前に記載しておきます。混乱されませんようお気をつけて下さい
COM(Common) : SSD1306と液晶パネルを接続する端子の名称。
液晶パネルでの横ラインの単位としても使用します。

SEG(Segment) : SSD1306と液晶パネルを接続する端子の名称。
液晶パネルでの縦ラインの単位としても使用します。

Columun(Col) : GDDRAMでの横ライン(行)
Row : GDDRAMでの縦ライン(列)

上記を踏まえ下図を見て行きます。
この図は、SSD1306と液晶パネルを繋ぐ端子の配置を表したもので、今後の画面表示においての基準座標を表したものでもあります。

※画面表示は、説明用サンプルでカラー表示は行われません。
OLEDモジュールを図の方向から見た場合、液晶パネルの右下が原点となり、縦方向のラインが原点から左へSEG0〜127と128個並び、横方向のラインが原点から上へとCOM0〜62(偶数番号)、COM1〜63(奇数番号)が交互に計64個並んだ配線となります。

この配置は、物理的な配線なので変動する事はありません。
また、各座標の配置がわかりやすい様に、SEGは"青"、COMの偶数番号は"黄色"、奇数番号は"緑色"で彩色してあります。

続いて、表示部分を管理するSSD1306の構成を見て行きます。

※電源投入もしくはリセット直後の状態です。
SSD1306内部では、GDDRAMのアドレスが"Display Controller"を介して"Segment Driver"、GDDRAMのアドレスを2分割する形で32ラインずつが"割り当てられた"Common Driver"によって管理されています。

電源投入時は、上の図のようにGDDRAMの半分づつのアドレス(黄色と緑で分割されている範囲)が偶数番号、奇数番号のCOM端子に割り当てられていますが、割り当てられるGDDRAMの値は設定により変化します。色付けしている事で、GDDRAMとOLEDでの表示の差異が認識し易いかと思うので、確認しておいて下さい。

次に、GDDRAMの構成を調べてみましょう。

GDDRAM (Graphic Display Data RAM)

表示用データを格納するためのSRAMで、横128 x 縦64のドット(1kbyte)のマトリックスで構成されています。GDDRAMへは、データの出し入れ以外には直接影響を及ぼすコマンドは実装されていません。(基本的に上書きでしか変更出来ないわけです。)

GDDRAMに格納される画像データの1画面は、横128 x 縦8ドット(128byte)を1PAGEとした単位を用い、縦に8PAGE並べた状態で構成されています。
PAGE :
横128 x 縦8ドット(128byte)のブロックで、画像データを書き込む場合には、このPAGEが縦方向を指定する座標となります。
PAGEは、横に128個並んだColumnで構成されています。
まずは、Columnを構成するBit(Dot)について。
Bit(Dot) :
GDDRAMの1Bitもしくは画面中の1点を表す最小単位
0 : 画面描画なし
1 : 画面に描画
引き続きColumnについて。
Column(Col) :
Column 0
列です。
Bit(Dot)を縦に8bit(1byte)並べた単位をColumn(Col)と呼びます。

Columnに割当てられるBitは以下の表を参照して下さい。
データバイト
7 6 5 4 3 2 1 0
D7 D6 D5 D4 D3 D2 D1 D0
D7 - D0 : 上記Bit(Dot)の値が割り当てられます。
また、ColumnのBitは、
LSB (Least Significant Bit): 最下位ビット(4bit)
MSB (Most Significant Bit): 最上位ビット(4bit)
の2つに分けて扱われることもあります。
LSB D0
D1
D2
D3
MSB D4
D5
D6
D7
画像データの書き込みは、Column(Col)の単位(1byte)で行われ、このColumn(Col)が横に128列(128byte)並んだ集まりがPAGEとなります。
最後に、表記としては何度も登場しているコチラ。
Row :
Column 0 Column 1 • • • Column 126 Column 127
Row 0
• • •
行です。
1行は、図の様にBitが横に0〜127までの128bit並んでいる状態を指します。
Rowの座標は、画像データやPAGEを構成する要素の一つではありますが、主にレイアウトまわりでの座標として用いられ、GDDRAMへの直接アクセスで使用する事はありません。
長々と書いて来ましたが、要約すると、
液晶画面の座標系は、COM(Common)SEG(Segment)
GDDRAMの座標系は、PAGEColumn(Col)
レイアウト座標系は、RowColumn(Col)
を用いると覚えておけば問題ないでしょう。

OLEDモジュールを使うための準備

いよいよ動かしてみます。
ここでは、MicroPythonをインストールしたESP8266ベースのNodeMCUを使ってOLEDモジュールと下図の様に配線。
配線が終わったら、MicroPythonを起動。
MicroPython v1.10-8-g8b7039d7d on 2019-01-26; ESP module with ESP8266
Type "help()" for more information.
>>>
シリアルコンソールでNodeMCUへアクセスすると、上のようにMicroPythonのインタラクティブモードが起動しているかと思います。

ちなみに、MicroPythonにはSSD1306モジュールが標準で入っています。
>>>import ssd1306
>>>ssd1306.SSD1306_I2C.
__class__ __init__ __module__ __name__
__qualname__ blit fill fill_rect
hline invert line pixel
rect scroll text vline
init_display poweroff poweron contrast
show write_cmd write_data
>>>
こんな感じで、すぐに使えて問題なしです。 が! 使いません。
前回に引き続き便利な専用モジュールを使用せずに動作を確認する事が目的なので。。。
>>>
>>>
from machine import I2C, Pin
i2c = I2C(scl=Pin(5), sda=Pin(4), freq=400000)
>>>
はじめにI2C接続の準備としてmachineモジュールに含まれるI2CとPinの2つのクラスを読み込み、I2Cコンストラクタで以下の値を指定。
scl=GPIO5(D1)をPinコンストラクタでオブジェクト化
sda=GPIO4(D2)をPinコンストラクタでオブジェクト化
freq=400000(400kHz)
"freq"の指定は省略可能です。

スレーブアドレスを確認

>>>i2c.scan()
[60]
>>>
"[60]" = "0x3C"と表示されました。
データシートでは、スレーブアドレスの指定について以下の様に記載されています。
7 6 5 4 3 2 1 0
0 1 1 1 1 0 SA0 R/W
0 1 1 1 1 0 0 - 0x3C
0 1 1 1 1 0 1 - 0x3D
SA0(D/C#) : Slave Address bit - ジャンパーチップ抵抗で選択
SA0 = 0 : D/C#をGNDに接続した状態を指します。
SA1 = 1 : D/C#を3.3V電源に接続した状態を指します。
R/W(WR#) : Read/Write(R/W#) selection input
R/W#(WR#) = 0 : データ書込みモード有効化(全インターフェース共通)。
R/W#(WR#) = 1 : シリアルインタフェース : 未使用
8080インタフェース : 書込みモード無効化
6800インタフェース : 読取りモード有効化
※スレーブアドレスはR/Wを除く上位7bitで指定します。
お気づきと思いますが、8bitのbin(2進数)で考え、hex(16進数)で入力する事がこの先の基本となります。変換すればdec(10進数)でもoct(8進数)でも問題ないのですが、慣れておく事をお勧めします。

MicroPythonでのI2Cへの書込み

I2Cでの書込みには、以下の"writeto_mem"メソッドを使います。
I2C.writeto_mem(addr, memaddr, buf, *, addrsize=8)
addr:スレーブアドレス - 0x3C
memaddr:メモリアドレス / レジスタアドレス - 制御バイト(0x00/0x40)
buf:バッファ / byte型 - コマンド/データ
addrsize:アドレスサイズ(ESP8266では8bit固定で使用不可)
bufは、コマンドとデータをまとめて書く事も可能ですが、ここでは理解しやすいように分けて書くようにしています。興味のある方は試してみて下さい。
コマンドを送信するなら、こんな感じ
>>>
i2c.writeto_mem(0x3c, 0x00, b'\command')
>>>
コマンドに続けて設定値を送信する場合
>>>
>>>
i2c.writeto_mem(0x3c, 0x00, b'\command')
i2c.writeto_mem(0x3c, 0x00, b'\value')
>>>
設定値が複数ある場合は、設定値送信の箇所を複数回繰り返します。
で、最後にデータ送信
>>>
i2c.writeto_mem(0x3c, 0x40, b'\data')
>>>
bufの箇所は、Byte型であれば何でも良いので使い慣れた書式で構いません。

制御バイトとデータバイト - コマンドワード

制御系は、AE-AQM0802と同じく"制御バイト"、"データバイト"で設定します。
詳細を確認してみましょう。
制御バイト
7 6 5 4 3 2 1 0
Co D/C 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0x00
0 1 0 0 0 0 0 0 0x40
1 0 0 0 0 0 0 0 0x80
Co : Continuation bit
Co = 0 : 複数バイトコマンド送信
1バイト単位、もしくは複数のコマンドをまとめた複数バイトの送信が
出来ます。
例 : コマンド / データ / コマンド / コマンド …

Co = 1 : 1バイトコマンド送信
コマンドを1バイト単位でのみ送信出来ます。ダブルバイトコマンドなど
複数バイトのコマンドも、1バイト単位で送信する事で実行可能です。
例 : コマンド

D/C : Data / Command Selection bit
D/C = 0 : 命令レジスタへ書込み(コマンドデコーダーへ)
D/C = 1 : データレジスタへ書込み(GDDRAMへ書込み)
以上となります。
"0x80"は、1バイトコマンドの送信しか出来ないので、あまり使う事が無いかと思います。
ここでは、確認しながら進めたいので"0x00"、"0x40"だけでも良さそうですね。
データバイト
7 6 5 4 3 2 1 0
D7 D6 D5 D4 D3 D2 D1 D0
D7 - D0 : 8ビットのデータバイト
制御バイトD/C = 0 : コマンド、データとして扱われます。
この場合のデータは、コマンドに続く数値や設定情報などを
指します。

制御バイトD/C = 1 : グラフィックデータとして扱われます。
つまり、画像データですね。
※こちらは、後ほど描画の説明の際に併せて解説します。
データバイトは以降で実際にコマンドを実行して確認して行きたいと思います。

ソフトウェア初期化フロー

SSD1306の起動後に正しく機能するように内部コマンドレジスタに適切な値を設定する必要があります。以下はデータシートに記載されていたSSD1306の初期化フローの例です。レジスタの値は、さまざまな条件やアプリケーションによって異なります。

一つづつ確認しながら進めます。
>>>
>>>
i2c.writeto_mem(0x3c, 0x00, b'\xA8')
i2c.writeto_mem(0x3c, 0x00, b'\x3F')
>>>

Set Multiplex Ratio (0xA8) - ハードウェア構成コマンド

D7 D6 D5 D4 D3 D2 D1 D0
1 0 1 0 1 0 0 0 0xA8
* * A5 A4 A3 A2 A1 A0
* * 0 0 1 1 1 1 0x0F
* * 1 0 0 0 0 0 0x20
* * 1 1 1 1 1 1 0x3F
A5 〜A0 : 画像データを表示する行数を15〜63(0x0F〜0x3F)の値で設定します。
実際の表示される行数は、A5~A0での指定行数 + 1行となります。
例えば、15(0x0F)を設定した場合は16行分になると言う事です。
※16行未満の値、0〜14(0x00~0x0E)は設定出来ません。
マルチプレックス・レイシオは、ディスプレイのCOM0を起点として表示対象となる画像の行数(Row)を指定するためのものです。指定行数については、後述のコマンドや液晶パネルのサイズにより変動するため"Raitio(比率)"との言葉が使われていると思われます。
サンプル - 01
31(0x1F)の値を設定して、表示がどの様になるか見てみましょう。
>>>
>>>
i2c.writeto_mem(0x3c, 0x00, b'\xA8')
i2c.writeto_mem(0x3c, 0x00, b'\x1F')
>>>
COM0を起点として、そこから32行分(31+1行)の画像が表示されます。それ以外は無信号状態となり画面には何も表示されません。
>>>
>>>
i2c.writeto_mem(0x3c, 0x00, b'\xD3')
i2c.writeto_mem(0x3c, 0x00, b'\x00')
>>>

Set Display Offset (0xD3) - ハードウェア構成コマンド

D7 D6 D5 D4 D3 D2 D1 D0
1 1 0 1 0 0 1 1 0xD3
* * A5 A4 A3 A2 A1 A0
* * 0 0 0 0 0 0 0x00
* * 1 0 0 0 0 0 0x20
* * 1 1 1 1 1 1 0x3F
A5 〜A0 : 表示開始行を0〜63(0x00〜0x3F)の値で設定した行数分移動します。
表示開始行の移動方向は、Rowの並び(Row0, Row1, Row2...)順と逆一方向となります。液晶画面の表示行数はCOM0~COM63の64行で固定となっているので、画面外に出た座標は、COM0からCOM63へ繋がる"LOOP"構造となっています。
サンプル - 02
"サンプル - 01"の表示開始行を16行(0x10)移動して、どの様に変化するか確認してみます。
>>>
>>>
i2c.writeto_mem(0x3c, 0x00, b'\xD3')
i2c.writeto_mem(0x3c, 0x00, b'\x10')
>>>
表示開始行が16行移動して、画面外に出た座標は終端部から16行分表示されています。これを利用して、48行(0x30)移動させるとRow0〜31を画面の中央に表示出来させるなどのレイアウトが可能となります。
>>>
i2c.writeto_mem(0x3c, 0x00, b'\x40')
>>>

Set Display Start Line (0x40~0x7F) - ハードウェア構成コマンド

D7 D6 D5 D4 D3 D2 D1 D0
0 1 X5 X4 X3 X2 X1 X0
0 1 0 0 0 0 0 0 0x40(0x00)
0 1 1 0 0 0 0 0 0x60(0x20)
0 1 1 1 1 1 1 1 0x7F(0x3F)
X5 〜X0 : 表示開始行に割当てるGDDRAMのRowの位置を0〜63の値で設定します。
設定を行う際の値は、D6の箇所が"1"で固定されているため0x40+設定値
となるので、0〜63(0x40〜0x7F)の値で設定する必要があります。
指定されたRowの値が表示開始行にアサインされ、指定された表示行数分があれば、その範囲のみが画面に表示されます。
サンプル - 03
"サンプル - 02"を使用して、このコマンドでGDDRAMの16行目(0x50)を指定してみます。
>>>
i2c.writeto_mem(0x3c, 0x00, b'\x50')
>>>
"Set Display Start Line"は、GDDRAMのRowで指定すると考えましょう。ここでは、16行目を指定しているので、上図の様にデフォルトのRow0からRow16に表示開始行が変更されます。これにより、"サンプル - 02"の表示は以下の様に変化します。
"サンプル - 02"でRow0〜Row31が表示されていた部分が、指定した16行目、Row16と以降のRow31まで、そして今まで表示されていなかったRow31〜47が表示される様になりました。これらにより、表示開始行を起点として、表示される範囲が変更された事が確認出来たかと思います。
>>>
i2c.writeto_mem(0x3c, 0x00, b'\xA0')
>>>

Set Segment Re-map (0xA0/0xA1) - ハードウェア構成コマンド

D7 D6 D5 D4 D3 D2 D1 D0
1 0 1 0 0 0 0 X0
1 0 1 0 0 0 0 0 0xA0
1 0 1 0 0 0 0 1 0xA1
X0 = 0(0xA0) : Columnの並び順をSEGと順方向にアサイン。
SEGの並び(SEG 0, SEG 1, SEG 2...)と、
Columnの並び方向(Column0, Column1, Column2...)が
同じ状態となります。

X0 = 1(0xA1) : Columnの並び順をSEGと逆方向にアサイン。
SEGの並び(SEG 0, SEG 1, SEG 2...)に対し、
Columnの並び方向(Column127, Column126, Column125...)
の様に逆順の状態となります。
簡単に言うと、画面の左右反転を指定するコマンドなのですが、ちょっと使い勝手が異なるのでサンプルで確認してみて下さい。
サンプル - 04
逆方向(0xA1)を指定して表示の変化を確認してみましょう。
>>>
i2c.writeto_mem(0x3c, 0x00, b'\xA1')
>>>
。。。何の変化もありませんね。
実はこのコマンドは、コマンド実行後にGDDRAMへ送信された画像データにしか反映されません。既存の画像データは元のままの表示が残ります。

試しに、GDDRAMの"Row 32〜47"の範囲だけを再送信してみると下図の様になりました。
"Row 16〜31"の範囲は実行前のままで、"Row 32〜47"の範囲は反転して表示されています。データシートによると、"display data columnアドレス"と"segment driver"間のマッピングを変更するとの記載があ流のですが、単純にアドレスマッピングを反転しているのであれば、全体的に反転されそうな気もします。GDDRAMの読み込みが出来ないので、"恐らく"でしかありませんが、データ送信時の受け取りアドレスを反転しているのではないだろうか?(GDDRAMに反転データを書き込んでいる?)と考えています。
上の図は、全ての画像データを更新したので左右反転した画面となっています。この状態から"0xA0"に変更した場合も、画像データの更新は必要です。
>>>
i2c.writeto_mem(0x3c, 0x00, b'\xC0')
>>>

Set COM Output Scan Direction (0xC0/0xC8) - ハードウェア構成コマンド

D7 D6 D5 D4 D3 D2 D1 D0
1 1 0 0 X3 0 0 0
1 1 0 0 0 0 0 0 0xC0
1 1 0 0 1 0 0 0 0xC8
X3 = 0(0xC0) : 通常モード。
COM0を表示開始行として、"Multiplex Ratio"で設定された値までの
範囲を対象に表示設定を有効化します。

X3 = 1(0xC8) : リマップモード。
"Multiplex Ratio"で設定された値を表示開始行として、COM0までの
範囲を対象に表示設定を有効化します。
画像データの割り当てや、オフセットの方向も逆方向となります。
全画面表示では、単なる上下反転に見えますが、厳密にはCOMに割当られるRow(PAGE)の並び順を設定しているわけです。この辺りはサンプルで検証してみます。ちなみに、こちらは"Set Segment Re-map"と異なり、設定するとすぐに表示に反映されます。
サンプル - 05
"サンプル - 04"の結果をリマップモード(0xC8)にして、動作を検証してみましょう。
>>>
i2c.writeto_mem(0x3c, 0x00, b'\xC8')
>>>
上下反転を想像していると、異なる結果になったかと思います。
この理由を説明するには、"Set Multiplex Ratio (0xA8)"まで遡る必要があります。実際に電源投入後にリマップモード(0xC8)に設定した状態として、ここまでの手順に当て嵌めてみます。
>>>
>>>
>>>
i2c.writeto_mem(0x3c, 0x00, b'\xC8')
i2c.writeto_mem(0x3c, 0x00, b'\xA8')
i2c.writeto_mem(0x3c, 0x00, b'\x1F')
>>>
一見同じに見えますが、起点COM0からCOM31の座標にRow31からRow0までと逆方向でアドレスがアサインされています。リマップモードであっても起点はCOM0であることがポイントなので覚えておきましょう。
>>>
>>>
i2c.writeto_mem(0x3c, 0x00, b'\xD3')
i2c.writeto_mem(0x3c, 0x00, b'\x10')
>>>
"Set Display Offset (0xD3)"の移動方向も反転します。
16行(0x10)移動させると、選択範囲が画面の中央に配置され、更に"Set Display Start Line"で16行目(0x50)指定し、"Set Segment Re-map"で逆方向(0xA1)を設定するとサンプルコードを実行した表示内容となります。

以上の様に、このコマンドは原点位置が左上に移動する上下反転ではなくCOM0を起点とした反転となっています。
>>>
>>>
i2c.writeto_mem(0x3c, 0x00, b'\xDA')
i2c.writeto_mem(0x3c, 0x00, b'\x02')
>>>

Set COM Pins Hardware Configuration (0xDA) - ハードウェア構成コマンド

D7 D6 D5 D4 D3 D2 D1 D0
1 1 0 1 1 0 1 0 0xDA
0 0 A5 A4 0 0 1 0
0 0 0 0 0 0 1 0 0x02
0 0 0 1 0 0 1 0 0x12
0 0 1 0 0 0 1 0 0x22
0 0 1 1 0 0 1 0 0x32
液晶モジュールは、メーカーや使用用途により異なるピン配置の液晶パネルが採用されます。このコマンドは、それらに対応するべくピン配置に併せてGDDRAMのアドレスアサインを変更するために用意されています。
適切なピン配置を指定すると、下図の画面の様に正常表示が行われます。(1ラインずつズレた画面が表示されている場合は指定が間違っているという事です。)
A5 = 0 / A4 = 0 (0x02) :

A5 = 0 / A4 = 1 (0x12) :

A5 = 1 / A4 = 0 (0x22) :

A5 = 1 / A4 = 1 (0x32) :
上の図は、COMが64ラインで、SSD1306の"Common Driver"と液晶パネルを接続するピン番号が同一である事を前提としています。指定内容により各ピンに割当てられるGDDRAMのアドレス(Row)は、それぞれの図中の行アドレスとなります。
>>>
>>>
i2c.writeto_mem(0x3c, 0x00, b'\x81')
i2c.writeto_mem(0x3c, 0x00, b'\x7F')
>>>

Set Contrast Control for BANK0 (0x81) - 基本コマンド

D7 D6 D5 D4 D3 D2 D1 D0
1 0 0 0 0 0 0 1 0x81
A7 A6 A5 A4 A3 A2 A1 A0
0 0 0 0 0 0 0 0 0x00
0 1 1 1 1 1 1 1 0x7F
1 1 1 1 1 1 1 1 0xFF
A7 〜A0 : 液晶ディスプレイの輝度を0x00〜0xFFまでの256階調で設定します。
コントラストを上げると、セグメント出力電流が増加します。
また、このコマンドと関係なく"Set Multiplex Ratio"で表示行数を少なくしても、電流量の変化から輝度に影響が出るようです。
※I2Cでは256階調などの細かい調整は出来ません。
>>>
i2c.writeto_mem(0x3c, 0x00, b'\xA4')
>>>

Entire Display ON (0xA4/0xA5) - 基本コマンド

D7 D6 D5 D4 D3 D2 D1 D0
1 0 1 0 0 1 0 X0
1 0 1 0 0 1 0 0 0xA4
1 0 1 0 0 1 0 1 0xA5
X0 = 0(0xA4) : GDDRAMの内容を表示
GDDRAMの内容に従って表示出力を有効にします。

X0 = 1(0xA5) : 全面表示オン
GDDRAMの内容に関係なく、強制的に全画面点灯状態で表示します。
現時点では使いこなせていないので、何とも言えないのですが使いどころが不明です。
GDDRAMの書換え時に挟み込むのかなぁ?とも思いましたが、これを使わなくとも別の方法がある気も。。。今後、気付いた事があれば内容を更新します。
サンプル - 06
>>>
i2c.writeto_mem(0x3c, 0x00, b'\xA5')
>>>
画面のdot全てが"ON"の状態となります。GDDRAMのデータは残ったままなので"0xA4"を設定すれば元に戻せます。
>>>
i2c.writeto_mem(0x3c, 0x00, b'\xA6')
>>>

Set Normal/Inverse Display (0xA6/0xA7) - 基本コマンド

D7 D6 D5 D4 D3 D2 D1 D0
1 0 1 0 0 1 1 X0
1 0 1 0 0 1 1 0 0xA6
1 0 1 0 0 1 1 1 0xA7
X0 = 0(0xA6) : 通常表示
GDDRAM 0 : 非表示
GDDRAM 1 : 表示

X0 = 1(0xA7) : 反転表示
GDDRAM 0 : 表示
GDDRAM 1 : 非表示
通常表示または反転表示の設定を行います。
SSD1306は2階調(点灯か消灯)のみなので、その階調を反転して表示します。GDDRAMのデータには影響ありません。
サンプル - 07
"サンプル - 05"を使って液晶画面の反転表示をしてみます。
>>>
i2c.writeto_mem(0x3c, 0x00, b'\xA7')
>>>
表示部の黄色と緑の箇所は変化をわかりやすくしていただけなので、ここでは本来の白と黒だけで表示しています。
>>>
>>>
i2c.writeto_mem(0x3c, 0x00, b'\xD5')
i2c.writeto_mem(0x3c, 0x00, b'\x80')
>>>

Set Display Clock Divide Ratio/ Oscillator Frequency (0xD5) - タイミング&駆動方式設定コマンド

D7 D6 D5 D4 D3 D2 D1 D0
1 1 0 1 0 1 0 1 0xD5
A7 A6 A5 A4 A3 A2 A1 A0
0 0 0 0 0 0 0 0 0x00
1 0 0 0 0 0 0 0 0x80
1 1 1 1 1 1 1 1 0xFF
A3〜A0 : 表示クロック(DCLK)の分周比(D)を0〜15(0x0〜0xF)の値で定義します。
分周比 = (0〜15) + 1
例 : 指定した値が0(0x0)の場合は、分周比 = 0(0x0) + 1 = 1

A7〜A4 : 発振器周波数、FOSCを0〜15(0x0〜0xF)の値で設定します。
設定値が大きいほど周波数が高くなります。
このコマンドは、2つの機能で構成されています。
  • Display Clock Divide Ratio (D)(A3〜A0)
    CLKからDCLK(表示クロック)を生成するための分周比を設定してください。
    分周比は1〜16(A3〜A0+1)で指定。
  • Oscillator Frequency (A7〜A4)
    CLSピンがプルアップされている場合は、CLKのソースである発振器周波数Foscを設定します。4ビット値(0x0〜0xF)により、0~15の周波数設定が可能になります。
>>>
>>>
i2c.writeto_mem(0x3c, 0x00, b'\x8D')
i2c.writeto_mem(0x3c, 0x00, b'\x14')
>>>

Charge Pump Setting (0x8D) - チャージポンプコマンド

D7 D6 D5 D4 D3 D2 D1 D0
1 0 0 0 1 1 0 1 0x8D
* * 0 1 0 A2 0 0
* * 0 1 0 0 0 0 0x10
* * 0 1 0 1 0 0 0x14
A2 = 0 : チャージポンプを無効にする。
A2 = 1 : 画面表示が有効である間、チャージポンプも有効にする。
SSD1306では、2つの外部コンデンサを用いた内部レギュレータ回路により、低電圧電源入力(VBAT)から7.5Vの電源(VCC)を生成することができます。このレギュレータはソフトウェアコマンド設定でオン/オフ出來るようになっています。
※画面表示とチャージポンプを有効化しないと画面表示は行われません。
  • 電源
    • VDD = 1.65V to 3.3VICロジック用
    • VBAT = 3.3V to 4.2Vチャージポンプレギュレータ回路用
  • チャージポンプレギュレータの関連ピンの説明
    • VBAT - チャージポンプレギュレータ回路用の電源
      Status VBAT VDD VCC
      チャージポンプ
      有効
      外部VBATに接続 外部VDD電源に接続 VCCとVSSの間にコンデンサを接続
      チャージポンプ
      無効
      VDD端子に接続 外部VDD電源に接続 外部VCC電源に接続
    • C1P / C1N - チャージポンプコンデンサ用端子:コンデンサで接続
    • C2P / C2N - チャージポンプコンデンサ用端子:コンデンサで接続
>>>
i2c.writeto_mem(0x3c, 0x00, b'\xAF')
>>>

Set Display ON/OFF (0xAE/0xAF) - 基本コマンド

D7 D6 D5 D4 D3 D2 D1 D0
1 0 1 0 1 1 1 X0
1 0 1 0 1 1 1 0 0xAE
1 0 1 0 1 1 1 1 0xAF
X0 = 0 : 表示OFF(スリープモード)
X0 = 1 : 通常モードで表示ON
このコマンドは、OLEDパネルディスプレイをオンまたはオフにするために使用されます。表示がオンの時は、Set Master Configurationコマンドで選択した回路がONになります。表示がOFFのときは、それらの回路はOFFになり、セグメントとコモン出力は、それぞれVSS状態とハイインピーダンス状態になります。
ノイズだらけの画面が表示されたかと思いますが、壊れたわけでなく正常動作です。
画面(GDDRAM)をクリアするコマンドは残念ながらありません。自力で消去しましょう。
って事で、次は描画まわりを調べてみたいと思います。

まとめ

ここまで調べてきた内容を整理してみます。
以下は、データシートに記載されていた初期化の流れです。
>>>
paste mode; Ctrl-C to cancel, Ctrl-D to finish
===
===
===
===
===
===
===
===
===
===
===
===
===
===
===
===
===
===
===
===
from machine import I2C, Pin
i2c = I2C(scl=Pin(5), sda=Pin(4),freq=400000)
i2c.writeto_mem(0x3c, 0x00, b'\xA8')
i2c.writeto_mem(0x3c, 0x00, b'\x3F')
i2c.writeto_mem(0x3c, 0x00, b'\xD3')
i2c.writeto_mem(0x3c, 0x00, b'\x00')
i2c.writeto_mem(0x3c, 0x00, b'\x40')
i2c.writeto_mem(0x3c, 0x00, b'\xA0')
i2c.writeto_mem(0x3c, 0x00, b'\xC0')
i2c.writeto_mem(0x3c, 0x00, b'\xDA')
i2c.writeto_mem(0x3c, 0x00, b'\x02')
i2c.writeto_mem(0x3c, 0x00, b'\x81')
i2c.writeto_mem(0x3c, 0x00, b'\x7F')
i2c.writeto_mem(0x3c, 0x00, b'\xA4')
i2c.writeto_mem(0x3c, 0x00, b'\xA6')
i2c.writeto_mem(0x3c, 0x00, b'\xD5')
i2c.writeto_mem(0x3c, 0x00, b'\x80')
i2c.writeto_mem(0x3c, 0x00, b'\x8D')
i2c.writeto_mem(0x3c, 0x00, b'\x14')
i2c.writeto_mem(0x3c, 0x00, b'\xAF')

>>>
画面(GDDRAM)をクリアするには、ブランクのデータ(0x00)をGDDRAMに転送する必要があります。 実際にブランクデータ(0x00)を転送するには以下の様に行います。
>>>
i2c.writeto_mem(0x3c, 0x40, b'\x00')
>>>
ただし、これでは8bitの一つの列(Column)がブランクで埋められただけで画面にはさほど影響がありません。全画面を消去するには、横(128 Column) x 縦(8 Page) x 8bitの描画内容(8 Rows)の範囲を指定する必要があります。
ここでは試しに、ブランクを白(1)で指定して動作を確認してみたいと思います。
>>>
>>>
blank = bytes(128*8*[255])
i2c.writeto_mem(0x3c, 0x40, blank)
>>>
Pythonのコマンドに関しては細かい説明を省きますが、先ほどの通り全画面を対象に横 x 縦 x [255](0xFF)をバイト型データとしてGDDRAMへの転送を行ってみました。さて、結果は。。。
想定していた画面と違いますね。
初期化手順か、描画の部分にもんだがある様です。この辺りを次回"描画まわり"の解説で確認して行きたいと思います。

0 件のコメント:

コメントを投稿