パーツ
- ラズパイピコ
- microSDカードリーダーモジュール
- LCDモジュール(Pico-LCD 1.14)

SDK・ライブラリ・ドライバ
- pico-sdk
ラズパイ動かすのに必要な色々全般?よくわからん。
https://github.com/raspberrypi/pico-sdk - pimoroni-pico
画面を描画するのに便利なやつがいっぱい入ってる。それ以外のもめっちゃ入ってる。
https://github.com/pimoroni/pimoroni-pico - no-OS-FatFS-SD-SPI-RPi-Pico
SDカードとのやりとりに便利なやつ
https://github.com/carlk3/no-OS-FatFS-SD-SPI-RPi-Pico
ファイル構成

pimoroniいっぱい入ってるからどんどん間引いて最小構成にした。
↓コピー元とかも書いた
プロジェクトフォルダ
├ common(pimoroni-pico/commonフォルダ)
├ drivers
│└ st7789(pimoroni/drivers/st7789)
├ libraries
│├ bitmap_fonts(pimoroni-pico/libraries/bitmap_fonts)
│├ hershey_fonts(pimoroni-pico/libraries/hershey_fonts)
│├ no-OS-FatFS-SD-SPI-RPi-Pico(carlk3/no-OS-FatFS-SD-SPI-RPi-Pico)
│├ pico_display(pimoroni-pico/libraries/pico_display)
│└ pico_graphics(pimoroni-pico/libraries/pico_graphics)
├ pico-sdk
CMakeLists.txt
main.cpp
main.hpp
ソース
画像ファイル(240*135且つRGB565のbin形式)が「all_frames_240.bin」というファイル名でmicroSDに保存してある前提でのソース
CMakeLists.txt
cmake_minimum_required(VERSION 3.13)
# pico-sdkのパスを変数に格納
set(PICO_SDK_PATH "${CMAKE_CURRENT_LIST_DIR}/pico-sdk")
# PICO SDKの初期化に必要なスクリプトを読み込む
# 環境変数ではなく、上で定義した変数を使って include する
include(${PICO_SDK_PATH}/pico_sdk_init.cmake)
# プロジェクトの名前と使用言語を指定
# cppを使う場合はCXXを指定する
project(atx_c_cpp C CXX ASM)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Pico SDKの初期化して使えるようにする
pico_sdk_init()
# 実行ファイルとしてビルドするソースファイルを指定
add_executable(${PROJECT_NAME}
main.cpp
)
# USBシリアル通信を有効にする
# 1は有効、0は無効
pico_enable_stdio_usb(${PROJECT_NAME} 1)
pico_enable_stdio_uart(${PROJECT_NAME} 0)
add_subdirectory(common)
add_subdirectory(drivers/st7789)
add_subdirectory(libraries/bitmap_fonts)
add_subdirectory(libraries/pico_graphics)
add_subdirectory(libraries/pico_display)
add_subdirectory(libraries/no-OS-FatFS-SD-SPI-RPi-Pico/FatFs_SPI)
# 必要なライブラリを指定
target_link_libraries(${PROJECT_NAME} PUBLIC
pico_stdlib # 標準ライブラリ(stdio、sleepなど)
FatFs_SPI # SPI制御用ハードウェアライブラリ
hardware_spi
hardware_pwm
hardware_dma
pico_display
pico_graphics
pimoroni_bus
bitmap_fonts
st7789
)
# ビルド成果物の出力先を指定
pico_add_extra_outputs(${PROJECT_NAME})
main.hpp
#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "hardware/dma.h"
#include "hardware/spi.h"
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "sd_card.h"
#include "ff.h"
#include "pico_display.hpp"
#include "st7789.hpp"
#include "pico_graphics.hpp"
using namespace pimoroni;
#define BUFFER_SIZE (PicoDisplay::WIDTH * PicoDisplay::HEIGHT * 2) // RGB565 = 2 bytes per pixel
uint8_t image_buffer[BUFFER_SIZE];
#define LCD_SPI spi1
void write_to_lcd(uint8_t *data, size_t len);
void write_command(uint8_t cmd);
void write_data(uint8_t *data, size_t len);
void set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
void lcd_init();
void setup_dma();
main.cpp
#include "main.hpp"
int dma_ch;
dma_channel_config config;
int main() {
// シリアルポートの初期化
stdio_init_all();
// SPIの初期化
spi_init(LCD_SPI, 40000000); // SPIのクロックを40MHzに設定
// DMASPIを有効化
spi_set_format(LCD_SPI, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST);
// // SPIのピン設定
gpio_init(SPI_DEFAULT_DC);
gpio_set_dir(SPI_DEFAULT_DC, GPIO_OUT);
gpio_put(SPI_DEFAULT_DC, 1); // 初期状態:データモード
// チップセレクトピンをSPI機能に設定
gpio_init(SPI_BG_FRONT_CS);
gpio_set_dir(SPI_BG_FRONT_CS, GPIO_OUT);
gpio_put(SPI_BG_FRONT_CS, 1);
gpio_set_function(SPI_DEFAULT_SCK, GPIO_FUNC_SPI); // クロックピンをSPI機能に設定
gpio_set_function(SPI_DEFAULT_MOSI, GPIO_FUNC_SPI); // MOSIピンをSPI機能に設定
// バックライトピン
gpio_init(SPI_BG_FRONT_PWM);
gpio_set_dir(SPI_BG_FRONT_PWM, GPIO_OUT);
gpio_put(SPI_BG_FRONT_PWM, 1); // バックライトON
// リセットピンを初期化
gpio_init(SPI_BG_FRONT_RESET);
sleep_ms(5000); // シリアルポートの安定化のために5秒待機
FRESULT fr;
FATFS fs;
FIL fil;
int ret;
uint8_t buf[64800];
char filename[] = "all_frames_240.bin";
// SDカードの初期化
if (!sd_init_driver()) {
printf("SDカードの初期化に失敗しました。\r\n");
while (true);
}
// ドライブのマウント
fr = f_mount(&fs, "0:", 1);
if (fr != FR_OK) {
printf("ドライブのマウントに失敗しました。エラーコード: %d\r\n", fr);
while (true);
}
// ファイルを読み取り専用で開く
fr = f_open(&fil, filename, FA_READ);
if (fr != FR_OK) {
printf("ファイルのオープンに失敗しました。エラーコード: %d\r\n", fr);
while (true);
}
printf("LCDの初期化を開始します\r\n");
lcd_init();
// ファイルからデータを読み取る
printf("データを読み取ります '%s':\r\n", filename);
//DMA準備
setup_dma();
printf("LCDに画像を描画します\r\n");
UINT bytesRead;
fr = f_read(&fil, buf, sizeof(buf), &bytesRead);
if (fr != FR_OK) {
printf("ファイルの読み取りに失敗しました。エラーコード: %d\r\n", fr);
f_close(&fil);
while (true);
}
write_to_lcd((uint8_t *)buf, bytesRead);
dma_channel_unclaim(dma_ch); // DMAチャンネルを解放
write_to_lcd((uint8_t *)buf, bytesRead); // 読み取ったデータをLCDに書き込む
// ファイルを閉じる
fr = f_close(&fil);
if (fr != FR_OK) {
printf("ファイルのクローズに失敗しました。エラーコード: %d\r\n", fr);
while (true);
}
// ドライブのアンマウント
f_unmount("0:");
printf("SDカードのテストが完了しました。\r\n");
while (true) {
// 無限ループでプログラムを終了しないようにする
tight_loop_contents();
}
}
// LCDにコマンドを送信する関数
void write_command(uint8_t cmd) {
gpio_put(SPI_DEFAULT_DC, 0);
gpio_put(SPI_BG_FRONT_CS, 0);
spi_write_blocking(LCD_SPI, &cmd, 1);
gpio_put(SPI_BG_FRONT_CS, 1);
}
// LCDにデータを送信する関数
void write_data(uint8_t *data, size_t len) {
gpio_put(SPI_DEFAULT_DC, 1); // データモード
gpio_put(SPI_BG_FRONT_CS, 0); // 通信開始
spi_write_blocking(LCD_SPI, data, len);
gpio_put(SPI_BG_FRONT_CS, 1); // 通信終了
}
// 描画する表域を設定する関数
void set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
uint8_t cmd = 0x2A;
write_command(cmd);
uint8_t data[] = { x0 >> 8, x0 & 0xFF, x1 >> 8, x1 & 0xFF };
write_data(data, 4);
cmd = 0x2B;
write_command(cmd);
uint8_t data2[] = { y0 >> 8, y0 & 0xFF, y1 >> 8, y1 & 0xFF };
write_data(data2, 4);
printf("LCD初期化完了");
}
void setup_dma() {
// DMAチャンネルを取得
dma_ch = dma_claim_unused_channel(true);
// DMAチャンネルの設定
config = dma_channel_get_default_config(dma_ch);
channel_config_set_transfer_data_size(&config, DMA_SIZE_8); // データサイズを8ビットに設定
channel_config_set_read_increment(&config, true); // 読み取りインクリメントを有効にする
channel_config_set_write_increment(&config, false); // 書き込みインクリメントを無効にする
channel_config_set_dreq(&config, spi_get_dreq(LCD_SPI, true)); // DREQを設定
}
void write_to_lcd(uint8_t *data, size_t len) {
// 描画開始コマンドを送る
write_command(0x2C); // LCDにデータ書き込みの開始を指示
gpio_put(SPI_DEFAULT_DC, 1); // データモードに設定
gpio_put(SPI_BG_FRONT_CS, 0); // チップセレクトをアサート
dma_channel_configure(
dma_ch, // DMAチャンネル
&config, // DMA設定
&spi_get_hw(LCD_SPI)->dr, // SPI送信レジスタ
data, // 転送元データ
len, // 転送するデータの長さ
true // DMA転送を開始する
);
dma_channel_wait_for_finish_blocking(dma_ch); // DMA転送が完了するまで待機
while (spi_is_busy(LCD_SPI)) {
tight_loop_contents(); // SPIがビジー状態でないことを確認
}
gpio_put(SPI_BG_FRONT_CS, 1); // チップセレクトをディアサート
}
void lcd_init() {
// LCDのリセット
gpio_init(SPI_BG_FRONT_RESET);
gpio_set_dir(SPI_BG_FRONT_RESET, GPIO_OUT);
gpio_put(SPI_BG_FRONT_RESET, 0);
sleep_ms(50);
gpio_put(SPI_BG_FRONT_RESET, 1);
sleep_ms(50);
sleep_ms(100);
write_command(0x01); // リセット
sleep_ms(150);
write_command(0x11); // Sleep Out(低電力モードからの起動)
sleep_ms(500);
write_command(0x3A); // Interface Pixel Format(データのカラービットの長さ)
uint8_t data = 0x55; //16ビットカラーを使用するため、0x55を設定
write_data(&data, 1);
// 表示方向制御
write_command(0x36);
uint8_t madctl = 0x60; // 方向に応じて変更
write_data(&madctl, 1);
write_command(0x21); // Inversion ON(明るさ調整)
write_command(0x29); // Display ON
sleep_ms(100);
spi_get_hw(LCD_SPI)->dmacr = SPI_SSPDMACR_TXDMAE_BITS; // DMAを有効化
// なぜか描画位置がずれるのでオフセットを設定
const int X_OFFSET = 40;
const int Y_OFFSET = 53;
set_window(X_OFFSET, Y_OFFSET, PicoDisplay::WIDTH - 1 + X_OFFSET, PicoDisplay::HEIGHT - 1 + Y_OFFSET); // 描画領域を設定
}
正直あんまよく理解してないけど奇跡的に動いてる。
結果

おわりに
多分CMakeの設定次第でエラーが出たりすると思うけど、
そこさえ是正できれば動くコードではあるよ