Qman's Diary

多趣味人間の備忘録

2024-12-01

Raspberry Pi Picoのビルド環境をポータブル化してみた

タグ
技術
Advent Calendar
イベント

Qiita全国学生対抗戦

Raspberry Pi Picoの開発環境の一つとして、公式が提供するC/C++ SDKがある。開発環境の構築も、Windowsならインストーラが用意されているので、そこまで苦労することではない(特別なショートカットを介してVSCodeを起動しないといけないのはちょっとどうかと思うが)。

しかし、インストーラでできる開発環境はポータブルではない。当然といえば当然かもしれないが、今回、開発環境(より正確に言えばビルドできる環境)をポータブル化するニーズが生じてしまった。それは即ち、インストーラがやってくれていたことを自分でやる必要があるということになる。

プロジェクトの詳細については言及しないが、せっかくなので開発環境をポータブル化する方法について備忘録を残そうと思う。最終的には、完全にまっさらなWindows Sandbox上で.uf2ファイルのビルドに成功したので、ポータブル化に成功したと言っても過言ではないと思う。

OSはWindows 10/11、Pico SDKのバージョンは1.5.1とする。なお、CPUはx86_64を前提としており、ArmのWindowsについてはツールチェーンが存在するかどうか保証できない。

依存関係をかき集める

ビルドにはいくつものソフトが必要になる。具体的には以下の通り。やるだけならGitは必要なかった。

  • gcc(arm-none-eabi)
  • CMake
  • Ninja
  • Python3
  • (ソフトではないが)pico-sdk、pico-example

それぞれ、ダウンロードする手順を示す。前述の通り、ここでダウンロードしているのはx86_64向けのツールであり、Armの場合は別に用意されたツールがあればそれを利用する必要がある。

gcc(arm-none-eabi)

バージョン: 13.3.Rel1(執筆時最新版)

クロスコンパイラ。名前の通りArm向けのバイナリを吐き出す。

ここから適当に最新版(arm-gnu-toolchain-[バージョン名]-mingw-w64-i686-arm-none-eabi.zip)をダウンロードする。普通に検索するとDeprecatedになったサイトが出てくるので注意。展開したディレクトリを利用する。

ちなみに総容量1.3GB中1.2GBがコイツ。なんとかならんか……

CMake

バージョン: 3.31.0(執筆時最新版)

ビルドツール。SDKが採用している。

ここから「Windows x64 zip(cmake-[バージョン名]-windows-x86_64.zip)」をダウンロードして展開する。

Ninja

バージョン: 1.12.1(執筆時最新版)

CMakeなどのビルドツールから使われることを意識した軽量ビルドツール。ガチで軽量なので本体はexe1個。

ここから「ninja-win.zip」をダウンロードして展開する。

Python3

バージョン: 3.13.0(執筆時最新版)

なんかビルドプロセスで必要とされていた。正直よくわかっていないが必要みたいなのでダウンロードする。

ここから必要なバージョンの詳細ページに移動して、「Windows embeddable package (64-bit)」をダウンロードして展開する。

pico-sdk、pico-example

バージョン: 1.5.1

GitHubからダウンロードする。

pico-sdkはここ、pico-exampleはここ。デフォルトではmasterブランチが表示されているので、Tagsから欲しいバージョン(今回は1.5.1)に移動して、右上のCodeボタンから「Download ZIP」を選ぶ。

pico-sdkに関しては、libディレクトリの中にサブモジュールが存在している。TinyUSBなどが必要になる場合は、そのサブモジュールとなっているリポジトリについても同様にダウンロードし、該当ディレクトリ内に展開しておくこと。

ディレクトリを配置する

ここまでで用意したディレクトリ群はバージョン名やアーキテクチャ名などがくっついているやたら長い名前になっている。削ってしまっても問題はないので、ディレクトリをツール名だけにしてしまうことにする。

そして、それらをプロジェクトディレクトリに配置する。プロジェクトディレクトリは(多分)どこに置いても良い。このディレクトリの名前がそのまま最後に吐き出されるuf2ファイルの名前になる。

project-name/
  cmake/
  gcc-arm-none-eabi/
  ninja/
  pico-examples/
  pico-sdk/
  python/

必要なファイルを用意する

CMakeLists.txtを書く

元々どこかからコピペしてきたものを改変したものだった気がするが、出どころを忘れたので以下に記載する。

cmake_minimum_required(VERSION 3.30)
set(FAMILY rp2040)
include(${CMAKE_CURRENT_SOURCE_DIR}/pico_sdk_import.cmake)

# TinyUSBを使う場合の記述
include(${PICO_SDK_PATH}/lib/tinyusb/hw/bsp/family_support.cmake)

family_get_project_name(PROJECT ${CMAKE_CURRENT_LIST_DIR})
project(${PROJECT})
family_initialize_project(${PROJECT} ${CMAKE_CURRENT_LIST_DIR})

add_executable(${PROJECT})

# ./srcをSRC_DIRとして指定
set(SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src")

# SRC_FILESにSRC_DIRの中にある.cをすべて指定
file(GLOB SRC_FILES "${SRC_DIR}/*.c")

target_sources(${PROJECT} PUBLIC
        ${SRC_FILES}
        )

# ./src/libもincludeする(弊プロジェクトでは./src/libにヘッダファイルを置いていたため)
target_include_directories(${PROJECT} PUBLIC
        ${CMAKE_CURRENT_SOURCE_DIR}/src/lib
        )

family_configure_target(${PROJECT})

# Pico SDKの標準ライブラリはここに追加していく。TinyUSBなども"tinyusb_device"のような形で追加できる。
target_link_libraries(${PROJECT} PUBLIC hardware_adc pico_stdlib) 

pico_enable_stdio_usb(${PROJECT} 0)

コメントが付加されている部分は任意に改変して良い。特にsrc周りは今後のディレクトリの構造を決定してしまうので注意。このsrcの中に肝心のソースコードを入れる。

pico_sdk_import.cmakeをコピーしてくる

pico-sdk/externalディレクトリの中にpico_sdk_import.cmakeがある。これをコピーして、プロジェクトディレクトリ直下に置く。

project-name/
  cmake/
  gcc-arm-none-eabi/
  ninja/
  pico-examples/
  pico-sdk/
  python/
+ src/
+   〇〇.c
+   lib/
+     〇〇.h
+ CMakeLists.txt
+ pico_sdk_import.cmake

ビルド用のPowershellスクリプトを書く

ビルドプロセスはいくつかのコマンドの集合体である。さらにはファイルコピーのような過程も入るし、環境変数も用意しなければいけない。今回はPowershellでスクリプトを記述した。

Function LoggerOrig($message) {
    Write-Output "[PICO PROJECT BUILDER] $message"
}

$currentDir = (Get-Location).Path
$Env:PICO_SDK_PATH = "${currentDir}/pico-sdk"
$Env:PICO_EXAMPLES_PATH = "${currentDir}/pico-examples"
$Env:CC = "${currentDir}/gcc-arm-none-eabi/bin/arm-none-eabi-gcc.exe"
$Env:CXX = "${currentDir}/gcc-arm-none-eabi/bin/arm-none-eabi-g++.exe"
$originalPath = $Env:PATH
$Env:PATH = "${currentDir}/gcc-arm-none-eabi/bin/;${currentDir}/ninja;${currentDir}/python;${currentDir}/cmake/bin;"


# remove build directory if exists
if (Test-Path "${currentDir}/build") {
    LoggerOrig "Removing build directory..."
    Remove-Item -Recurse -Force -Path "${currentDir}/build"
}

# make build directory
LoggerOrig "Creating build directory..."
mkdir "${currentDir}/build" > $null

# copy pico-sdk to build
LoggerOrig "Copying pico-sdk to build directory..."
Copy-Item -Recurse -Path "${currentDir}/pico-sdk" -Destination "${currentDir}/build/pico-sdk"

LoggerOrig "Executing cmake and ninja..."
cmake -B "build" -G Ninja
Set-Location "${currentDir}/build"
ninja
LoggerOrig "Done!"
Set-Location $currentDir
$Env:PATH = $originalPath

【未検証】どうもps1ファイルに日本語を含めると文字化けした気がする。そのためここではコメントやログなどもすべて英語で記述している。

このスクリプトが実行しているのは、以下の処理である。

  1. 環境変数PICO_SDK_PATHPICO_EXAMPLES_PATHCCCXXを設定する。前者二つはRaspberry Pi Pico SDK関連のものであり、後者はCおよびC++コンパイラを指定している。これによりCMakeの引数でコンパイラを渡さずとも済むので、スクリプトの見やすさが向上する(気がする)。

元の環境変数PATHを書き換え、プロジェクトディレクトリ内に入れたツールだけを参照するようにする。

  1. buildディレクトリがあったら一旦中身ごと消去する。キャッシュのせいで設定が変更されないことがあるため。
  2. buildディレクトリを再度作り直す。
  3. pico-sdkディレクトリをbuildディレクトリ直下にコピーする。
  4. CMakeとNinjaでビルドを行う。
  5. PATHを元に戻す。このスクリプトを単体で一度実行するだけなら不要だが、VSCodeのターミナル上でこれを実行すると書き換わってしまって面倒なのでこの手順を踏んでいる。

elf2uf2をあらかじめ用意する

ビルドする

前節までの状態でビルドを試みても失敗してしまう。これは、elf2uf2(文字通り、elfファイルをuf2ファイルに変換するツール)をビルドしようとして失敗しているからだ。elf2uf2自体はRP2040上ではなくホストのPCで実行するものであり、そしてelf2uf2が見つからなかった場合はその場でビルドしようとするようになっている。あらかじめ用意したツールチェーン(gccなど)はRP2040のためのものなので、ホストPC向けビルドは当然失敗して停止してしまう。

ということで、あらかじめelf2uf2の実行バイナリを用意する。これ自体は今回用意したプロジェクトの外側で行うこと。ここでは、たまたまPCにあったMinGW-w64のg++を使ってビルドする。他の環境でのやり方は各自で調べてほしい。

ファイル自体はGitHubのリポジトリから引っ張ってくる。pico-sdkにも含まれているが必要なファイルが少し離れた場所にあって面倒。とりあえず、main.cppに加えelf.huf2.hがあれば良い。揃ったら、以下のコマンドでビルドする。

g++ -o elf2uf2 main.cpp -static -lstdc++ -lgcc

MinGWに依存しないように、内部で利用しているDLLを静的にリンクしている(Dependenciesを使って確認すると、ちゃんとWindowsに元から入ってそうなDLLにだけ依存していることがわかる)。

バイナリができたら、pico-sdk/tools/elf2uf2/直下にビルドされたelf2uf2.exeを配置する。実際のところどこに置いても良いのだが、後述の処理でそう指定しているため今回は場所を指定した。

elf2uf2を探す処理を書き換える

今のままではまだelf2uf2をうまく探してくれないので、そもそもelf2uf2を探そうとする処理を無効化してしまうことにする。

pico-sdk/tools/FindELF2UF2.cmakeを開き、以下のように全て書き換える。

if (NOT ELF2UF2_FOUND)
    add_executable(ELF2UF2 IMPORTED)   
    set_property(TARGET ELF2UF2 PROPERTY IMPORTED_LOCATION ${PICO_SDK_PATH}/tools/elf2uf2/elf2uf2)
    set(ELF2UF2_FOUND 1)
endif()

元々、このcmakeファイルは、ビルドなどをして最終的にelf2uf2のバイナリを用意した後、ELF2UF2_FOUND変数を0から1に書き換える。そこで、その探したりビルドしたりするプロセスを丸ごと消し飛ばして、必要なプロパティだけ設定して変数を書き換えてしまえば、意図したように動作するという仕組み……だと思う。

この手順はほぼすべてRaspberry Pi Forumsからのコピペである。いくら探してもこのフォーラム以外には記事がなく、しかもこのフォーラム自体はLinuxを想定したものだったようなので、マジで一歩間違えてたら一生彷徨ってたかもしれない。

なお、元のフォーラムにおいてはpioasmも同様にビルドしたいというニーズがあったようだ。今回は触っていないが、pico-sdk/tools/にはpioasmのコードとFindPioasm.cmakeファイルもあるので、似たような手法であらかじめビルドしておくと良いかもしれない。

まとめ

これで、環境は完成した。実際にPowershellスクリプトを動作させてみると、buildディレクトリ内にuf2ファイルが生成されているのがわかる。少なくともビルドだけであれば、Windows Sandbox上でさえ動作するはずだ。

今回はビルド環境をポータブル化したが、うまく応用すれば開発環境も同様にポータブル化できそうな雰囲気がある。現状VSCodeを特殊なショートカットを経由して起動しないといけないのは環境変数周りの問題が大きそうな気がするので、なんとかならないだろうか?

ちなみに、このショートカットはとあるPowershellスクリプトを介して様々な設定を行った上でVSCodeを起動するという仕組みになっている。見た感じレジストリにもなにか触っているようなので、簡単になんとかするわけにはいかなそうな雰囲気がある。

今回は、卒業制作のなかで得られた知見を記事に起こした。来年からは学生としてこのアドベントカレンダーに参加することがなくなると考えると少々寂しい気持ちにもなる。それでもやはり、知見をアウトプットするという作業にはかけがえのない価値があると思うので、これからも何らかの形でアドベントカレンダーに参加したい。

Recent Articles
>> キューマンのコンテンツ置き場

Profile

オタクコンテンツで命を繋いでいる人間

Accounts

Category