Raspberry Pi に 128x64 OLED ディスプレイ (SSD1306)  を繋いで文字を表示する

# はじめに

Raspberry Pi に 128x64 OLED (SSD1306) を繋げて Python から文字を表示してみました。私が買ったのは DIYmall というメーカーの [これ](http://www.diymalls.com/OLED/0.96-Inch-I2C-IIC-OLED-Display-Module)です。**たぶん** [これ](https://amzn.to/3eF3Z1I) とかも同様の手順で動くと思います。

<iframe width="560" height="315" src="https://www.youtube.com/embed/ztMcqIAPnEw" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

# モジュールのインストールとサンプルプログラムの実行

結論と言うか概要から先に書くと以下の [Pillow](https://pillow.readthedocs.io/en/stable/) と言う Python のモジュールを使って絵を書いてから以下の Adafruit のモジュールで 128x64 OLED ディスプレイに書き出します。以下のモジュールは既にメンテナンスはされておらず非推奨となっていますが現時点で最新の Raspberry Pi OS 11 (bullseye) と Raspberry Pi 3 の組み合わせで動作することを確認しています。

https://github.com/adafruit/Adafruit_Python_SSD1306

まずは raspi-config を起動します。

```
$ sudo raspi-config
```

次に以下を選択して

```
3 Interface Options    Configure connections to peripherals
```
以下を選択します。

```
I5 I2C           Enable/disable automatic loading of I2C kernel module
```

"Would you like the ARM I2C interface to be enabled?" と聞かれるので "Yes" と答えてから raspi-config を終了させてください。

配線は[ここ](https://learn.adafruit.com/monochrome-oled-breakouts/python-wiring)らへんです。

```
Pi 3.3V <-> OLED VIN
Pi GND <-> OLED GND
Pi GPIO 2 (SDA) <-> OLED SDA
Pi GPIO 3 (SCL) <-> OLED SCL
```

次に上記 Adafruit の Python モジュール Adafruit_Python_SSD1306 をインストールします。

```
$ sudo python3 -m pip install --upgrade pip setuptools wheel
$ sudo pip3 install Adafruit-SSD1306
```

Adafruit のサンプルプログラムを動かしてみます。

```
$ git clone https://github.com/adafruit/Adafruit_Python_SSD1306
$ cd ./Adafruit_Python_SSD1306/examples/
$ pytnon3 ./stats.py
```

OLED ディスプレイに IP アドレスなどの情報が表示されます。デフォルトでは解像度が 128x32 となっているので stats.py の 46 行目から 50 行目を次のように変更し 128x64 表示とできます。

```
# 128x32 display with hardware I2C:
# disp = Adafruit_SSD1306.SSD1306_128_32(rst=RST)

# 128x64 display with hardware I2C:
disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST)
```

他のサンプルプログラムでは猫の画像が表示されたり (image.py) 文字がアニメーションで動いたり (animate.py) します。

# 単に 3 行の文字を表示する

日本語を扱うためにオープンソースの[源ノ角ゴシック](https://github.com/adobe-fonts/source-han-sans/blob/master/README-JP.md)フォントを Python プログラムと同じディレクトリにダウンロードします。

```
$ wget https://github.com/adobe-fonts/source-han-sans/raw/release/SubsetOTF/JP/SourceHanSansJP-Medium.otf
```

プログラムは以下のような感じのものを strings.py として書きました。

```:strings.py
import Adafruit_SSD1306

from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont

def initialize_display():
    # Raspberry Pi pin configuration is "NONE" as it isn't used
    RST = None

    # 128x64 display with hardware I2C
    disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST)

    # Initialize and clear display
    disp.begin()
    disp.clear()
    disp.display()

    return(disp)

def initialize_image(disp):
    # Create a blank image object for drawing by using Pillow with 1-bit color.
    width = disp.width
    height = disp.height
    image = Image.new('1', (width, height))

    draw = ImageDraw.Draw(image)

    return(image, draw)

def write_image(disp, draw, image):
    top = -2
    x = 0

    # Write three lines of text.
    draw.text((x, top + 0), 'こんにちは', font=font, fill=255)
    draw.text((x, top + 20), '１２３４５６７８９０',  font=font, fill=255)
    draw.text((x, top + 40), 'Hello, World!', font=font, fill=255)

    disp.image(image)
    disp.display()

if __name__ == "__main__":

    # Load ORT font
    font = ImageFont.truetype('SourceHanSansJP-Medium.otf', 16)

    disp = initialize_display()
    image, draw = initialize_image(disp)

    write_image(disp, draw, image)
```

次のように実行します。

```
$ python3 ./string.py
```

画面に

```
こんにちは
０１２３４５６８７８
Hello, World!
```

と表示されれば成功です。数字が8までしか表示されていないのでフォントサイズが16だと1行で9文字までのようです。

# 補足

上記 Phtnon のプログラムでは [Pillow](https://pillow.readthedocs.io/en/stable/) という画像処理ライブラリで初期化を行い (initialize_image) 128x64 のイメージを作ってから OLED ディスプレイに書き出しています (write_image)。

プログラムの中で GPIO の番号が指定されていませんが I2C という通信方式は。[ここ](https://www.lapis-tech.com/jp/common/miconlp/tips/2-17-i2c%E3%81%A8%E3%81%AF/)とか[ここ](https://bokunimo.net/raspi/i2c.html) とか　[ここ](https://www.mathworks.com/help/supportpkg/raspberrypiio/ref/enablei2c.html) などが参考になりましたが、Raspberry Pi では I2C_SDA は GPIO 2 で I2C_SCL は GPIO 3 となるようです。

I2C はマスター・スレーブ構成になっており Raspberry Pi (マスター) からスレーブ (OLED ディスプレイ) が見えているかは i2cdetect コマンドを使って確認ができます。 [3c](https://www.raspberrypi-spy.co.uk/2018/04/i2c-oled-display-module-with-raspberry-pi/) などのスレーブのアドレスが見えていれば正常です。

```
$ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
```

フォントは好きなものが使えます。英数字だと [dafont.com](https://www.dafont.com/bitmap.php) が元のサンプルコードで紹介されていました。

https://www.dafont.com/bitmap.php

日本語フォントも源ノ角ゴシックやIPAexフォントをはじめ様々な種類のオープンソースのフォントから選ぶことができます。ただ調べきれてませんが Pillow はバリアブルフォントなどには対応していないようで対応しているフォントを使用する必要があるようです。 ~~それにしても昔の Linux デスクトップ環境で日本語の TrueType フォントに苦労した時代とはえらい差です。~~

# おまけ

もっと良い関数の分け方 (あるいは Class の使い方) があるかもしれませんが、write_image を何度も呼べば表示されている文字を変えることができます。以下のプログラムでは「こん」「にち」「は」「世界」という文字を 1 秒毎に書き換えるプログラムです。stats.py でも良いですが Raspberry Pi で得たなんらかの情報を表示するのに使えそうです。

```:string2.py
import time

import Adafruit_SSD1306

from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont

def initialize_display():
    # Raspberry Pi pin configuration is "NONE" as it isn't used
    RST = None

    # 128x64 display with hardware I2C
    disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST)

    # Initialize and clear display
    disp.begin()
    disp.clear()
    disp.display()

    return(disp)

def initialize_image(disp):
    # Create blank image object for drawing by using Pillow with 1-bit color.
    width = disp.width
    height = disp.height
    image = Image.new('1', (width, height))

    draw = ImageDraw.Draw(image)

    return(image, draw)

def write_image(disp, draw, image, string):
    top = -2
    x = 0

    # Write three lines of text.
    draw.text((x, top + 0), string, font=font, fill=255)

    disp.image(image)
    disp.display()

if __name__ == "__main__":

    # Load ORT font
    font = ImageFont.truetype('SourceHanSansJP-Medium.otf', 48)

    disp = initialize_display()
    image, draw = initialize_image(disp)
    strings = ["こん", "にち", "は", "世界"]

    while True:
        try:
            for string in strings:
                # clear the display by writing a black rectangle
                draw.rectangle((0,0,disp.width,disp.height), outline=0, fill=0)
                write_image(disp, draw, image, string)
                time.sleep(1)
        except KeyboardInterrupt:
            break
```