M5StackGrayで2つのレーザー距離センサの同時接続テスト

2021.7.17 Coskx Lab  

1 はじめに

M5Stack Grayにレーザー距離センサVL53L0X(STMicroelectronics)を2つ接続して周囲の対象物までの距離を測定します。

VL53L0Xは,赤外線レーザにより4m程度までの距離を測定することができ,M5StackにはI2Cでデータを転送します。
VL53L0XはESP32とI2C通信で測距値を送っています。I2Cでは同一空間に同じI2Cアドレスの機器をおくことができません。
VL53L0Xを複数使用するとき,何もしなければすべて同じI2Cアドレスを持っていて,そのままでは複数のVL53L0XをM5Stackに接続することはできません。
VL53L0Xは起動後電源が切れるまでの間,一時的にI2Cアドレスを変更することができます。 その仕組みを利用して,2つのVL53L0XをM5Stackに接続します。
VL53L0Xを基板上に取り付けた製品は多数ありますが,ここでは秋月電子で販売されているAE-VL53L0Xを使用します。

2 使用環境

3 接続

図のように2つのAE-VL53L0XをM5Stackに接続します。2つのAE-VL53L0XのSDAとSCLはそれぞれ同じところに接続します。
XSHUTはあらかじめ決めておいたGPIOに接続します。AE-VL53L0XのGPIOはAE-VL53L0X内部でプルアップされているので何もつながないことにします。

構想段階で1台目センサのXSHUTはGPIO26,2台目のセンサのXSHUTはGPIO19につなぐと決めておきます。
今後,配線においてもプログラムにおいても矛盾がないようにします。


名称機能配線色M5Stack配線先
V+3.3~5V 入力3.3V
GNDGNDGND
SDAデータ線SDA (21)
SCLクロック線SCL (22)
XSHUTシャットダウン入力端子M5StackGPIOピンへ
GPIOGPIO(2.8Vレベル)接続しない



注意1 モータなどのノイズ発生機器と同時にレーザ距離センサを使う場合,使用していない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とデータをやり取りしますが,そのためのライブラリが必要です。
Arduino IDEにて,「ツール」メニューから,「ライブラリを管理」を選び,「ライブラリマネージャ」を開きます。
上部にある「タイプ すべて」,「トピック すべて」の右側の長いボックスが検索欄なので,
そこに「VL53L0X」と書き込むと検索されます。複数の「VL53L0X」が見つかりますが,
「by PoLolu」と書いてあるものを選んでインストールします。


5 テストプログラム

https://github.com/pololu/vl53l0x-arduino
https://klab.hateblo.jp/entry/2021/04/18/094705
を参考にさせていただきました。

2つのVL53L0Xを同時に使いますが,defaultでは2つとも同じI2Cアドレス0x29を持っています。
アドレスの競合を避けるために,2台目センサのアドレスを0x2bに変更します。

VL53L0XにはXSHUTという端子があり,これをLにするとそのVL53L0Xは無効になります。
この仕組みを利用して1台目センサを無効にしておいて,そのすきに2台目センサと通信してI2Cアドレスを変更します。

ここで,VL53L0XのXSHUTは2.8V信号で,2.8Vでプルアップされているため,XSHUTラインのGPIO19,GPIO26はOpenDrain設定にしておく必要があります。

失敗の経験から
2台目センサは無効にする必要がないので,XSHUTは放置しておいたのですが,M5Stackをリセットしても,2台目センサへの電源供給は止まらないので,センサは変更されたI2Cアドレスを持ち続けています。
プログラム側は,そのことがわからないため,I2Cアドレスを変更しようとして再起動のときに失敗します。
2台目センサも一度無効にすることで,以前設定したI2Cアドレスを忘れてくれるので,その後の設定がうまくいきます。

  動作テスト用プログラム本体

//DistanceSenSor_DualVL53l0xTest01.ino//

#include <M5Stack.h>
#include <Wire.h>
#include <VL53L0X.h>

//https://github.com/pololu/vl53l0x-arduino
//https://klab.hateblo.jp/entry/2021/04/18/094705
//2つのVL53L0Xを同時に使いますが,defaultでは2つとも同じI2Cアドレス0x29を持っています。
//アドレスの競合を避けるために,2台目センサのアドレスを0x2bに変更します。
//VL53L0XにはXSHUTという端子があり,これをLにするとそのVL53L0Xは無効になります。
//この仕組みを利用して1台目センサを無効にしておいて,そのすきに2台目センサと通信してI2Cアドレスを変更します。
//GPIO26を1台目センサのXSHUTに,GPIO19を2台目センサのXSHUTに接続しておきます。
//
//2台のセンサのXSHUTをLにすると,センサは停止状態=初期状態になります。(以前設定されていたI2Cアドレスも消去されます)
//1台目センサのXSHUTをLに保ったまま2台目センサのXSHUTをHにすると,defaultアドレスで有効なのは2台目センサだけになります
//この状況で2台目センサのアドレス変更作業を行います。
//作業が終わったら,1台目センサのXSHUTおHに戻して1台目センサを復活させます。
//ここで,VL53L0XのXSHUTは2.8V信号で,2.8Vでプルアップされているため,XSHUTがつながっているGPIO26,19はOpenDrain設定にしておく必要があります。

//SingleMode   (not ContinuousMode)
//必要なときに1回だけ測距 ただし,1つのセンサが測距し終わったら次のセンサの測距が始まる直列駆動

VL53L0X sensor1;
VL53L0X sensor2;

int measurementstatus1 = 0; //0:何もしていない 1:リクエスト中
int measurementstatus2 = 0; //0:何もしていない 1:リクエスト中

const int SDApin = 21; //SDAピン番号
const int SCLpin = 22; //SCLピン番号

const int XSHUTofSensor1pin = 26; //1台目センサのXSHUTのピン番号
const int XSHUTofSensor2pin = 19; //2台目センサのXSHUTのピン番号
const uint8_t Sensor2I2Caddress = 0x2b; //変更後の2台目センサの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);//オープンドレイン設定
  digitalWrite(XSHUTofSensor1pin, LOW); //1台目センサを無効状態にする
  digitalWrite(XSHUTofSensor2pin, LOW); //2台目センサを無効状態にする
  delay(100); //無効状態=初期化を確実にする

  Wire.begin(SDApin,SCLpin); //ここをGray用に設定しないと通信に失敗する
  Wire.setClock(400000); // use 400 kHz I2C

  M5.Lcd.setCursor(0, 120);
  //I2Cアドレス使用状況の検証
  checkI2CAddress();

  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台目センサを有効状態に戻す

  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]
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.println("Dual VL53L0X Started");
}


void loop()
{
  int distance1 = sensor1.readRangeSingleMillimeters();
  if (sensor1.timeoutOccurred()) {
    M5.Lcd.setCursor(0, 80);
    M5.Lcd.print("SENSOR1 TIMEOUT");
  } else {
    M5.Lcd.setCursor(0, 80);
    M5.Lcd.print("        ");
  }
  M5.Lcd.setCursor(0, 30);
  M5.Lcd.print("                          ");
  M5.Lcd.setCursor(0, 30);
  M5.Lcd.printf("S1 distance[mm]:    %5d  \n",distance1);

  int distance2 = sensor2.readRangeSingleMillimeters();
  if (sensor2.timeoutOccurred()) {
    M5.Lcd.setCursor(0, 100);
    M5.Lcd.print("SENSOR2 TIMEOUT");
  } else {
    M5.Lcd.setCursor(0, 100);
    M5.Lcd.print("        ");
  }
  M5.Lcd.setCursor(0, 50);
  M5.Lcd.print("                          ");
  M5.Lcd.setCursor(0, 50);
  M5.Lcd.printf("S2 distance[mm]:    %5d  \n",distance2);

}

6 トラブルシューティング

動作が不安定,時折初期化を失敗するとき,モータを含むシステムで使って誤動作するとき
(1)センサケーブルが細くて耐ノイズ性に問題があります。ケーブルはきれいに束ねない方がよいようです。
(2)使用していないセンサのケーブル(例えばGPIOの紫ケーブル)はノイズの原因になりますので取り外した方が良いでしょう。(下の画像は紫ケーブルを外したところ。細いピンなどで詰めを持ち上げれば簡単に外れます。)
(3)XSHUTを使っている場合,センサ内部でプルアップされているので,マイコン側はオープンドレインに設定するのがセオリですが,ノイズに弱くなるので,単なる出力設定にすると改善されることがあります。
pinMode(〇〇〇〇, OUTPUT_OPEN_DRAIN);
ではなく
pinMode(〇〇〇〇, OUTPUT);



7 まとめ

2つのレーザ距離センサVL53L0Xが同時に動作していることを確認できました。
ただしGPIOピンを最初に一回使うだけなのに2本占有してしまいました。