VL53LXX
今気づいたんですが、VL53L0Xを買ったつもりがVL53LXXって印字されてるやつが届いた。違うじゃん。どうしよう、VL53L0Xと同じような感じで使えんのかな。
超音波使うやつはこの前使ってみたんだけどなんかでっかいので、ちっちゃいやつを探したら見つけた。レーザーですって。すごいじゃん。ただ接続がI2Cなので、超音波のやつを引っこ抜いて代わりにぶっ挿すっていう使い方はできない。できないのね。だから動かしてみて遊んだだけです。
お買い物
これ買ったんすよ。でも印字違うの。
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






