CircuitPythonはAdafruitさんが支援しているシステムなので、グラフィックスは当然GFXだろうと思ったら、全然コンセプトの違う表示システムなので参った。CircuitPythonのPicoDVI用のグラフィックスライブラリとしては、displayioと呼ばれるライブラリが対応しているようです。
Adafruitさんのオリジナルの紹介ページは以下を参照してください。
displayioは、お絵かき用のグラフィックスライブラリというよりは、UI構築用のグラフィックスライブラリに見えますね。グラフィックス要素を描くというよりは、画面上に置いていく感じ。それと、オブジェクト指向の特徴もうまく利用して、Pythonのような対話的なプログラミング言語に合致した、面白いグラフィックスライブラリになってます。
このページの目次
初めてのdisplayio
PR2350A-DVIでモニタ画面に文字と簡単な図形を表示するプログラム例を以下に示します。

下記のプログラムは、TRYGEAR-VIZ-RP2350B, PICO-HDMI-PLUS, PICOLAB-HDMI, PICO-HDMI-PADでも同様に動きます。
# 表示システムの初期化を行う
import displayio, picodvi, board, framebufferio
displayio.release_displays()
fb = picodvi.Framebuffer(
width=320, height=240, color_depth=8,
clk_dp=board.GP14, clk_dn=board.GP15,
red_dp=board.GP12, red_dn=board.GP13,
green_dp=board.GP18, green_dn=board.GP19,
blue_dp=board.GP16, blue_dn=board.GP17)
display = framebufferio.FramebufferDisplay(fb)
group = displayio.Group()
display.root_group = group
# 文字列の表示を行う
from adafruit_display_text import label
import terminalio
group.append(label.Label(terminalio.FONT, text="MicroFan", x=0, y=10, scale=2, color=0xFFFFFF))
group.append(label.Label(terminalio.FONT, text="RP2350A-DVI:PicoDVI", x=0, y=30, scale=2, color=0xFFFFFF))
# 図形の表示を行う
from adafruit_display_shapes.line import Line
from adafruit_display_shapes.rect import Rect
from adafruit_display_shapes.circle import Circle
from adafruit_display_shapes.triangle import Triangle
group.append(Line(0, 80, 320, 80, 0xFF00FF))
group.append(Rect(0, 120, 100, 100, fill=0xFF0000, outline=0xFFFF00))
group.append(Circle(160, 170, 50, fill=0x00FF00, outline=0xFFFF00))
group.append(Triangle(260, 120, 210, 220, 310, 220, fill=0x0000FF, outline=0xFFFF00))RP2040-UNO-HDMIの場合には、上記のプログラムの6-11行を以下の内容に差し替えてください。
fb = picodvi.Framebuffer(
width=320, height=240, color_depth=8,
clk_dp=board.GP17, clk_dn=board.GP16, # 端子の配置はRP2040-UNO-HDMI用
red_dp=board.GP19, red_dn=board.GP18,
green_dp=board.GP21, green_dn=board.GP20,
blue_dp=board.GP23, blue_dn=board.GP22)このプログラムを実行すると、画面の上の方に文字列が表示され、下の方に四角、丸三角の図形が表示されます。
こいつ動くぞ - 固まってない?(変更可能な)文字列や図形
一般的なグラフィックスライブラリの紹介だと、次のプログラムの例に行くところですが。。。
「こいつ動くぞ!」
ということで、話が続きます。
Pythonのプロンプトで、以下のように入力すると、何が起きるでしょうか?
group[3].y=50
面白いことにというか当然のこととして、最初のプログラムの実行が終了しても、pythonはインタプリタなので、そのプログラムの続きをプロンプトに対して引き続き入力して実行できます。
また、groupにappend()された描画要素は、0から順番にアクセス可能で、インデックスが3の要素は、Rectで四角形の図形です。プログラムでは、Rectにより四角形が画面に書かれたというよりは置かれただけなので、その位置を変更してやると、四角形の位置を動かすことができてしまいます。
また、以下のように入力すると、図形の色を変えることもできます。
group[3].fill=0xFFFFFF
さらに、groupの0番目の要素の文字列の内容を以下のように指定すると、表示される文字列も変えることができます。
group[0].text="Python"
対話的にグラフィックスを操作できるって面白いですよね。
いろいろな図形要素を対象に、位置や色を変える操作を行ってみてください。
ただし、なんでもオッケーではなく、図形の形が変わる(図形の描画表現を作成するために確保したビットマップバッファの構成を変える?)ことはできないようです。
位置を変えるこれはできても、
group[3].x=100
形を変えるこれはできないようです。
group[3].width=100
四角形の幅や高さ、円の直径、三角形の頂点の位置(したがって三角形は動かせない)などは変えられないようです。
それでも、図形や文字列の位置や色を変えられるだけでも、ゲームの作成などで色々楽しめそうですね。
ボールを描こう
円を表すCircleを使ってボールを表示します。
import displayio, picodvi, board, framebufferio
displayio.release_displays()
fb = picodvi.Framebuffer(
width=320, height=240, color_depth=8,
clk_dp=board.GP14, clk_dn=board.GP15,
red_dp=board.GP12, red_dn=board.GP13,
green_dp=board.GP18, green_dn=board.GP19,
blue_dp=board.GP16, blue_dn=board.GP17)
display = framebufferio.FramebufferDisplay(fb)
group = displayio.Group()
display.root_group = group
# ここまではグラフィックス利用の初期化で今後も共通
from adafruit_display_shapes.circle import Circle
ball = Circle(100, 100, 10, fill=0x00FF00, outline=0xFFFF00)
group.append(ball)すでに確認済の様に、プロンプトに以下のように入力すると、ボールを動かすことができますよね。
group[0].x=200
ここで、x, y 座標は図形の左上の座標を示しています。円だと、中心座標を操作したくなりますが、その場合には、x0, y0を使用します。
実際に使ってみると、円が動くのでx, x0の違いが判りますね。
group[0].x0=200
ボールを動かそう
ボールの位置を保持する x0, y0 を操作してボールを画面内で動かします。
横方向と縦方向の移動速度(0.02秒当たりの移動ピクセル量)を指定し、それをx0, y0に追加してボールを動かします。
ボールの位置が画面を逸脱する前に移動速度を反転させて跳ね返らせます。
import displayio, picodvi, board, framebufferio
displayio.release_displays()
fb = picodvi.Framebuffer(
width=320, height=240, color_depth=8,
clk_dp=board.GP14, clk_dn=board.GP15,
red_dp=board.GP12, red_dn=board.GP13,
green_dp=board.GP18, green_dn=board.GP19,
blue_dp=board.GP16, blue_dn=board.GP17)
display = framebufferio.FramebufferDisplay(fb)
group = displayio.Group()
display.root_group = group
# ここまではグラフィックス利用の初期化で今後も共通
from adafruit_display_shapes.circle import Circle
ball = Circle(100, 100, 10, fill=0x00FF00, outline=0xFFFF00)
group.append(ball)
import time
vx = 2
vy = 5
while True:
ball.x0 += vx
ball.y0 += vy
if ball.x0 <= ball.r or ball.x0 >= (display.width-1)-ball.r:
vx = -vx
if ball.y0 <= ball.r or ball.y0 >= (display.height-1)-ball.r:
vy = -vy
time.sleep(0.02)ボールを増やそう
この方式でボールを増やすと、そのボールを動かす処理がボールの数だけ増える
具体例は後で書きます。
ボール自身に動いてもらおう
ボールを増やしやすいように、ボール自身に動いてもらおう
ボールをオブジェクトにして動く能力を付与し、勝手に動いてもらう。
import displayio, picodvi, board, framebufferio
displayio.release_displays()
fb = picodvi.Framebuffer(
width=320, height=240, color_depth=8,
clk_dp=board.GP14, clk_dn=board.GP15,
red_dp=board.GP12, red_dn=board.GP13,
green_dp=board.GP18, green_dn=board.GP19,
blue_dp=board.GP16, blue_dn=board.GP17)
display = framebufferio.FramebufferDisplay(fb)
group = displayio.Group()
display.root_group = group
# ここまではグラフィックス利用の初期化で今後も共通
from adafruit_display_shapes.circle import Circle
class Ball(Circle):
# 初期座標、縦横速度、色を与える
def __init__(self, x0, y0, r, vx, vy, color):
super().__init__(x0, y0, r, fill=color, outline=0xFFFF00)
self.vx = vx
self.vy = vy
def tick(self):
# 縦横速度分移動して
self.x0 += self.vx
self.y0 += self.vy
# 画面の端に来たら速度を反転させて跳ね返る
if self.x0 <= self.r or self.x0 >= (display.width-1)-self.r:
self.vx = -self.vx
if self.y0 <= self.r or self.y0 >= (display.height-1)-self.r:
self.vy = -self.vy
ball = Ball(100, 100, 10, 2, 5, 0x00FF00)
group.append(ball)
import time
while True:
group[0].tick()
time.sleep(0.02)ボールがいっぱい
import displayio, picodvi, board, framebufferio, time
displayio.release_displays()
fb = picodvi.Framebuffer(
width=320, height=240, color_depth=8,
clk_dp=board.GP14, clk_dn=board.GP15,
red_dp=board.GP12, red_dn=board.GP13,
green_dp=board.GP18, green_dn=board.GP19,
blue_dp=board.GP16, blue_dn=board.GP17)
display = framebufferio.FramebufferDisplay(fb)
group = displayio.Group()
display.root_group = group
# ここまではグラフィックス利用の初期化で今後も共通
from adafruit_display_shapes.circle import Circle
class Ball(Circle):
# 初期座標、縦横速度、色を与える
def __init__(self, x0, y0, r, vx, vy, color):
super().__init__(x0, y0, r, fill=color, outline=0xFFFF00)
self.vx = vx
self.vy = vy
def tick(self):
# 縦横速度分移動して
self.x0 += self.vx
self.y0 += self.vy
# 画面の端に来たら速度を反転させて跳ね返る
if self.x0 <= self.r or self.x0 >= (display.width-1)-self.r:
self.vx = -self.vx
if self.y0 <= self.r or self.y0 >= (display.height-1)-self.r:
self.vy = -self.vy
color = [
0xFF0000, 0xFFFF00, 0x00FF00, 0x00FFFF, 0x0000FF, 0xFF00FF, 0xFFFFFF,
]
import random
# ボールを20個作ろう
for c in range(0, 20):
# 座標と速度は乱数で設定、色は作成順に順番に
ball = Ball(random.randint(10,200), random.randint(10,200), 10,
random.randint(1,7), random.randint(1,7), color[c%7])
group.append(ball)
import time
# 作成したすべてのボールに、繰り返し自分の(単位時間当たりの)処理を行うように依頼する
while True:
for ball in group: # すべてのボールに
ball.tick() # 自分の状態に合わせて動けと指示
time.sleep(0.02)ボールを操作しよう
沢山のボールが勝手に動くようになりましたが、ここでちょっと話題を変えて、1つのボールをスイッチで操作するプログラムを作ってみましょう。
ここでは、操作用のスイッチを利用できるRP2350A-DVI, PICO-HDMI-PLUSを使用したプログラム例を示します。
import displayio, picodvi, digitalio, board, framebufferio
displayio.release_displays()
fb = picodvi.Framebuffer(
width=320, height=240, color_depth=8,
clk_dp=board.GP14, clk_dn=board.GP15,
red_dp=board.GP12, red_dn=board.GP13,
green_dp=board.GP18, green_dn=board.GP19,
blue_dp=board.GP16, blue_dn=board.GP17)
display = framebufferio.FramebufferDisplay(fb)
group = displayio.Group()
display.root_group = group
# ここまではグラフィックス利用の初期化で今後も共通
# スイッチの初期化
### 以下の3行はPICO-HDMI-PLUSのみで、RP2350A-DVIでは削除
pullup = digitalio.DigitalInOut(board.GP9)
pullup.direction = digitalio.Direction.OUTPUT # プルアップ電源にするので出力設定
pullup.value = True # 出力値は HIGH, 1, 理想的には3.3V
###
sw1 = digitalio.DigitalInOut(board.GP10) # 初期状態は入力用
sw2 = digitalio.DigitalInOut(board.GP11)
sw3 = digitalio.DigitalInOut(board.GP20)
sw4 = digitalio.DigitalInOut(board.GP21)
# 動かすボールの処理
from adafruit_display_shapes.circle import Circle
ball = Circle(10, 10, 10, fill=0x00FF00, outline=0xFFFF00)
group.append(ball)
v = 5
import time
while True:
if sw1.value == 0: # 左
if ball.x0 <= ball.r:
ball.x0 = ball.r
else:
ball.x0 -= v
if sw2.value == 0: # 右
if ball.x0 >= (display.width-1)-ball.r:
ball.x0 = (display.width-1)-ball.r
else:
ball.x0 += v
if sw3.value == 0: # 上
if ball.y0 <= ball.r:
ball.y0 = ball.r
else:
ball.y0 -= v
if sw4.value == 0: # 下
if ball.y0 >= (display.height-1)-ball.r:
ball.y0 = (display.height-1)-ball.r
else:
ball.y0 += v
time.sleep(0.02)
# スイッチを押さないとボールは動かないよ!ボールの反射に音を付けてみよう
ボールが画面の端に行きついて跳ね返る際に音を出すようにしましょう。
音を出すための圧電スピーカーが必要なので、このプログラムもPICO-HDMI-PLUS用に作成します。
音を出す機能は、simpleioのtone()を使用します。
プログラムは、ボールが自律的に動くようにオブジェクト化したものをベースに修正を加えます。
まず、5-10行までを置き換えて、PICO-HDMI-PLUS用に変更します。次に、18,33,36に音を出すための記述を加えます。音の周波数は、縦と横の壁で変えてみました。
import displayio, picodvi, board, framebufferio
displayio.release_displays()
fb = picodvi.Framebuffer(
width=320, height=240, color_depth=8,
clk_dp=board.GP14, clk_dn=board.GP15, # 端子の配置はPICO-HDMI-PLUS用
red_dp=board.GP12, red_dn=board.GP13,
green_dp=board.GP18, green_dn=board.GP19,
blue_dp=board.GP16, blue_dn=board.GP17)
display = framebufferio.FramebufferDisplay(fb)
group = displayio.Group()
display.root_group = group
# ここまではグラフィックス利用の初期化で今後も共通
import simpleio
from adafruit_display_shapes.circle import Circle
class Ball(Circle):
# 初期座標、縦横速度、色を与える
def __init__(self, x0, y0, r, vx, vy, color):
super().__init__(x0, y0, r, fill=color, outline=0xFFFF00)
self.vx = vx
self.vy = vy
def tick(self):
# 縦横速度分移動して
self.x0 += self.vx
self.y0 += self.vy
# 画面の端に来たら速度を反転させて跳ね返る
if self.x0 <= self.r or self.x0 >= (display.width-1)-self.r:
simpleio.tone(board.GP8, 1000, 0.01)
self.vx = -self.vx
if self.y0 <= self.r or self.y0 >= (display.height-1)-self.r:
simpleio.tone(board.GP8, 500, 0.01)
self.vy = -self.vy
ball = Ball(100, 100, 10, 2, 5, 0x00FF00)
group.append(ball)
import time
while True:
group[0].tick()
time.sleep(0.02)音がすると、それだけでゲームみたいな感じ。印象がかなり変わりますね。音がモニターから聞けないのが残念かな。
ブロック崩しゲーム
趣向は変わってブロック崩しゲーム。
import time
import board
import digitalio
import displayio
import framebufferio
import picodvi
import vectorio
import simpleio
# --- ディスプレイの初期化 ---
displayio.release_displays()
fb = picodvi.Framebuffer(
width=360,
height=200,
clk_dp=board.GP14,
clk_dn=board.GP15,
red_dp=board.GP12,
red_dn=board.GP13,
green_dp=board.GP18,
green_dn=board.GP19,
blue_dp=board.GP16,
blue_dn=board.GP17,
color_depth=8
)
display = framebufferio.FramebufferDisplay(fb, rotation=0)
WIDTH = display.width
HEIGHT = display.height
main_group = displayio.Group()
display.root_group = main_group
# カラーパレットの定義
palette = displayio.Palette(5)
palette[0] = 0x000000 # 背景: 黒
palette[1] = 0xFFFFFF # パドル: 白
palette[2] = 0xFF0000 # ブロック内側: 赤
palette[3] = 0x00FF00 # ボール: 緑
palette[4] = 0xFFFF00 # ブロック枠線: 黄
# --- オーディオピンの定義 (GP26) ---
AUDIO_PIN = board.GP26
# --- 操作用スイッチの設定 ---
btn_up = digitalio.DigitalInOut(board.GP29)
btn_up.direction = digitalio.Direction.INPUT
btn_up.pull = digitalio.Pull.UP
btn_down = digitalio.DigitalInOut(board.GP31)
btn_down.direction = digitalio.Direction.INPUT
btn_down.pull = digitalio.Pull.UP
# --- ゲームオブジェクトのパラメータ設定 ---
# 1. 右端のパドル
PADDLE_WIDTH = 8
PADDLE_HEIGHT = 40
paddle_x = WIDTH - PADDLE_WIDTH - 10
paddle_y = (HEIGHT - PADDLE_HEIGHT) // 2
paddle = vectorio.Rectangle(pixel_shader=palette, color_index=1, width=PADDLE_WIDTH, height=PADDLE_HEIGHT, x=paddle_x, y=paddle_y)
main_group.append(paddle)
# 2. 円形のボール
BALL_RADIUS = 8
ball_x = float(WIDTH // 2)
ball_y = float(HEIGHT // 2)
ball_dx = -1.5
ball_dy = 1.0
ball = vectorio.Circle(pixel_shader=palette, color_index=3, radius=BALL_RADIUS, x=int(ball_x), y=int(ball_y))
main_group.append(ball)
# 3. ブロックの配置設定
BLOCK_ROWS = 5
BLOCK_COLS = 6
BLOCK_WIDTH = 14
BLOCK_HEIGHT = 30
BLOCK_PADDING = 4
blocks = []
block_frames = []
def create_blocks():
for b in blocks:
try: main_group.remove(b)
except ValueError: pass
for f in block_frames:
try: main_group.remove(f)
except ValueError: pass
blocks.clear()
block_frames.clear()
for col in range(BLOCK_COLS):
for row in range(BLOCK_ROWS):
bx = 10 + col * (BLOCK_WIDTH + BLOCK_PADDING)
by = 15 + row * (BLOCK_HEIGHT + BLOCK_PADDING)
frame = vectorio.Rectangle(pixel_shader=palette, color_index=4, width=BLOCK_WIDTH, height=BLOCK_HEIGHT, x=bx, y=by)
main_group.append(frame)
block_frames.append(frame)
b = vectorio.Rectangle(pixel_shader=palette, color_index=2, width=BLOCK_WIDTH - 2, height=BLOCK_HEIGHT - 2, x=bx + 1, y=by + 1)
main_group.append(b)
blocks.append(b)
create_blocks()
def reset_ball():
global ball_x, ball_y, ball_dx, ball_dy
ball_x = float(WIDTH // 2)
ball_y = float(HEIGHT // 2)
ball_dx = -1.5
ball_dy = 1.0
ball.x = int(ball_x)
ball.y = int(ball_y)
# --- メインゲームループ ---
while True:
if not btn_up.value:
paddle.y = max(0, paddle.y - 4)
if not btn_down.value:
paddle.y = min(HEIGHT - PADDLE_HEIGHT, paddle.y + 4)
# ボールの移動
ball_x += ball_dx
ball_y += ball_dy
ball.x = int(ball_x)
ball.y = int(ball_y)
# 壁との衝突判定 (durationを0.01に調整)
if ball_y - BALL_RADIUS <= 0 or ball_y + BALL_RADIUS >= HEIGHT:
ball_dy = -ball_dy
simpleio.tone(AUDIO_PIN, 300, duration=0.01)
if ball_x - BALL_RADIUS <= 0:
ball_dx = -ball_dx
simpleio.tone(AUDIO_PIN, 300, duration=0.01)
# パドルとの衝突判定 (durationを0.01に調整)
if ball_dx > 0:
if (ball_x + BALL_RADIUS >= paddle.x) and (ball_x - BALL_RADIUS <= paddle.x + PADDLE_WIDTH):
if (ball_y + BALL_RADIUS >= paddle.y) and (ball_y - BALL_RADIUS <= paddle.y + PADDLE_HEIGHT):
ball_dx = -ball_dx
simpleio.tone(AUDIO_PIN, 440, duration=0.01)
# ブロックとの衝突判定 (durationを0.01に調整)
for i, b in enumerate(blocks):
if (ball_x - BALL_RADIUS < b.x + (BLOCK_WIDTH - 2) and ball_x + BALL_RADIUS > b.x and
ball_y - BALL_RADIUS < b.y + (BLOCK_HEIGHT - 2) and ball_y + BALL_RADIUS > b.y):
ball_dx = -ball_dx
main_group.remove(b)
main_group.remove(block_frames[i])
blocks.pop(i)
block_frames.pop(i)
simpleio.tone(AUDIO_PIN, 220, duration=0.01)
break
if len(blocks) == 0:
time.sleep(1)
create_blocks()
reset_ball()
time.sleep(1)
if ball_x - BALL_RADIUS > WIDTH:
# ミス時の音は少し長めの設定のままにしています(必要に応じてここも0.01等に調整してください)
simpleio.tone(AUDIO_PIN, 110, duration=0.3)
time.sleep(1)
reset_ball()
time.sleep(1)
time.sleep(0.016)関連製品
PR2350A-DVI

PICO-HDMI-PLUS
Raspberri Pi PICO/PICO WにHDMIコネクタとタクトスイッチx4、圧電スピーカーを追加する拡張ボードです。PICOが超小型のテレビゲーム機やPCモニタ出力付きのArduino実験ボードに変身します。
TRYGEAR-VIZ RP2350B

PICOLAB-HDMI

