Embedded SwiftでRaspberry Pi Picoを動かす、Dockerでビルドして

# Embedded SwiftをDockerでビルドして、Raspberry Pi Picoを動かす

Appleから[swift-embedded-examples](https://github.com/apple/swift-embedded-examples)という、Raspberry Pi PicoやESP32-C6、STM32などをSwiftで動かすサンプルが公開されています
これをベースにSwiftでRaspberry Pi Pico Wを動かすのを試してみました
また、Raspberry Pi Picoは書き込みの際、USBメモリとして振る舞うので、専用の書き込みソフトが不要です
そこで、Dockerを利用してビルドし、開発環境を色々いい感じにするのも試してみました

https://github.com/fuziki/swift-raspberry-pi-pico-examples

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">raspberry pi pico wもswiftで動かせた！<br>なんでもできちゃうじゃん <a href="https://t.co/qIBCyewkkO">pic.twitter.com/qIBCyewkkO</a></p>&mdash; ふじき (@fzkqi) <a href="https://twitter.com/fzkqi/status/1819930616919634271?ref_src=twsrc%5Etfw">August 4, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


# SwiftをDockerでRaspberry Pi Pico向けにビルドする
## Dockerfile
[Swiftの開発チームが公開しているイメージ](https://github.com/swiftlang/swift-docker)をベースにします

```Dockerfile:Dockerfile
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のスタートガイド](https://datasheets.raspberrypi.com/pico/getting-started-with-pico-JP.pdf)を参考にしました
Swiftをビルドするために追加でNinjaとPython3が必要だったので追加しました

## Dockerイメージをビルドする
docker buildします
Apple Siliconではビルドできなかったので、明示的にアーキテクチャとしてamd64を指定する必要がありました

```sh
$ docker build --platform linux/amd64 -t swift-build-env-amd64 .
```

## Swiftをビルド
[swift-embedded-examplesのpico-w-blink-sdk](https://github.com/apple/swift-embedded-examples/tree/main/pico-w-blink-sdk)をビルドしてみます
print関数でUSB UARTを使用したいので、stdio_init_all()を追加します

```Main.swift
@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()
        }
    }
}
```

作成したイメージを使って、プログラムをビルドします

```sh
$ docker run -it --rm -v $(pwd):/workspace swift-build-env-amd64 /workspace/build.sh
```

swift-embedded-examplesのpico-w-blink-sdkのREADMEを参考にしています

```build.sh
#!/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を参考に作成しました

https://github.com/fuziki/swift-raspberry-pi-pico-examples/blob/main/ex00-pico-w-blink/CMakeLists.txt

ビルドに成功すると、buildディレクトリにuf2ファイルが作成されています

## Raspberry Pi Pico Wに書き込み
Raspberry Pi Pico WにあるBOOTSEL(たぶんBoot Select)ボタンを押下しなが、PCと接続します
RPI RP2というUSBデバイスが認識されるので、作成したuf2ファイルをコピペします

![copy2pi.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214313/ff02702b-8f63-ee09-dc4c-82d08737c145.png)

## 動作確認
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言語のインタフェースを呼び出しているかだけチェックしています

```project.yml
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"
```

全文はこちら

https://github.com/fuziki/swift-raspberry-pi-pico-examples/blob/main/project.yml

## ビルドを通すためにpico-sdkにパッチをあてる
arm向けのgccがDocker内にしか無い関係でそのままではビルドが通らなかったので、適当にパッチをあてました
Xcodeでビルドされる場合はパッチをあてた方を参照するように、GCC_PREPROCESSOR_DEFINITIONSにフラグを渡します
この辺はもう少しうまくやりたいです

```patch
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の使える箇所が増えると、夢が広がっていきます