筆者:米田 聡
本連載では、人気のマイコンボード「Raspberry Pi Pico/Pico 2」シリーズを活用していきます。同ボードは、無線LANやBluetoothの通信機能を搭載するモデルもあり、入手しやすく価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。今回は、前回Raspberry Pi Pico 2に取り付けた小型LCDに画像を表示するプログラムを紹介します。
シェルスクリプトマガジン Vol.97は以下のリンク先でご購入できます。
図1 RGB565の非圧縮ビットマップ画像に変換するPythonプログラム(rgb565.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
#!/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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
#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 埋め込んだアセンブリ言語のリスト
1 2 3 4 5 6 7 8 9 10 11 |
.section ".rodata" .balign 4 .global _sample_picture .global _picture_size _sample_picture: .incbin "include/sample.bin" .set _picture_size, . - _sample_picture .section ".text |