測距センサ VL53L0X を用いた身長測定器
子供って長時間じっとできないので身長をはかるのってなかなか大変。。。
そこで低価格で高精度のセンサを見つけたので場所と時間を取らない身長測定器をつくりました。
目次
構成
-
距離センサ VL53L0X
標準が120cmで、広範囲モードで220cmくらいまでは測定できるようです。データシートを読みながらツイート https://t.co/iigUvPf5Gz
— 朱雀技研 (@suzakulab) 2016年8月19日
凄い広範囲なのに分解能1mmのセンサです!Arduino用ライブラリは以下にあります。
https://github.com/pololu/vl53l0x-arduino
-
6軸モーションセンサ MPU6050
3軸ジャイロスコープと3軸加速度センサで姿勢情報をセンシングできます。今回は水平方向のx軸、y軸の加速度のみ使用して測定器の水平度を計測してます。しかしメチャクチャ安い!Arduino用ライブラリは以下にあります(MPU6050とI2Cdevを使用)。
https://github.com/jrowberg/i2cdevlib/tree/master/Arduino - LED(Neopixel)
測定器が非水平の時は赤く、水平の時は緑に光ります。
-
LCDディスプレイ
梅沢無線で¥400!山積みにされてるのを購入。以下が参考になります。
https://www.arduino.cc/en/Tutorial/HelloWorld -
マイコンArduino Nano互換品
激安の互換品を使用してます 🙂 - 測定ボタン(タクトスイッチ)
押すことで動作モードを変更します。
測定器
センサ VL53L0Xを木箱の底に配置し距離を測定します。距離はLCDディスプレイに表示されます。
木箱内部にマイコンArduino Nanoと6軸モーションセンサMPU6050に配置し、電源には9V電池を使用しました。
MPU6050の水平方向の(重力)加速度を検出して測定器の水平具合を計測し、水平時にLEDが緑色になります。
使い方
ディスプレイ表示
通常モード
電源スイッチを入れると起動し通常モードで動作します。通常モード時は常に距離を測定しディスプレイに表示されます。測定器が水平時にはLEDは緑に光り、非水平時は赤く光ります。
測定モード
測定ボタン(タクトスイッチ)を押している間は測定モードとなります。測定器を頭に乗せて測定モードにしてから水平状態で距離が検出されるとLEDが青くひかり測定値が保持され身長が計測できます。
距離センサVL53L0Xで長女ちゃん身長測定 pic.twitter.com/1tMOndvOgy
— HomeMadeGarbage (@H0meMadeGarbage) 2016年9月16日
再度測定ボタンを押すと通常モードに戻ります。
Arduino IDE用コード
距離センサ VL53L0XはLONG_RANGEモードとHIGH_ACCURACYモードで使用。
距離は単位をcmにしてセンサ位置分補正してます(1mm)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
#include "I2Cdev.h" #include "MPU6050.h" #include <Adafruit_NeoPixel.h> #include <VL53L0X.h> #include <LiquidCrystal.h> // Arduino Wire library is required if I2Cdev I2CDEV_ARDUINO_WIRE implementation // is used in I2Cdev.h #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE #include "Wire.h" #endif int mode = 0; MPU6050 accelgyro; // class default I2C address is 0x68 int16_t ax, ay, az; int diffAccel = 100; #define PIN 8 Adafruit_NeoPixel pixels = Adafruit_NeoPixel(1, PIN, NEO_GRB + NEO_KHZ800); //LiquidCrystal lcd(RS[4], E[6], DB4[11], DB5[12], DB6[13], DB7[14]); LiquidCrystal lcd(12, 11, 5, 4, 3, 2); char s[16]; VL53L0X sensor; float height; // Uncomment this line to use long range mode. This // increases the sensitivity of the sensor and extends its // potential range, but increases the likelihood of getting // an inaccurate reading because of reflections from objects // other than the intended target. It works best in dark // conditions. #define LONG_RANGE // 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 void setup() { // join I2C bus (I2Cdev library doesn't do this automatically) #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE Wire.begin(); #elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE Fastwire::setup(400, true); #endif Serial.begin(38400); pinMode(9, INPUT_PULLUP); pixels.begin(); pixels.setBrightness(50); pixels.setPixelColor(0, pixels.Color(0,0,0)); pixels.show(); lcd.begin(16, 2); lcd.setCursor(0, 0); lcd.print("x"); lcd.setCursor(9, 0); lcd.print("y"); lcd.setCursor(0, 1); lcd.print("Height:"); sensor.init(); sensor.setTimeout(500); #if defined LONG_RANGE // lower the return signal rate limit (default is 0.25 MCPS) sensor.setSignalRateLimit(0.1); // increase laser pulse periods (defaults are 14 and 10 PCLKs) sensor.setVcselPulsePeriod(VL53L0X::VcselPeriodPreRange, 18); sensor.setVcselPulsePeriod(VL53L0X::VcselPeriodFinalRange, 14); #endif #if defined HIGH_SPEED // reduce timing budget to 20 ms (default is about 33 ms) sensor.setMeasurementTimingBudget(20000); #elif defined HIGH_ACCURACY // increase timing budget to 200 ms sensor.setMeasurementTimingBudget(200000); #endif // initialize device Serial.println("Initializing I2C devices..."); accelgyro.initialize(); // verify connection Serial.println("Testing device connections..."); Serial.println(accelgyro.testConnection() ? "MPU6050 connection successful" : "MPU6050 connection failed"); if(accelgyro.testConnection()){ pixels.setPixelColor(0, pixels.Color(255,0,0)); pixels.show(); } // use the code below to change accel/gyro offset values accelgyro.setXAccelOffset(40); //0 accelgyro.setYAccelOffset(-985); //0 accelgyro.setZAccelOffset(968); //16384 } void loop() { //測定モード while(digitalRead(9) == 0 && mode == 0){ // read raw accel accelgyro.getAcceleration(&ax, &ay, &az); lcd.setCursor(1, 0); lcd.print(dtostrf(ax,6,0,s)); lcd.setCursor(10, 0); lcd.print(dtostrf(ay,6,0,s)); if(abs(ax) < diffAccel && abs(ay) < diffAccel){ pixels.setPixelColor(0, pixels.Color(0,255,0)); pixels.show(); if (sensor.timeoutOccurred() == 0) { //距離測定 height = sensor.readRangeSingleMillimeters()/10.0 + 0.1; lcd.setCursor(8, 1); if(height <= 220.0){ lcd.print(dtostrf(height,5,1,s)); pixels.setPixelColor(0, pixels.Color(0,0,255)); pixels.show(); mode = 1; } } }else{ pixels.setPixelColor(0, pixels.Color(255,0,0)); pixels.show(); } } //modeリセット if(digitalRead(9) == 1 && mode == 1){ mode = 2; delay(100); } if(digitalRead(9) == 0 && mode == 2){ mode = 0; delay(100); } //通常モード if(digitalRead(9) == 1 && mode == 0){ // read raw accel accelgyro.getAcceleration(&ax, &ay, &az); lcd.setCursor(1, 0); lcd.print(dtostrf(ax,6,0,s)); lcd.setCursor(10, 0); lcd.print(dtostrf(ay,6,0,s)); if(abs(ax) < diffAccel && abs(ay) < diffAccel){ pixels.setPixelColor(0, pixels.Color(0,255,0)); pixels.show(); }else{ pixels.setPixelColor(0, pixels.Color(255,0,0)); pixels.show(); } if (sensor.timeoutOccurred() == 0) { //距離測定 height = sensor.readRangeSingleMillimeters()/10.0 + 0.1; Serial.print(height); Serial.print("\t"); lcd.setCursor(8, 1); if(height <= 220.0){ lcd.print(dtostrf(height,5,1,s)); } } } } |
比較的高速で測定でき精度も高いので、これで落ち着きのない長男くんの身長でも簡単に測ることができます♪
追記(16/9/23)
hackaday.com様にて紹介いただきました!
170cmくらいまではそれなりの精度で測れてます。180cm超えると誤差大きくなりますね。。。水平度測るセンサMPU6050の値平均化してノイズ除去とか まだまだ詰めれそうです。
追記(16/9/26)
Adafruit様のブログで取り上げていただきました。
追記(16/9/28)
各所で取り上げていただき感謝です 😛 !
- EngineersGarage様
- electronics-lab様
なんと本家Arduino様やAtmel様もTweetくださいました!!
Save your walls from pencil marks! Measure your kid’s height using an Arduino and distance sensor: https://t.co/KTvi8L0dBT pic.twitter.com/OiHeTdBMsO
— Arduino (@arduino) 2016年9月21日
Don’t want to pencil your walls? Try this height-measuring Arduino device instead: https://t.co/WOomZpaUHS pic.twitter.com/ZvidD67YTn
— Atmel Corporation (@Atmel) 2016年9月22日
こんばんわ
検索で辿りつきました。
精度はどのくらいでしょうか?
大体でも教えて頂けると、有り難いです。
コメントありがとうございます!
VL53L0X自体の精度はかなり高いです。分解能1mmで180cmくらいまでいけてます。
この身長計の精度としましては加速度センサの誤差(というか使い方の問題)が
支配的で現状 真値±2cmくらいです。。。
こんばんわ
コメント後、最近やっとモジュールを入手し、実験してみました。
仰るように精度良いですね。
使えそうです。
相談なのですが、埃防止に透明カバーをつけようと思っています。
英語のデータシートをざっと読んでみたところ、offsetあるいはcross-talkで設定っぽいのは
分かったのですが、これはどっちをいじればいいのか、自信がありません。
この資料をみて、こんな感じでやればいい程度のアドバイスを頂けると有り難いです。
宜しくお願いします。
度々済みません。
悩み所は、デフォルトで透明カバーというか、透明なガラスが検出されてしまうので、
そいつを検出しないようにできるのか?ってところです。
自レスです。
板を汚して済みません。
調べた限りですが、赤外線レーザーが反射しているので、IRコーティングされたガラス、カバーでないとダメだと分かりました。
相変わらず、offsetの設定をどうやるかは分かっていません。
コメントありがとうございます。
データシート
データシートのp.12によりますと オフセット補正は距離に依存しない誤差を
クロストーク補正は距離に依存する誤差を補正するとのことです。
カバーに関するアプリケーションノート
この資料ではおっしゃるようにIRコーティング推奨されていますね。
クロストーク補正の詳細はVL53L0X API User Manualに詳細記載とのことですが見つけれません。。。
オフセットやクロストーク補償はpololuのライブラリでは用意されていないようですがadafruitのライブラリでは使用できそうです。調査は必要ですが。。。。
こんにちは。
このセンサーを調べていて辿り着きました。
質問なのですが、このセンサーは無限遠、もしくは測定範囲外の遠距離を測ろうとしたときどのような値を返すのでしょうか?
ご教示お願いします。
コメントありがとうございます!
この測定器ではLONG_RANGEモードとHIGH_ACCURACYモードで使用してるのですが、
190cm以上で精度が悪くなっていって217cm以上になると測定値が低下して全く測定できない状態になります
返信どうもです。
全く測定できないとのことですが、arduino側からは具体的にどういう数値が見えるのでしょう?
データーシートはざっと眺めましたが、そういった点に関しては良く分かりませんでした。
よろしくお願いします。
217〜218cmを超えたあたりで130cm〜70cmなどの誤った値を返してきます。
ここではpololuのライブラリ https://github.com/pololu/vl53l0x-arduino
をつかって
readRangeSingleMillimeters()
で測定しています。mm単位の値を返してきます。
以下でcmにしてモジュールの高さ分補正しています。
height = sensor.readRangeSingleMillimeters()/10.0 + 0.1;