VL53L0Xのつもりで買った測距センサーのVL53LXXをArduinoで使ってみたい

VL53LXX

今気づいたんですが、VL53L0Xを買ったつもりがVL53LXXって印字されてるやつが届いた。違うじゃん。どうしよう、VL53L0Xと同じような感じで使えんのかな。

超音波使うやつはこの前使ってみたんだけどなんかでっかいので、ちっちゃいやつを探したら見つけた。レーザーですって。すごいじゃん。ただ接続がI2Cなので、超音波のやつを引っこ抜いて代わりにぶっ挿すっていう使い方はできない。できないのね。だから動かしてみて遊んだだけです。

お買い物

https://www.ebay.com/itm/1pcs-GY-VL53L0XV2-L53L0X-TOF-Time-Of-Flight-Distance-Sensor-940nm-Laser-Ranging/183687917709

これ買ったんすよ。でも印字違うの。

VL53L0Xのデータシート

https://www.st.com/resource/en/datasheet/vl53l0x.pdf

接続

Arduino ↔ VL53LXX
5V ↔ VIN
SDA ↔ SDA
SCL ↔ SCL
GND ↔ GND

こんなか。VL53LXXにGPIO1,XSHUTていうピンがあるけど使わない。ていうか何に使うのかよく分からん。

スケッチ例を使ってみる

ライブラリをインストール

これかなぁと思いインストール。VL53L1Xっていうのもあるんだね。GitHubは

https://github.com/pololu/vl53l0x-arduino

こちら。

スケッチ例を開いて書き込み

これ。ContinuousとSingleの違いはなんじゃろー。データシートから引用すると

1. Single ranging
Ranging is performed only once after the API function is called. System returns to SW standby automatically.
2. Continuous ranging
Ranging is performed in a continuous way after the API function is called. As soon as the measurement is finished, another one is started without delay. User has to stop the ranging to return to SW standby. The last measurement is completed before stopping.

そもこのVL53L0XにアクセスするためにはAPIを使ってアクセスしなきゃならんそうで。Singleの場合、APIをコールするとVL53L0Xは1回だけ測距する。Continuoousの場合、APIをコールしたらstopしない限り測距し続ける。ていうのは分かったんだけど、そうするとどうなるのかがよくわからない。どっちかが精度高くなるとか応答が早いとかあるんだろうか。まぁどっちでもいいので開いて、書き込んでみて、シリアルモニタを開くと、出た!えらいスピードで計測結果が流れていく、ので、1秒ディレイを入れた。おー動く。

スマホカメラで撮るとレーザーが出てるのが分かるね。

実測値と比較してみる

ライブラリのreadmeを見るとmmで出力されるらしいんだけど、どうもズレてるような気がしてならないので、定規を使って現実の値と比較してみる。

こんな。この精度が高くねーじゃんと言いたくなるでしょうが専門的な装置なんて持ってないのでこれで測る。

結果

5cm 10cm 15cm
1回目 50 100 153
2回目 47 100 160
3回目 50 99 152
4回目 45 101 152
5回目 49 98 150
6回目 48 97 152
7回目 49 96 152
8回目 48 101 152
9回目 48 97 150
10回目 47 97 151

あれ?意外といーじゃん!もっとガッツリ変で、校正入れる感じになるかなーとか思ったけど大丈夫な気がする。

バラツキをなんとかしたい

つっても、数値がバラつく。まぁそんな精度求めてるわけでもないし、取得頻度が高いわけでもないんだけど、なんか気持ち悪いので。こういう時は値を滑らかにするようなフィルタをかければよかったんだった。そういや方位計算する時やったな。移動平均と、ローパスと、中央値を試してみる。

移動平均

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

#define SAMPLE_SIZE 10 //20, 50, 100

int count = 0;
int discardcount = 0;
uint16_t temp[SAMPLE_SIZE];
uint16_t sum = 0;
uint16_t average = 0;

VL53L0X sensor;

void setup()
{
  Serial.begin(9600);
  Wire.begin();

  sensor.setTimeout(500);
  if (!sensor.init())
  {
    Serial.println("Failed to detect and initialize sensor!");
    while (1) {}
  }

  sensor.startContinuous();
}

void loop() {

  temp[count] = sensor.readRangeContinuousMillimeters();
  if (sensor.timeoutOccurred()) {Serial.print(" TIMEOUT");}
  count++;
  
  if (count == SAMPLE_SIZE) {
    count = 0;
  }

  if (discardcount < SAMPLE_SIZE) {
    discardcount++;
    return;
  }

  for (int i = 0; i < SAMPLE_SIZE; i++) {
    sum += temp[i];
  }

  average = sum / SAMPLE_SIZE;
  sum = 0;

  Serial.print(average);
  Serial.println();
}

Continuousのスケッチ例をこんな感じで書き換えた。相変わらずスコープが分からん。実測値を10cmで、標本数を10, 20, 50, 100にして試してみると

10 20 50 100
1回目 22:08:20.964 -> 96 22:08:55.446 -> 101 22:09:28.327 -> 99 22:10:06.548 -> 99
2回目 22:08:20.998 -> 96 22:08:55.481 -> 101 22:09:28.362 -> 99 22:10:06.595 -> 99
3回目 22:08:21.032 -> 97 22:08:55.481 -> 101 22:09:28.396 -> 99 22:10:06.595 -> 100
4回目 22:08:21.066 -> 97 22:08:55.514 -> 101 22:09:28.431 -> 99 22:10:06.642 -> 100
5回目 22:08:21.100 -> 97 22:08:55.548 -> 101 22:09:28.465 -> 99 22:10:06.689 -> 100
6回目 22:08:21.134 -> 97 22:08:55.581 -> 101 22:09:28.499 -> 99 22:10:06.689 -> 99
7回目 22:08:21.168 -> 97 22:08:55.615 -> 102 22:09:28.533 -> 99 22:10:06.736 -> 99
8回目 22:08:21.201 -> 97 22:08:55.649 -> 102 22:09:28.568 -> 99 22:10:06.782 -> 99
9回目 22:08:21.235 -> 97 22:08:55.682 -> 101 22:09:28.604 -> 99 22:10:06.782 -> 99
10回目 22:08:21.270 -> 97 22:08:55.716 -> 101 22:09:28.638 -> 99 22:10:06.829 -> 99

タイムスタンプついてる。ブレがずいぶん小さくなった。

ローパス(指数加重平均)

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

uint16_t newdata = 0;
uint16_t lastdata = 0;
uint16_t filtereddata = 0;

VL53L0X sensor;

void setup()
{
  Serial.begin(9600);
  Wire.begin();

  sensor.setTimeout(500);
  if (!sensor.init())
  {
    Serial.println("Failed to detect and initialize sensor!");
    while (1) {}
  }

  sensor.startContinuous();
}

void loop() {

  float weight = 0.8;

  newdata = sensor.readRangeContinuousMillimeters();
  if (sensor.timeoutOccurred()) {Serial.print(" TIMEOUT");}

  if (lastdata == 0) {
    lastdata = newdata;
    return; 
  }
   
  filtereddata = (1.0 - weight) * lastdata + (newdata * weight);
  lastdata = newdata;

  Serial.print(filtereddata);
  Serial.println();
}

こんな感じで書き換えた。重み付けを0.8, 0.85, 0.9と変えてみる。結果は

0.8 0.85 0.9
1回目 23:50:33.653 -> 94 23:54:09.415 -> 99 23:54:39.888 -> 99
2回目 23:50:33.687 -> 94 23:54:09.449 -> 99 23:54:39.923 -> 101
3回目 23:50:33.722 -> 96 23:54:09.483 -> 101 23:54:39.923 -> 101
4回目 23:50:33.722 -> 95 23:54:09.518 -> 99 23:54:39.957 -> 101
5回目 23:50:33.756 -> 94 23:54:09.551 -> 100 23:54:39.991 -> 98
6回目 23:50:33.791 -> 95 23:54:09.584 -> 101 23:54:40.024 -> 102
7回目 23:50:33.824 -> 93 23:54:09.618 -> 101 23:54:40.057 -> 99
8回目 23:50:33.859 -> 97 23:54:09.652 -> 97 23:54:40.091 -> 100
9回目 23:50:33.892 -> 95 23:54:09.652 -> 100 23:54:40.125 -> 101
10回目 23:50:33.926 -> 97 23:54:09.685 -> 101 23:54:40.159 -> 101

けっこーバラつく。ある程度は滑らかになってるみたいだけどね。

中央値

つーか中央値ってどう求めるんだ。ちょっと調べてみたらソートして真ん中の値を引っ張ればいいみたい。標本数が偶数の場合は真ん中2つの数字の平均を取るらしい。うーむ。偶数の場合はめんどくさそうなので奇数の場合だけ考えよう。そうすれば要素数は最初に決めてるんだし簡単に引っ張ってこれるはず。問題はソートか…。初めてやるなぁ、とりあえずやってみよう。

配列のソート

バブルソート
uint16_t BubbleSort (uint16_t samples[]) {
  uint16_t tmp = 0;

  for (int j = 0; j < SAMPLE_SIZE - 1; j++) { for (int i = SAMPLE_SIZE - 1; i > j; i--) {
      if (samples[i - 1] > samples[i]) {
        tmp = samples[i - 1];
        samples[i - 1] = samples[i];
        samples[i] = tmp;
      }
    }
  }
  debug(samples);
  return samples[(SAMPLE_SIZE - 1) / 2];
}

こんな、バブルソートは遅いって話だったけどぴょんこぴょんこ数字は出てくる。標本数が11, 51, 101の結果

11 51 101
1回目 13:08:03.561 -> 101 13:08:56.342 -> 104 13:09:53.361 -> 101
2回目 13:08:03.595 -> 101 13:08:56.377 -> 104 13:09:53.396 -> 101
3回目 13:08:03.628 -> 101 13:08:56.410 -> 104 13:09:53.431 -> 101
4回目 13:08:03.662 -> 101 13:08:56.444 -> 104 13:09:53.466 -> 101
5回目 13:08:03.695 -> 101 13:08:56.478 -> 104 13:09:53.500 -> 101
6回目 13:08:03.729 -> 101 13:08:56.512 -> 104 13:09:53.534 -> 101
7回目 13:08:03.762 -> 100 13:08:56.546 -> 104 13:09:53.568 -> 101
8回目 13:08:03.797 -> 100 13:08:56.581 -> 103 13:09:53.602 -> 101
9回目 13:08:03.830 -> 101 13:08:56.581 -> 103 13:09:53.636 -> 101
10回目 13:08:03.864 -> 101 13:08:56.615 -> 103 13:09:53.669 -> 101

バラツキも少なめ。…クイックソートも試してみようと思ったけど別にいーや。気が向いたらやろう。あとソート済みの配列がこれでできたから、新しく測距した値は選択ソートにすれば処理が早そう。これも気が向いたらにしよう。

注意点

今は静止状態で測距してるからあんまり感じないけど、どのフィルタも標本数が多くなってくると応答性が悪くなる。測距した値が変化すると、即時反映するのではなく緩やかな変化になってた。精度と応答性はトレードオフなのかなーと思いました。

長い距離が測れない時

スケッチ例のSingleに、設定する行がある。

//#define LONG_RANGE

コメントアウトされてるのを復帰させると長い距離も測れるようになる。試したらできた。

精度と速度を変更したい時

これもスケッチ例のSingleに、設定する行がある。

// Uncomment ONE of these two lines to get
// - higher speed at the cost of lower accuracy OR
// - higher accuracy at the cost of lower speed

//#define HIGH_SPEED
//#define HIGH_ACCURACY

どっちかだけなので、どっちかコメントから復帰する。こっちは試してない。

参考

https://www.denshi.club/cookbook/sensor/distance/2vl53l0x.html

http://msms1003.blogspot.com/2017/12/vl53l0x.html

http://yuqlid.hatenablog.com/entry/2016/09/11/002804

https://www.codereading.com/algo_and_ds/algo/bubble_sort.html

カテゴリー: したい タグ: , , , パーマリンク

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です