M5StackGrayで3つのレーザー距離センサの同時接続テスト
2021.7.17 Coskx Lab
1 はじめに
M5Stack Grayにレーザー距離センサVL53L0X(STMicroelectronics)を3つ接続して周囲の対象物までの距離を測定します。
ライブラリーに少し手を入れて,GPIOピン2本で3つのVL53L0Xを同時に使えるようにしました。
GPIOピンの枯渇に困ったときにはこの手を使えます。
ここでは秋月電子で販売されているAE-VL53L0Xを使用します。
2 使用環境
- Windows 10 64-bit
- Arduino IDE 1.8.13
- M5Stack Gray ESP32-D0WDQ6-V3 (revision 3)
MPU6886 + BMM150 16MB 40MHz
- AE-VL53L0X 3個
3 接続
図のように2つのAE-VL53L0XをM5Stackに接続します。2つのAE-VL53L0XのSDAとSCLはそれぞれ同じところに接続します。
XSHUTはあらかじめ決めておいたGPIOに接続します。AE-VL53L0XのGPIOはAE-VL53L0X内部でプルアップされているので何もつながないことにします。
構想段階で1台目センサのXSHUTはGPIO26,2台目センサのXSHUTはGPIO19,3台目のセンサのXSHUTは使わないと決めておきます。
今後,配線においてもプログラムにおいても矛盾がないようにします。
名称 | 機能 | 配線色 | M5Stack配線先 |
V+ | 3.3~5V 入力 | 赤 | 3.3V |
GND | GND | 黒 | GND |
SDA | データ線 | 黄 | SDA (21) |
SCL | クロック線 | 緑 | SCL (22) |
XSHUT | シャットダウン入力端子 | 青 | 1台目:GPIO26,2台目:GPIO19 |
GPIO | GPIO(2.8Vレベル) | 紫 | 接続しない |
注意1 モータなどのノイズ発生機器と同時にレーザ距離センサを使う場合,使用していないXSHUT(青)とGPIO(紫)のもケーブルは,コネクタから外してください。ノイズの影響でレーザ距離センサが誤動作する場合があります。
注意2 AE-VL53L0XのV+には「3.3~5V 入力」と書いてありますが,これは3.3Vのシステムでも5Vのシステムでも使えますということを表しています。
AE-VL53L0Xはよくできていて,AE-VL53L0Xの電源に3.3Vを与えると3.3VでI2C通信し,5Vを与えると5VでI2C通信します。
M5Stackで使うとき,M5Stackの信号レベルは3.3Vであるため,3.3Vで通信したいのでAE-VL53L0Xの電源には3.3Vを与えます。
信号レベルが5Vのマイコンにつなげるときは,5Vで通信したいので5Vを与えます。
4 準備
VL53L0XはI2CでCPUとデータをやり取りしますが,そのためのライブラリが必要です。
「by PoLolu」のVL53L0Xライブラリを手直しして使います。
VL53L0Xm1.zipをダウンロードして,解凍して中のVL53L0Xm1.cppとVL53L0Xm1.hを.inoと同じ作業フォルダに入れてください。
5 テストプログラム
https://github.com/pololu/vl53l0x-arduino
https://klab.hateblo.jp/entry/2021/04/18/094705
を参考にさせていただきました。
3つのVL53L0Xを同時に使いますが,defaultでは3つとも同じI2Cアドレス0x29を持っています。
アドレスの競合を避けるために,2台目センサのアドレスを0x2bに,3台目センサのアドレスを0x2dに変更します。
VL53L0XにはXSHUTという端子があり,これをLにするとそのVL53L0Xは無効になります。
この仕組みを利用して1台目,2台目センサを無効にしておいて,そのすきに3台目センサと通信してI2Cアドレスを変更します。
2台目センサを有効にして,(1台目はまだ無効)に2台目センサと通信してI2Cアドレスを変更します。
ここで,VL53L0XのXSHUTは2.8V信号で,2.8Vでプルアップされているため,XSHUTラインのGPIO26はOpenDrain設定にしておく必要があります。
3台目センサは無効にする必要がないのですが,作業が終わってM5Stackがリセットされても,3台目センサへの電源供給は止まらないので,3台目センサは変更されたI2Cアドレスを持ち続けています。
プログラムが再起動したときに,すでに3台目のアドレスが変更されている場合は,それを壊さないように再初期化します。その時に,ライブラリに追加した次のの関数を使用します。
justsetAddress()
また,Singlemodeでの測距データ取得を,1つの関数ではなく,複数の関数で行い,複数センサでの測距を並列して行えるようにしました。
そのためにライブラリに追加した関数は次の3つです。
requestSingle()
dataReady()
readSingle()
動作テスト用プログラム本体
//DistanceSenSor_TripleVL53l0xTrial0.ino//
#include <M5Stack.h>
#include <Wire.h>
#include "VL53L0Xm1.h"
//https://github.com/pololu/vl53l0x-arduino
//https://klab.hateblo.jp/entry/2021/04/18/094705
//3つのVL53L0Xを同時に使いますが,defaultでは3つとも同じI2Cアドレス0x29を持っています。
//アドレスの競合を避けるために,2台目センサのアドレスを0x2bに,3台目センサのアドレスを0x2dに変更します。
//VL53L0XにはXSHUTという端子があり,これをLにするとそのVL53L0Xは無効になります。
//この仕組みを利用して1台目センサと2台目センサを無効にしておいて,そのすきに3台目センサと通信してI2Cアドレスを変更します。
//次に1台目センサを無効にしておいて,そのすきに2台目センサと通信してI2Cアドレスを変更します。
//GPIO26を1台目センサのXSHUTに接続しておきます。
//GPIO19を1台目センサのXSHUTに接続しておきます。
//
//1台目と2台目センサのXSHUTをLにすると,defaultアドレスで有効なのは3台目センサだけになります。
//この状況で3台目センサのアドレス変更作業を行います。
//この作業が終わったら,2台目センサのXSHUTおHに戻して2台目センサを復活させます。
//この状況で,defaultアドレスで有効なのは3台目センサだけになります。
//この状況で2台目センサのアドレス変更作業を行います。
//この作業が終わったら,1台目センサのXSHUTおHに戻して1台目センサを復活させます。
//ここで,VL53L0XのXSHUTは2.8V信号で,2.8Vでプルアップされているため,XSHUTがつながっているGPIO26はOpenDrain設定にしておく必要があります。
//しかし,ノイズ低減のため,通常出力に設定します。
//
//M5Stackがresetボタンでリセットされたとき,更新プログラムが書き込まれたとき,VL53L0Xはリセットされないので,
//書き換えられているセンサのI2Cアドレスは書き換わったままになります。
//この状態になったとき困るのは3台目のセンサのみです。
//その場合は,先に
//justsetAddress()
//を行なって,クラスインスタンス内のI2Cアドレスのみ変更します。
//SingleMode (not ContinuousMode)
//必要なときに1回だけ測距
VL53L0X sensor1;
VL53L0X sensor2;
VL53L0X sensor3;
int measurementstatus1 = 0; //0:何もしていない 1:リクエスト中
int measurementstatus2 = 0; //0:何もしていない 1:リクエスト中
int measurementstatus3 = 0; //0:何もしていない 1:リクエスト中
const int SDApin = 21; //SDAピン番号
const int SCLpin = 22; //SCLピン番号
const int XSHUTofSensor1pin = 26; //1台目センサのXSHUTのピン番号
const int XSHUTofSensor2pin = 19; //1台目センサのXSHUTのピン番号
const uint8_t Sensor2I2Caddress = 0x2b; //変更後の2台目センサのI2Cアドレス
const uint8_t Sensor3I2Caddress = 0x2d; //変更後の3台目センサのI2Cアドレス
void checkI2CAddress() {
int address, error;
//見つかったaddressの対になるadrressは+0x80にある
for (address = 1; address<128; address++) {
Wire.beginTransmission(address);
error = Wire.endTransmission();
if(error==0){
M5.Lcd.print(address,HEX);M5.Lcd.print(" ");
}
delay(1);
}
M5.Lcd.println();
}
void setup()
{
M5.begin();
M5.Power.begin();
M5.Lcd.setTextSize(2); //高さ16 26文字/行
//pinMode(XSHUTofSensor1pin, OUTPUT_OPEN_DRAIN);//オープンドレイン設定
//pinMode(XSHUTofSensor2pin, OUTPUT_OPEN_DRAIN);//オープンドレイン設定
pinMode(XSHUTofSensor1pin, OUTPUT);//通常出力設定 ノイズ誤動作軽減
pinMode(XSHUTofSensor2pin, OUTPUT);//通常出力設定 ノイズ誤動作軽減
digitalWrite(XSHUTofSensor1pin, LOW); //1台目センサを無効状態にする
digitalWrite(XSHUTofSensor2pin, LOW); //2台目センサを無効状態にする
delay(1000); //無効状態=初期化を確実にする
Wire.begin(SDApin,SCLpin); //ここをGray用に設定しないと通信に失敗する
Wire.setClock(400000); // use 400 kHz I2C
M5.Lcd.setCursor(0, 100);
//I2Cアドレス使用状況の検証
checkI2CAddress();
Wire.beginTransmission(Sensor3I2Caddress);
if (Wire.endTransmission()==0) { //3台目センサが前回設定のアドレスを覚えていたとき
sensor3.setTimeout(500); //[msec]
sensor3.justsetAddress(Sensor3I2Caddress);
if (!sensor3.init())
{
M5.Lcd.println("Sensor3 not reinitialized");
while (1);
}
M5.Lcd.println("Sensor3 reinitialized");
} else {
sensor3.setTimeout(500); //[msec]
if (!sensor3.init())
{
M5.Lcd.println("Sensor3 not initialized");
if (sensor3.timeoutOccurred()) M5.Lcd.println("Time out");
while (1);
}
sensor3.setAddress((uint8_t)Sensor3I2Caddress); //3台目センサーのアドレス変更
M5.Lcd.println("Sensor3 initialized");
}
digitalWrite(XSHUTofSensor2pin, HIGH); //2台目センサを有効状態に戻す
sensor2.setTimeout(500); //[msec]
if (!sensor2.init())
{
M5.Lcd.println("Sensor2 not initialized");
while (1);
}
sensor2.setAddress((uint8_t)Sensor2I2Caddress); //2台目センサーのアドレス変更
M5.Lcd.println("Sensor2 initialized");
digitalWrite(XSHUTofSensor1pin, HIGH); //1台目センサを有効状態に戻す
//checkI2CAddress();
sensor1.setTimeout(500); //[msec]
if (!sensor1.init())
{
M5.Lcd.println("Sensor1 not initialized");
while (1);
}
M5.Lcd.println("Sensor1 initialized");
//I2Cアドレス使用状況の検証
checkI2CAddress();
sensor1.setMeasurementTimingBudget(50000); //測定タイミングバジェット(1回の距離測定)に許容される時間[micros]
sensor2.setMeasurementTimingBudget(50000); //測定タイミングバジェット(1回の距離測定)に許容される時間[micros]
sensor3.setMeasurementTimingBudget(50000); //測定タイミングバジェット(1回の距離測定)に許容される時間[micros]
M5.Lcd.setCursor(0, 0);
M5.Lcd.println("Triple VL53L0X Started");
}
void loop()
{
if (measurementstatus1 == 0) {
sensor1.requestSingle(); //start the measurement
measurementstatus1 = 1;
} else if (sensor1.timeoutOccurred()) {
M5.Lcd.setCursor(0, 30);
M5.Lcd.print("Sensor1 TIMEOUT ");
measurementstatus1 = 0;
} else if (measurementstatus1 == 1) {
if (sensor1.dataReady()){ //センサデータ取得完了だったら
measurementstatus1 = 0;
int result = 0;
result = sensor1.readSingle();
M5.Lcd.setCursor(0, 30);
M5.Lcd.print(" ");
M5.Lcd.setCursor(0, 30);
M5.Lcd.printf("S1 result[mm]:%5d \n",result);
}
}
if (measurementstatus2 == 0) {
sensor2.requestSingle(); //start the measurement
measurementstatus2 = 1;
} else if (sensor2.timeoutOccurred()) {
M5.Lcd.setCursor(0, 50);
M5.Lcd.print("Sensor2 TIMEOUT ");
measurementstatus2 = 0;
} else if (measurementstatus2 == 1) {
if (sensor2.dataReady()){ //センサデータ取得完了だったら
measurementstatus2 = 0;
int result = 0;
result = sensor2.readSingle();
M5.Lcd.setCursor(0, 50);
M5.Lcd.print(" ");
M5.Lcd.setCursor(0, 50);
M5.Lcd.printf("S2 result[mm]:%5d \n",result);
}
}
if (measurementstatus3 == 0) {
sensor3.requestSingle(); //start the measurement
measurementstatus3 = 1;
} else if (sensor3.timeoutOccurred()) {
M5.Lcd.setCursor(0, 70);
M5.Lcd.print("Sensor3 TIMEOUT ");
measurementstatus3 = 0;
} else if (measurementstatus3 == 1) {
if (sensor2.dataReady()){ //センサデータ取得完了だったら
measurementstatus3 = 0;
int result = 0;
result = sensor3.readSingle();
M5.Lcd.setCursor(0, 70);
M5.Lcd.print(" ");
M5.Lcd.setCursor(0, 70);
M5.Lcd.printf("S3 result[mm]:%5d \n",result);
}
}
}
6 トラブルシューティング
動作が不安定,時折初期化を失敗するとき,モータを含むシステムで使って誤動作するとき
(1)センサケーブルが細くて耐ノイズ性に問題があります。ケーブルはきれいに束ねない方がよいようです。
(2)使用していないセンサのケーブル(例えばGPIOの紫ケーブル)はノイズの原因になりますので取り外した方が良いでしょう。。(下の画像は紫ケーブルを外したところ。細いピンなどで詰めを持ち上げれば簡単に外れます。)
(3)XSHUTを使っている場合,センサ内部でプルアップされているので,マイコン側はオープンドレインに設定するのがセオリですが,ノイズに弱くなるので,単なる出力設定にすると改善されることがあります。
pinMode(〇〇〇〇, OUTPUT_OPEN_DRAIN);
ではなく
pinMode(〇〇〇〇, OUTPUT);
7 まとめ
2つのレーザ距離センサVL53L0Xが同時に動作していることを確認できました。
最初に一回使うだけのGPIOピンを2本のみ占有になりました。
自由に使えるGPIOピンは少ないので,このライブラリの修正は使えると思います。
AR-VL53L0Xは,ケーブルが耐ノイズ性が低いです。そのため,3個使用の場合,ちょっとしたことで,初期化が出来なくなったり,動作が不安定になったりします。
問題が生じたときは,トラブルシューティングでの記述を試してみてください。