センサーと組み合わせ、リアルタイムで距離を計測して表示する
距離を測定するセンサーには、レーザー光や電磁波、超音波を利用するものがあります。今回は、赤外線レーザー光を利用したTOF(Time of Flight)センサーを使います。TOFとは、センサーから発信した光が対象物に反射して往復するまでの時間を計測し、距離を定める方法です。
VL53L0X
VL53L0Xという距離センサーモジュールを利用します。測定範囲は、約30mmから最大2mで、I2C接続でデータを取得できます。ESP32はI2Cにも対応していますので、センサーモジュールをそのまま直結して使うことができます。
I2C
I2C(Inter-Integrated Circuit)とは、SPI通信と同様のシリアル通信規格です。主な特徴は、次のとおりです。
- 2本の信号線(クロック、データ)
- マスター/スレーブ方式
- アドレス指定により複数のスレーブデバイスに対応
- SPIに比べて低速
I2Cでは、コントローラとなるマスターと、センサーなどのスレーブの間で通信を行います。スレーブデバイスは、それぞれ固有のアドレスを持っており、マスターがこのアドレスを指定して通信を行います。つまりI2Cのアドレスは、デバイスを識別するためのもので、I2C通信を行う上で非常に重要な要素となります。
またI2Cでは、電源と信号線2つの合計4つの接続だけで通信可能です。そのため、信号線だけで4つ使うSPIよりもシンプルな配線となります。
配線とソース
ESP32では、I2C接続でも任意のGPIO端子で利用可能です。ここでは、GPIO18、19を利用しました。また、.NET nanoFrameworkには、VL53L0X用のクラスが用意されています。VL53L0Xの制御は、けっこう複雑なコードが必要なのですが、そのあたりはクラス内に実装されていますので、とても短いコードで距離を測定できます。
最初にNuGetパッケージマネージャで、次のパッケージをインストールしておきます。
- nanoFramework.Hardware.Esp32
- nanoFramework.Iot.Device.Vl53L0X
距離センサーの値を取得して、デバッグ表示するコードは、次のようになります。
public static void Main() { // I2C接続に用いるGPIOの設定(1) Configuration.SetPinFunction(18, DeviceFunction.I2C1_DATA); Configuration.SetPinFunction(19, DeviceFunction.I2C1_CLOCK); // I2C通信のアドレス設定(2) var connectionSettings = new I2cConnectionSettings(1, Vl53L0X.DefaultI2cAddress); // I2C通信オブジェクトの生成(3) using var i2c = I2cDevice.Create(connectionSettings); // Vl53L0Xオブジェクトの生成(4) var vL53L0X = new Vl53L0X(i2c); // 測定モードを連続モードにする(5) vL53L0X.MeasurementMode = MeasurementMode.Continuous; while (true) { try { var dist = vL53L0X.Distance; // 値がOperationRange.OutOfRangeは測定異常(6) if (dist != (ushort)OperationRange.OutOfRange) { // 計測距離(mm)の表示(7) Debug.WriteLine($"Distance: {dist}"); } else { Debug.WriteLine("Invalid data"); } } catch (Exception ex) { Debug.WriteLine($"Exception: {ex.Message}"); } Thread.Sleep(500); } }
I2C接続の設定手順は、SPIと同様です。I2C接続に用いるGPIOの設定後(1)、設定オブジェクトを生成して(2)、通信オブジェクトを生成します(3)。設定オブジェクトといっても、SPIと異なり、I2Cのアドレスを設定する程度です。
Vl53L0Xクラスのコンストラクタで、通信オブジェクトを指定して初期化します(4)。そして、測定モードを、連続取得モードに設定します(5)。この測定モードでは、一定周期で計測が行われますので、適宜Distanceプロパティを参照すれば、随時距離が参照できるはずです(7)。測定値が、固定のOperationRange.OutOfRange(8190)となった場合は、正しく測定できなかったという意味となります(6)。
なお、Vl53L0Xのアドレスは、標準で0x29(Vl53L0X.DefaultI2cAddress)となります。Vl53L0Xでは、アドレスをハードウェア的に変更することはできないので、もし同じアドレスのデバイスを使いたい場合は、初期化時にアドレスを変更する必要があります。いったんデフォルトのアドレスでVl53L0Xオブジェクトを生成後、Vl53L0XクラスのChangeI2cAddressメソッドで、アドレス変更のコマンドを発行します。それからあらためて、新しいアドレスでI2C通信オブジェクトを作り、Vl53L0Xオブジェクトを生成しなおす必要があります。
マトリックスLEDでレベルメーター
先ほどのマトリックスLEDを使って、距離をレベル表示するようにしてみます。
public static void Main() { // MAX7219、Vl53L0Xの準備 ~中略~ m7219.Rotation = RotationType.Right; // レベルメーターのバイト列データ(1) var levelmeter = new byte[] { 0b10000000, 0b11000000, 0b11100000, 0b11110000, 0b11111000, 0b11111100, 0b11111110, 0b11111111, }; while (true) { try { // 距離をレベル(0~8)に変換(2) var level = Math.Min(400, vL53L0X.Distance) / 50; // バッファクリア(3) m7219.Clear(0, 1, false); // 距離レベルより小さい表示データを内部バッファに書き込む(4) for (var i = 0; i < level; i++) { m7219[new DeviceIdDigit(deviceId: 0, digit: i)] = levelmeter[i]; } m7219.Flush(); } catch (Exception ex) { Debug.WriteLine($"Exception: {ex.Message}"); } Thread.Sleep(100); } }
MAX7219、Vl53L0Xのオブジェクトを作成した後、レベルメーターのバイト列データ(1)を定義しています。スマホの電波強度のアンテナ表示みたいなイメージです。
測定した距離は、0から8(400ミリ以上は8)になるように変換します(2)。あらかじめ内部バッファをクリアしてから(3)、内部バッファにレベルメータのイメージのデータを書き込みます。定義したバイト列の配列から、距離レベルより小さいインデックスのものを指定します(4)。
このコードを実行して、センサーに手をかざすと、リアルタイムにレベルメーターが変化します。
マトリックスLEDでリアルタイム距離表示
次に、距離をそのまま数値で表示してみましょう。カスケード接続したマトリックスLEDを用います。
~中略~ var dist = vL53L0X.Distance; if (dist != (ushort)OperationRange.OutOfRange) { writer.ShowMessage(dist.ToString(), alwaysScroll: false); // スクロールなし }
値をそのまま表示するには、さきほどの電光掲示板のプログラムと同様に、MatrixGraphicsクラスのShowMessageメソッドを利用します。今回はスクロール表示は不要なので、ShowMessageメソッドのalwaysScrollパラメータは、falseにします。
実行すると、リアルタイムで距離が表示されます。
最後に
今回は、ドットマトリックスLEDを使って、電光掲示板表示や、センサーの値を表示するプログラムを作成しました。次回は、Bluetooth通信を利用したプログラムを作成する予定です。