【EV3 MicroPython】DIST-ToFセンサによる距離の計測（&I2C通信）

# DIST-ToF

LEGO社のサードパーティ製品を製造・販売している[mindsensors](http://www.mindsensors.com/)社から，ToF距離センサ（[DIST-ToF](http://www.mindsensors.com/ev3-and-nxt/210-high-precision-time-of-flight-tof-distance-sensor-for-nxt-or-ev3)）が販売されています．

ToFは"Time of Flight"の略で，（赤外）光が跳ね返ってくるまでの時間から距離を計測するという技術です[^1]．
ToF技術により，対象物の位置をピンポイント，かつ，正確に計測できるようになりました．

mindsensorsが販売しているセンサの多くは，EV3MicroPythonのベースとなっているev3devで対応しています[^2]．
そして，ev3devで対応しているセンサは，EV3MicoroPythoh（pybricks2.0）の`iodevices`モジュールの`Ev3devSensor`クラスで簡単に制御することができます．

しかし，このDIST-ToFセンサは，ev3devに対応していません（コマンドが実装されていません）．
ただし，このセンサはI2C通信により制御することができます．

そこで，今回はEV3MicroPythonで直接I2C通信を扱う方法について紹介していきます．
そして，Ev3MicroPythonでDIST-ToFセンサを扱えるようにしていきます．


# I2C通信

I2Cは，フィリップス社（現NXP社）が開発したシリアルバス（シリアル通信の一種）で，シリアル・データライン（SDA）とシリアル・クロックライン（SCL）の２本の線を使ってデバイス間の通信を行う手法です．
少ない本数で複数のデバイスと通信できるため，組み込み機器や携帯電話などで利用されています．

なぜ，2本線で「複数のデバイス」間の通信ができるのかというと，それは，デバイス毎にアドレスが設定されているからです[^4]．

また，I2C通信はマスター・スレーブ方式[^3]を採用しています．今回の場合は，EV3がマスター，DIST-ToFがスレーブとなります．

EV3MicroPython（Pybricks2.0）では，I2C通信を扱うクラスとして`iodevices`モジュールに`I2CDevice`クラスが用意されています．`I2CDevice`クラスの仕様については，[こちら](file:///Users/asahi/.vscode/extensions/lego-education.ev3-micropython-2.0.0/resources/docs/iodevices.html#i2c-device)を参照してください．


# EV3MicoPythonでI2C通信

以下のソースコードは，EV3MicroPythonで，`I2CDevice`クラスを用いてDIST-TOFのセンサ値を取得するプログラムの実装例です．取得した値は，EV3の液晶画面に表示されます．
実装したプログラムは，RobotC向けに公開されている[GitHub](https://github.com/botbench/robotcdriversuite)のコードを参考にしています．

```python:ms-dist-tof.py
#!/usr/bin/env pybricks-micropython
from pybricks.hubs import EV3Brick
from pybricks.parameters import Port
from pybricks.tools import wait
from pybricks.media.ev3dev import Font
from pybricks.iodevices import I2CDevice

# Write your program here
ev3 = EV3Brick()
ev3.speaker.beep()
font = Font(size=20)

dist = I2CDevice(Port.S4, 0x02 >> 1)

def get_ms_dist(dist):
    data = dist.read(0x42, length=2)
    return (0xFF & data[0]) + ((0xFF & data[1]) << 8)

ev3.screen.set_font(font)
while True:
    ev3.screen.clear()
    ev3.screen.print("DIST :", get_ms_dist(dist))
    wait(100)
```
まず，`I2CDevice`クラスから，`dist`インスタンス（クラスオブジェクト）を生成します（14行目）．
コンストラクタの引数には，センサを接続するポート（`Port`クラス）と，センサのアドレス（16進数）を入力します．

ここで，DIST-ToFの（デフォルト）アドレスは`0x02`です．
ただし，アドレスを1ビット右にシフトしたものを入力するようにしてください[^4]．

`get_ms_dist`関数（16-18行目）では，以下の処理を行なっています．

1. 引数として，`I2CDevice`クラスのクラスオブジェクトを渡す（`dist`インスタンスを想定）．
2. `I2CDevice`クラスの`read`関数を呼び出し，センサの値を取得します．
    * ここで，DIST-ToFの距離情報を取得するためのアドレスは`0x42`で，データ長は2バイトとなります．
3. 2バイトのデータを足し合わせて，1つのデータにします．これを距離データとして返します．

DIST-ToF用のクラスを用意し，クラス内で`get_dist`関数を実装する方がスマートなプログラムになりそうです．

前述のように，DIST-ToFは通信方式にI2Cを採用しているため，アドレスの異なる他のセンサと組み合わせることで，EV3の1つのポートで複数のセンサを制御することができます．

[^1]: 最近では，センサの素子レベルでこのような計測を行う回路を組み込めるようになり，これを含めてToFと呼ばれることが多いとされています．

[^2]: ev3devで対応しているセンサ（サードパーティー社製を含む）については，[こちら](http://docs.ev3dev.org/projects/lego-linux-drivers/en/ev3dev-stretch/sensors.html)を参照してください．

[^3]: マスタースレーブ方式：複数のデバイスが協調して動作する際に，複数のデバイスの制御・操作を司る「マスター」機と，マスター機の一方的な制御下で動作する「スレーブ」機に役割を分担する方式です（この呼び名は，最近，問題担っていますが...）．

[^4]: 「メールアドレス」を思い浮かべてください．世界中で多くの機器がインターネットに接続されていますが，宛先に設定したメールアドレス（のサーバ）にのみ，メールが届くようになっています．

[^5]: I2C通信では，8ビットのデータのうち，上位7ビットは「スレーブアドレス」，下位1ビットは「送信・受信指定ビット」となっています．ここで必要な情報は，「スレーブアドレス」だけなので，下位1ビットを外した値を入力します．