144labの入江田です。
今回TinyGoという素敵なプロダクトを試してみました。
TinyGoとは
TinyGoは本家Go言語の組み込み向けのサブセット版。
本家Go言語はPOSIX-OSに対する機能依存が大きく、リッチなランタイムを持っています。その為、本来はOSを持たない組み込み用途には不向きでした。
TinyGoはPOSIX-OSに依存する機能を簡易的な実装で代用しつつLLVM(コンパイラを作る為のフレームワーク)を使って組み込み向けアーキテクチャをサポートするGo言語のコンパイラです。
つまりサポートするCPUアーキテクチャはLLVMがサポートするものを前提にしています。
サポートアーキテクチャ
- ARMのCortex-M0系
- WebAssembly
- AVR系(まだ荒くバグっぽいらしい)
追加予定のアーキテクチャ
- Xtensa(espressifがLLVM実装鋭意対応中)
現状の主なサポートターゲット
- Arduino Uno
- BBC:Microbit
- BLUEPILL
- CIRCUIT PLAYGROUND EXPRESS
- DIGISPARK
- ITSYBITSY M0
- PCA10031
- PCA10040
- PCA10056
- NRF52840-MDK
- REEL BOARD
- WebAssembly
Nordic系チップへの対応が優先的に進んでいます。 WebAssembly出力はLLVMがサポートしていたこともあり副産物的にサポートされました。
余談
WebAssemblyをサポートしたことでTinyGoのコントリビュータは急激に増えています。
本家のGo言語は最近WebAssembly出力が可能になったんですが、成果物のサイズがTinyGoの方が十分の一程度とコンパクトなのでWebへの利用が拡大しそうな兆候が現れてきています。
サンプルを動かしてみる
本来、Go言語製プロダクトや本体はどのPC-OS上でもビルドが容易という特徴があるのですが、TinyGoはLLVMと連携する為、C/C++の依存ライブラリの解決が必要です。なのでソースからビルドするのは少し大変です。
そこでdockerを使います。 hub.docker.comには常に最新のTinyGoのビルド済みイメージが公開されています。
https://hub.docker.com/r/tinygo/tinygo
これを利用するにはdockerをセットアップ済みであれば簡単です。 dockerのセットアップについては https://docs.docker.com/install/ こちら。
TinyGoは本家Goと違い、必ずパッケージ単位でビルドします。 なのでsampleというパッケージフォルダの配下にソースをおきます。
sample/sample.go
package main import ( "machine" "time" ) func main() { machine.InitLEDMatrix() left := machine.GPIO{machine.BUTTONA} left.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT}) right := machine.GPIO{machine.BUTTONB} right.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT}) var ( x uint8 = 2 y uint8 = 2 ) machine.ClearLEDMatrix() for { if !left.Get() { switch { case x > 0: x-- case x == 0: if y > 0 { x = 4 y-- } } } if !right.Get() { switch { case x < 4: x++ case x == 4: if y < 4 { x = 0 y++ } } } machine.SetLEDMatrix(x, y) time.Sleep(time.Millisecond * 100) } }
ビルド
ターゲットは-target <ターゲット>
オプションで指定します。
- arduino
- microbit
- bluepill
- circuitplay-express
- digispark
- itsybitsy-m0
- pca10031
- pca10040
- pca10056
- nrf52840-mdk
- reelboard
- wasm
micro:bit用のビルドをする場合、以下のようなコマンドでビルドできます。 (初回はhub.docker.comからのダウンロードが実行されるので少し時間がかかります。)
docker run -it --rm -v $PWD:/src -w /src -e GOPATH=/ tinygo/tinygo tinygo build -target microbit -o sample.hex sample
動作
こうしてできたsample.hexをmicrobitに書き込めば動いちゃった。
周辺チップドライバ
ここに順次追加されています https://github.com/tinygo-org/drivers
Currently supported devices
Device Name | Interface Type |
---|---|
APA102 RGB LED | SPI |
BH1750 ambient light sensor | I2C |
BlinkM RGB LED | I2C |
BMP180 barometer | I2C |
"Easystepper" stepper motor controller | GPIO |
ESP8266/ESP32 AT Command set for WiFi/TCP/UDP | UART |
MAG3110 magnetometer | I2C |
MMA8653 accelerometer | I2C |
MPU6050 accelerometer/gyroscope | I2C |
WS2812 RGB LED | GPIO |
感想
TinyGoはLLVMの強力な支援の下、とてもコンパクトな出力が得られます。 上記のサンプルではHEXファイルサイズが6KBです。 これだけ小さいと書き込みも一瞬です。
ちなみにMakeCode(pxt)経由で同じことを実装すると 最低でも600KBクラスのhexファイルができます。
ここはコンパイラ型言語と動的言語(JS/TS)の違いが極端に出るのです。コンパイラ型言語では利用しないコードを成果物から綺麗に除去することができ、コンパクトな成果が得られますが、動的言語処理系は豊富なランタイム機能のどれを使わないかは明確にすることが困難なのでとりあえず使う可能性のあるものは全て成果物に含めなければなりません。
またコンパイラ型言語の場合かなりのケアレスミスをコンパイル時に的確に指摘してくれます。さらにエディタにGo言語の解析ツールを入れればコード補完や事前チェックでおかしなところを指摘してくれます。
WebIDEほど簡単ではないけれど、dockerのおかげで環境構築も簡単かつ短時間(10数秒ほど)で確保できます(micro:bitのArduino環境構築は5〜10分くらいかかっちゃう)。
あとGo言語の特徴なんですが、とにかく型の種類が少なく役割が明確なので使い分けに迷わずに済みます。Arduino(C/C++)の場合文字列の型だけでもchar*
、wchar_t*
、string
、wstring
、String
、に加え、const
、const&
修飾などいろんな組み合わせのものが混在しています。Goは基本string
だけでOKです。
さらにコンパイルが一瞬です。dockerコンテナをクリーンする時間の方が長いくらい。
正直C/C++はポータビリティ確保しようとするとコストが跳ね上がるし依存解決も開発環境ごとに苦労は絶えない。ArduinoIDEがだいぶ簡単にしてくれてるとはいえ、ヘッダやライブラリ探索の仕組みも複雑すぎるのでトラブルが起こった時の対応がとても難しい。
まとめ
- 開発環境構築が簡単かつ早い
- ビルドが一瞬
- コンパイルエラーや事前チェックが的確
- GPIO/SPI/I2C/UARTの抽象化がよく出来てる
- 成果物がコンパクトかつ遅くない
また、現在TinyGoは開発が活発になってきていて、毎週のように機能改善が上がってきています。
さらにGo言語の良いところの大部分を継承しています。
- シンプルな言語仕様(本の厚みにすればC++は異常に分厚くてその差は十倍以上あると思う)
- 型安全でC/C++にある「未定義の振る舞い」のようなものは存在しないし見つかれば塞がれていく。びっくり挙動がない訳ではないけれどそれらにもちゃんと定義がある。
- 豊富な開発支援ツールの一部が使える。ユニットテストやコード補完、コードチェッカなど。
- ジェネリックなsliceやmapコンテナを持っているので別途コンテナライブラリとかが必要にならない。
- 文字の記述もメモリ表現もUTF-8ベースなのでインターネットプロトコルとの親和性が高い。
- ソースは追いかけやすく、どこまでも読みやすい。
さあ、あなたもGo言語を使って組み込み開発を始めてみませんか?
TinyGo 0.4 ワークアラウンド
修正されたTinyGo 0.4.1がリリースされましたのでこのワークアラウンドは不要になりました!
2019/03/13 時点での最新TinyGo 0.4 にてちょっと問題があります。
状況によってllvm-arが見つからないと怒られるのですが、名称がミスマッチなだけでした。
Dockerfile
FROM tinygo/tinygo:latest RUN ln -s /usr/bin/llvm-ar-7 /usr/bin/llvm-ar
こうして手を入れたイメージでビルドしましょう!