TinyGoで始める組み込みプログラミング

144labの入江田です。

今回TinyGoという素敵なプロダクトを試してみました。

TinyGoとは

TinyGoは本家Go言語の組み込み向けのサブセット版。

本家Go言語はPOSIX-OSに対する機能依存が大きく、リッチなランタイムを持っています。その為、本来はOSを持たない組み込み用途には不向きでした。

TinyGoはPOSIX-OSに依存する機能を簡易的な実装で代用しつつLLVMコンパイラを作る為のフレームワーク)を使って組み込み向けアーキテクチャをサポートするGo言語のコンパイラです。

つまりサポートするCPUアーキテクチャLLVMがサポートするものを前提にしています。

サポートアーキテクチャ

  • ARMのCortex-M0系
  • WebAssembly
  • AVR系(まだ荒くバグっぽいらしい)

追加予定のアーキテクチャ

現状の主なサポートターゲット

  • 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:bitArduino環境構築は5〜10分くらいかかっちゃう)。

あとGo言語の特徴なんですが、とにかく型の種類が少なく役割が明確なので使い分けに迷わずに済みます。Arduino(C/C++)の場合文字列の型だけでもchar*wchar_t*stringwstringString、に加え、constconst&修飾などいろんな組み合わせのものが混在しています。Goは基本stringだけでOKです。

さらにコンパイルが一瞬です。dockerコンテナをクリーンする時間の方が長いくらい。

正直C/C++はポータビリティ確保しようとするとコストが跳ね上がるし依存解決も開発環境ごとに苦労は絶えない。ArduinoIDEがだいぶ簡単にしてくれてるとはいえ、ヘッダやライブラリ探索の仕組みも複雑すぎるのでトラブルが起こった時の対応がとても難しい。

まとめ

  • 開発環境構築が簡単かつ早い
  • ビルドが一瞬
  • コンパイルエラーや事前チェックが的確
  • GPIO/SPI/I2C/UARTの抽象化がよく出来てる
  • 成果物がコンパクトかつ遅くない

また、現在TinyGoは開発が活発になってきていて、毎週のように機能改善が上がってきています。

  • ドキュメントの拡充
  • サポートターゲットの拡充
  • サポート周辺デバイスの拡充
  • Go言語特有の非同期並行処理がマイコンでも利用可能になりつつある

さらに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

こうして手を入れたイメージでビルドしましょう!

adafruitのブログでTinyGoが取り上げられました!

https://blog.adafruit.com/2019/03/12/tinygo-go-on-microcontrollers-tinygolang-tinygo-runs-on-adafruit-circuit-playground-express/