筆者:米田 聡
本連載では、人気のマイコンボード「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