センサーと組み合わせ、リアルタイムで距離を計測して表示する
距離を測定するセンサーには、レーザー光や電磁波、超音波を利用するものがあります。今回は、赤外線レーザー光を利用した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通信を利用したプログラムを作成する予定です。
