GoにいればGoに従え(17) TinyGo、電子コンパス読み取り。micro:bit v2

Joseph Halfmoon

前回micro:bit v2 搭載のSTmicroelectronics社製LSM303AGR「6軸センサ」とのI2C接続を確認。今回はLSM303AGRの2機能のうち、電子コンパス(磁気センサ)の読み取りを行ってみます。立派なアプリケーション・ノートが用意されてます。これさえあればバッチリ?ちゃんと読めよ、自分。

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

前回は LSM303AGRのデータシートを参照させていただきながら、加速度センサ側と磁気センサ側に独立してある WHOAMI レジスタの内容をI2C経由で読み取ってみました。これで接続はできた、と。

しかし実際にセンサデータの読み取りが出来たわけではありません。それにデータシートを眺めていてもどこから手を付けてよいのやら。しかしそんなことはSTmicroelectronics社のヒトはお見通しだったようです。

LSM303AGR必携、アプリケーションノート

以下のアプリケーションノートは、LSM303AGRを使うときに必ず読んだ方がよろしいような気がする(個人の感想デス)ドキュメントです。

AN4825

実際に設定、読み取りのプログラムを書くときに必要そうな手順、注意点など書かれてます。まあ、メンドイので私は適当に関係しそうなところだけ拾い読みしただけなんでありますが。。。

LSM303AGRの制御用Goソース

前回のWHOAMIレジスタ読み取り用に作ったソースに若干の手を加えたものが以下です。操作対象のレジスタが大分増えているのでそれなりに長くなってきてます。Goのお作法により、大文字で始まる関数だけがmain側に見える筈。多分。

main.goの隣にmb20lsm303.goなどというファイル名でおいてますが、同じmainパッケージ内の別ファイルという位置づけ?です。

// LSM303AGR interface, for micro-bit v2 only
package main

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

const LSM303AGR_ACC = 0x19
const LSM303AGR_MAG = 0x1E
const WHOAMI_A_ADR = 0x0F
const WHOAMI_M_ADR = 0x4F
const CFG_REG_A_M = 0x60
const CFG_REG_B_M = 0x61
const CFG_REG_C_M = 0x62
const STATUS_REG_M = 0x67
const OUTX_L_REG_M = 0x68
const OUTX_H_REG_M = 0x69
const OUTY_L_REG_M = 0x6A
const OUTY_H_REG_M = 0x6B
const OUTZ_L_REG_M = 0x6C
const OUTZ_H_REG_M = 0x6D

var i2c0 = machine.I2C0 // internal I2C bus

func InitInternalI2C() {
    i2c0.Configure(machine.I2CConfig{
        Frequency: machine.TWI_FREQ_100KHZ,
        SCL:       machine.SCL0_PIN,
        SDA:       machine.SDA0_PIN,
    })
    time.Sleep(10 * time.Millisecond)
}

func read1Byte(devAdr byte, regAdr byte) (bool, byte) {
    dat := []byte{0}
    err := i2c0.ReadRegister(devAdr, regAdr, dat)
    if err != nil {
        return false, 0
    }
    return true, dat[0]
}

func write1Byte(devAdr byte, regAdr byte, vd byte) bool {
    dat := []byte{vd}
    err := i2c0.WriteRegister(devAdr, regAdr, dat)
    if err != nil {
        return false
    }
    return true
}

func ReadWhoAmI(opt bool) (bool, byte) {
    var tmpADR byte
    var tmpREG byte
    if opt {
        tmpADR = LSM303AGR_ACC
        tmpREG = WHOAMI_A_ADR
    } else {
        tmpADR = LSM303AGR_MAG
        tmpREG = WHOAMI_M_ADR
    }
    return read1Byte(tmpADR, tmpREG)
}

func LetMagContinuous() bool {
    ok, reg := read1Byte(LSM303AGR_MAG, CFG_REG_A_M)
    if !ok {
        return false
    }
    reg = reg & 0xFC //continues mode
    return write1Byte(LSM303AGR_MAG, CFG_REG_A_M, reg)
}

func ReadMagConfig() (bool, byte, byte, byte) {
    okA, regA := read1Byte(LSM303AGR_MAG, CFG_REG_A_M)
    okB, regB := read1Byte(LSM303AGR_MAG, CFG_REG_B_M)
    okC, regC := read1Byte(LSM303AGR_MAG, CFG_REG_C_M)
    return okA && okB && okC, regA, regB, regC
}

func RegMagData() (bool, int16, int16, int16) {
    ok, reg := read1Byte(LSM303AGR_MAG, STATUS_REG_M)
    if !ok {
        fmt.Printf("ERROR DATA 0\r\n")
        return false, 0, 0, 0
    }
    cnt := 3
    if (reg & 0x80) == 0 {
        for cnt > 0 {
            ok, reg = read1Byte(LSM303AGR_MAG, STATUS_REG_M)
            if ok && ((reg & 0x80) != 0) { break }
            cnt--
        }
    }
    if cnt <= 0 {
        fmt.Printf("ERROR DATA 1\r\n")
        return false, 0, 0, 0
    }
    ok, regXH := read1Byte(LSM303AGR_MAG, OUTX_H_REG_M)
    ok, regXL := read1Byte(LSM303AGR_MAG, OUTX_L_REG_M)
    ok, regYH := read1Byte(LSM303AGR_MAG, OUTY_H_REG_M)
    ok, regYL := read1Byte(LSM303AGR_MAG, OUTY_L_REG_M)
    ok, regZH := read1Byte(LSM303AGR_MAG, OUTZ_H_REG_M)
    ok, regZL := read1Byte(LSM303AGR_MAG, OUTZ_L_REG_M)
 	datX := int16(uint16(regXH)<<8 + uint16(regXL))
 	datY := int16(uint16(regYH)<<8 + uint16(regYL))
 	datZ := int16(uint16(regZH)<<8 + uint16(regZL))
    return true, datX, datY, datZ
}
今回のmain.go

前回 WHOAMI を2個読み出すだけだったソースに磁気センサのコンフィギュレーションレジスタ3個の読み取りと、磁気センサのX、Y、Zの各方向の値の読み取り機能を加えたものです。なお、磁気センサのコンフィギュレーションを読んだところ「アイドル状態」であったので、「連続モード」で動くようにカツを入れてます(ホントは一回でいいと思うのだけれど。)

package main

import (
    "fmt"
    "time"
)

func main() {
    InitInternalI2C()
    opt := false

    for {
        opt = !opt
        success, dat := ReadWhoAmI(opt)
        if success {
            if opt {
                fmt.Printf("ACC WHO AM I: %02x\r\n", dat)
            } else {
                fmt.Printf("MAG WHO AM I: %02x\r\n", dat)
            }
        } else {
            fmt.Printf("ERROR: ReadWoAMI\r\n")
        }
        if LetMagContinuous() {
            fmt.Printf("LetMagContinuous\r\n")
        } else {
            fmt.Printf("ERROR: LetMagContinuous\r\n")
        }
        success, cA, cB, cD := ReadMagConfig()
        if success {
            fmt.Printf("Mag Config: %02x %02x %02x\r\n", cA, cB, cD)
        } else {
            fmt.Printf("ERROR: reading Mag Config\r\n")
        }
        success, iX, iY, iZ := RegMagData()
        if success {
            fmt.Printf("Mag Data: %d %d %d\r\n", iX, iY, iZ)
        } else {
            fmt.Printf("ERROR: reading Mag Data\r\n")
        }
        time.Sleep(time.Second * 2)
    }
}
動作結果の確認

例によって、以下でビルドしてフラッシュ書き込み(書き込み後、即実行)です。

$ tinygo flash -target=microbit-v2

なお、LetMagContinuous()を行わない初期設定状態だと、CFG_REG_A_Mレジスタの値は0x03が読めてました。これは最下位の2ビット、MD[1:0]フィールド に2進11が立っている状態なので、Idle modeだと思います。この状態のままだとセンサは動作しておらずSTATUS_REG_Mの最上位ビットにあるZyxdaフラグが立つことはありません(Zyxdaフラグは磁気の測定を行って値を更新したときに1が立つフラグみたいです。)

フラッシュ書き込み後、USBシリアルに接続したTeraTermに報告された磁気センサの値(x, y, zの順、符合付16ビット整数)が以下に

GoMagResults

緑の元の方向のときの値を覚えておいてくだされや。ほぼ90度くらい回転させてみたところ赤のように値が更新されました。その後の青のところの前で先ほどの緑のときの方向近くに戻すとまた値が変わりました。

まだレジスタの生の整数値なので、mG(ミリガウス)単位への変換は組み込んでません。でもねえ、磁束密度の「ガウス」はSIでないので非推奨だよねえ。多分。アプリケーションノートはガウスで書かれてますけど、本当はSIでテスラを単位として書かねばいけないのでは。でもテスラと書くと別なものがおおいにヒットしそうで怖い。個人の感想です。

GoにいればGoに従え(16) TinyGo、6軸センサに誰?と micro:bit v2 へ戻る

GoにいればGoに従え(18) TinyGo、加速度センサ読み取り。micro:bit v2 へ進む