街中でよく見かけるLEDパネルが個人でも1枚単位で入手できるようになって数年経ち、日に日に価格も安くなっていますが、タイトルのような最も単純なことをお手軽に(安価に)やろうとしたときに予想外にまとまった情報が少なかった(特に日本語)のと、Microsoft Copilotとかが例によって古い内容を答えてきたので、またもや【令和7年最新版】としてここに一旦まとめておきます。お手軽なのでCとかではなくCircuitPythonを使います。
というわけで高いRaspberry Piなどを使わないのでパネル込み時価5000円で遊べます。
表示するのは64x32ピクセルのbmp画像ファイルです1。HUB75
なので基本的にダイナミック点灯というか、古のCRTよろしく行ごとに走査しながら点灯させているので冒頭のデジカメ写真ではよくわからないかもしれませんが、直視ではこれよりはもう少し階調のわかる表示ができます。アイコン画像なども十分表示できるでしょう。
Raspberry Pi Pico
純正品なら2でもWでもどれでも大丈夫だと思います。未確認ですがCircuitPythonが入れられるものなら互換品でも大丈夫だと思います。モデルに合ったCircuitPythonを入れておいてください。
LEDパネルと接続方法


最近Amazonなどでもよく見かけるHUB75
インタフェースのヤツです。自分は64x32ドット3mmピッチのものを3500円ぐらいで入手しました2。ちょっと修正すれば64x64のやつでもいけるはず。
以下の通りRaspberry Pi Picoと接続します。電源も念のため別系統で3Aぐらいのものを用意しましたが、実際の消費電流は1A程度でした。
接続については他に情報がたくさんあるのでここではこれ以上詳細の説明はしません。
rgb_pins=[
board.GP2, # R1
board.GP3, # G1
board.GP6, # B1
board.GP7, # R2
board.GP8, # G2
board.GP9, # B2
],
addr_pins=[board.GP10, board.GP16, board.GP18, board.GP20], # A # B # C # D
clock_pin=board.GP11,
latch_pin=board.GP12, # (STB)
output_enable_pin=board.GP13,
画像ファイル表示のミニマムなコード
必要なライブラリをimport
して最初にdisplayio
を初期化します。
import gc
import board
import displayio
import rgbmatrix
import framebufferio
import time
displayio.release_displays()
...
つぎに先ほどのピン接続でrgbmatrix
を設定します。
カラーのビット深度は一応framebuffer
のRGB565
をカバーする6
(6×3色=18>5+6+5=16)にしておきます。
今回パネルはデイジーチェインせず1枚なのでtile
は1
、LEDセルの並びは通常のZ型配列、doublebuffer
はTrue
です。
matrix = rgbmatrix.RGBMatrix(
width=64,
height=32,
bit_depth=6,
rgb_pins=[ # Preserve GP4 & GP5 for standard STEMMA-QT
board.GP2, # R1
board.GP3, # G1
board.GP6, # B1
board.GP7, # R2
board.GP8, # G2
board.GP9, # B2
],
addr_pins=[board.GP10, board.GP16, board.GP18, board.GP20], # A # B # C # D
clock_pin=board.GP11,
latch_pin=board.GP12,
output_enable_pin=board.GP13,
tile=1,
serpentine=False,
doublebuffer=True,
)
...
64x32のbmpファイルはPCからCircuitPythonのドライブ(H:とか)に適当にフォルダを作成して保存しておきます。今回は24ビットカラーのファイルを用意しましたが、いい感じに減色してくれるようで問題ありませんでした。
そしてTileGrid
を作成します。引数には先ほど読み込んだbitmap
とそこから作るpixel_shader
、描画開始座標(今回はパネル左上)を渡します。32x32画像などを表示する場合はxを16にするとセンターに表示されます。
image_path = "/graphics/test.bmp"
try:
bitmap = displayio.OnDiskBitmap(image_path)
except Exception as e:
print(f"Error loading image: {e}")
tile_grid = displayio.TileGrid(
bitmap,
pixel_shader=bitmap.pixel_shader,
x=0,
y=0
)
...
続いて先ほどのmatrix
を元にframebuffer
を作成し、空のdisplay_group
も作成してそれを描画対象に指定します。今回はグループは1つだけ、またグループと言っても今回は1枚のbitmapだけがグループ内の要素となります。
display = framebufferio.FramebufferDisplay(matrix)
display_group = displayio.Group()
display.root_group = display_group
...
そしてwhile
ループになります。念のためガベージコレクションして、先ほどのTile_grid
をgroup
へappend
すれば画像が表示されます3。ここが実際の表示開始部分になります。古い情報ではshow()
が使われているものがありましたがこれは廃止され4、この形式に変更されたようです5。
あまり早くループを回すとメモリエラーが発生するようなので最後に適当にウェイトを入れておきます。
while True:
try:
gc.collect()
if image_path:
group.append(tile_grid)
time.sleep(5)
...
複数の画像を切り替えるには続けて最初のtile_grid
をremove
して次に表示するファイルから作成したTile_Grid2
とかを再度append
すればOKです。最初のファイルと同様にimage_path2
とbitmap2
などとして設定しておいてください。
巷のスクロールやアニメーションのデモも同様の手法で画像を更新して実現しているようです。
# 上のtile_glidの実装の下に
tile_grid2 = displayio.TileGrid(
bitmap2,
pixel_shader=bitmap2.pixel_shader,
x=0,
y=0
)
...
# 上の`time.sleep(5)の後に
if image_path2:
group.remove(tile_grid)
gc.collect()
group.append(tile_grid2)
time.sleep(5)
group.remove(tile_grid2)
...
基本的にはこれだけです。
Web上の情報にはスクロールとか文字表示とかアニメーションとかもっと複雑なものがいろいろありますが、まずは自分がお手軽なPythonでの画像表示の基本的な流れを理解するために試したものです。
最後にcode.py
の全体を載せておきます。
import gc
import board
import displayio
import rgbmatrix
import framebufferio
import time
displayio.release_displays()
# === Setup for Pico ===
# Setup rgbmatrix display (change pins to match your wiring)
matrix = rgbmatrix.RGBMatrix(
width=64,
height=32,
bit_depth=6,
rgb_pins=[ # Preserve GP4 & GP5 for standard STEMMA-QT
board.GP2, # R1
board.GP3, # G1
board.GP6, # B1
board.GP7, # R2
board.GP8, # G2
board.GP9, # B2
],
addr_pins=[board.GP10, board.GP16, board.GP18, board.GP20], # A # B # C # D
clock_pin=board.GP11,
latch_pin=board.GP12,
output_enable_pin=board.GP13,
tile=1,
serpentine=False,
doublebuffer=True,
)
display = framebufferio.FramebufferDisplay(matrix)
group = displayio.Group()
display.root_group = group
# === end of pico setup === #
image_path = "/graphics/test.bmp"
image_path2 = "/graphics/test2.bmp"
try:
bitmap = displayio.OnDiskBitmap(image_path)
bitmap2 = displayio.OnDiskBitmap(image_path2)
except Exception as e:
print(f"Error loading image: {e}")
tile_grid = displayio.TileGrid(
bitmap,
pixel_shader=bitmap.pixel_shader,
x=0,
y=0
)
tile_grid2 = displayio.TileGrid(
bitmap2,
pixel_shader=bitmap2.pixel_shader,
x=0,
y=0
)
# === Main Loop ===
while True:
try:
gc.collect()
if image_path:
group.append(tile_grid)
time.sleep(5)
if image_path2:
group.remove(tile_grid)
gc.collect()
group.append(tile_grid2)
time.sleep(5)
group.remove(tile_grid2)
except MemoryError:
print("MemoryError! Trying to recover...")
group = displayio.Group()
display.root_group = group
gc.collect()
time.sleep(1)
今回は以上です。
-
実用性についてツッコんではいけない。 ↩
-
2.5mmピッチのやつは倍ぐらいの値段するので却下。 ↩
-
これを書いた後で見つけたのですが、ここまでの話というか公式ガイドの日本語訳が https://steam-tokyo.com/circuitpython-displayio-lib-1-5/ こちらにあります(ただし一部情報が古い)。 ↩
-
なんでわかりにくくしたんだ、という気はする。 ↩