Embedded SwiftをDockerでビルドして、Raspberry Pi Picoを動かす
Appleからswift-embedded-examplesという、Raspberry Pi PicoやESP32-C6、STM32などをSwiftで動かすサンプルが公開されています
これをベースにSwiftでRaspberry Pi Pico Wを動かすのを試してみました
また、Raspberry Pi Picoは書き込みの際、USBメモリとして振る舞うので、専用の書き込みソフトが不要です
そこで、Dockerを利用してビルドし、開発環境を色々いい感じにするのも試してみました
raspberry pi pico wもswiftで動かせた!
— ふじき (@fzkqi) August 4, 2024
なんでもできちゃうじゃん pic.twitter.com/qIBCyewkkO
SwiftをDockerでRaspberry Pi Pico向けにビルドする
Dockerfile
Swiftの開発チームが公開しているイメージをベースにします
FROM swiftlang/swift:nightly-main-jammy
RUN apt-get update && apt-get install -y \
gcc \
cmake \
gcc-arm-none-eabi \
libnewlib-arm-none-eabi \
build-essential \
git \
ninja-build \
python3 \
&& apt-get clean
RUN git clone --depth 1 -b 1.5.1 https://github.com/raspberrypi/pico-sdk.git \
&& cd pico-sdk \
&& git submodule update --init
RUN git clone -b master https://github.com/raspberrypi/pico-examples.git
ENV PICO_SDK_PATH=/pico-sdk
ENV PICO_TOOLCHAIN_PATH=/usr/bin/arm-none-eabi-gcc
WORKDIR /workspace
ENTRYPOINT ["/bin/bash"]
必要なツール類をインストールして適当に設定してます
Raspberry Pi Picoのスタートガイドを参考にしました
Swiftをビルドするために追加でNinjaとPython3が必要だったので追加しました
Dockerイメージをビルドする
docker buildします
Apple Siliconではビルドできなかったので、明示的にアーキテクチャとしてamd64を指定する必要がありました
$ docker build --platform linux/amd64 -t swift-build-env-amd64 .
Swiftをビルド
swift-embedded-examplesのpico-w-blink-sdkをビルドしてみます
print関数でUSB UARTを使用したいので、stdio_init_all()を追加します
@main
struct Main {
static func main() {
stdio_init_all() // 追加
let led = UInt32(CYW43_WL_GPIO_LED_PIN)
if cyw43_arch_init() != 0 {
print("Wi-Fi init failed")
return
}
let dot = {
cyw43_arch_gpio_put(led, true)
sleep_ms(250)
cyw43_arch_gpio_put(led, false)
sleep_ms(250)
}
let dash = {
cyw43_arch_gpio_put(led, true)
sleep_ms(500)
cyw43_arch_gpio_put(led, false)
sleep_ms(250)
}
while true {
dot()
dot()
dot()
dash()
dash()
dash()
dot()
dot()
dot()
}
}
}
作成したイメージを使って、プログラムをビルドします
$ docker run -it --rm -v $(pwd):/workspace swift-build-env-amd64 /workspace/build.sh
swift-embedded-examplesのpico-w-blink-sdkのREADMEを参考にしています
#!/bin/bash
export PICO_BOARD=pico_w
export PICO_SDK_PATH='/pico-sdk'
export PICO_TOOLCHAIN_PATH='/usr/bin/arm-none-eabi-gcc'
cmake -B build -G Ninja .
cmake --build build
CMakeはswift-embedded-examplesを参考に作成しました
ビルドに成功すると、buildディレクトリにuf2ファイルが作成されています
Raspberry Pi Pico Wに書き込み
Raspberry Pi Pico WにあるBOOTSEL(たぶんBoot Select)ボタンを押下しなが、PCと接続します
RPI RP2というUSBデバイスが認識されるので、作成したuf2ファイルをコピペします
動作確認
BOOTSELボタンを押下せずに、再度PCに接続するなどして電源を入れて、Raspberry Pi Pico W上にあるLEDがチカチカしてたら成功です
Xcode上でコーティングする
せっかくSwiftなので、Xcode上でコーディングするのも試してみました
XcodeGenでxcprojectを作成する
Swift 5系ではBridging Headerが指定できないため、XcodeGenを利用してxcprojectを作成するようにしました
CMakeを見ながら必要そうな箇所を適当に設定しました
Xcode上ではpico-sdkの実態まではビルドしないため、リンカのエラーは握り潰し、あくまでXcode上ではSwiftから正しくpico-sdkのC言語のインタフェースを呼び出しているかだけチェックしています
name: swift-raspberry-pi-pico-examples
settings:
OTHER_LDFLAGS:
- "-Wl,-undefined,dynamic_lookup"
targets:
pico-sdk:
type: framework
platform: macOS
sources:
- path: pico-sdk
type: folder
- path: built-headers
type: folder
ex00-pico-w-blink:
type: framework
platform: macOS
sources:
- path: ex00-pico-w-blink
excludes:
- "build/**"
settings:
SWIFT_OBJC_BRIDGING_HEADER: ex00-pico-w-blink/BridgingHeader.h
GCC_PREPROCESSOR_DEFINITIONS:
- "$(inherited)"
- "XCODE_WORKAROUND=1"
- "PICO_DEFAULT_UART_INSTANCE=1"
HEADER_SEARCH_PATHS:
- "$(inherited)"
- "$(PROJECT_DIR)/pico-sdk/src/common/pico_stdlib/include"
- "$(PROJECT_DIR)/pico-sdk/src/rp2_common/hardware_gpio/include"
- ...中略...
- "$(PROJECT_DIR)/pico-sdk/lib/lwip/src/include"
- "$(PROJECT_DIR)/include"
全文はこちら
ビルドを通すためにpico-sdkにパッチをあてる
arm向けのgccがDocker内にしか無い関係でそのままではビルドが通らなかったので、適当にパッチをあてました
Xcodeでビルドされる場合はパッチをあてた方を参照するように、GCC_PREPROCESSOR_DEFINITIONSにフラグを渡します
この辺はもう少しうまくやりたいです
312a313,314
> #elif XCODE_WORKAROUND
> #define __force_inline inline __attribute__((always_inline))
337a340,343
> #ifdef XCODE_WORKAROUND
> #define pico_default_asm(...) __asm ("")
> #define pico_default_asm_volatile(...) __asm volatile ("")
> #else
339a346
> #endif
まとめ
Appleから公開されたswift-embedded-examplesを参考に、SwiftをDockerでビルドしてRaspberry Pi Pico Wを動かしてみました
すんなりとDockerでビルドできて感動しました
Swiftの使える箇所が増えると、夢が広がっていきます