2019年4月14日日曜日

MycroPythonとSSD1306 ~ 描画まわり ~

MicroPythonでOLEDに描画してみます。

前回、思い通りの表示にならなかったわけですが、引き続き秋月電子通商さんの0.96インチ 128x64ドット有機ELディスプレイ(OLED)を使って調べて行きます。
まずは、この何の面白味もない画面を描画する事を目標とします。
2階調の画像が用意できる方は、そちらでも良いですけどね。

データシートに記載されていた初期化手順では想定通りに動かなかったので既存ライブラリの初期化を参考に確認してみましょう。

"Adafruit"のSSD1306の初期化設定

下記は、Adafruit_Python_SSD1306の初期化箇所をMicrpPython用にしてたものです。
>>>
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'\xAE')
i2c.writeto_mem(0x3c, 0x00, b'\xD5')
i2c.writeto_mem(0x3c, 0x00, b'\x80')
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'\x8D')
i2c.writeto_mem(0x3c, 0x00, b'\x14')
i2c.writeto_mem(0x3c, 0x00, b'\x20')
i2c.writeto_mem(0x3c, 0x00, b'\x00')
i2c.writeto_mem(0x3c, 0x00, b'\xA1')
i2c.writeto_mem(0x3c, 0x00, b'\xC8')
i2c.writeto_mem(0x3c, 0x00, b'\xDA')
i2c.writeto_mem(0x3c, 0x00, b'\x12')
i2c.writeto_mem(0x3c, 0x00, b'\x81')
i2c.writeto_mem(0x3c, 0x00, b'\xCF')
i2c.writeto_mem(0x3c, 0x00, b'\xD9')
i2c.writeto_mem(0x3c, 0x00, b'\xF1')
i2c.writeto_mem(0x3c, 0x00, b'\xDB')
i2c.writeto_mem(0x3c, 0x00, b'\x40')
i2c.writeto_mem(0x3c, 0x00, b'\xA4')
i2c.writeto_mem(0x3c, 0x00, b'\xA6')
# I2C、Pinクラスのインポート
# I2Cオブジェクトの作成
# 画面の表示をOFFに設定
# 分周比と周波数の設定
#  - 分周比=1、周波数=8を指定
# Multiplex Ratioの設定
#  - 63(64行)を指定
# Display Offsetの設定
#  - 0なので移動なしを指定
# 表示開始行を0行目(0x40)で設定
# チャージポンプの設定
#  - 有効化(0x14)を指定
# Mem Addressing Modeの設定
#  - 0x00~0x11で指定
# SEGをRe-map(0xA1)に設定
# COMをRe-map(0xC8)に設定
# COMのピン配置の設定
#  - 0x12を指定
# コントラストの設定
#  - 207(0xCF)を指定
# Pre-charge Periodの設定
#  - 0x00~0xFFで指定
# VCOMH Deselect Levelの設定
#  - 0x00~0x70で指定
# GDDRAMの内容を表示に設定
# 通常表示に設定

>>>
緑文字は、前回と設定が異なっている箇所をピックアップしたもので、上記のコメントを参照して違いを確認してみましょう。また、赤文字は、新しく出てきたコマンドで、どの様な動作をするのか調査してみます。

緑文字の箇所の差異を確認するため赤字以外のコードを実行して、前回同様に下記のコードを実行して変化を確認します。
>>>
>>>
blank = bytes(128*8*[255])
i2c.writeto_mem(0x3c, 0x40, blank)
>>>
緑文字の変更点が反映された表示画面となっています。
ただ、全画面を埋めるはずだったのですが。。128 x 8 bitの範囲しか埋められていませんね。
残りの赤文字の箇所を確認しつつ理由を調べてみましょう。

初期化の追加コマンド

>>>
>>>
i2c.writeto_mem(0x3c, 0x00, b'\x20')
i2c.writeto_mem(0x3c, 0x00, b'\x00')

>>>

Set Memory Addressing Mode (0x20) - アドレス指定コマンド

D7 D6 D5 D4 D3 D2 D1 D0
0 0 1 0 0 0 0 0 0x20
* * * * * * A1 A0
* * * * * * 0 0 0x00
* * * * * * 0 1 0x01
* * * * * * 1 0 0x02
* * * * * * 1 1 0x03
SSD1306には、ページアドレッシングモード、水平アドレッシングモード、および垂直アドレッシングモードの3つの異なるメモリアドレッシングモードがあります。このコマンドは、GDDRAMへの書き込み方法を上記の3つのモードのいずれかに設定します。

動作をわかりやすくするために、以下の画像データを複数回送信して確認してみます。
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
i2c.writeto_mem(0x3c, 0x40, b'\x00\x00\x00\x3E\x51\x49\x45\x3E')
i2c.writeto_mem(0x3c, 0x40, b'\x00\x00\x00\x00\x42\x7F\x40\x00')
i2c.writeto_mem(0x3c, 0x40, b'\x00\x00\x00\x42\x61\x51\x49\x46')
i2c.writeto_mem(0x3c, 0x40, b'\x00\x00\x00\x21\x41\x45\x4B\x31')
i2c.writeto_mem(0x3c, 0x40, b'\x00\x00\x00\x18\x14\x12\x7F\x10')
i2c.writeto_mem(0x3c, 0x40, b'\x00\x00\x00\x27\x45\x45\x45\x39')
i2c.writeto_mem(0x3c, 0x40, b'\x00\x00\x00\x3C\x4A\x49\x49\x30')
i2c.writeto_mem(0x3c, 0x40, b'\x00\x00\x00\x01\x71\x09\x05\x03')
i2c.writeto_mem(0x3c, 0x40, b'\x00\x00\x00\x36\x49\x49\x49\x36')
i2c.writeto_mem(0x3c, 0x40, b'\x00\x00\x00\x06\x49\x49\x29\x1E')
>>>
上は、0~9までの表示を行うためのデータを数値毎に表示出来る様に並べたものです。
以下は、それらを一つのバイト型データにまとめたもの。今回は、こちらを使います。
>>>num = b'\x00\x00\x00\x3E\x51\x49\x45\x3E\x00\x00\x00\x00\x42\x7F\x40\x00\x00\x00\x00\x42\x61\x51\x49\x46\x00\x00\x00\x21\x41\x45\x4B\x31\x00\x00\x00\x18\x14\x12\x7F\x10\x00\x00\x00\x27\x45\x45\x45\x39\x00\x00\x00\x3C\x4A\x49\x49\x30\x00\x00\x00\x01\x71\x09\x05\x03\x00\x00\x00\x36\x49\x49\x49\x36\x00\x00\x00\x06\x49\x49\x29\x1E'
>>>
A1 = 0 / A0 = 0 (0x00) : Horizontal addressing mode
水平方向へアドレスを指定するモードです。このモードでは、GDDRAMへのアクセス後にColumnアドレスポインタが自動的に1列分増加されます。ColumnアドレスポインタがColumn終了アドレスに達すると、ColumnアドレスポインタはColumn開始アドレスにリセットされ、PAGEアドレスポインタが1ページ分増加されます。ColumnアドレスポインタとPAGEアドレスポインタの両方が終了アドレスに達すると、ポインタはそれぞれの開始アドレスにリセットされます。
>>>
>>>
>>>
. . . 
i2c.writeto_mem(0x3c, 0x00, b'\x20')
i2c.writeto_mem(0x3c, 0x00, b'\x00')
for i in range(8):
    i2c.writeto_mem(0x3c, 0x40, num)
. . . 
>>>
0〜9までの数値の表示を8回繰り返した状態です。Column終了アドレスから次ページのColumn開始アドレスへと描画アドレスが移動していますね。
>>>
. . . 
for i in range(5):
    i2c.writeto_mem(0x3c, 0x40, num)
. . . 
>>>
さらに、続けて5回繰り返した結果です。PAGE、Columnの終了アドレスに達すると、PAGE、Column共に開始アドレスへと戻り描画が継続されます。

水平方向へ繰り返しアドレスが継続して描画され流のが確認できました。
実際に使用する場合は、描画アドレスを指定してレイアウトを組む必要もあります。その様な場合には、"Horizontal addressing mode"と"Vertical addressing mode"のみで使用出来るコマンドが用意されているので、そちらも解説しておきます。
>>>
>>>
>>>
i2c.writeto_mem(0x3c, 0x00, b'\x21')
i2c.writeto_mem(0x3c, 0x00, b'\x00')
i2c.writeto_mem(0x3c, 0x00, b'\x7F')
>>>

Set Column Address (0x21) - アドレス指定コマンド

D7 D6 D5 D4 D3 D2 D1 D0
0 0 1 0 0 0 0 1 0x21
* A6 A5 A4 A3 A2 A1 A0
* B6 B5 B4 B3 B2 B1 B0
A6 〜A0 : Column開始アドレスを0〜127(0x00〜0x7F)で指定
D6 D5 D4 D3 D2 D1 D0
A6 A5 A4 A3 A2 A1 A0
0 0 0 0 0 0 0 0x00
1 1 1 1 1 1 1 0x7F

B6 〜B0 : Column終了アドレスを0〜127(0x00〜0x7F)で指定
D6 D5 D4 D3 D2 D1 D0
B6 B5 B4 B3 B2 B1 B0
0 0 0 0 0 0 0 0x00
1 1 1 1 1 1 1 0x7F
このコマンドは、GDDRAMのColumn開始アドレスと終了アドレスを指定するためのものです。"Horizontal addressing mode"を有効にした場合、1列のColumnデータへのアクセスが終了すると、自動的に次のColumnアドレスにインクリメントされます。ColumnアドレスポインタがColumn終了アドレスまで到達すると、次の1行増加したRowアドレスへ移動した後、Column開始アドレスにリセットされます。
>>>
>>>
>>>
i2c.writeto_mem(0x3c, 0x00, b'\x22')
i2c.writeto_mem(0x3c, 0x00, b'\x00')
i2c.writeto_mem(0x3c, 0x00, b'\x07')
>>>

Set Page Address (0x22) - アドレス指定コマンド

D7 D6 D5 D4 D3 D2 D1 D0
0 0 1 0 0 0 1 0 0x22
* * * * * A2 A1 A0
* * * * *B2 B1 B0
A2 〜A0 : PAGE開始アドレスを0〜7(0x00〜0x07)で指定
D2 D1 D0 CODE function
A2 A1 A0
0 0 0 0x00 PAGE 0
1 1 1 0x07 PAGE 7

B2 〜B0 : PAGE終了アドレスを0〜7(0x00〜0x07)で指定
D2 D1 D0 CODE function
B2 B1 B0
0 0 0 0x00 PAGE 0
1 1 1 0x07 PAGE 7
このコマンドは、GDDRAMのPAGE開始アドレスと終了アドレスを指定するためのものです。"Horizontal addressing mode"を有効にした場合、1 PAGEのデータへのアクセスが終了すると、次のPAGEアドレスに自動的にインクリメントされます。PAGEアドレスポインタがPAGE終了アドレスまで到達すると、PAGE開始アドレスにリセットされます。
サンプル - 08
GDDRAMへ書込むアドレスと、範囲を指定して転送してみます。
>>>
>>>
>>>
>>>
>>>
>>>
>>>
i2c.writeto_mem(0x3c, 0x00, b'\x21')
i2c.writeto_mem(0x3c, 0x00, b'\x28')
i2c.writeto_mem(0x3c, 0x00, b'\x7F')
i2c.writeto_mem(0x3c, 0x00, b'\x22')
i2c.writeto_mem(0x3c, 0x00, b'\x02')
i2c.writeto_mem(0x3c, 0x00, b'\x04')
i2c.writeto_mem(0x3c, 0x40, num)
>>>
指定した範囲内に0~9の数値が書き込まれ、さらに範囲を超えたものは指定初期位置から継続して書き込まれいるのが確認出来ます。
A1 = 0 / A0 = 1 (0x01) : Vertical addressing mode
垂直方向へアドレスを指定するモードです。このモードでは、GDDRAMへのアクセス後にPAGEアドレスポインタが自動的に1ページ分増加されます。PAGEアドレスポインタがPAGE終了アドレスに達すると、PAGEアドレスポインタはPAGE開始アドレスにリセットされ、Columnアドレスポインタが1列分増加されます。PAGEアドレスポインタとColumnアドレスポインタの両方が終了アドレスに達すると、ポインタはそれぞれの開始アドレスにリセットされます。
>>>
>>>
>>>
. . . 
i2c.writeto_mem(0x3c, 0x00, b'\x20')
i2c.writeto_mem(0x3c, 0x00, b'\x01')
for i in range(13):
    i2c.writeto_mem(0x3c, 0x40, num)
. . . 
>>>
なんだかノイズの様な表示になりましたが、0〜9までの画像データが8byte(64bit)縦に並んでいるので文字としては認識出来なくなっているわけです。

サンプル - 08で使ったコマンドは、"Vertical addressing mode"でも有効なので、レイアウトを変えて読めるようにしてみたいと思います。
サンプル - 09
0~9の数値を1ページ毎に8byteずらしながらGDDRAMへ転送してみます。
>>>
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . . 
for i in range(8):
    i2c.writeto_mem(0x3c, 0x00, b'\x21')
    i2c.writeto_mem(0x3c, 0x00, (i*8).to_bytes(1, 'big'))
    i2c.writeto_mem(0x3c, 0x00, b'\x7F')
    i2c.writeto_mem(0x3c, 0x00, b'\x22')
    i2c.writeto_mem(0x3c, 0x00, i.to_bytes(1, 'big'))
    i2c.writeto_mem(0x3c, 0x00, b'\x07')
    i2c.writeto_mem(0x3c, 0x40, b'\x00\x00\x00\x3E\x51\x49\x45\x3E')

>>>
0~9の数値画像データは縦が8bit(1byte)と、PAGEの縦幅と同じサイズなのでページ毎に0~9の数値が視認出来る形でGDDRAMへ書き込まれています。
更に、ページ毎に1byte(8bit)右側に表示位置がズレて、最終ページではページ終端アドレスを超えたものがページ開始アドレスにリセットされて書き込まれている事が確認出来ます。
A1 = 1 / A0 = 0 (0x02) : Page addressing mode
このモードでは、GDDRAMへのアクセス後にColumnアドレスポインタが自動的に1列分増加されます。ColumnアドレスポインタがColumn終了アドレスに達すると、ColumnアドレスポインタはColumn開始アドレスにリセットされるだけで、PAGEアドレスポインタは更新されません。別のPAGEへアクセスするには、新たにPAGEアドレスとColumnアドレスを指定する必要があります。
>>>
>>>
>>>
. . . 
i2c.writeto_mem(0x3c, 0x00, b'\x20')
i2c.writeto_mem(0x3c, 0x00, b'\x02')
for i in range(2):
    i2c.writeto_mem(0x3c, 0x40, num)
. . . 
>>>
0〜9までの数値の表示を2回繰り返した状態です。
このモードでは、1ページの範囲内から出る事はありません。PAGEアドレスとColumnアドレスを指定するためのコマンドが用意されているので、そちら使ってアドレスを設定します。専用のコマンドも確認しておきましょう。
>>>
i2c.writeto_mem(0x3c, 0x00, b'\x00')
>>>

Set Lower Column Start Address for Page Addressing Mode (0x00~0x0F) - アドレス指定コマンド

D7 D6 D5 D4 D3 D2 D1 D0
0 0 0 0 X3 X2 X1 X0
0 0 0 0 0 0 0 0 0x00
0 0 0 0 1 1 1 1 0x0F
X3 〜A0 : Column開始アドレスの下位4bitを0〜15(0x10〜0x1F)で指定
Column開始アドレス、0~127を示す8bitの下位4bitを指定します。
例えば、127(0x7F)であれば以下の赤枠の箇所を設定します。
0 1 1 1 1 1 1 1
下位4bit
>>>
i2c.writeto_mem(0x3c, 0x00, b'\x10')
>>>

Set Higher Column Start Address for Page Addressing Mode (0x10~0x1F) - アドレス指定コマンド

D7 D6 D5 D4 D3 D2 D1 D0
0 0 0 1 X3 X2 X1 X0
0 0 0 1 0 0 0 0 0x10
0 0 0 1 1 1 1 1 0x1F
X3 〜A0 : Column開始アドレスの上位4bitを0〜15(0x10〜0x1F)で指定
Column開始アドレス、0~127を示す8bitの上位4bitを指定します。
例えば、127(0x7F)であれば以下の赤枠の箇所を設定します。
0 1 1 1 1 1 1 1
上位4bit
>>>
i2c.writeto_mem(0x3c, 0x00, b'\xB0')
>>>

Set Page Start Address for Page Addressing Mode (0xB0~0xB7) - アドレス指定コマンド

D7 D6 D5 D4 D3 D2 D1 D0
1 0 1 1 0 X2 X1 X0
1 0 1 1 0 0 0 0 0xB0
1 0 1 1 0 1 1 1 0xB7
X2 〜X0 : PAGE開始アドレスを0〜7(0xB0〜0xB7)で指定
PAGE開始アドレスを3bitの0〜7 PAGEで指定します。
サンプル - 10
実際に動作確認してみます。
>>>
>>>
>>>
. . .
. . .
. . .
. . .
. . .
. . .
. . .
i2c.writeto_mem(0x3c, 0x00, b'\x20')
i2c.writeto_mem(0x3c, 0x00, b'\x02')
for i in range(8):
    bits = (i * 8)
    high = (bits >> 4)
    i2c.writeto_mem(0x3c, 0x00, (bits-(high << 4)).to_bytes(1, 'big'))
    i2c.writeto_mem(0x3c, 0x00, (0b00010000+high).to_bytes(1, 'big'))
    i2c.writeto_mem(0x3c, 0x00, (0b10110000+i).to_bytes(1, 'big'))
    i2c.writeto_mem(0x3c, 0x40, num)

>>>
このモードでは、PAGE単位でGDDRAMへ書き込みを行います。指定できるのはColumn開始アドレス(上位下位アドレス4bitずつ)とPAGEアドレスだけとなり、PAGE終了アドレスを超える画像データは、PAGE開始アドレスにリセットされて書き込まれます。
A1 = 1 / A0 = 1 (0x11) : 無効
メモリアドレッシングは上記の3つのモードのいずれかに設定する必要があるとの事なので、この"無効"はコマンド自体が無効である事なのかな?と思っています。
>>>
>>>
i2c.writeto_mem(0x3c, 0x00, b'\xD9')
i2c.writeto_mem(0x3c, 0x00, b'\xF1')

>>>

Set Pre-charge Period (0xD9) - タイミング&駆動方式設定コマンド

D7 D6 D5 D4 D3 D2 D1 D0
1 1 0 1 1 0 0 1 0xD9
A7 A6 A5 A4 A3 A2 A1 A0
0 0 0 0 0 0 0 0 0x00
0 0 1 0 0 0 1 0 0x22
1 1 1 1 1 1 1 1 0xFF

A3 〜A0 : フェーズ1の周期 0〜15(0x0〜0xF)DCLK
クロック0は無効

A7 〜A4 : フェーズ2の周期 0〜15(0x0〜0xF)DCLK
クロック0は無効
このコマンドは、プリチャージ期間の長さを設定するために使用されます。
とは言え、プリチャージって何でしょう?
この辺りを理解するには、"Segment Drivers""Common Drivers"の働きを理解する必要があります。
Segment Drivers : 液晶パネルへ128本の電流を供給しています。
駆動電流は0~100μAまでを256段階で調整可能。

Common Drivers : 電圧走査パルスを生成しています。
セグメント駆動波形は以下の3つのフェーズに分けられます。
  1. Phase 1
    次の画像表示用に、現行画像のOLED画素電荷を放電。
  2. Phase 2
    OLED画素がVSS(0V)から目標電圧に達するまで駆動。
    この期間は、1~15 DCLK(Display Clock)までプログラムで指定出来ます。 OLEDパネルの画素の静電容量値が大きい場合は、コンデンサに蓄電するため目標電圧に達する期間が長くなります。
  3. Phase 3
    OLED画素をフェーズ1~2で準備した電流源を用いた電流駆動への切替を行う。
フェーズ3が終了すると、フェーズ1に戻り次の行の画像データを表示します。OLEDパネル上の画像表示をリフレッシュするために、この3ステップサイクルが連続的に実行されています。

ここで出てきたDCLKは、
DCLK = FOSC / D
で求める事が出来ます。
FOSC : 内部クロック周波数
0~15(333~407kHz)で指定。

D : 分周比(divide ratio)
1~16で指定。

初期化処理で設定された値は、FOSC=8(370kHz)、D=1なので、 DCLK=370kHz/1=370kHz
370kHzは、約2.7μ秒となります。よって15 DCLKは約40.54μ秒、2 DCLKは約5.4μ秒です。
何気に表示されていると思っていたOLEDパネルですが、高速で各行をリフレッシュし続けて画面を表示しているわけです。

Phase1〜2をDCLKの単位でカウントして、以上の様に間隔を設定しています。
>>>
>>>
i2c.writeto_mem(0x3c, 0x00, b'\xD8')
i2c.writeto_mem(0x3c, 0x00, b'\x40')

>>>

Set VCOMH Deselect Level (0xDB) - タイミング&駆動方式設定コマンド

D7 D6 D5 D4 D3 D2 D1 D0
1 1 0 1 1 0 1 1 0xDB
0 A6 A5 A4 0 0 0 0
0 0 0 0 0 0 0 0 0x00
0 0 1 0 0 0 0 0 0x20
0 1 0 0 0 0 0 0 0x40
A6 〜A4 : 0〜0.9(0x00〜0x40) で指定します。詳細は下記。
D6 D5 D4 CODE VCOMH解放レベル
A6 A5 A4
0 0 0 0x00 ~ 0.65 x VCC
0 0 1 0x10 ~ 0.71 x VCC
0 1 0 0x20 ~ 0.77 x VCC
0 1 1 0x30 ~ 0.83 x VCC
1 0 0 0x40 ~ 0.89 x VCC
データシートに記載されているのは、0x00、0x20、0x30の3つだけ
ですが、Adafruitの初期化処理では0x40が指定されています。
0x40は、VCOMのほぼ最大定格なので表示サイズや画像データに
よっては避けた方が良いかもしれません。
今のところ理解できているのは、Vcomの上限値を設定しているのかな?程度で、ぶっちゃけ現時点で完全にコマンドの内容を把握できていません。
"Adafruit"の初期設定も一通り調べた結果として、想定していた表示が出来ると思われます。
上手く動作しなかったコードを再度試してみます。
>>>
>>>
blank = bytes(128*8*[255])
i2c.writeto_mem(0x3c, 0x40, blank)
>>>
表示されましたでしょうか?"上の様な表示で無い場合は初期化設定を見直して下さい。
また、[255]の箇所を[0]にすると全画面黒での表示となります。

では、当初の目標であった表示を試してみます。 "num"を0〜7の値に変更して再登録しておきます。
>>>num = b'\x00\x00\x00\x3E\x51\x49\x45\x3E\x00\x00\x00\x00\x42\x7F\x40\x00\x00\x00\x00\x42\x61\x51\x49\x46\x00\x00\x00\x21\x41\x45\x4B\x31\x00\x00\x00\x18\x14\x12\x7F\x10\x00\x00\x00\x27\x45\x45\x45\x39\x00\x00\x00\x3C\x4A\x49\x49\x30\x00\x00\x00\x01\x71\x09\x05\x03'
>>>
枠線のデータを登録。
>>>frame = b'\xff\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x00\x00\x00\x7F\x09\x09\x09\x06\x00\x00\x00\x7E\x11\x11\x11\x7E\x00\x00\x00\x3E\x41\x49\x49\x7A\x00\x00\x00\x7F\x49\x49\x49\x41\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\xff'
>>>
以上を描画してみます。
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
i2c.writeto_mem(0x3c, 0x40, bytes(frame*8))
i2c.writeto_mem(0x3c, 0x00, b'\x21')
i2c.writeto_mem(0x3c, 0x00, b'\x4C')
i2c.writeto_mem(0x3c, 0x00, b'\x53')
i2c.writeto_mem(0x3c, 0x00, b'\x22')
i2c.writeto_mem(0x3c, 0x00, b'\x00')
i2c.writeto_mem(0x3c, 0x00, b'\x07')
i2c.writeto_mem(0x3c, 0x40, num)
# 枠線をframe x 8のサイズで描画。
# Columnアドレスを指定します。
# Column開始アドレス0x4C(76列目)
# Column終了アドレス0x53(83列目)
# Pageアドレスを指定します。
# Page開始アドレス0x00(0ページ)
# Page終了アドレス0x07(7ページ)
# 番号(num)を描画

>>>
正常に機能すれば、上の様な表示が確認出来るかと思います。
と、ここまでで当初目標としていた表示は達成出来たので、以降はオマケです。

グラフィックアクセラレーションコマンド

Continuous Horizontal Scroll Setup (0x26/0x27) - スクロールコマンド

水平スクロールパラメータを設定するための6つのデータバイトで構成され、スクロールの開始ページ、終了ページ、およびスクロール速度を決定します。
※コマンド実行前に、水平スクロールを必ず無効(0x2E)にして下さい。
RAMの内容が破損する可能性があります。

水平スクロールでの列数は、128列に固定されています。
D7 D6 D5 D4 D3 D2 D1 D0
0 0 1 0 0 1 1 X0 0x26/0x27
0 0 0 0 0 0 0 0 0x00
* * * * * B2 B1 B0
* * * * * C2 C1 C0
* * * * * D2 D1 D0
0 0 0 0 0 0 0 0 0x00
1 1 1 1 1 1 1 1 0xFF
X0 : 水平スクロールの方向を0(右)、1(左)で指定します。
D0 CODE function
X0
0 0x26 右スクロール
1 0x27 左スクロール

B2〜B0 : 開始PAGEアドレスをPAGE0〜PAGE7で指定
D2 D1 D0 CODE function
B2 B1 B0
0 0 0 0x00 PAGE 0
1 1 1 0x07 PAGE 7

C2〜C0 : スクロールの間隔(frame数)を設定する
フレーム周波数は以下の式で求められます。

FFRM = FOSC / (D x K x No. of Mux)
FOSC : 内部クロック周波数
0~15(333~407kHz)で指定。

D : 分周比(divide ratio)
1~16で指定。

K : 1行あたりの表示クロック数です。
K = Phase 1 period + Phase 2 period + Phase 3 period
で求める事が出来ます。実際に設定値をあてはめると、
2 + 2 + 50 = 54DCLKとなります。
データシートにPhase 3について、
"電流駆動パルス幅の長さが50に設定されて〜"と
記載されていますが、SSD1306に設定用のコマンドなどは
なさそうなので、50DCLKが規定値と考えて良さそうです。
ちなみに、1DCLK=約2.7μ秒なので54DCLKは、
約145.8μ秒です。

No. of Mux : "Set Multiplex Ratio (0xA8)"で指定された値です。
15〜63(0x0F〜0x3F)で指定。

合ってるのか不明ですが、FFRM = 107Hz(9.3m秒)となるのかな?
D2 D1 D0 CODE function
C2 C1 C0
0 0 0 0x00 5 frames
0 0 1 0x01 64 frames
0 1 0 0x02 128 frames
0 1 1 0x03 256 frames
1 0 0 0x04 3 frames
1 0 1 0x05 4 frames
1 1 0 0x06 25 frames
1 1 1 0x07 2 frames

D2〜D0 : 終了PAGEアドレスをPAGE0〜PAGE7で指定
D2 D1 D0 CODE function
D2 D1 D0
0 0 0 0x00 PAGE 0
1 1 1 0x07 PAGE 7
>>> i2c.writeto_mem(0x3c, 0x00, b'\x26\x00\x05\x00\x07\x00\xFF')
>>>

Continuous Vertical and Horizontal Scroll Setup (0x29/0x2A) - スクロールコマンド

垂直スクロールパラメータを設定するための5つのデータバイトで構成され、スクロール開始ページ、終了ページ、スクロール速度、および垂直スクロールオフセットを決定します。

B2〜B0、C2〜C0、D2〜D0は水平スクロールの設定用です。E5〜E0は垂直スクロールのオフセット設定用です。これらのデータバイトは、対角線(水平+垂直)スクロールの設定用です。垂直スクロールオフセットE5〜E0が"0(0x00)"に設定されている場合、水平スクロールのみが実行されます。
※コマンド実行前に、水平スクロールを必ず無効(0x2E)にして下さい。
RAMの内容が破損する可能性があります。
D7 D6 D5 D4 D3 D2 D1 D0
0 0 1 0 0 1 X1 X0 0x29/0x2A
0 0 0 0 0 0 0 0 0x00
* * * * * B2 B1 B0
* * * * * C2 C1 C0
* * * * * D2 D1 D0
* * E5 E4 E3 E2 E1 E0
X1〜X0 : 0〜0.9(0x00〜0x40) で指定します。詳細は下記。
D1 D0 CODE function
X0 X1
0 1 0x29 縦と右のスクロール
1 0 0x2A 縦と左のスクロール

B2〜B0 : 開始PAGEアドレスをPAGE0〜PAGE7で指定
D2 D1 D0 CODE function
B2 B1 B0
0 0 0 0x00 PAGE 0
1 1 1 0x07 PAGE 7

C2〜C0 : スクロールの間隔を設定する
D2 D1 D0 CODE function
C2 C1 C0
0 0 0 0x00 5 frames
0 0 1 0x01 64 frames
0 1 0 0x02 128 frames
0 1 1 0x03 256 frames
1 0 0 0x04 3 frames
1 0 1 0x05 4 frames
1 1 0 0x06 25 frames
1 1 1 0x07 2 frames

D2〜D0 : 終了PAGEアドレスをPAGE0〜PAGE7で指定
D2 D1 D0 CODE function
D2 D1 D0
0 0 0 0x00 PAGE 0
1 1 1 0x07 PAGE 7

E5〜E0 : 0〜64行(0x00〜0x3F)で垂直スクロールの移動値を設定
1(0x01)の場合は、 上方向(垂直方向)へ1行スクロール。
63(0x3F)だと、下方向へ1行スクロールしている様に見えます。
D5 D4 D3 D2 D1 D0
E5 E4 E3 E2 E1 E0
0 0 0 0 0 0 0x00
1 1 1 1 1 1 0x3F
>>> i2c.writeto_mem(0x3c, 0x00, b'\x29\x00\x00\x00\x07\x01')
>>>

Deactivate Scroll (0x2E) - スクロールコマンド

D7 D6 D5 D4 D3 D2 D1 D0
0 0 1 0 1 1 1 0 0x2E
スクロールを停止するコマンドです。 GDDRAMの書き換えや、スクロール設定の変更を行う際には、このコマンドを実行してスクロールを停止して下さい。

Activate Scroll (0x2F) - スクロールコマンド

D7 D6 D5 D4 D3 D2 D1 D0
0 0 1 0 1 1 1 1 0x2F
スクロールを開始するためのコマンドです。 このコマンドを実行した後に、GDDRAMデータの書き換え、スクロール設定の変更は行う事は禁止されています。

Set Vertical Scroll Area(0xA3) - スクロールコマンド

このコマンドは、垂直スクロール領域を設定するための2つのデータバイト(垂直スクロール固定領域指定、垂直スクロール領域指定)で構成されています。垂直スクロールの行数は"Set Multiplex Ratio (0xA8)"で指定された値が上限値となります。
D7 D6 D5 D4 D3 D2 D1 D0
1 0 1 0 0 0 1 1 0xA3
* * A5 A4 A3 A2 A1 A0
* B6 B5 B4 B3 B2 B1 B0
A5〜A0 : 0〜63(0x00〜0x3F)行で垂直スクロール固定領域を指定。
GDDRAMのRow0を起点として行数の設定を行います。
0(0x00)を指定すると固定領域は0行なので、全画面が垂直スクロール領域
として指定可能になります。逆に63(0x3F)を指定すると、Row0~62までの
63行分の領域が固定化され残り1行のみが垂直スクロール可能領域として
設定可能となります。
D5 D4 D3 D2 D1 D0
A5 A4 A3 A2 A1 A0
0 0 0 0 0 0 0x00
1 0 0 0 0 0 0x3F

B6〜B0 : 0〜64(0x00〜0x40)行で垂直スクロール領域を指定。
垂直スクロール固定領域の次の行を起点として行数の設定を行います。
0(0x00)を指定すると垂直スクロール領域は0行なので全画面で垂直
スクロールが固定化されます。垂直スクロール固定領域が0行で、64
(0x40)が指定されると、全画面が垂直スクロール領域になります。
また、垂直スクロール固定領域と垂直スクロール領域の合計値が64行
未満の場合は、残りの行範囲は垂直スクロール固定領域となります。
D6 D5 D4 D3 D2 D1 D0
B6 B5 B4 B3 B2 B1 B0
0 0 0 0 0 0 0 0x00
1 0 0 0 0 0 0 0x40

果たしてこの設定を使う事があるのか?とどの様な場面で使用するのか?が不明です。

0 件のコメント:

コメントを投稿