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

2021.7.15 Coskx Lab  

1 はじめに

M5StackGrayで2つのレーザー距離センサの同時接続テストにてM5Stack Grayにレーザー距離センサVL53L1X(STMicroelectronics)を2つ接続して周囲の対象物までの距離を測定しました。

ところが,2つのVL53L1Xを同時に使うのに2本のGPIOピンを占有してしまいました。
ライブラリーに少し手を入れて,GPIOピン1本で2つのVL53L1Xを同時に使えるようにしました。
GPIOピンの枯渇に困ったときにはこの手を使えます。

2 使用環境

3 接続

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

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


注意1 モータなどのノイズ発生機器と同時にレーザ距離センサを使う場合,使用していないXSHUT(青)とGPIO(紫)のケーブルは,コネクタから外してください。ノイズの影響でレーザ距離センサが誤動作する場合があります。

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



4 準備

VL53L1XはI2CでCPUとデータをやり取りしますが,そのためのライブラリが必要です。
「by PoLolu」のVL53L1Xライブラリを手直しして使います。
VL53L1Xm1.zipをダウンロードして,解凍して中のVL53L1Xm1.cppとVL53L1Xm1.hを.inoと同じ作業フォルダに入れてください。

5 テストプログラム

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

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

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

2台目センサは無効にする必要がないのですが,作業が終わってM5Stackがリセットされても,2台目センサへの電源供給は止まらないので,2台目センサは変更されたI2Cアドレスを持ち続けています。
プログラムが再起動したときに,すでに2台目のアドレスが変更されている場合は,それを壊さないように再初期化します。その時に,ライブラリに追加した次の2つの関数を使用します。
justsetAddress()
reinit()

  動作確認用プログラム本体

//DistanceSenSor_DualVL53l1xTrial0.ino//

#include <M5Stack.h>
#include <Wire.h>
#include "VL53L1Xm1.h"

//https://www.arduino.cc/reference/en/libraries/vl53l1x/
//https://klab.hateblo.jp/entry/2021/04/18/094705
//2つのVL53L1Xを同時に使いますが,defaultでは2つとも同じI2Cアドレス0x29を持っています。
//アドレスの競合を避けるために,2台目センサのアドレスを0x2bに変更します。
//VL53L1XにはXSHUTという端子があり,これをLにするとそのVL53L1Xは無効になります。
//この仕組みを利用して1台目センサを無効にしておいて,そのすきに2台目センサと通信してI2Cアドレスを変更します。
//GPIO26を1台目センサのXSHUTに接続しておきます。
//
//1台目センサのXSHUTをLにすると,defaultアドレスで有効なのは2台目センサだけになります
//この状況で2台目センサのアドレス変更作業を行います。
//作業が終わったら,1台目センサのXSHUTおHに戻して1台目センサを復活させます。
//ここで,VL53L1XのXSHUTは2.8V信号で,2.8Vでプルアップされているため,XSHUTがつながっているGPIO26はOpenDrain設定にしておく必要があります。
//
//M5Stackがresetボタンでリセットされたとき,更新プログラムが書き込まれたとき,VL53L1Xはリセットされないので,I2Cアドレスは書き換わったままになります。
//その場合は,init()の代わりに
//justsetAddress()
//reinit()
//を行います

//SingleMode   (not ContinuousMode)
//必要なときに1回だけ測距

VL53L1X sensor1;
VL53L1X 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 uint8_t Sensor2I2Caddress = 0x2b; //変更後の2台目センサのI2Cアドレス

void setup()
{
  M5.begin();
  M5.Power.begin();
  M5.Lcd.setTextSize(2); //高さ16 26文字/行

  pinMode(XSHUTofSensor1pin, OUTPUT_OPEN_DRAIN);//オープンドレイン設定
  digitalWrite(XSHUTofSensor1pin, LOW); //1台目センサを無効状態にする
  delay(100); //無効状態=初期化を確実にする

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

  //I2Cアドレス使用状況の検証
  /*
  M5.Lcd.setCursor(0, 110);
  int address, error;
  for (address = 1; address<255; address++) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    if(error==0){
      M5.Lcd.print(address,HEX);M5.Lcd.print(" ");
    }
    delay(10);
    // 10 68 75 90 E8 F5
  }
  */
  
  Wire.beginTransmission(Sensor2I2Caddress);
  if (Wire.endTransmission()==0) { //2台目センサが前回設定のアドレスを覚えていたとき
    sensor2.setTimeout(500); //[msec]
    sensor2.justsetAddress(Sensor2I2Caddress);
    if (!sensor2.reinit())
    {
      M5.Lcd.println("Failed to detect and reinitialize sensor2!");
      while (1);
    }
  } else {
    sensor2.setTimeout(500); //[msec]
    if (!sensor2.init())
    {
      M5.Lcd.println("Failed to detect and initialize sensor2!");
      if (sensor2.timeoutOccurred()) M5.Lcd.println("Time out");
      while (1);
    }
    sensor2.setAddress((uint8_t)Sensor2I2Caddress); //2台目センサーのアドレス変更
  }

  digitalWrite(XSHUTofSensor1pin, HIGH); //1台目センサを有効状態に戻す
  //delay(100);

  if (!sensor1.init())
  {
    M5.Lcd.println("Failed to detect and initialize sensor1!");
    while (1);
  }

  //I2Cアドレス使用状況の検証
  /*
  M5.Lcd.println();
  for (address = 1; address<255; address++) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    if(error==0){
      M5.Lcd.print(address,HEX);M5.Lcd.print(" ");
    }
    delay(10);
    // 10 29 2B 68 75 90 A9 AB E8 F5
    // 29 2B が増える 同時にA9 ABも増える
    // 2台目のアドレスを2Dに設定すると 29 2DとA9 ADが増える
  }
  */

  sensor1.setDistanceMode(VL53L1X::Long);
  sensor2.setDistanceMode(VL53L1X::Long);
  sensor1.setMeasurementTimingBudget(50000); //測定タイミングバジェット(1回の距離測定)に許容される時間[micros]
  sensor2.setMeasurementTimingBudget(50000); //測定タイミングバジェット(1回の距離測定)に許容される時間[micros]
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.println("Dual VL53L1X Started");
}

void loop()
{
  if (measurementstatus1 == 0) {
    sensor1.readSingle(false); //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.read(false);
      M5.Lcd.setCursor(0, 30);
      M5.Lcd.print("                        ");
      M5.Lcd.setCursor(0, 30);
      M5.Lcd.printf("status: %s\n",VL53L1X::rangeStatusToString(sensor1.ranging_data.range_status));
      M5.Lcd.printf("S2 result[mm]:%5d  \n",result);
    }
  }
  if (measurementstatus2 == 0) {
    sensor2.readSingle(false); //start the measurement
    measurementstatus2 = 1;
  } else if (sensor1.timeoutOccurred()) {
      M5.Lcd.setCursor(0, 70);
      M5.Lcd.print("Sensor2 TIMEOUT         ");
      measurementstatus2 = 0;
  } else if (measurementstatus2 == 1) {
    if (sensor2.dataReady()){ //センサデータ取得完了だったら
      measurementstatus2 = 0;
      int result = 0;
      result = sensor2.read(false);
      M5.Lcd.setCursor(0, 70);
      M5.Lcd.print("                        ");
      M5.Lcd.setCursor(0, 70);
      M5.Lcd.printf("status: %s\n",VL53L1X::rangeStatusToString(sensor2.ranging_data.range_status));
      M5.Lcd.printf("S1 result[mm]:%5d  \n",result);
    }
  }
}

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

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



7 まとめ

2つのレーザ距離センサVL53L1Xが同時に動作していることを確認できました。
最初に一回使うだけのGPIOピンを1本のみ占有になりました。
自由に使えるGPIOピンは少ないので,このライブラリの修正は使えると思います。