
Micropythonライブラリ(SSD1306 OLED ディスプレイ):ssd1306
1.概要
Micropythonの外部ライブラリとして、SSD1306 OLED ディスプレイを操作するssd1306の紹介をします。
実験用に「0.96インチ 128×64ドット有機ELディスプレイ(OLED) 白色(580円/個(税込))」を使用していきます。

1-1.製品仕様
秋月電子のHPには基本仕様以外の記載はなく、参考資料しかないため自分で必要な情報は確認する必要があります。
メモ用で仕様要点を書きに記載しました。
電源電圧$${V_{DD}}$$:1.65~3.3V
秋月の仕様は3.3~5.0Vなのは何故?(多分3.3~5.0が正だと思うけど)
I2Cで通信可能

1-2.原理の理解
有機EL(Electro Luminescence)とは、電圧をかけると発光する有機物です。本製品は、この現象を利用した有機発光ダイオード(Organic light emitting diode:OLED)で作られたディスプレイです。
※いわゆる有機ELディスプレイと同じとのことです。
OLEDの基本的な構造は陰極(カソード)と陽極(アノード)の間に有機薄膜を挟んだものであり、電流が陽極から陰極に流れると有機薄膜がそのエネルギーを受け取り、電子が放出されます。これらの電子が有機材料に衝突することで有機分子が励起され、光が発生します。

OLED(有機EL)ディスプレイと液晶ディスプレイ(LCD)を比較した時、OLEDの特徴は下記の通りです。
低消費電力
高画質
現在はLCDに比べると大型化が苦手(高コスト)
有機ELは素子そのものが発光しているため、応答時間が短い
有機ELはバックライトが不要のため、薄型化や軽量化が可能


https://www.futaba.co.jp/product/oled/about
1-3.部材の購入
購入品は本体のみ(0.96インチ 128×64ドット有機ELディスプレイ(OLED) 白色)となります。
その他試験に必要な部品は下記の通りです。
Raspberry Pi Pico
ブレッドボード
ジャンピングワイヤー
2.環境構築
2-1.Raspberry Pi Picoの準備
Raspberry Pi Picoを始めるために、IDEインストールとファームウェアの書き込みが必要になります。詳細は下記の通りです。
2-2.ライブラリのインポート
micropython-libから外部ライブラリ”ssd1306”をインポートします。インポート方法は下記記事「Microtpyhon/外部ライブラリ」をご確認ください。

2-3.部品の組付け
本製品ははんだ付けなどは不要のため、そのまま部品を下図の通り配線しました。
SSD1306|VCC▶Pico|36pin(3.3V)
SSD1306|GND▶Pico|38pin(GND)
SSD1306|▶Pico|GP0(SDA)
SSD1306|▶Pico|GP1(SCL)

3.API:基本操作
まずは基本操作を説明します。資料は下記参照しました。
3-1.ssd1306オブジェクト
SSD1306 OLEDディスプレイを使用するために、ssd1306ライブラリからオブジェクトを作成します。シリアル通信の方法により、オブジェクト作成方法は下記3つがあります。
I2C インタフェース
ハードウェア SPI インタフェース
ソフトウェア SPI インタフェース
今回はI2Cインターフェスで実行していくため、SPIはサンプルコードを記載します。
【I2C インタフェース】
オブジェクト化する時に通信するI2Cオブジェクトとディスプレイの幅、高さ情報を渡します。
[API@aad1306.py]
class SSD1306_I2C(SSD1306):
def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False):
self.i2c = i2c
self.addr = addr
self.temp = bytearray(2)
self.write_list = [b"\x40", None] # Co=0, D/C#=1
super().__init__(width, height, external_vcc)
def write_cmd(self, cmd):
self.temp[0] = 0x80 # Co=1, D/C#=0
self.temp[1] = cmd
self.i2c.writeto(self.addr, self.temp)
def write_data(self, buf):
self.write_list[1] = buf
self.i2c.writevto(self.addr, self.write_list)
[IN]
from machine import Pin, I2C
import ssd1306
i2c = I2C(0, scl=Pin(1), sda=Pin(0)) # I2C初期化
display = ssd1306.SSD1306_I2C(128, 64, i2c) #(幅, 高さ, I2Cオブジェクト)
【ハードウェア SPI インタフェース】
[API@aad1306.py]
class SSD1306_SPI(SSD1306):
def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
self.rate = 10 * 1024 * 1024
dc.init(dc.OUT, value=0)
res.init(res.OUT, value=0)
cs.init(cs.OUT, value=1)
self.spi = spi
self.dc = dc
self.res = res
self.cs = cs
import time
self.res(1)
time.sleep_ms(1)
self.res(0)
time.sleep_ms(10)
self.res(1)
super().__init__(width, height, external_vcc)
def write_cmd(self, cmd):
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
self.cs(1)
self.dc(0)
self.cs(0)
self.spi.write(bytearray([cmd]))
self.cs(1)
def write_data(self, buf):
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
self.cs(1)
self.dc(1)
self.cs(0)
self.spi.write(buf)
self.cs(1)
[IN]
from machine import Pin, SPI
import ssd1306
hspi = SPI(1) # sck=14 (scl), mosi=13 (sda), miso=12 (unused)
dc = Pin(4) # data/command
rst = Pin(5) # reset
cs = Pin(15) # chip select: このためのピンが無いモジュールもあります
display = ssd1306.SSD1306_SPI(128, 64, hspi, dc, rst, cs)
【ソフトウェア SPI インタフェース】
[IN]
from machine import Pin, SoftSPI
import ssd1306
spi = SoftSPI(baudrate=500000, polarity=1, phase=0, sck=Pin(14), mosi=Pin(13), miso=Pin(12))
dc = Pin(4) # data/command
rst = Pin(5) # reset
cs = Pin(15) # chip select: このためのピンが無いモジュールもあります
display = ssd1306.SSD1306_SPI(128, 64, spi, dc, rst, cs)
3-2.文字の入力:text()
文字の入力はtext()関数を使用します。詳細は別章で記載しました。
なおtextは英語のみであり、日本語だと(少なくとも本装置)表示できませんでした。
3-3.ディスプレイに表示:show()
文字の出力はshow()を使用します。内容としてはFrameBuffer の内容をディスプレイメモリに書き出す処理となります。
[API]
def show(self):
x0 = 0
x1 = self.width - 1
if self.width != 128:
# narrow displays use centred columns
col_offset = (128 - self.width) // 2
x0 += col_offset
x1 += col_offset
self.write_cmd(SET_COL_ADDR)
self.write_cmd(x0)
self.write_cmd(x1)
self.write_cmd(SET_PAGE_ADDR)
self.write_cmd(0)
self.write_cmd(self.pages - 1)
self.write_data(self.buffer)
下記コードを実行すると文字がディスプレイに表示されます。
[IN]
from machine import Pin, I2C
import ssd1306
i2c = I2C(0, scl=Pin(1), sda=Pin(0)) # I2C初期化
display = ssd1306.SSD1306_I2C(128, 64, i2c) #(幅, 高さ, I2Cオブジェクト)
display.text('Hello, World!', 0, 0, 1)
display.show()
[OUT]

4.基本関数
4-1.ディスプレイの電源ON/OFF
ディスプレイの電源ON/OFFは下記の通りです。
[API]
def poweroff(self):
self.write_cmd(SET_DISP)
def poweron(self):
self.write_cmd(SET_DISP | 0x01)
poweron():電源ON※ピクセルはメモリに残存
poweroff():電源OFF※ピクセルを再描写
[IN]
from machine import Pin, I2C
import ssd1306
i2c = I2C(0, scl=Pin(1), sda=Pin(0)) # I2C初期化
display = ssd1306.SSD1306_I2C(128, 64, i2c) #(幅, 高さ, I2Cオブジェクト)
display.text('Hello, World!', 0, 0, 1)
display.show()
display.poweroff() # ディスプレイの電源を切る
[OUT]
図上
[IN]
from machine import Pin, I2C
import ssd1306
i2c = I2C(0, scl=Pin(1), sda=Pin(0)) # I2C初期化
display = ssd1306.SSD1306_I2C(128, 64, i2c) #(幅, 高さ, I2Cオブジェクト)
display.text('Hello, World!', 0, 0, 1)
display.show()
display.poweroff() # ディスプレイの電源を切る
display.poweron() # ディスプレイの電源を入れる
[OUT]
図下

4-2.コントラスト調整:contrast()
コントラストの調整はcontrast()メソッドを使用します。下図は値を0, 50, 150, 255に設定してみました。結果として0は真っ暗ですが、他は大きな違いは確認されませんでした。
[API]
def contrast(self, contrast):
self.write_cmd(SET_CONTRAST)
self.write_cmd(contrast)
[IN]
from machine import Pin, I2C
import ssd1306
i2c = I2C(0, scl=Pin(1), sda=Pin(0)) # I2C初期化
display = ssd1306.SSD1306_I2C(128, 64, i2c) #(幅, 高さ, I2Cオブジェクト)
display.text('Hello, World!', 0, 0, 1)
display.contrast(0) #暗くする
display.show()
[OUT]

4-3.白黒反転:invert(1)
画面の白黒反転(文字:黒、バックライト:白)は"invert(1)"を使用します。なおinvert(0)は通常通りの表示となります。
[API]
def invert(self, invert):
self.write_cmd(SET_NORM_INV | (invert & 1))
[IN]
from machine import Pin, I2C
import ssd1306
i2c = I2C(0, scl=Pin(1), sda=Pin(0)) # I2C初期化
display = ssd1306.SSD1306_I2C(128, 64, i2c) #(幅, 高さ, I2Cオブジェクト)
display.text('Hello, World!', 0, 0, 1)
display.invert(1)
display.show()
[OUT]

4-4.反転:rotate()
文字の反転はrotete(<bool型>)を使用します。Trueが180°回転でFlaseが0°回転です。私の結果はFalseにすると、何もしない時と同じ結果となりました。
[API]
def rotate(self, rotate):
self.write_cmd(SET_COM_OUT_DIR | ((rotate & 1) << 3))
self.write_cmd(SET_SEG_REMAP | (rotate & 1))
[IN]
from machine import Pin, I2C
import ssd1306
i2c = I2C(0, scl=Pin(1), sda=Pin(0)) # I2C初期化
display = ssd1306.SSD1306_I2C(128, 64, i2c) #(幅, 高さ, I2Cオブジェクト)
display.text('Hello, World!', 0, 0, 1)
display.rotate(False)
display.show()
[OUT]

5.グラフィック機能
より詳細に表示させたい場合のメソッドを紹介します。なおGitHubのssd1306.pyに記載が見つからなかったため、詳細な説明は省きます。
本機能を使用することでディスプレイに複雑な描写も可能です。
[IN]
from machine import Pin, I2C
import ssd1306
# I2C初期化(SDAとSCLピンを設定)
i2c = I2C(0, scl=Pin(1), sda=Pin(0)) # I2C初期化
display = ssd1306.SSD1306_I2C(128, 64, i2c) #(幅, 高さ, I2Cオブジェクト)
#MicroPython のロゴを描画し、テキストを印字
display.fill(0) #バックライトを消灯(color=0)
# MicroPython のロゴ
display.fill_rect(0, 0, 32, 32, 1) #塗りつぶし矩形(x, y, width, height, color)
display.fill_rect(2, 2, 28, 28, 0) #塗りつぶし矩形(x, y, width, height, color)
display.vline(9, 8, 22, 1) #垂直線(x, y, height, color)
display.vline(16, 2, 22, 1) #垂直線(x, y, height, color)
display.vline(23, 8, 22, 1) #垂直線(x, y, height, color)
display.fill_rect(26, 24, 2, 4, 1) #塗りつぶし矩形(x, y, width, height, color)
#テキスト
display.text('MicroPython', 40, 0, 1)
display.text('SSD1306', 40, 12, 1)
display.text('OLED 128x64', 40, 24, 1)
display.show()
[OUT]

5-1.バックライトの設定:fill()
バックライトの色をfill()で設定します。デフォルト(黒)はfill(0)であり、白はfill(1)です。
[IN]
from machine import Pin, I2C
import ssd1306
i2c = I2C(0, scl=Pin(1), sda=Pin(0)) # I2C初期化
display = ssd1306.SSD1306_I2C(128, 64, i2c) #(幅, 高さ, I2Cオブジェクト)
display.fill(1) # スクリーン全体を colour=0 で埋める
display.show()
[OUT]
上:fill(0)
下:fill(1)

5-2.ピクセルの取得/描写:pixel()
pixelの情報取得したり、ディスプレイにドットを表示させるにはpixel(<x>, <y>, <色(0/1)>)を使用します。
サンプルとして4点ドットを描写し、ドットがある場所とない場所のピクセル情報をプリントしました。結果として色に1を指定するとドットが確認できました。
[IN]
from machine import Pin, I2C
import ssd1306
i2c = I2C(0, scl=Pin(1), sda=Pin(0)) # I2C初期化
display = ssd1306.SSD1306_I2C(128, 64, i2c) #(幅, 高さ, I2Cオブジェクト)
display.pixel(0, 0, 1)
display.pixel(10, 0, 1)
display.pixel(0, 10, 1)
display.pixel(30, 30, 1)
print(display.pixel(0, 0), display.pixel(1,0))
display.show()
[OUT]
1 0

5-3.線:line/vline/hline
線の描写は3通りあります。
line(<x1>,<y1><x1>,<y2>,<色>):直線
指定方法により斜めに線の描写が可能
vline(<x>,<y><幅(長さ)>,<色>):垂直線※最初のx,yは始点
hline(<x>,<y><幅(長さ)>,<色>):水平線※最初のx,yは始点
[IN]
from machine import Pin, I2C
import ssd1306
i2c = I2C(0, scl=Pin(1), sda=Pin(0)) # I2C初期化
display = ssd1306.SSD1306_I2C(128, 64, i2c) #(幅, 高さ, I2Cオブジェクト)
display.line(0, 0, 50, 30, 1) #直線(x0, y0, x1, y1, color)
display.vline(60, 0, 30, 1) #垂直線(x, y, height, color)
display.hline(0, 40, 50, 1) #水平線(x, y, width, color)
display.show()
[OUT]

5-4.長方形:rect/fill_rect
長方形の描写は塗りつぶしあり/無しで下記があります。
rect(<x1>,<y1><x1>,<y2>,<色>):塗りつぶし無し
fill_rect(<x1>,<y1><x1>,<y2>,<色>):塗りつぶしあり
[IN]
from machine import Pin, I2C
import ssd1306
i2c = I2C(0, scl=Pin(1), sda=Pin(0)) # I2C初期化
display = ssd1306.SSD1306_I2C(128, 64, i2c) #(幅, 高さ, I2Cオブジェクト)
display.rect(0, 0, 50, 20, 1) # 四角形の描画
display.fill_rect(60, 30, 120, 50, 1) # 塗りつぶし四角形の描画
display.show()
[OUT]

5-5.テキスト:text()
テキストの表示はtext(<x1>,<y1>,<色>)で表示できます。基本的に日本語は使用できませんので英語や数値を使用します。
テストした感じだとASCIIコードの文字($${2^{7bit}=128}$$文字)を使用できそうです。
[IN]
from machine import Pin, I2C
import ssd1306
i2c = I2C(0, scl=Pin(1), sda=Pin(0)) # I2C初期化
display = ssd1306.SSD1306_I2C(128, 64, i2c) #(幅, 高さ, I2Cオブジェクト)
display.text('Hello World', 0, 0, 1) # x=0, y=0, colour=1 でテキストを描画
display.text('123456789', 0, 10, 1)
display.text('abcdefghijklmnopqrstuvwxyz', 0, 20, 1)
display.text('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 0, 30, 1)
display.text('#$%&()*+,-./:;<=', 0, 40, 1)
display.text('>?@{|}~', 0, 50, 1)
display.text('こんにちは', 0, 60, 1)
display.show()
[OUT]

5-6.スクロール:scroll
スクロールをする場合はscroll(<pixcel>, 0)とします。
※スクロールの効果が分かりませんが、見た感じ同じ文字を繰り返すように見えます。
参考としてscroll()値を10, 30, 50, 100に変更して出力しました。
[IN]
from machine import Pin, I2C
import ssd1306
i2c = I2C(0, scl=Pin(1), sda=Pin(0)) # I2C初期化
display = ssd1306.SSD1306_I2C(128, 64, i2c) #(幅, 高さ, I2Cオブジェクト)
display.text('Hello World', 0, 0, 1) # x=0, y=0, colour=1 でテキストを描画
display.text('a', 0, 30, 1) # x=0, y=0, colour=1 でテキストを描画
display.text('a A b B', 0, 50, 1) # x=0, y=0, colour=1 でテキストを描画
display.scroll(100, 0) # 20 ピクセルだけ右にスクロール
display.show()
[OUT]

6.応用編
6-1.別のFrameBuffer描写:blit()
現在の FrameBuffer の上に、与えられた座標で別の FrameBuffer を描画するにはblit()を使用します。サンプルコードは下記の通りです。
※詳細を理解できていないため説明は省略
[IN]
from machine import Pin, I2C
import ssd1306
i2c = I2C(0, scl=Pin(1), sda=Pin(0)) # I2C初期化
display = ssd1306.SSD1306_I2C(128, 64, i2c) #(幅, 高さ, I2Cオブジェクト)
import framebuf
fbuf = framebuf.FrameBuffer(bytearray(8 * 8 * 1), 8, 8, framebuf.MONO_VLSB)
fbuf.line(0, 0, 7, 7, 1)
display.blit(fbuf, 10, 10, 0) # x=10, y=10, key=0 の上に描画
display.show()
display.show()
[OUT]

7.おまけ:センサー値の表示
過去記事で温度センサーから値を取得しました。これを応用して、センサーの値をディスプレイに表示させてみます。
配線は下図の通りです。

7-1.スクリプト
コードおよび設計思想は下記の通りです。(詳細は記事参照)
温度データはPicoのI2Cモジュールからスクラッチで作成
横の文字数制限があるため、出力する文字は簡素化
℃はASCII文字ではなく使用できないためCで代用
[IN]
from machine import Pin, I2C
import time
import ssd1306
# SSD1306:ディスプレイ表示
i2c_SSD1306 = I2C(0, scl=Pin(1), sda=Pin(0))
display = ssd1306.SSD1306_I2C(128, 64, i2c_SSD1306)
#温度センサ(ADT7410):温度データ
i2c_ADT7410 = I2C(1, scl=Pin(3), sda=Pin(2))
#条件設定
address = 0x48 # ADT7410のI2Cアドレス
register = 0x00 # 温度レジスタのアドレス
num_bytes = 2 #読み込むバイト数: 2バイト
#ADT7410から温度データを読み込む関数
def read_temperature(i2c, address: int, register: int, num_bytes: int):
data = i2c.readfrom_mem(address, register, num_bytes) #2bytesのデータをByte型で取得
# データの結合と13ビットにシフト※抽出時はdataは整数
temp_raw = (data[0] << 8 | data[1]) >> 3
# 温度計算
if temp_raw & 0x1000: # MSBが1の場合は負の温度
temp_raw -= 8192
temperature = temp_raw * 0.0625
return temperature
try:
while True:
temp = read_temperature(i2c=i2c_ADT7410,
address=address,
register=register,
num_bytes=num_bytes)
display.fill(0)
display.text(f'Temp: {temp}C', 0, 0, 1)
display.show()
time.sleep(1)
except Exception as e:
print(e)
[OUT]
7-2.実行結果
実行結果は下記の通りです。センサーの値をリアルタイムで確認することが出来ました。

参考資料
別添1:Raspberry Pi向け
別添2:Raspberry Pi Pico向け
あとがき
とりあえず簡単に文字が表示できるとめっちゃ助かる。ただ、中身を理解しないと動作原理が分からないので、どこかで学習はしたい。