TinyGo を使うと Go でマイコン開発ができます。
組込開発で goroutine + pin interrupts を使ってみたので紹介します。
(個人的には) 気持ちよく記載することができます。
完成品
- 開始時にスクロールで
TinyGo demo
と表示 - 以降ループ
- 77ms 毎に白い LED を点滅、 LCD 左下のカウンタを更新
- 500ms 毎に LCD 右下のカウンタを更新
- LCD の 1 行目にボタン状態を表示
- PyPortal 本体の LED にボタン状態を表示
TinyGo examples (pin interrupts, goroutine)https://t.co/gGVGw8Mb4h
— takasago (WFH) (@sago35tk) May 17, 2020
環境
- tinygo version 0.14.0-dev windows/amd64 (using go version go1.14.1 and LLVM version 10.0.1)
- PyPortal
- D3 ピン : Switch (with pull-up)
- D4 ピン : LED
- I2C : AE-AQM0802A (I2C 8 x 2 character LCD with st7032)
goroutine 部分
以下のように go timer77ms() などにより 3 つの goroutine を起動しています。
goroutine からは chan 経由で for / select
に処理を依頼します。
aqm0802 (I2C 接続のキャラクタ液晶) に対しては単一の main() 関数のみからアクセスするため、安全に更新できます。
Go らしいすっきりとした記載になっていて、個人的には非常に書きやすいです。
func main() {
// 省略
go timer77ms(chCnt1, led2)
go timer500ms(chCnt2)
go disp(chBtn, chDisp, led1)
for {
select {
case d := <-chDisp:
aqm0802.SetCursor(0, 0)
aqm0802.Print(d.String())
case cnt := <-chCnt1:
aqm0802.SetCursor(0, 1)
aqm0802.Print(fmt.Sprintf("%4d", cnt))
case cnt := <-chCnt2:
aqm0802.SetCursor(4, 1)
aqm0802.Print(fmt.Sprintf("%4d", cnt))
}
time.Sleep(1 * time.Millisecond)
}
}
pin interrputs 部分
※2020/05/17 時点では、まだ merge されていないので注意が必要です
※API も変更になるかもしれません
以下の関数は、入力 pin (実際のマイコンの足に対応する) を引数にとって、 pin の値が変わった時に通知する chan を返す
という動きをします。
pin interrupts では、割込発生時には func(p machine.Pin)
がコールされます。
この func(p machine.Pin)
は、割込にて処理されるため長時間ブロックするような処理を書いてはいけません。
後述のコード例では ch <- b
している個所について select
を使っていますが、万が一の際にブロックしないようにするためです。
また、 ch := make(chan bool, 3)
により少しバッファを持たせているのもブロック対策です。
空きメモリに合わせて多めに取っておくのは一つの手ですし、チャタリングしないようにしておくのも良いです。
内部で var state chvolatile.Register8
という見慣れない表記があります。
その中では p
により、該当ピンの情報を知ることができますが、該当ピンの状態は (処理するまでに) 既に更新されている可能性があります。
なので、ソフト的に処理するようにしています。
#割込が発生しない場合があると、内部状態が食い違ってしまいますが・・・
func pinInterruptChan(pin machine.Pin) <-chan bool {
var state volatile.Register8
ch := make(chan bool, 3)
pin.SetInterrupt(machine.PinToggle, func(p machine.Pin) {
b := false
if state.Get() != 1 {
state.Set(1)
b = true
} else {
state.Set(0)
}
select {
case ch <- b:
default:
}
})
return ch
}
まとめ
TinyGO で goroutine + pin interrupts を使ってみました。
ソースコードは以下にあります。
現状は、 pin interrupts がマージされていないので、開発版の tinygo を自分でビルドする必要があります。