LEDMATRIXJPライブラリ: 美咲フォントを使用したNEOPIXEL マトリックスでの日本語表示 (MICROPYTHON)

以下のページで紹介したLEDMATRIXライブラリを使用すると、NeoPixelマトリックスに文字や図形を簡単に表示できるようになりました。

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

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

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

美咲フォントの修正

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

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

LEDMATRIXJPのライブラリコード

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

"""
LEDMATRIXJP: WS2812B (NeoPixel) Matrix Display driver
for MicroPython
Version 1.0

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

Copyright 2023 K.Kakizaki

"""

from machine import Pin
import framebuf
from micropython import const
from neopixel import NeoPixel
from misakifont import MisakiFont
import time

def LEDColor(r, g, b) :
    return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)

class LEDMATRIXJP(framebuf.FrameBuffer):

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

    RED = LEDColor(255, 0, 0)
    GREEN = LEDColor(0, 255, 0)
    BLUE = LEDColor(0, 0, 255)
    CYAN = LEDColor(0, 255, 255)
    YELLOW = LEDColor(255, 255, 0)
    PURPLE = LEDColor(255, 0, 255)
    WHITE = LEDColor(255, 255, 255)
    BLACK = LEDColor(0, 0, 0)

    SIMPLE=const(0x01)
    ZIGZAG=const(0x02)
    TOP=const(0x10)
    BOTTOM=const(0x20)
    RIGHT=const(0x40)
    LEFT=const(0x50)

    def __init__(self, pin, width, height, skip=0, link=SIMPLE):
        self._pin = Pin(pin, Pin.OUT)
        self._width = width
        self._height = height
        self._skip = skip
        self._link = link
        self._np = NeoPixel(self._pin, width * height + skip)
        self._brt = 10
        self.buffer = bytearray(self._height * self._width * 2)
        super().__init__(self.buffer, self._width, self._height, framebuf.RGB565)
        self._font = ASCII
        self._mf = None
        self.fill(0)
        for i in range(0, skip):
            self._np[i] = (0,0,0)
        self.show()

    def color(self, r, g, b):
        return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)

    def setPixelColor(self, n, r, g, b):
        self._np[n] = (r, g, b)

    def setBrightness(self, brt):
        if (brt > 255):
            brt = 255
        if (brt < 0):
            brt = 0
        self._brt = brt
        self.show()

    def getBrightness(self):
        return self._brt

    def text(self, txt, x, y, color):
        if self._font == ASCII:
            super().text(txt, x, y, color)
        elif self._font == MISAKI:
            for c in txt:
                fnt = self._mf.font(ord(c))
                self._draw_font(fnt, x, y, color, 1)
                x += 8
        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, 1)
                x += 8
 
    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 setFont(self, f):
        self._font = f
        if f != ASCII and self._mf == None:
            self._mf = MisakiFont()

    def showScrollText(self, text, color, bgcolor=0, y=0, dir=0, ms=50):
        text = str(text)
        for x in range(self._width,-8*len(text)-1,-1):
            self.fill(bgcolor)
            self.text(text,x,y,color)
            self.show()
            time.sleep_ms(ms)

    def setScrollText(self, text, color, bgcolor=0, y=0, dir=0, ms=50):
        self._text = str(text)
        self._color = color
        self._bgcolor = bgcolor
        self._sty = y
        self._stx = self._width
        self._stxe = -8*len(self._text)-1
        self._dir = dir
        self._ms = ms

    def runScrollText(self):
        if self._stx > self._stxe:
            self.fill(self._bgcolor)
            self.text(self._text, self._stx, self._sty, self._color)
            self.show()
            self._stx = self._stx - 1
            return True
        else:
            return False

    def size(self):
        return (self._width, self._height)

    def show(self):
        for x in range(0, self._width):
            for y in range(0, self._height):
                idx = x + y * self._width
                v1 = self.buffer[idx*2]
                v0 = self.buffer[idx*2+1]
                vr = (v0 >> 3) << 3
                vg = (((v0 & 0x7) << 3) | (v1 >> 5)) << 2
                vb = (v1 & 0x1F) << 3
                rate = self._brt/255
                if self._link == SIMPLE:
                    lidx = x + y * self._width
                else:
                    lidx = x * self._height
                    if x % 2 == 0:
                        lidx += y
                    else:
                        lidx += (self._height - y - 1)
                self._np[lidx + self._skip] = (int(vr*rate), int(vg*rate), int(vb*rate))
        self._np.write()

使用法と使用上の注意

日本語を表示するための機能以外は基本的に、LEDMATRIXJPライブラリは、LEDMATRIXライブラリと同じ使用法と使用上の注意となります。LEDMATRIXライブラリの記事も併せてごらんください。

デモプログラム

ESP32-PIXELやESP32-KEY-R2, ESP32-SLIM等を対象として作成したLEDMATRIXのデモプログラムです。

LEDMATRIXコンストラクタの第一引数に、LEDの制御用に割り当てたピン番号を指定します。

from ledmatrixjp import LEDMATRIXJP
import time

color = [LEDMATRIXJP.RED, LEDMATRIXJP.GREEN, LEDMATRIXJP.BLUE, LEDMATRIXJP.CYAN, LEDMATRIXJP.YELLOW, LEDMATRIXJP.PURPLE, LEDMATRIXJP.WHITE,    ]
# brt = [0,1,2,3,5,10,20,30,50,100]
brt = [0,1,2,3,5,10,20,30]

# lmx = LEDMATRIXJP(15,18,8) # ESP32-PIXEL
# lmx = LEDMATRIXJP(2,8,8)
# lmx = LEDMATRIXJP(23,16,16,link=LEDMATRIXJP.ZIGZAG) # LEDパネル 16x16
lmx = LEDMATRIXJP(23,32,8,link=LEDMATRIXJP.ZIGZAG) # LEDパネル 32x8
# lmx = LEDMATRIXJP(23,32,16,link=LEDMATRIXJP.ZIGZAG) # LEDパネル 16x16を2枚

lmx.setFont(LEDMATRIXJP.MIX)

if lmx.size()[0] <= 8:
    scrms = 100
elif lmx.size()[0] <= 20:
    scrms = 50
else:
    scrms = 20

lmx.setBrightness(0)
lmx.fill(LEDMATRIXJP.GREEN)
for n in brt:
    lmx.setBrightness(n)
    time.sleep_ms(200)
time.sleep_ms(1000)

lmx.fill(0)
lmx.setBrightness(10)
time.sleep_ms(1000)

if lmx.size()[1] == 18:
    str = 'ESP32-PIXEL LEDマトリックス'
else:
    str = '日本語表示 LEDマトリックス'
    
lmx.showScrollText(str, LEDMATRIXJP.YELLOW, ms=scrms)
time.sleep_ms(1000)

for y in range(lmx.size()[1]+1,0,-1):
    lmx.fill_rect(0, 0, y*2, y, color[y%len(color)])
    lmx.show()
    time.sleep_ms(100)
time.sleep_ms(1000)

lmx.showScrollText('気温、しつ度、気圧、加速度、明るさ', LEDMATRIXJP.CYAN, ms=scrms)
time.sleep_ms(1000)

lmx.fill(0)
for y in range(lmx.size()[1],-1,-1):
    lmx.line(0, y, lmx.size()[0]-1, 0, color[y%len(color)])
    lmx.show()
    time.sleep_ms(100)
time.sleep_ms(1000)

lmx.showScrollText('MicroFan マイクロファン', LEDMATRIXJP.RED, ms=scrms)
time.sleep_ms(1000)

for r in range(1,lmx.size()[0]//2):
    lmx.ellipse(lmx.size()[0]//2,lmx.size()[1]//2,r,r,LEDMATRIXJP.BLUE,True)
    lmx.show()
    time.sleep_ms(300)
time.sleep_ms(1000)

lmx.fill(LEDMATRIXJP.BLACK)
str = '> <'
x = lmx.size()[0]//2 - (8*len(str))//2
y = (lmx.size()[1] - 8) // 2
if lmx.size()[0] <= 8:
    lmx.text('#',0,0,LEDMATRIXJP.GREEN)
else:
    lmx.text(str,x,y,LEDMATRIXJP.CYAN)
    lmx.text('#',x+8,y,LEDMATRIXJP.GREEN)
for n in [0,1,1,1,2,2,3,5,7,10]:
    lmx.setBrightness(n)
    lmx.show()
    time.sleep_ms(300)