シェルスクリプトマガジン

Raspberry Pi Pico W/WHで始める電子工作(Vol.97掲載)

筆者:米田 聡

本連載では、人気のマイコンボード「Raspberry Pi Pico/Pico 2」シリーズを活用していきます。同ボードは、無線LANやBluetoothの通信機能を搭載するモデルもあり、入手しやすく価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。今回は、前回Raspberry Pi Pico 2に取り付けた小型LCDに画像を表示するプログラムを紹介します。

シェルスクリプトマガジン Vol.97は以下のリンク先でご購入できます。

図1 RGB565の非圧縮ビットマップ画像に変換するPythonプログラム(rgb565.py)

#!/usr/bin/python3

import sys
import argparse
from PIL import Image

def rgb565_convert(image_path, output_path ,order='little'):
    """
    画像をRGB565形式に変換し、バイナリファイルに保存します。

    Args:
        image_path: 入力画像のパス
        output_path: 出力バイナリファイルのパス
    """
    try:
        img = Image.open(image_path).convert("RGB")  # RGBに変換
    except FileNotFoundError:
        print(f"Error: 画像ファイル '{image_path}' が見つかりません。")
        return
    except Exception as e:
        print(f"Error: 画像を開く際にエラーが発生しました: {e}")
        return

    width, height = img.size

    with open(output_path, "wb") as f:
        for y in range(height):
            for x in range(width):
                r, g, b = img.getpixel((x, y))

                # RGB565に変換
                r5 = r >> 3  # 上位5ビット
                g6 = g >> 2  # 上位6ビット
                b5 = b >> 3  # 上位5ビット

                rgb565 = (r5 << 11) | (g6 << 5) | b5

                # バイナリ書き込み (リトルエンディアン)
                f.write(rgb565.to_bytes(2, byteorder=order))

    print(f"画像 '{image_path}' をRGB565形式に変換し、'{output_path}' に保存しました。")

if __name__=="__main__":

    parser = argparse.ArgumentParser()
    parser.add_argument("files",type=str,nargs="*")
    parser.add_argument("--order", type=str, choices=['little', 'big'], default="little")

    args = parser.parse_args()
    files = args.files
    order = args.order

    if len(files) != 2:
        print("Usage: rgb565.py infile outfile [--order little | big ]")
    else:
        rgb565_convert(files[0], files[1], order)

図2 sample.binをLCDに表示するC++プログラム(main.cpp)

#include <Arduino.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <Adafruit_ST7789.h>
#include <SPI.h>

// 以下がPico SDKのヘッダーファイル
#include "hardware/dma.h"
#include "hardware/spi.h"
#include "pico/stdlib.h"

#define TFT_CS         17
#define TFT_RST        21
#define TFT_DC         20

// サンプル画像を埋め込む
__asm(\
  ".section \".rodata\" \n"\
  ".balign 4\n"\

  ".global _sample_picture\n"\
  ".global _picture_size\n"\

  "_sample_picture:\n"\
  ".incbin \"include/sample.bin\"\n"\
  ".set _picture_size, . - _sample_picture\n"\
  ".section \".text\"\n"\
);

extern const uint16_t _sample_picture[];
extern uint32_t _picture_size[];

// Adafruit ST77xxを初期化生成
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);

void setup() {
  Serial1.begin(115200);  // USBシリアルを使用する場合はSerialに変更、以下同じ

  // SPIクロック100MHz
  tft.setSPISpeed(100*000*000);
  // 解像度240×240、SPIモード3
  tft.init(240, 240, SPI_MODE3);
  // 黒で消去
  tft.fillScreen(ST77XX_BLACK);

  // 空いているDMAチャンネルを得る
  int dma_ch = dma_claim_unused_channel(true);
  // DMA設定を取得
  dma_channel_config c = dma_channel_get_default_config(dma_ch);
  // データ転送単位:DMA_SIZE_8=8iビット
  channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
  // 読み出し側アドレスのインクリメントを有効
  channel_config_set_read_increment(&c, true);
  // 書き込み側アドレスのインクリメントは行わない
  channel_config_set_write_increment(&c, false);
  // spi0送信FIFOのデータ要求を使用する
  channel_config_set_dreq(&c, DREQ_SPI0_TX);
  // DMAにパラメータを設定する
  dma_channel_configure(
    dma_ch,                 // DMAチャンネル
    &c,                     // 設定
    &spi_get_hw(spi0)->dr,  // 書き込み先:SPI FIFOデータレジスタ(SSPDR)
    _sample_picture,        // 読み出し元アドレス
    (uint)_picture_size,    // 転送するデータカウント数
    false                   // 今すぐ開始するか(しない)
  );
  // LCDディスプレイメモリーの書き込み矩形を設定
  tft.setAddrWindow(0, 0, tft.width(), tft.height());
  
  ulong starttime = micros();
  tft.startWrite();                                   // 書き込み開始
  dma_channel_start(dma_ch);                          // DMA開始
  dma_channel_wait_for_finish_blocking(dma_ch);       // DMA終了待ち
  tft.endWrite();                                     // 書き込み終了
  ulong elapsed = micros() - starttime;
  // 描画に費やした時間を表示
  Serial1.printf("elapsed time = %lu\n", elapsed);
}

void loop() {
}

図4 埋め込んだアセンブリ言語のリスト

    .section ".rodata"
    .balign 4

    .global _sample_picture
    .global _picture_size

_sample_picture:
    .incbin "include/sample.bin"
    .set _picture_size, . - _sample_picture

    .section ".text