3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TinyGoAdvent Calendar 2024

Day 23

TinyGoのgoroutineによる非同期Lチカ

Last updated at Posted at 2024-12-22

Lチカとはマイコンの基本とされる制御でLEDのON/OFFを繰り返しチカチカと点滅させるプログラムである。複数のLEDでLチカを実装するのは意外に難しいが、TinyGoのgoroutineを使えば容易に実現できる。
この記事ではハードウェアにzero-kb02を使用した実装例を紹介する。

IMG_1731.gif

ハードウェア構成

zero-kb02はRP2040チップを搭載したキーボードである。今回は、このキーボードをマイコンボードとして利用する。外部出力されているGPIO14とGPIO15にLEDを接続した構成で使用した。同じRP2040を使用しているRaspberry Pi Picoなどでも同様に動作する。

image.png

普通の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を参照してほしい。

image.png

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?