RP2040 TYPE-C 16MBを使う | virt_flyのブログ

virt_flyのブログ

フライトシミュレーターソフトのFlightGearで仮想飛行を楽しむブログです。

RP2040 TYPE-C 16MBでTFTにキャラ画像表示

↑RP2040 TYPE-C 16MBを使い、混在する異なるサイズの画像を2インチTFTに連続表示(動画はGIF)

 

16MB使うにはCircuitpython

 

■Micropythonはフラッシュメモリーがいくらあっても2MBと見做す

 

Raspberry Pi Zeroに替えて、Picoを使ってパラパラアニメを作ろうと思ったら、画像はRAWファイルでも12~3枚が限度。無印のPicoのフラッシュメモリーが2MBしかないからです。

 

16MBあると思われるPico互換機のRP2040 TYPE-C 16MBが、以前Aliexpressで購入したまま眠っていましたので、これなら使えそうと考えました。

 

RP2040 TYPE-C 16MBボード

↑フラシュメモリーを16MBもつPico互換のRP2040 TYPE-C 16MB 眼がよく見えなくてハンダがひどいことになってます

 

早速MicropythonをインストールしてRAW画像を入れようとしたところ、12~3枚くらいで入らなくなってしまいました。これじゃPicoと同じ2MBじゃないか、騙されたのではと思ったものですが、どうやら無印Pico用のMicropythonはなべてフラッシュメモリーは2MBと見做すようになっているみたいです。

 

■専用のMicropythonをビルドする手もあるが

 

CircuitPythonを試すか、使えるMicropythonが探しても見つからないので自分でビルドしてつくる他なさそうです。専用のものを作っても管理が大変だし、ビルドは面倒でしょうから、CircuitPythonが使えたら手っ取り早く、無難でしょう。

 

Circuitpythonはあまりプログラミングしてこなかったし久しぶりでどこからダウンロードしてくればいいのかも忘れていたくらいなので、コード作成は生成AIに任せました。

 

RP2040 TYPE-C 16MBと2インチTFTディスプレイ

↑RP2040 TYPE-C 16MBでフラシュメモリー16MB を使うにはCircuitepython が手っ取り早い

 

結果は、Circuitpythonならインストールするだけで16MBを認識しすぐ使えることがわかり、またパラパラアニメも予定の28枚の画像を使って実現できました。

 

■GMT020-02専用のst7789ドライバーは無敵

 

なお、ディスプレイにはクセのある2インチTFT(GMT020-02)を使用しましたので、生成AIがコード作成にもたつきましたが、CircuitpythonとMicropythonでは違いがあるものの、以前紹介した自作専用のst7789ドライバーを示したところ、ハードの特性をつかみ、速やかにコード作成がすすみました。GMT020-02専用のst7789ドライバーはここでも貢献、無敵です。

 

■混在するサイズの異なる画像を連続表示

 

愚痴ですが、ChatGPTには振り回されました。CircuitpythonはBMP画像しか扱えない、RAWも使えなくもないがBMPにしろというので用意したのに、途中からBMPはダメだRAW画像にせよと。しかも、以前作成してあったRAW画像がなぜか表示できないので、ChatGPTが用意した変換コードでRAW形式に作り替えたところ、なぜか縦長画像を生成していて、コードをどんなにいじろうともなおらない原因になっていたのです。以前作成してあったRAW画像もいつのまにか表示できるようになっていたり、他にも、Circuitpythonでは画像の回転や反転は不可能と言っていたのに、いつのまにか可能なことがまるわかりのコードを出してきたりと、相手がAIでも不信がわくというもの。

 

結局、コードはCopilotに書かせました。

 

シンプルなコードではつまらないので、RAW画像は240x240と240x320の両サイズ混在で連続表示を可能にするものとしました。そのため、大きなサイズの画像を表示した後に小さな画像を表示すると、小さくてカバーできず大きな画像の残像がはみだして覗き見苦しくなります。これを覆い隠すため、大きい画像と小さい画像との間に背景画像を挟んで表示させることにしますが、画像が変わるたびにいちいちすべて背景画像を描いていてはちらつきの元。したがって、小さい画像の表示の前に小さな画像を表示していた場合は間に背景画像は描かない、もちろん大きな画像を表示する場合にはその前の背景画像の描画は不要なので省くようにコードを記述しました。冒頭のGIFを参照ください。背景画像に歩き回るキャラクターの背景色(暗灰色)を使用したので、うまい具合にはみ出し部分の見分けはつかなくなっています。

 

import board
import busio
import digitalio
import time

# ============================
# ST7789 Driver
# ============================

class ST7789:
  def __init__(self, spi, width, height, reset, dc, cs):
    self.spi = spi
    self.width = width
    self.height = height

    self.reset = digitalio.DigitalInOut(reset)
    self.dc = digitalio.DigitalInOut(dc)
    self.cs = digitalio.DigitalInOut(cs)

    self.reset.direction = digitalio.Direction.OUTPUT
    self.dc.direction = digitalio.Direction.OUTPUT
    self.cs.direction = digitalio.Direction.OUTPUT

    self.cs.value = True
    self.dc.value = False

  def cmd(self, c):
    self.cs.value = False
    self.dc.value = False
    self.spi.write(bytes([c]))
    self.cs.value = True

  def data(self, d):     self.cs.value = False
    self.dc.value = True
    if isinstance(d, int):
      self.spi.write(bytes([d]))
    else:
      self.spi.write(d)
    self.cs.value = True

  def reset_display(self):
    self.reset.value = False
    time.sleep(0.05)
    self.reset.value = True
    time.sleep(0.05)

  def init(self):
    self.reset_display()

    self.cmd(0x11)
    time.sleep(0.12)

    self.cmd(0x36)
    self.data(0x00) #デフォルト
    #self.data(0xC0) #180度回転
    #self.data(0x40) #左右反転
    #self.data(0x80) #上下反転
    #self.data(0x20) #90度回転

    self.cmd(0x3A)
    self.data(0x55)

    self.cmd(0x21)
    self.cmd(0x29)
    time.sleep(0.02)

  def window(self, x0, y0, x1, y1):
    self.cmd(0x2A)
    self.data(x0 >> 8)
    self.data(x0 & 255)
    self.data(x1 >> 8)
    self.data(x1 & 255)

    self.cmd(0x2B)
    self.data(y0 >> 8)
    self.data(y0 & 255)
    self.data(y1 >> 8)
    self.data(y1 & 255)

    self.cmd(0x2C)


  def draw_raw_320_stream(self, filename):     # 240x320 全面表示(rowstart補正不要)
    self.window(0, 0, 239, 319)

    bufsize = 4096

    self.cs.value = False
    self.dc.value = True

    with open(filename, "rb") as f:
      while True:
        buf = f.read(bufsize)
        if not buf:
          break
        self.spi.write(buf)

    self.cs.value = True

  # 240x240 RAW を rowstart=40 に表示(ストリーミング)
  def draw_raw_240_stream(self, filename, rowstart=40):
    y0 = rowstart
    y1 = rowstart + 239

    self.window(0, y0, 239, y1)

    bufsize = 4096

    self.cs.value = False
    self.dc.value = True

    with open(filename, "rb") as f:
      while True:
        buf = f.read(bufsize)
        if not buf:
          break
        self.spi.write(buf)

    self.cs.value = True

  # 連続ループ再生(RAMを使わない)
  def play_sequence_stream(self, filelist, delay_ms=200, rowstart=40):
    while True:
      for filename in filelist:
       #self.draw_raw_240_stream(filename, rowstart=rowstart)
       self.draw_raw_320(filename)
       time.sleep(delay_ms / 1000)


# ============================
# Main Program
# ============================

spi = busio.SPI(clock=board.GP18, MOSI=board.GP19)
while not spi.try_lock():
  pass
spi.configure(baudrate=40000000, polarity=0, phase=0)

tft = ST7789(
  spi,
  240, 320, # ← パネルは 240×320
  reset=board.GP20,
  dc=board.GP16,
  cs=board.GP17
)

tft.init() # 表示したい RAW 画像のリスト files = [
  ("digit4.raw", 320),
  ("image01.raw", 240),
  ("digit3.raw", 320),
  ("image02.raw", 240),
  ("image03.raw", 240),
  ("image04.raw", 240),
  ("image05.raw", 240),
  ("digit2.raw", 320),
  ("digit1.raw", 320),
  ("digit0.raw", 320),
  ("digit_blank.raw", 320),
  ("image06.raw", 240)
]

# --- 背景を1回だけ描く ---
tft.draw_raw_320_stream("back_gray.raw")

previous_size = None

while True:
  for filename, size in files:

    # --- 背景描画の条件 ---
    # 1. 今回の画像が 240x320 の場合 → 描かない
    # 2. 今回が 240x240 で、前回が 240x320 の場合 → 描く
    # 3. 今回が 240x240 で、前回も 240x240 の場合 → 描かない(ちらつき防止)

    if size == 320:
      tft.draw_raw_320_stream(filename)
    else:
        # 前回が320 → 残像が出るので背景を描く
        tft.draw_raw_320_stream("back_gray.raw")

      # 240x240 を rowstart=40 で描く
      tft.draw_raw_240_stream(filename, rowstart=40)

    previous_size = size
    time.sleep(0.2)

このコードは、CopilotならびにChatGPTの助けを得て作成しました。