GoにいればGoに従え(13) TinyGo、micro:bit v2、I2Cは要変更

Joseph Halfmoon

micro:bitボードをv1.5からv2.0に機材変更するのにともない、v1.5で動作していたプログラムがv2.0でも動くのか確認作業中です。前回はキー割り込みとオンボードの温度センサは問題なく動作OK。I2Cも大丈夫だろ、と甘くみたらばダメでした。ここにもv1.5とv2.0の違いがあったのね。今回はその変更点をば。

※「GoにいればGoに従え」Go関連記事の総Index

micro:bit のI2Cバス、v1.5とv2.0の違い

結論から言うと以下のようでした。

    • micro:bit v1.5、オンボードペリフェラルとカードエッジの端子で1本のI2Cバスをシェアしていた。
    • micro:bit v2.0、オンボードペリフェラル用の内部I2Cバスと、カードエッジ用の外部I2Cバスが分離された。TinyGo上では、I2C0というバスが内部用で、I2C1というバスが外部用になった。また、I2C用のピン名も変更されている。

つまり、v1.5用のTinyGoソースをそのままコンパイルするとコンパイルはノーエラーですが、ピクりともバスは動きませぬ。外部用のI2C1に行先を向けなおした上に、ピン名も変更せねばなりません。

つまり、下記のように外部用I2Cを使う場合は以下のようにI2C1を指定した上でgoI2c1

v1.5のころは、SCL_PINとかで良かったピン名をSCL1_PINなどと変更する必要がありました。こんな感じ。goI2c1config

なお、micro:bitのI2Cバスについては以下に解説があります。

Use of the I2C bus

今回のメイン・プログラム

今回のメインプログラムは、前回使用の「チョイ直し」です。前回のおさらいをすると、

    1. goルーチン(コルーチン?による並行動作機構)によりLED Matrixは動的点灯する(パターン指定されれば)
    2. キーをプッシュすると割り込みでうけする。AキーとBキーで異なるパターンを設定する(それによってLED表示パターンが即座に変わる)
    3. メインループは上記とかかわりなく無限ループしながら、10秒置きに温度を測定して標準出力(USBシリアル)に温度を出力する

今回は3のメインループ部分に、「測定した温度をI2C1に接続した外部LCD(AQM1602)に表示する」という機能を追加してみました。前回同様氷点下の温度には対応してないっす。手抜き。

package main

import (
    "fmt"
    "machine"
    "time"
)

var pat = 0

func boardTemperatureC() int {
    return int(machine.ReadTemperature() / 1000)
}

func main() {
    var msg1 []byte = []byte("Temperature")
    var msg2 []byte = []byte("00")
    var temp int

    dispPattern := []uint32{
        0x00000000, 0x01151151, 0x00E8C62E, 0x01FFFFFF,
    }
    InitLED()
    go DispLoop(dispPattern[:])

    InitAQM1602()

    keyA := machine.BUTTONA
    keyB := machine.BUTTONB
    keyA.Configure(machine.PinConfig{Mode: machine.PinInput})
    keyB.Configure(machine.PinConfig{Mode: machine.PinInput})
    keyA.SetInterrupt(machine.PinFalling, func(machine.Pin) {
        pat = 1
    })
    keyB.SetInterrupt(machine.PinFalling, func(machine.Pin) {
        pat = 2
    })

    for {
        temp = boardTemperatureC()
        fmt.Printf("TEMP: %d\r\n", temp)
        if temp < 99 {
            msg2[1] = byte((temp % 10) + 0x30)
            msg2[0] = byte((temp / 10) + 0x30)
        }
        fmt.Printf("CHR: %02x %02x\r\n", msg2[0], msg2[1])
        DispStrOnLCD(msg1, msg2)
        time.Sleep(time.Second * 10)
    }
}
I2C経由のAQM1602インタフェースのコード

以下のコードは、『GoにいればGoに従え(8)』のメインプログラムだったソースを流用して改造したものです。上記のmain.goのお隣にこっそり置いておけばTinyGoが見つけてビルドしてくれます。

package main

import (
    "machine"
    "time"
)

const AQM1602 = 0x3E

var i2c = machine.I2C1 //micro-bit v2 only

func WriteAQM1602Data(dat byte) {
    i2c.WriteRegister(AQM1602, 0x40, []byte{dat})
    time.Sleep(1 * time.Millisecond)
}

func WriteAQM1602Command(cmd byte) {
    i2c.WriteRegister(AQM1602, 0x00, []byte{cmd})
    time.Sleep(20 * time.Millisecond)
}

// micro-bit v2 only
func InitAQM1602() {
    i2c.Configure(machine.I2CConfig{
        Frequency: machine.TWI_FREQ_100KHZ,
        SCL:       machine.SCL1_PIN,
        SDA:       machine.SDA1_PIN,
    })
    time.Sleep(100 * time.Millisecond)
    WriteAQM1602Command(0x38)
    WriteAQM1602Command(0x39)
    WriteAQM1602Command(0x14)
    WriteAQM1602Command(0x77)
    WriteAQM1602Command(0x56)
    WriteAQM1602Command(0x6C)
    WriteAQM1602Command(0x38)
    WriteAQM1602Command(0x01)
    WriteAQM1602Command(0x0C)
}

func DispStrOnLCD(arg1 []byte, arg2 []byte) {
    WriteAQM1602Command(0x01) // Clear Display
    WriteAQM1602Command(0x80) // Go to TOP LINE HOME
    WriteAQM1602Command(0x00) // End of Command, Data bytes will follow
    for i := 0; i < len(arg1); i++ {
        WriteAQM1602Data(arg1[i])
    }
    WriteAQM1602Command(0xC0) // Go to BOTTOM LINE HOME
    WriteAQM1602Command(0x00) // End of Command, Data bytes will follow
    for i := 0; i < len(arg2); i++ {
        WriteAQM1602Data(arg2[i])
    }
}
念のためLEDマトリックスの駆動コード

以下のファイルは前回とまったく同じものですが、念のため掲げておきます。

package main

import (
    "machine"
    "time"
)

var row = 1

func DispLoop(dispPattern []uint32) {
    for {
        switch row {
        case 1:
            DispLED1(dispPattern[pat])
        case 2:
            DispLED2(dispPattern[pat])
        case 3:
            DispLED3(dispPattern[pat])
        case 4:
            DispLED4(dispPattern[pat])
        case 5:
            DispLED5(dispPattern[pat])
        }
        row++
        if row > 5 {
            row = 1
        }
        time.Sleep(time.Millisecond * 7)
    }	
}

func InitLED() {
    machine.LED_ROW_1.Configure(machine.PinConfig{Mode: machine.PinOutput})
    machine.LED_ROW_2.Configure(machine.PinConfig{Mode: machine.PinOutput})
    machine.LED_ROW_3.Configure(machine.PinConfig{Mode: machine.PinOutput})
    machine.LED_ROW_4.Configure(machine.PinConfig{Mode: machine.PinOutput})
    machine.LED_ROW_5.Configure(machine.PinConfig{Mode: machine.PinOutput})
    machine.LED_COL_1.Configure(machine.PinConfig{Mode: machine.PinOutput})
    machine.LED_COL_2.Configure(machine.PinConfig{Mode: machine.PinOutput})
    machine.LED_COL_3.Configure(machine.PinConfig{Mode: machine.PinOutput})
    machine.LED_COL_4.Configure(machine.PinConfig{Mode: machine.PinOutput})
    machine.LED_COL_5.Configure(machine.PinConfig{Mode: machine.PinOutput})
    machine.LED_ROW_1.Low()
    machine.LED_ROW_2.Low()
    machine.LED_ROW_3.Low()
    machine.LED_ROW_4.Low()
    machine.LED_ROW_5.Low()
}

func SetLED(arg uint32) {
    if (arg & 0x01084210) != 0 {
        machine.LED_COL_1.Low()
    } else {
        machine.LED_COL_1.High()
    }
    if (arg & 0x00842108) != 0 {
        machine.LED_COL_2.Low()
    } else {
        machine.LED_COL_2.High()
    }
    if (arg & 0x00421084) != 0 {
        machine.LED_COL_3.Low()
    } else {
        machine.LED_COL_3.High()
    }
    if (arg & 0x00210842) != 0 {
        machine.LED_COL_4.Low()
    } else {
        machine.LED_COL_4.High()
    }
    if (arg & 0x00108421) != 0 {
        machine.LED_COL_5.Low()
    } else {
        machine.LED_COL_5.High()
    }
}

func DispLED1(arg uint32) {
    machine.LED_ROW_1.Low()
    machine.LED_ROW_2.Low()
    machine.LED_ROW_3.Low()
    machine.LED_ROW_4.Low()
    machine.LED_ROW_5.Low()
    if (arg & 0x01F00000) != 0 {
        SetLED(arg & 0x01F00000)
        machine.LED_ROW_1.High()
    }
}

func DispLED2(arg uint32) {
    machine.LED_ROW_1.Low()
    machine.LED_ROW_2.Low()
    machine.LED_ROW_3.Low()
    machine.LED_ROW_4.Low()
    machine.LED_ROW_5.Low()
    if (arg & 0x000F8000) != 0 {
        SetLED(arg & 0x000F8000)
        machine.LED_ROW_2.High()
    }
}

func DispLED3(arg uint32) {
    machine.LED_ROW_1.Low()
    machine.LED_ROW_2.Low()
    machine.LED_ROW_3.Low()
    machine.LED_ROW_4.Low()
    machine.LED_ROW_5.Low()
    if (arg & 0x00007C00) != 0 {
        SetLED(arg & 0x00007C00)
        machine.LED_ROW_3.High()
    }
}

func DispLED4(arg uint32) {
    machine.LED_ROW_1.Low()
    machine.LED_ROW_2.Low()
    machine.LED_ROW_3.Low()
    machine.LED_ROW_4.Low()
    machine.LED_ROW_5.Low()
    if (arg & 0x000003E0) != 0 {
        SetLED(arg & 0x000003E0)
        machine.LED_ROW_4.High()
    }
}

func DispLED5(arg uint32) {
    machine.LED_ROW_1.Low()
    machine.LED_ROW_2.Low()
    machine.LED_ROW_3.Low()
    machine.LED_ROW_4.Low()
    machine.LED_ROW_5.Low()
    if (arg & 0x0000001F) != 0 {
        SetLED(arg & 0x0000001F)
        machine.LED_ROW_5.High()
    }
}
実機動作確認

例によって以下のようにしてビルド&フラッシュ書き込みします。

$ tinygo flash -target=microbit-v2

microbit v2機でI2C経由でLCDに文字が書けました。goI2c1_DUT

micro:bitのv1.5とv2.0の回路図を以前読んでいた筈なのだけれど完全に忘れてましたな。年寄の忘却力デス。

GoにいればGoに従え(12) TinyGo、micro:bit v2、キーと温度センサ確認 へ戻る

GoにいればGoに従え(14) TinyGo、micro:bit v2、CDSセンサ読み取り へ進む