Embedded Swift Swiftでマイコン(ESP32)を動かす

# Embedded Swift Swiftでマイコン(ESP32)を動かす

Appleにより、[Embedded SwiftでESP32を動かすサンプル](https://github.com/apple/swift-matter-examples)が公開されました
このサンプルを試してみた際のTipsと、デジタル入出力、サーボモータ、LCDを実装してみた際の手順を紹介します

デジタル入出力、サーボモータ、LCDを実装してみたリポジトリはこちらです

https://github.com/fuziki/swift-esp32-examples

デジタル入出力の動作の様子

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">SwiftでESP32動かせた！<br>マイコン動かせると夢が広がっちゃう <a href="https://t.co/bcbcI2SAuK">pic.twitter.com/bcbcI2SAuK</a></p>&mdash; ふじき (@fzkqi) <a href="https://twitter.com/fzkqi/status/1818094167152812433?ref_src=twsrc%5Etfw">July 30, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

こちらのボードで動作確認しました

https://akizukidenshi.com/catalog/g/g117846/

# swift-matter-examplesを試してみる
## 環境構築

swift-matter-examplesのチュートリアル通りに進めれば大丈夫でした

https://apple.github.io/swift-matter-examples/tutorials/tutorial-table-of-contents/

1点ハマったポイントとして、`espressif/esp-idf` と `espressif/esp-matter.git` を `~/esp` にcloneするステップがあります
これらのリポジトリをユーザのホームディレクトリ直下のespディレクトリ以外にcloneした場合、手元ではうまく環境構築できませんでした
一方で `swift-matter-examples` など実際に実装するコードはどのディレクトリにあっても問題ないようです

Docker環境は、macOSはUSBのパススルーがないですし、自分には要らないかな？と思いスキップしています
macOS上に環境を作って問題ないのであれば、スキップしても問題なさそうでした

## 動作確認
必要な環境変数を追加します
これはターミナルを起動して、毎度1回だけ実行する必要があります

```
$ export TOOLCHAINS=org.swift.59202407091a
$ . ~/esp/esp-idf/export.sh
$ . ~/esp/esp-matter/export.sh
```

cloneしたswift-matter-examplesのサンプルのディレクトリに移動します

```
$ git clone https://github.com/apple/swift-matter-examples.git
$ cd swift-matter-examples/smart-light
```

esp32c6をターゲットに設定します
このターゲットの設定は実際に動作させたいプログラムのディレクトリごとに実行する必要があるようです

```
$ idf.py set-target esp32c6
```

ESP32をPCに接続し、ビルドして書き込みます

```
$ idf.py build flash monitor
```

成功するとターミナルに `🏎️   Hello, Embedded Swift!` と表示されています

# swift-matter-examples以外の使い方を試してみる
swift-matter-examplesのLチカ以外に、デジタル入出力、サーボモータ、LCDを試してみました
espressifが公開しているesp-idfのexamplesが大変参考になりました

https://github.com/espressif/esp-idf/tree/master/examples

使いたいコンポーネントのサンプルコードをC言語からSwiftに書き換え、シンボルの参照に必要なヘッダをBridging Headerに追加します
例えば、デジタル入出力では `<driver/gpio.h>` を追加します

```BridgingHeader.h
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <led_strip.h>
#include <sdkconfig.h>
#include <nvs_flash.h>
#include <led_driver.h>
#include <device.h>
#include <driver/gpio.h> // 追加
```

これで、　`gpio_reset_pin` などGPIOの設定に必要な関数を利用できます

```swift
let ledPin: gpio_num_t = GPIO_NUM_21
let buttonPin: gpio_num_t = GPIO_NUM_22

@_cdecl("app_main")
func app_main() {
    // LED
    gpio_reset_pin(ledPin);
    gpio_set_direction(ledPin, GPIO_MODE_OUTPUT)

    // Button
    gpio_reset_pin(buttonPin);
    gpio_set_direction(buttonPin, GPIO_MODE_INPUT)
    gpio_set_pull_mode(buttonPin, GPIO_PULLUP_ONLY)

    while true {
        if gpio_get_level(buttonPin) == 0 {
            for _ in 0..<5 {
                gpio_set_level(ledPin, 1)
                sleep(1)
                gpio_set_level(ledPin, 0)
                sleep(1)
            }
        }
        sleep(1)
    }
}
```


また、esp-idfに含まれていないコンポーネント（例えばLCD）を利用したい場合は、`idf_component.yml` に追加します
例えば、LCDを利用する場合は下記を追加します

```idf_component.yml
dependencies:
  espressif/cmake_utilities:
    version: 0.*
    rules: # will add "optional_component" only when all if clauses are True
      - if: "idf_version >=5.0"
      - if: "target in [esp32c2]"
  lvgl/lvgl: "~8.3.0" # 追加
  esp_lvgl_port: "^1" # 追加
```

esp-idfに詳しくないのですが、おそらくC言語で開発する場合と遜色なく、esp-idfの資源を利用できそうでした
また、SwiftがC言語のAPIを十全に呼び出せるため、困った際はC言語の例を調べて、
Swiftに書き換えれば大丈夫そうでした
今回、実装してみたデジタル入出力、サーボモータ、LCDのコードはこちらのリポジトリにあります

https://github.com/fuziki/swift-esp32-examples

# Xcode上でプログラミングする
せっかくなので、Xcode上でのプログラミングにも実験的にチャレンジしてみました
Swift 5系では、Swift Package ManagerでBridging Headerを使えないので、xcodegenでxcodeprojを作成するようにしました

```project.yml
name: swift-esp32-examples
settings:
  OTHER_LDFLAGS:
    - "-Wl,-undefined,dynamic_lookup"
targets:
  ex00-empty-template:
    type: framework
    platform: macOS
    sources:
      - path: ex00-empty-template/main/
        group: ex00-empty-template
      - path: ex00-empty-template/
        buildPhase: none
        excludes:
          - build/**
    settings:
      SWIFT_OBJC_BRIDGING_HEADER: ex00-empty-template/main/BridgingHeader.h
      HEADER_SEARCH_PATHS:
        - "$(inherited)"
        - "$(PROJECT_DIR)/headers/**"
```

ビルドターゲットにESP32を設定できないので、frameworkをターゲットにしました
また、Xcode上でのframeworkビルド時に、esp-idf系もビルドされると大変なので、シンボルを正しく利用しているかヘッダで検証だけして、ビルド時のリンクではスキップするようにしました
Xcode上でシンボルを検証するためのヘッダは、esp-idfなどのリポジトリをXcodeで開こうとすると固まる問題があったため、必要そうなヘッダのみをコピーして持ってきました
ESP32に書き込む際は、毎回ターミナルで `idf.py build` を打ち込んでおり、この辺はもう少し改良の余地がありそうです

# まとめ
swift-matter-examplesを動かしてみて、それをベースにデジタル入出力、サーボモータ、LCDを試してみました
環境構築後は大きな問題もなくすんなり動きました
SwiftはC言語のAPIを十全に呼び出せるため、espの資源を活用できそうです
まだまだ、Swiftで本腰を入れてマイコン開発するのは難しそうですが、とても可能性を感じました