SparkFun moto:bit の I2C 操作と MicroPython からの利用

# きっかけ

micro:bit 用のモータドライバーの一種である [SparkFun moto:bit](https://www.switch-science.com/catalog/3382/)を入手しました。

![IMG_4065.JPG](https://qiita-image-store.s3.amazonaws.com/0/106817/b3324b92-8689-8fc5-6c91-477e3f03723e.jpeg)

[Kitronik のモータドライバボード](https://www.switch-science.com/catalog/3229/)より安くてよいのですが、基盤をながめてみると GPIO からモータドライバICに直接つながっているわけではなさそう。[回路図](https://cdn.sparkfun.com/assets/learn_tutorials/6/4/1/SparkFun_Micro_Bit_Moto_Bit.pdf)を参照してみると、I2C で送った命令を PSoC で処理してモータドライバを制御するというインテリジェントなものになっている模様。

おかげで他の入出力ピンを消費することなく、モータ制御ができるのはよいのですが、利用インタフェースは Microsoft の Makecode にしか提供されていません。MicroPython から使いたかったので、Makecode 用のブロックの実装を調べて I2C でどう制御しているのか調べてみました。

moto:bit ブロックのソース(TypeScript)は以下で参照できます。

https://github.com/sparkfun/pxt-moto-bit/blob/master/motobit.ts

# 調査結果

## I2C アドレス

I2C アドレスは 0x59 (89)

## I2C で送る制御情報のフォーマット

すべて２バイト構成です。１バイト目は制御の種類、２バイト目はパラメータになっています。

## スピードの指定

左モータ(LEFT MOTOR)のスピード指定を行う場合は１バイト目が 0x21、右モータ(RIGHT MOTOR)のスピード指定を行う場合は１バイト目が 0x20 になります。

２バイト目には進行方向とスピードを指定します。

* 最上位ビットが 1 であれば 前進(Forward)

    + 下位７ビット 0-127 でスピードを指定（大きくなるほど速い）

* 最上位ビットが 0: 後進(Reverse)

    + 下位７ビット 0-127 でスピードを指定（大きくなるほど遅い）

※ Makecode のブロックで指定するスピードは 0-100 (大きいほど速い)で、これを　0-127 にマッピングして制御しています

## 進行方向の反転指定

モータの前進/後進を反転する指定があります。

左モータ(LEFT MOTOR)の反転指定を行う場合は１バイト目が 0x13、右モータ(RIGHT MOTOR)の反転指定を行う場合は１バイト目が 0x12 になります。
　
２バイト目は反転する/しないの指定で、1であれば反転、0であれば反転を止める(元に戻す)指定となります。

## モータの有効化/無効化の指定

モータの有効化/無効化の指定を行う場合は1バイト目は 0x70 になります。

２バイト目は有効化/無効化の指定で、1であれば有効化、0であれば無効化となります。

# そんなわけで作成してみた MycroPython ライブラリ

## ソースコード

```python
from microbit import i2c


class Motor:
    # command
    LEFT = 0x21
    RIGHT = 0x20
    LEFT_INVERT = 0x13
    RIGHT_INVERT = 0x12
    ENABLE = 0x70

    # direction
    FORWARD = 0x80
    REVERSE = 0x00

    # i2c
    _I2C_ADDR = 0x59
    _I2C_SDA = 20
    _I2C_SCL = 19
    _i2c_buf = bytearray(2)

    def __init__(self, motor):
        self.motor = motor

    def set_speed(self, speed, direction=FORWARD):
        pwr = speed * 127 // 100
        Motor._i2c_buf[0] = self.motor
        Motor._i2c_buf[1] = (pwr | direction) \
                            if direction == Motor.FORWARD else (127 - pwr)
        i2c.write(Motor._I2C_ADDR, Motor._i2c_buf)

    def invert(self, invert):
        if self.motor == Motor.LEFT:
            Motor._i2c_buf[0] = Motor.LEFT_INVERT
        else:
            Motor._i2c_buf[0] = Motor.RIGHT_INVERT
        Motor._i2c_buf[1] = int(invert)
        i2c.write(Motor._I2C_ADDR, Motor._i2c_buf)

    @staticmethod
    def enable():
        Motor._i2c_buf[0] = Motor.ENABLE
        Motor._i2c_buf[1] = 1
        i2c.write(Motor._I2C_ADDR, Motor._i2c_buf)

    @staticmethod
    def disable():
        Motor._i2c_buf[0] = Motor.ENABLE
        Motor._i2c_buf[1] = 0
        i2c.write(Motor._I2C_ADDR, Motor._i2c_buf)


motor_left = Motor(Motor.LEFT)
motor_right = Motor(Motor.RIGHT)
```

ソースは github にも置いておきました。

https://github.com/MinoruInachi/micropython-microbit-mylib/blob/master/motobit.py

## 使い方

### モジュールのインポート

`import motobit`

### API

`motobit.Motor.enable()`

    モータを有効化します。

`motobit.motor_left.set_speed(speed, direction=Motor.FORWARD)`

    左モータのスピードを設定します。
    speed: モータのスピードを 0-100 で指定。100 が最大スピード
    direction: Motor.FORWARD で前進、Motor.REVERSE で後進

`motobit.motor_right.set_speed(speed, direction=Motor.FORWARD)`

    右モータのスピードを設定します。
    speed: モータのスピードを 0-100 で指定。100 が最大スピード
    direction: Motor.FORWARD で前進、Motor.REVERSE で後進

`motobit.motor_left.invert(invert)`

    左モータの進行方向を反転するかを設定します。
    invert: True - 反転する、False - 反転しない

`motobit.motor_left.invert(invert)`

    右モータの進行方向を反転するかを設定します。
    invert: True - 反転に設定する、False - 反転に設定しない

`motobit.Motor.disable()`

    モータを無効化します。

## サンプルコード

[SparkFun サイトの "Experiment 1: Driving and Turning"](https://learn.sparkfun.com/tutorials/microbot-kit-experiment-guide/experiment-1-driving-and-turning)と同じことを MicroPython で書いてみたコードです。

```python
from microbit import button_a, sleep
from microbot import *

while True:
    if button_a.is_pressed():
        Motor.enable()
        motor_left.set_speed(100)
        motor_right.set_speed(100)
        sleep(1000)
        motor_left.set_speed(100, Motor.REVERSE)
        motor_right.set_speed(100)
        sleep(200)
        motor_left.set_speed(100)
        motor_right.set_speed(100)
        sleep(1000)
        Motor.disable()
```

# 余談

1. この moto:bit を使った [micro:bot kit](https://www.switch-science.com/catalog/3385/) は、Forward で BACK 方向に進み、Reverse で FRONT 方向に進んでしまいます。最初、組み立てを間違ったのかと思ったのですが、SparkFun の [実験動画](https://youtu.be/EL4c4ujjQ48)でもそのように動いています。ひどいですねぇ。

1. MicroPython ドキュメントにある microbit.i2c.init() を使ったところ、定義されていないとエラーになりました。まあ、デフォルト(freq=100000, sda=pin20, scl=pin19)で問題ないのですが。github の履歴によれば  2016/11/11 に追加されているのですが、開発環境にはまだ反映されていないようです。

1. ~~micro:bit の公式サイトで MicroPython ドキュメントが日本語にならなかったの残念です。[勝手訳](http://www5f.biglobe.ne.jp/~inachi/microbit/micropython/)を公開しています。~~ [日本語ドキュメント](http://microbit-micropython.readthedocs.io/ja/latest/index.html)は公式からリンクしてもらいました