「Raspberry Pi Pico」で有機ELディスプレイ(OLED)「SSD1306」を使う方法を紹介します。
以前「ArduinoIDE」を使用した「C言語」での使用方法を紹介しましたが、今回は開発環境「Tonny」を使用した「MicroPython」での使用方法を紹介します。
図形の描画について、今回使用したライブラリでは円と三角の標準関数がなかったため、自作関数で描画してます。
「Raspberry Pi Pico」の使い方や開発環境「Tonny」のインストール方法、初期設定については以下のリンクで詳しく紹介しています。


「ArduinoIDE」を使用した「C言語」でのSSD1306の使用方法は以下のリンクで詳しく紹介しています。

1.有機ELディスプレイSSD1306について
SSD1306は有機ELディスプレイ(OLED)と呼ばれる表示器です。
画面サイズ違いで2種類(128×64、128×32)あり、比較的安価(どちらも500~700円程)で購入できます。
I2C通信とSPI通信に対応したものがありますが、I2C通信の方が通信線が2本で電源線と合わせても全部で4本のため扱いやすいので、今回はI2C通信のものを使用します。
外観は下画像のようになります。4つの端子(GND、電源3.3~5V、SCL、SDA)があります。
付属品として4ピンのピンヘッダが付属しているのではんだ付けして使用できます。

上画像の左が「128×64」で右が「128×32」です。
どちらも使い方は一緒で画面の高さ(Height)の設定(64か32)を変えるだけです。

上画像は基板の裏側で部品が剥き出しです。
金属に触れると壊れる可能性があるのでビニール絶縁テープ等を貼っておくのがおすすめです。
SSD1306については以下のリンクで詳しく紹介しています。

2.配線方法
「Raspberry Pi Pico」の端子配列は以下のようになります。(公式サイトより抜粋)

配線方法はブレッドボードを使用して下画像のように行いました。

「Raspberry Pi Pico」本体の1番端子とブレッドボードの1番ピンを合わせておくと端子番号の確認がしやすいです。
「SSD1306」と「Raspberry Pi Pico」は以下表のように接続します。
SSD1306 | Raspberry Pi Pico |
---|---|
VCC | 3V3(OUT) |
GND | GND |
SDA | GP16 |
SCL | GP17 |
「タクトスイッチ青」は「Raspberry Pi Pico」の「GP11(15)ーGND(13)」間に接続、
「タクトスイッチ赤」は「Raspberry Pi Pico」の「GP15(20)ーGND(18)」間に接続します。
今回使用したブレッドボードはサンハヤト製の6連結ピンのものです。
安価なものはありますが 5連結ピンのものが多く、ピンが少ないため今回のような使い方はできません。少し割高ですが6連結ピンの方が自由度があっておすすめです。
タクトスイッチは端子が4つのものが多く2つづつペアで内部で繋がっているため、ボタンを押した時にどことどこが繋がるのか確認してから使用しましょう。色違いのものがあるとわかりやすくて便利です。
SSD1306は今回は画面サイズ「128×64」のものを使用しました。
個人的には表示色は白色が一番見やすくて綺麗と思います。
「Raspberry Pi Pico」は単体で買うと端子がついていません。
ピンヘッダーを別で買ってはんだ付けする必要がありますが、ピンヘッダーは細ピンヘッダーが差しやすくて使いやすいです。
ハンダ付が不安な方はハンダ付け済みのものを購入しましょう。
3.サンプル画面の紹介
基本的な表示が確認できるように下画像のようなサンプル画面を準備しました。

①タイトル表示
②ボタンの動作で内容が変化
(全画面の色を反転表示)
③ボタンを押した回数を表示
④指定した座標から平行線
⑤始点終点を座標で指定した線
⑥指定した座標から垂線
⑦円と塗り潰し円
⑧三角と塗り潰し三角
⑨四角と塗り潰し四角
動作紹介
青と赤のボタンを押した時の動作は下画像のようになります。

「青ボタン」を押すと画面の「BTN = OFF」が「BTN = ON」に変わって全画面が反転します。
基板上のLEDも点灯させてます。

「赤ボタン」を押すと画面の「CNT=000」の数値がボタンを押すごとに1づつ増えていくカウンタとして動作します。
4.サンプルプログラム(コピペ)
サンプル画面を表示するプログラムは以下のようになります。
「Thonny」に「コピペ」して実行してください。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでコピーできます。
from machine import Pin, I2C # 入出力モジュール
import ssd1306 # 液晶表示器用ライブラリ
import math # 数学関数
LED = machine.Pin(25, machine.Pin.OUT) # GP25をLEDとして出力端子に設定
BTN1 = Pin(11, Pin.IN, Pin.PULL_UP) # GP11をBTN1として入力端子(プルアップ)に設定
BTN2 = Pin(15, Pin.IN, Pin.PULL_UP) # GP15をBTN2として入力端子(プルアップ)に設定
state = False # BTN1ボタン状態格納用
cnt = 0 # BTN2のON回数カウント用
# I2C設定 (I2C識別ID 0or1, SDA, SCL)
i2c = I2C(0, sda=Pin(16), scl=Pin(17) )
# 使用するSSD1306のアドレス取得表示(通常は0x3C)
addr = i2c.scan()
print( "OLED I2C Address :" + hex(addr[0]) )
# ディスプレイ設定(幅, 高さ, 通信仕様)
display = ssd1306.SSD1306_I2C(128, 64, i2c)
# 円描画関数(三角関数を使用)
def circle(x, y, l, color):
for r in range(360):
display.pixel(int(x + l * math.cos(math.radians(r))), int(y - l * math.sin(math.radians(r))), color)
# 塗り潰し円描画関数(三角関数を使用)
def fill_circle(x, y, l, color):
for r in range(360):
display.line(x, y, int(x + l * math.cos(math.radians(r))), int(y - l * math.sin(math.radians(r))), color)
# 三角描画関数(線で描画)
def triangle(x1, y1, x2, y2, x3, y3, color):
display.line(x1, y1, x2, y2, color) # 指定座標から指定座標までの線
display.line(x2, y2, x3, y3, color) # 指定座標から指定座標までの線
display.line(x1, y1, x3, y3, color) # 指定座標から指定座標までの線
# 塗り潰し三角(正三角形)描画関数(三角関数を使用)
def fill_triangle(x, y, l, color):
for i in range(int(l/2 * math.tan(math.radians(60)))):
h = l/2 * math.tan(math.radians(60)) # 高さ
display.hline(x + math.ceil((i*l/h)/2), y - i, l - math.ceil(i*l/h), color) # 指定座標から塗り潰し三角
# 以下繰り返し
while True:
# 液晶画面表示内容設定
display.fill(0) # 表示内容消去
display.text('SSD1306 TEST', 17, 2, True) # ('内容', x, y, 色) テキスト表示
display.hline(0, 12, 128, True) # (x, y, 長さ, 色) 指定座標から横線
display.vline(64, 12, 20, True) # (x, y, 長さ, 色) 指定座標から 縦線
display.line(0, 32, 128, 32, True) # (x1, y1, x2, y2, 色) 指定座標1から指定座標2までの線
display.rect(88, 41, 18, 18, True) # (x, y, 幅, 高さ, 色)指定座標に四角
display.fill_rect(109, 41, 18, 18, True) # (x, y, 幅, 高さ, 色)指定座標に 塗り潰し四角
# 以下自作関数で描画
circle(9, 50, 9, True) # (x, y, 半径, 色) 円描画関数呼び出し
fill_circle(31, 50, 9, True) # (x, y, 半径, 色) 塗り潰し円を描画関数呼び出し
triangle(42, 58, 52, 42, 62, 58, True) # (x1, y1, x1, y1, x1, y1, 色) 三角描画関数呼び出し
fill_triangle(65, 58, 20, True) # (x, y, 底辺長さ, 色) 塗り潰し三角(※正三角形)
# ボタン1処理(ON/OFF)
if BTN1.value() == 0: # BTN1がONなら
display.text('BTN=ON ', 2, 20, True) # テキスト表示
display.invert(True) # 表示色反転
LED.value(1) # LEDを点灯
else: # BTN1がONでなければ
display.text('BTN=OFF', 2, 20, True) # テキスト表示
display.invert(False) # 表示色戻る
LED.value(0) # LEDを消灯
# ボタン2処理(カウンタ)
if BTN2.value() == 0 and state == False: # BTN2がONかつボタン状態Falseなら
state = True # ボタン状態True
cnt = cnt + 1 # カウント+1
if BTN2.value() == 1: # BTN2がOFFなら
state = False # ボタン状態False
display.text('CNT={:03}'.format(cnt), 68, 20, True) # カウント数表示(3桁0埋め)
# 設定した内容を表示
display.show()
5.必要なライブラリのインストール方法
インストールするパッケージ(ライブラリ)「micropython-ssd1306」のインストール方法を紹介します。
まずは「Thonny」を起動し、下画像のように[ツール]をクリックします。

表示されるリストの中から、下画像のように[パッケージを管理]を選択します。

下画像のようなウインドウが表示されるのでインストールしたいパッケージ(ライブラリ)の一部でも良いので入力して「PyPlを検索」をクリックします。

下画像のように「検索結果」に候補が複数表示されます。
今回は「MicroPython」環境で使用する「SSD1306」用ライブラリをインストールしたいため「micropython-ssd1306」をクリックします。

ここでWindowsの場合に以下のようにエラーが発生する場合があります。
これはSSL認証に関するエラーで、以下のリンクで対処方法を紹介しています。


エラーが発生しない場合は、下画像のような画面が表示されるので「インストール」をクリックします。

インストールが無事完了すると下画像のような画面になり、ウインドウ左の<インストール>に追加したパッケージ(ライブラリ)がリストで表示されます。

<インストール>リストの中の項目を選択すると、下画像のように詳細情報が確認できます。
GitHubへのリンクや詳細ページへのリンクが表示されるため、使用方法等を確認することができます。

これでパッケージ(ライブラリ)の追加は完了しました。
「閉じる」ボタンを押して終了です。
6.基本的な表示方法の紹介
文字や図形等の基本的な表示は以下のように表示位置をx, y座標で指定して行います。
# 表示内容消去
display.fill(0) # (0で黒塗り、1で白塗り)
# 文字の表示(色指定のTrueは白、Falseで黒)
display.text('表示内容', x, y, 色) # 指定座標にテキスト表示
# 図形の表示(円や三角は無し、今回は自作してます。色指定のTrueは白、Falseで黒)
display.hline(x, y, 長さ, 色) # 指定座標から横線
display.vline(x, y, 長さ, 色) # 指定座標から縦線
display.line(x1, y1, x2, y2, 色) # 指定座標1から指定座標2までの線
display.rect(x, y, 幅, 高さ, 色) # 指定座標に四角
display.fill_rect(x, y, 幅, 高さ, 色) # 指定座標に 塗り潰し四角
display.invert(色指定) # (Trueで反転、Falseで戻る) 表示色反転
display.scroll(x, y) # (x, y) 指定座標に全画面移動
以下の公式サイトでも基本的な使用方法や、SPI通信仕様の使い方も紹介されています。
7.まとめ
「Raspberry Pi Pico」で「MicroPython」を使用した有機ELディスプレイ(OLED)SSD1306の使い方を紹介しました。
「MicroPython」は開発環境「Tonny」で手軽に使えて便利ですが、今回使用したライブラリでは文字サイズの指定ができなかったり、円を描くコマンドが無かったりしたので、少し凝った画面を作るなら「Circuit Python」の方がいいかもしれません。
「Circuit Python」を使用したSSD1306の使用方法も以下のリンクで紹介しています。

コメント
こんにちは
(ご無沙汰致しました)
Pico 2 W が発売されましたので、入手して、また触り出しました。
こちらのページで
====
4.サンプルプログラム(コピペ)
のscriptで
# I2C設定 (I2C識別ID 0or1, SDA, SCL)
i2c = I2C(0, sda=Pin(16), scl=Pin(17) ) において
sdaの前の「0」を省くと、エラーが出ます
この「0,」は必須の項目ですね?
==== 例えば
from machine import Pin, I2C
import ssd1306
# using default address 0x3C
i2c = I2C(0,sda=Pin(16), scl=Pin(17))
display = ssd1306.SSD1306_I2C(128, 64, i2c)
display.fill(0)
# 使用するSSD1306のアドレス取得表示(通常は0x3C)
addr = i2c.scan()
print( “OLED I2C Address :” + hex(addr[0]) )
=== として
script実行すると
Thonnyシェルに
OLED I2C Address :0x3c
と表示されます.
案内のある
https://docs.micropython.org/en/latest/esp8266/tutorial/ssd1306.html
では、ここの「0,」は、無かったので、省くと
TypeError: ‘id’ argument required
TypeError: ‘id’ 引数が必要です
のエラーが出ていました。
1306を繋いだだけでサンプル実行をしていました。
そこで
こちらのサンプルscriptを参照して、
i2c = I2C(0, sda=Pin(16), scl=Pin(17) ) の様に 「0,」を入れると
エラー無く実行されます。
問い
「0」の必要な意味、この行(ここでの)での「0」の意義を
お教え下さい。
暫く、触って居なかったので、すっかり忘れて仕舞いました。
失礼します
実行したscriptは
====
from machine import Pin, I2C
import ssd1306
# using default address 0x3C
i2c = I2C(0,sda=Pin(16), scl=Pin(17)) #0は必要
display = ssd1306.SSD1306_I2C(128, 64, i2c)
display.fill(0)
display.fill_rect(0, 0, 32, 32, 1)
display.fill_rect(2, 2, 28, 28, 0)
display.vline(9, 8, 22, 1)
display.vline(16, 2, 22, 1)
display.vline(23, 8, 22, 1)
display.fill_rect(26, 24, 2, 4, 1)
display.text(‘MicroPython’, 40, 0, 1)
display.text(‘SSD1306’, 40, 12, 1)
display.text(‘OLED 128×64’, 40, 24, 1)
display.rotate(False) #表示の天地替え
display.show()
=====としました。1306に表示されます。
おひさしぶりです^^
Pico2W 出ましたね、私も買いましたがあまり変わってないのでブログのネタ的には微妙な感じです。
最近はBluetoothで遊んでます。
i2c = I2C(0, sda=Pin(16), scl=Pin(17) ) の「0」についてですが、
これは、Picoには2系統のI2C(I2C0、I2C1)があるので、どちらを使うかを指定するためです。
例えば「I2C1」を使用する場合は対応する端子番号も変更して以下のようにします。
i2c = I2C(1, sda=Pin(14), scl=Pin(15) )
どの端子が使用できるかはPicoの端子配列で確認しましょう。
Picoは2系統ありますが、I2C通信が1つしかないデバイスなら省略しても問題なく動作すると思うので、
micropythonの例では省略されてるんでしょうね。
ありがとう ございます。
私も、検索していて、今、発見しました。
今コメントでお話しの通りですね。
https://www.tomshardware.com/how-to/oled-display-raspberry-pi-pico
のページの記述で、それと 判断出来ました。
ピンアサインの図で確認しました。
0_ch か 1_ch i2c1 と i2c0 の違いですね、16/17 の組みなら「0」ですね。
⬆このページも面白そうな記事です。
また、別のページで
ESP32で3DCUBEで立方体が(実際はおそらく四角形)回転している様に
見えるアニメがscriptがあったので、
Pico2W にそっくり同じで書き込んだら・・・・
スピードは、遅いですが同様にアニメで動きます。
やはり、動きが有ると,断然カッコ良く、面白くなります。
以前、サーキットpythonのサンプルscriptで
アニメがあったので、走らせたら,全く動きが遅くて
『なーんだ、つまらない』って感じがした記憶が有ります。
Pico2で少し、cpu回転が上がった様です。
こちらのページにあります
https://qiita.com/inachi/items/ceb3e8e12022a7cbdf7b
====
# 3D CUBE MicroPython version with ESP32 and ssd1306 OLED
from machine import Pin, I2C
from micropython import const
from time import sleep_ms
from math import sin, cos
from ssd1306 import SSD1306_I2C
X = const(64)
Y = const(32)
f = [[0.0 for _ in range(3)] for _ in range(8)]
cube = ((-20,-20, 20), (20,-20, 20), (20,20, 20), (-20,20, 20),
(-20,-20,-20), (20,-20,-20), (20,20,-20), (-20,20,-20))
i2c = I2C(0,scl=Pin(17), sda=Pin(16))
oled = SSD1306_I2C(X * 2, Y * 2, i2c)
while True:
for angle in range(0, 361, 3): # 0 to 360 deg 3step
for i in range(8):
r = angle * 0.0174532 # 1 degree
x1 = cube[i][2] * sin(r) + cube[i][0] * cos(r) # rotate Y
ya = cube[i][1]
z1 = cube[i][2] * cos(r) – cube[i][0] * sin(r)
x2 = x1
y2 = ya * cos(r) – z1 * sin(r) # rotate X
z2 = ya * sin(r) + z1 * cos(r)
x3 = x2 * cos(r) – y2 * sin(r) # rotate Z
y3 = x2 * sin(r) + y2 * cos(r)
z3 = z2
x3 = x3 + X
y3 = y3 + Y
f[i][0] = x3 # store new values
f[i][1] = y3
f[i][2] = z3
oled.fill(0) # clear
oled.line(int(f[0][0]), int(f[0][1]), int(f[1][0]), int(f[1][1]), 1)
oled.line(int(f[1][0]), int(f[1][1]), int(f[2][0]), int(f[2][1]), 1)
oled.line(int(f[2][0]), int(f[2][1]), int(f[3][0]), int(f[3][1]), 1)
oled.line(int(f[3][0]), int(f[3][1]), int(f[0][0]), int(f[0][1]), 1)
oled.line(int(f[4][0]), int(f[4][1]), int(f[5][0]), int(f[5][1]), 1)
oled.line(int(f[5][0]), int(f[5][1]), int(f[6][0]), int(f[6][1]), 1)
oled.line(int(f[6][0]), int(f[6][1]), int(f[7][0]), int(f[7][1]), 1)
oled.line(int(f[7][0]), int(f[7][1]), int(f[4][0]), int(f[4][1]), 1)
oled.line(int(f[0][0]), int(f[0][1]), int(f[4][0]), int(f[4][1]), 1)
oled.line(int(f[1][0]), int(f[1][1]), int(f[5][0]), int(f[5][1]), 1)
oled.line(int(f[2][0]), int(f[2][1]), int(f[6][0]), int(f[6][1]), 1)
oled.line(int(f[3][0]), int(f[3][1]), int(f[7][0]), int(f[7][1]), 1)
oled.line(int(f[1][0]), int(f[1][1]), int(f[3][0]), int(f[3][1]), 1) # cross
oled.line(int(f[0][0]), int(f[0][1]), int(f[2][0]), int(f[2][1]), 1) # cross
oled.text(‘3D CUBE’, 0, 0)
oled.show() # display
sleep_ms(1)
======
勿論、scl とsda のpinは替えて有ります。
====
======
ありがとうございます。
また、お教え願います :)
解決したようでよかったです^^
色々挑戦されているようで感心します。
また何かありましたらどうぞ。