Lチカとはマイコンの基本とされる制御でLEDのON/OFFを繰り返しチカチカと点滅させるプログラムである。複数のLEDでLチカを実装するのは意外に難しいが、TinyGoのgoroutineを使えば容易に実現できる。
この記事ではハードウェアにzero-kb02を使用した実装例を紹介する。
ハードウェア構成
zero-kb02はRP2040チップを搭載したキーボードである。今回は、このキーボードをマイコンボードとして利用する。外部出力されているGPIO14とGPIO15にLEDを接続した構成で使用した。同じRP2040を使用しているRaspberry Pi Picoなどでも同様に動作する。
普通のLチカ
まずGPIO14に接続されているLEDを200msecごとに点灯と消灯を繰り返してみる。よく入門記事で紹介されているLチカのコードである。他の言語でも、おおむね似たようなコードになる。
package main
import (
"machine"
"time"
)
func main() {
led := machine.GPIO14
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
for {
led.High()
time.Sleep(time.Millisecond * 200)
led.Low()
time.Sleep(time.Millisecond * 200)
}
}
非同期Lチカ(タイマー監視)
前述のLチカに加えて、もう一つLEDを接続し300msecで点滅させることを考える。いくつか方法はあるが簡単なのは次のコードだ。タイマーの経過時間を常時監視、それぞれのタイミングでOn/Offを行っている。
package main
import (
"machine"
"time"
)
func toggle(pin *machine.Pin) {
if pin.Get() == false {
pin.Set(true) // 点灯
} else {
pin.Set(false) // 消灯
}
}
func main() {
// GPIO14とGPIO15を出力モードに設定
ledA := machine.GPIO14
ledB := machine.GPIO15
ledA.Configure(machine.PinConfig{Mode: machine.PinOutput})
ledB.Configure(machine.PinConfig{Mode: machine.PinOutput})
var lastToggleA time.Time
var lastToggleB time.Time
for {
// 200msecごとにLED Aを点灯/消灯
if time.Since(lastToggleA) >= 200*time.Millisecond {
toggle(&ledA)
lastToggleA = time.Now() // ledAの最後の切り替え時刻を更新
}
// 300msecごとにLED Bを点灯/消灯
if time.Since(lastToggleB) >= 300*time.Millisecond {
toggle(&ledB)
lastToggleB = time.Now() // ledBの最後の切り替え時刻を更新
}
}
}
LED AとLED Bの処理が分離していない。開発が進み他の処理も加わると瞬く間にコードが複雑化する。しかもこのコードは処理を伴う無限ループでポーリング処理による電力消費が大きい。
非同期Lチカ(goroutine)
TinyGoでは時空魔法goroutineが実装されているので、この程度であればシンプルなコードで実現できる。
package main
import (
"machine"
"time"
)
var ledA = machine.GPIO14
var ledB = machine.GPIO15
func processLedA(){
for {
ledA.High()
time.Sleep(time.Millisecond * 200)
ledA.Low()
time.Sleep(time.Millisecond * 200)
}
}
func processLedB(){
for {
ledB.High()
time.Sleep(time.Millisecond * 300)
ledB.Low()
time.Sleep(time.Millisecond * 300)
}
}
func main() {
ledA.Configure(machine.PinConfig{Mode: machine.PinOutput})
ledB.Configure(machine.PinConfig{Mode: machine.PinOutput})
go processLedA()
go processLedB()
select{} //プログラムの終了を防ぐ
}
関数processLedAとprocessLedBが独立してLチカを行う構成により、コードの見通しが良く、新しい機能の追加も容易になっている。
一見奇妙に見える最後のselect{}
は、main関数が終了してしまうことを防ぐために使用してる。Go言語では、main関数が終了するとプログラム全体が終了してしまいgoroutineも強制終了されるのだ。case文を持たないselect文は永久待ち状態になるので、goroutineが継続して実行されることが保証される。
最後に
この記事ではTinyGoによるマイコン制御はgoroutineによりシンプルに非同期処理が実現できることを示した。他にもコンパイル言語であるTinyGoはマイコン制御と相性がいい。著者は厳密な型付けによる安全性や高速な実行速度、メモリ効率の良さが気に入っている。より詳しい情報はTinyGoを参照してほしい。