Embedded Swift Swiftでマイコン(ESP32)を動かす
Appleにより、Embedded SwiftでESP32を動かすサンプルが公開されました
このサンプルを試してみた際のTipsと、デジタル入出力、サーボモータ、LCDを実装してみた際の手順を紹介します
デジタル入出力、サーボモータ、LCDを実装してみたリポジトリはこちらです
デジタル入出力の動作の様子
SwiftでESP32動かせた!
— ふじき (@fzkqi) July 30, 2024
マイコン動かせると夢が広がっちゃう pic.twitter.com/bcbcI2SAuK
こちらのボードで動作確認しました
swift-matter-examplesを試してみる
環境構築
swift-matter-examplesのチュートリアル通りに進めれば大丈夫でした
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が大変参考になりました
使いたいコンポーネントのサンプルコードをC言語からSwiftに書き換え、シンボルの参照に必要なヘッダをBridging Headerに追加します
例えば、デジタル入出力では <driver/gpio.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の設定に必要な関数を利用できます
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を利用する場合は下記を追加します
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のコードはこちらのリポジトリにあります
Xcode上でプログラミングする
せっかくなので、Xcode上でのプログラミングにも実験的にチャレンジしてみました
Swift 5系では、Swift Package ManagerでBridging Headerを使えないので、xcodegenでxcodeprojを作成するようにしました
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で本腰を入れてマイコン開発するのは難しそうですが、とても可能性を感じました