OLEDJPライブラリ: 美咲フォントを使用したOLEDディスプレイでの日本語表示 (MicroPython)

以下のページで紹介したOLEDCSライブラリを使用すると、OLEDディスプレイに文字や図形を簡単に表示できるようになりました。

OLEDJPドライバ

しかしながら表示できる文字は英字や数字などで漢字や仮名などを表示できないという制限がありました。ここでは、OLEDCSを拡張(継承による拡張ではなく書き換えて)日本語表示ができるOLEDJPを作成したので、それを紹介します。OLEDJPは日本語フォントとして美咲フォント(の修正版)を使用しています。

OLEDJPドライバは、ESP32やRasPi PICO (RP2040)などで動作確認をしています。

なお、ESP8266ではヒープ領域が少ないため、美咲フォントをメモリに読み込むことができず、OLEDJPドライバは使用できませんでした。

美咲フォントの修正

OLEDJPドライバでは、日本語フォントとして以下のサイトで公開されている美咲フォントを使用しています。長年にわたり開発と自由に利用できるように配布していただいているたま吉さんに感謝します。

公開されているフォントとソフトウェアは、RasPi PICOで動作確認されているようですが、ESP32系では、ヒープ不足で利用できないので、使用するデータ表現を変更し、フォントのメモリ占有量を削減する修正を加えたバージョンを作成したので、OLEDJPではそれを使用します。

OLEDJPライブラリ

ライブラリのソースをoledjp.pyに示します。開発ボードのlibフォルダの中に入れてください。

"""
OLED_I2C OLED Display Driver (ssd1306, ssh1106)
for MicroPython
Version 1.0

美咲フォントを使用した日本語表示機能装備
https://github.com/Tamakichi/pico_MicroPython_misakifont

Copyright 2023 K.Kakizaki

"""

import ssd1306
from misakifont import MisakiFont
from micropython import const

class OLEDJP_I2C(ssd1306.SSD1306_I2C):

    SSD1306 = const(0)
    SSH1106 = const(1)

    ASCII = const(0)
    MISAKI = const(1)
    MIX = const(2)

    SET_LOW_COL = const(0x00)
    SET_HIGH_COL = const(0x10)
    SET_PAGE_ADDR = const(0xb0)

    def __init__(self, width, height, i2c, chip=SSD1306, addr=0x3C, external_vcc=False):
        self._cx = 0
        self._cy = 0
        self._chip = chip
        self._font = ASCII
        self._fsize = 1
        self._mf = None
        super().__init__(width, height, i2c, addr, external_vcc)

    def print(self, text):
        txt = str(text)
        while (txt.find('\n') > 0):
            idx = txt.index('\n')
            ctxt = txt[0:idx]
            txt = txt[idx+1:]
            self.println(ctxt)
        l = len(txt)
        while (l + self._cx > self.width // (8 * self._fsize)):
            cl = self.width // (8 * self._fsize) - self._cx
            l = l - cl
            ctxt = txt[0:cl]
            txt = txt[cl:]
            self._textout(ctxt)
            self._scroll_check()
        self._textout(txt)
        self.show()
        
    def println(self, text=''):
        self.print(text)
        self._scroll_check()
        self.show()

    def text(self, txt, x, y, color=1):
        if self._font == ASCII and self._fsize == 1:
            super().text(txt, x, y, color)
        elif self._font == MISAKI or self._fsize == 2:
            for c in txt:
                fnt = self._mf.font(ord(c))
                self._draw_font(fnt, x, y, color, self._fsize)
                x += (8 * self._fsize)
        else:
            for c in txt:
                if ord(c) < 0x80:
                    super().text(c, x, y, color)
                else:
                    fnt = self._mf.font(ord(c))
                    self._draw_font(fnt, x, y, color, self._fsize)
                x += (8 * self._fsize)

    def _textout(self, txt, color=1):
        if self._font == ASCII and self._fsize == 1:
            super().text(txt, self._cx*8*self._fsize, self._cy*8*self._fsize, color)
            self._cx += len(txt)
        elif self._font == MISAKI or self._fsize == 2:
            for c in txt:
                fnt = self._mf.font(ord(c))
                self._draw_font(fnt, self._cx*8*self._fsize, self._cy*8*self._fsize, color, self._fsize)
                self._cx += 1
        else:
            for c in txt:
                if ord(c) < 0x80:
                    super().text(c, self._cx*8*self._fsize, self._cy*8*self._fsize, color)
                else:
                    fnt = self._mf.font(ord(c))
                    self._draw_font(fnt, self._cx*8*self._fsize, self._cy*8*self._fsize, color, self._fsize)
                self._cx += 1
 
    def _draw_font(self, font, x, y, color, size):
        for row in range(0, 7):
            for col in range(0, 7):
                if (0x80 >> col) & font[row]:
                    self.fill_rect(x + col * size, y + row * size, size, size, color)

    def _scroll_check(self):
        self._cx = 0
        self._cy += 1
        if (self._cy * 8*self._fsize >= self.height):
            self._cy -= 1
            self.scroll(0, -8*self._fsize)
            self.fill_rect(0, self.height-8*self._fsize, self.width-1, self.height-1, 0)

    def setFont(self, f):
        self._font = f
        if f != ASCII and self._mf == None:
            self._mf = MisakiFont()

    def setFontSize(self, fs):
        if fs != 1 and fs != 2:
            return self._fsize
        self._fsize = fs
        self.clear()
        if fs > 1 and self._mf == None:
            self._mf = MisakiFont()

    def setCursor(self, x, y):
        self._cx = x
        self._cy = y
    
    def getCursor(self):
        return (self._cx, self._cy)

    def clear(self):
        self.fill(0)
        self.setCursor(0,0)
        self.show()

    def show(self):
        if self._chip == SSD1306:
            super().show()
        else:
            for page in range(0, self.pages):
                self.write_cmd(SET_PAGE_ADDR | page)
                self.write_cmd(SET_LOW_COL | 2)
                self.write_cmd(SET_HIGH_COL | 0)
                self.write_data(self.buffer[page * 128:(page + 1) * 128])

OLEDJPの機能概要

oledjpの追加メソッドを紹介します。oledjpはssd1306を継承して作成しているので、従来のssd1306の機能はグラフィックスを含めて同様に使用できます。また、ここに示す追加機能は、oledcsと同じ機能です。oledcsを参照せよというのもたらいまわしのようなので、重複しますがここにでも紹介します。

  • print(obj) 文字列だけでなく、数値、オブジェクトなどstr()関数で文字列化可能なものは一通り出力できます。文字列の中に改行文字’\n’があればそこで改行します。
  • println(obj) print(obj)の最後に改行機能を加えたものです。引数が指定されていなければ改行のみを行います。
  • setCursor(x,y) 文字カーソルを指定した位置に移動させます。xは横、yは縦軸です
  • getCursor() 文字カーソルの現在位置を返します。
  • clear() 画面を消去し文字カーソルを(0, 0)に設定します。

なお、上記のメソッドでは、画面の変更を表示に反映させるためのshow()メソッドを使用する必要はありません。

このほかに、日本語のフォントを有効にするメソッドと、フォントの大きさを指定するメソッドが追加されています。

OLEDJPの利用例

oledjpの利用例を示します。従来は、ssd1306をインポートしSSD1306_I2Cをインスタンス化していましたが、そこをoledjpとOLEDJP_I2Cに置き換えると、これまでのプログラムと同様に動きます。

従来と同じ機能だけだと価値がありませんが、いちいち表示位置を指定せずにprint(), println()が使えるところが強みですね。また、OLEDCSの機能に加え、日本語表示ができるところが素晴らしいですね。

長い文字列でも行の右端で改行して出力されること、改行文字があればそこで改行されること、最下行の出力では、必要に応じて画面がスクロールすることなどが確認できます。

from machine import Pin, I2C
from oledjp import OLEDJP_I2C
import time

i2c = I2C(0) # STM32は3
# i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000) # ESP32-S3, C3
# i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=400000) # ESP32
# i2c = I2C(0, scl=Pin(13), sda=Pin(12), freq=400000) # RP2040

oled = OLEDJP_I2C(128, 64, i2c)

str1 = "電子工作,OLEDディスプレイ\n気温,しつ度,明るさ,加速度"
str2 = "圧電スピーカー,スイッチ\nカラーLED(WS2812)\nMicroPython\nマイクロファン"

# 美咲フォントのみを使用
oled.setFont(OLEDJP_I2C.MISAKI)
oled.println(str1)
oled.print(str2)

time.sleep(3)

# 英数記号文字はFrameBufferのフォントで、それ以外は美咲フォント
oled.clear() # fill(0) による消去では、カーソルが初期化されない
oled.setFont(OLEDJP_I2C.MIX)
oled.println(str1)
oled.print(str2)

time.sleep(3)

# フォントの大きさを2倍に
oled.setFontSize(2)
oled.print(str1)

SSH1106への対応

インスタンスの生成時にコントローラチップを特に指定しない場合には、SSD1306を使用した0.96インチのOLEDディスプレイを対象に処理を行いますが、以下の例のように、コントローラチップとしてSSH1106を指定すると、1.3インチディスプレイを操作することができます。

# oled = OLEDJP_I2C(128, 64, i2c)

# コントローラチップとしてSSH1106を指定
oled = OLEDJP_I2C(128, 64, i2c, chip=OLEDJP_I2C.SSH1106)


関連記事