ESP8266やArduinoで使える面白い周辺デバイスが無いかなと探索していた所、VL53L0Xと言うやたら小さいレーザー距離計モジュールを見つけた。
VL53L0X Time-of-Flight 距離センサモジュール
モジュール自身は米粒程度の大きさながら仕組みや使い方はなかなか複雑で、設定により2m程度までの距離をかなり高い精度で測れるデバイスである。デバイスから作りたい物を考えると言う手順が逆になっている今日この頃であるが、このデバイスで何か面白い物を作れないかとネットでアイデアを採掘していた所、MPU6050と言う3軸ジャイロ/3軸加速度センサーと組み合わせた子供の身長測定器作成の記事を見つけた。
当初記事を見た際はジャイロで水平確認して距離計で測定ねー、程度で流していたがしばらくして
「・・・あれ?距離と傾きが分かっていれば水平でなくても高さ分かるんじゃね?測定ポイント少し離れててもいけるんじゃね?棒の様な形の測定器できるんじゃね?」
と思いつき、開発開始。
左右の傾きによる補正た単純な三角関数で計算し、上下の傾きによる補正と計測したいポイントとのオフセットの計算は下記図で算出。
ひさびさに三角関数の計算をした。上記図がほとんど回答になっているが、良い頭の体操になるので詳細は説明しない。基本的な三角関数の計算のみ。
使用するモジュールはESP8266 mini D1タイプのモジュールとディスプレイとして白色I2C OLED、そして要のVL53L0XとMPU6050。電源としてリポバッテリーとESP8266用バッテリ―シールド。基板はESP8266電子工作用の汎用基板。
VL53L0Xのモジュールはスイッチサイエンスさんのモジュールを共立さんから購入。小さい。デジタルカメラ通してみると測定時光っているのが分かる。
https://www.switch-science.com/catalog/2894/
http://eleshop.jp/shop/g/gH37318/
では組立。バッテリーシールドのコネクターは外してリード線直付けし、基板に装着。バッテリーシールドには電源スイッチがないので、リポとシールドの間にスイッチをはさんでいる。リポは基盤裏に両面テープで固定。
I2C OLEDディスプレイを取り付け、とりあえず天気予報のソフトで動作確認。
I2C OLED、VL53L0X、MPU6050いずれもI2C通信のため、各デバイスを5V、GND、D1、D2で接続。
3Dプリンターでガワを作成。先端と本体は8mmのアルミ棒で接続。基板を両面テープやUVレジン系樹脂で固定。操作用にスイッチを2つ追加。
して、完成。
まぁまぁの出来。傾きは各45度を超えると測定外表示。
三姉妹をまとめて測定/記録後WiFi経由でサーバーに送信、サーバー側にデータを保存できるようにした。ESP8266でGmail送信したりmicroSDに記録するサンプルも転がっているので、測定結果をメール送信したりメモリカードに記録しておく事も出来そうだ。
簡単な紹介と動作している所の動画をアップしたので参考に。
サーバーに保存した記録はブラウザから閲覧。
ESP8266のコードは以下の通り。操作ボタンはD3 / D4をスイッチ経由でGNDに接続。MPU6050のライブラリは
https://github.com/jrowberg/i2cdevlib/tree/master/Arduino/MPU6050
から。VL53L0Xのライブラリは
https://github.com/pololu/vl53l0x-arduino
から。
#include <Arduino.h> #include <ESP8266WiFi.h> #include <ESP8266WiFiMulti.h> #include <WiFiClient.h> #include <ESP8266WebServer.h> #include <WebSocketsServer.h> #include <Hash.h> #include <ESP8266mDNS.h> #include <WiFiUdp.h> #include <FS.h> #include <Adafruit_NeoPixel.h> #include <Wire.h> #include <SPI.h> #include <Adafruit_GFX.h> #include <ESP_Adafruit_SSD1306.h> #define PIN_D1 5 #define PIN_D2 4 #define PIN_D3 0 #define PIN_D4 2 #define PIN_D5 14 #define PIN_D6 12 #define PIN_D7 13 #define PIN_D8 15 #define BUILTIN_LED 2 const unsigned char pic_cloud[] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xc0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x80, 0x0f, 0x80, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, 0xfe, 0x03, 0x00, 0x00, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x0f, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0xfc, 0x3f, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x18, 0x7e, 0x00, 0x00, 0x00, 0xe0, 0x00, 0xe0, 0x00, 0xf8, 0x00, 0x00, 0x00, 0xe0, 0x00, 0xf0, 0x01, 0xf0, 0x00, 0x00, 0x00, 0xe0, 0x00, 0xf8, 0x03, 0xe0, 0x01, 0x00, 0x00, 0xf8, 0x00, 0xfc, 0x07, 0xc0, 0x01, 0x00, 0x00, 0x7e, 0x00, 0xfe, 0x0f, 0xc0, 0x03, 0x00, 0x00, 0x3f, 0x00, 0xef, 0x1e, 0x80, 0x03, 0x00, 0x00, 0x0f, 0x80, 0xe7, 0x3c, 0x80, 0x07, 0x00, 0x80, 0x07, 0xc0, 0xe3, 0x78, 0x80, 0x1f, 0x00, 0x80, 0x03, 0xc0, 0xe1, 0x70, 0x00, 0x3f, 0x00, 0xc0, 0x01, 0xc0, 0xe0, 0x60, 0x00, 0x78, 0x00, 0xc0, 0x01, 0x00, 0xe0, 0x00, 0x00, 0xf0, 0x00, 0xc0, 0x01, 0x00, 0xe0, 0x00, 0x00, 0xe0, 0x00, 0xc0, 0x01, 0x00, 0xe0, 0x00, 0x00, 0xe0, 0x01, 0xc0, 0x01, 0x00, 0xe0, 0x00, 0x00, 0xe0, 0x01, 0xc0, 0x01, 0x00, 0xe0, 0x00, 0x00, 0xe0, 0x01, 0xc0, 0x03, 0x00, 0xe0, 0x00, 0x00, 0xe0, 0x00, 0x80, 0x07, 0x00, 0x40, 0x00, 0x00, 0xf0, 0x00, 0x80, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x00, 0xf8, 0xff, 0xff, 0xff, 0xff, 0x0f, }; #include <VL53L0X.h> VL53L0X sensor; #include "I2Cdev.h" #include "MPU6050.h" MPU6050 accelgyro; int16_t ax, ay, az; char buff[32]; #define BTN_MODE PIN_D4 //connect switch, and to gnd #define BTN_SEL PIN_D3 //connect switch, and to gnd static const uint8_t ALT_SDA = PIN_D2; static const uint8_t ALT_SCL = PIN_D1; Adafruit_SSD1306 display(-1); char oledstrbuff[64]; #if (SSD1306_LCDHEIGHT != 64) #error("Height incorrect, please fix Adafruit_SSD1306.h!"); #endif ESP8266WiFiMulti WiFiMulti; const char* ssid = "ssid"; const char* password = "password"; const char* host = "ESP8266"; int lastwifi; unsigned long lastupdateMillis = 0; bool freezedisplay = false; int linefeeder = 0; void oled_drawstr(int line,char *text){ display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(0,line*10); display.print(text); display.display(); } void oled_drawstr(char *text){ oled_drawstr(linefeeder++,text); } void oled_overwritestr(int line, char *text){ display.fillRect(0, line*10, 100, 10, BLACK); display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(0,line*10); display.print(text); display.display(); } void oled_overwritestr_d(int line, char *text){ display.fillRect(0, line, 100, 10, BLACK); display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(0,line); display.print(text); display.display(); } void oled_drawtext(int posx,int posy, int textsize, char *text){ display.setTextSize(textsize); display.setTextColor(WHITE); display.setCursor(posx,posy); display.print(text); } bool wifi_setup(){ oled_drawstr("ESP8266 WiFi"); sprintf(oledstrbuff,"AP :%s",ssid); oled_drawstr(oledstrbuff); Serial.printf("AP SSID:%s", ssid); WiFi.mode(WIFI_STA); if (String(WiFi.SSID()) != String(ssid)) { WiFi.begin(ssid, password); } int retrycount = 0; while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); retrycount++; if (retrycount > 20){ return false; oled_drawstr("conn timeout"); Serial.println("connection timeout"); return false; } } Serial.println(""); lastwifi = WiFi.status(); IPAddress ip = WiFi.localIP(); sprintf(oledstrbuff,"IP :%d.%d.%d.%d",ip[0],ip[1],ip[2],ip[3]); oled_drawstr(oledstrbuff); byte mac[6]; WiFi.macAddress(mac); sprintf(oledstrbuff,"MAC:%02X:%02X:%02X:%02X:%02X:%02X",mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]); oled_drawstr(oledstrbuff); if (!MDNS.begin(host)) { Serial.println("Error setting up MDNS responder!"); return false; } Serial.println("mDNS responder started"); WiFi.setSleepMode(WIFI_NONE_SLEEP); if (WiFi.status() != WL_CONNECTED) return false; return true; } void setup() { Serial.begin(74880); Serial.print("\n"); Serial.setDebugOutput(true); pinMode(BUILTIN_LED, OUTPUT); digitalWrite(BUILTIN_LED, HIGH); //high to turn off pinMode(BTN_MODE,INPUT_PULLUP); pinMode(BTN_SEL,INPUT_PULLUP); Wire.begin(ALT_SDA,ALT_SCL); display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // initialize with the I2C addr display.display(); delay(500); display.clearDisplay(); wifi_setup(); //set sensor long range, high accuracy sensor.setTimeout(5000); sensor.init(); sensor.setTimeout(500); sensor.setSignalRateLimit(0.1); sensor.setVcselPulsePeriod(VL53L0X::VcselPeriodPreRange, 18); sensor.setVcselPulsePeriod(VL53L0X::VcselPeriodFinalRange, 14); sensor.setMeasurementTimingBudget(200000); accelgyro.initialize(); Serial.println("Ready"); } #define S_HOFFSET 240.0 //laser range sensor horizontal offset from measurement point, in mm #define S_VOFFSET 2.0 //laser range sensor vertical offset from measurement point, in mm #define degtorad(angleDegrees) (angleDegrees * M_PI / 180.0) #define radtodeg(angleRadians) (angleRadians * 180.0 / M_PI) #define min(a,b) ((a)<(b)?(a):(b)) #define max(a,b) ((a)>(b)?(a):(b)) #define degconvert 57.2957786 #define averagenum 10 double averaging[averagenum]; int avindex = 0; int xangle; int yangle; uint16_t distance; double m_height = -1; int misscount; #define MODE_MEASURE 0 #define MODE_SELECT 1 #define MODE_SEND 2 char s_person[3][8] = {"Choujyo","Jijyo","Sanjyo"}; int m_mode = MODE_MEASURE; //mode int m_person = 0; //selected person bool m_measure = true; //measure on / pause int m_pheight[3] = {0,0,0}; //measure per person unsigned long btn_down_time = 0; bool lastbtnmode = false; bool lastbtnsel = false; double calcheight(double hangle, double vangle, double olen){ double reslen = 0; if (m_measure){ //hangle : horizontal angle //vangle : vertical angle //len : laser range, in mm double len = olen + S_VOFFSET; double hadjlen; //horizontal adjusted length double aangle_r, aangle_d, hyplen; double bangle_r, bangle_d; hadjlen = cos(degtorad(hangle)) * len; //adjust horizontal length aangle_r = atan2(hadjlen,S_HOFFSET); aangle_d = radtodeg(aangle_r); Serial.print("aangle:"); Serial.println(aangle_d); hyplen = sqrt(S_HOFFSET*S_HOFFSET + hadjlen*hadjlen); Serial.print("hyplen:"); Serial.println(hyplen); if (vangle < 0){ if (aangle_d + abs(vangle) < 90){ bangle_d = 90 - aangle_d - abs(vangle); bangle_r = degtorad(bangle_d); reslen = cos(bangle_r) * hyplen; }else if (aangle_d + abs(vangle) > 90){ bangle_d = (aangle_d + abs(vangle)) - 90; bangle_r = degtorad(bangle_d); reslen = cos(bangle_r) * hyplen; }else{ reslen = hyplen; } }else if (vangle > 0){ bangle_d = 90 - (aangle_d - vangle); bangle_r = degtorad(bangle_d); reslen = cos(bangle_r) * hyplen; }else //vertical flat reslen = hadjlen; Serial.print("bangle:"); Serial.println(bangle_d); averaging[avindex] = reslen; avindex = (avindex + 1) % averagenum; } reslen = 0; for (int i=0;i<averagenum;i++) reslen += averaging[i]; reslen /= averagenum; return reslen; } void checkprocessbtn() { int btnstatemode = digitalRead(BTN_MODE); int btnstatesel = digitalRead(BTN_SEL); if (btnstatemode == LOW && !lastbtnmode){ //switch on btn_down_time = millis(); lastbtnmode = true; if (m_mode == MODE_MEASURE) m_mode = MODE_SELECT; else m_mode = MODE_MEASURE; freezedisplay = false; }else if (btnstatemode == LOW){ if (millis() - btn_down_time > 2000){ //btn hold down over 2 sec m_mode = MODE_SEND; } }else if (btnstatemode == HIGH){ lastbtnmode = false; } if (btnstatesel == LOW && !lastbtnsel){ lastbtnsel = true; if (m_mode == MODE_MEASURE){ m_measure = !m_measure; if (!m_measure) m_pheight[m_person] = (m_height+5)/10; //save current measure to personal data. add 5 for rounding. }else if (m_mode == MODE_SELECT){ m_person = (m_person + 1) % 3; m_measure = true; }else{ wifi_transferdata(); } }else if (btnstatesel == HIGH){ lastbtnsel = false; } } void wifi_transferdata(){ oled_overwritestr(5," Sending."); uint16_t len; String lendata; lendata = "lendata="; for (int i=0;i<3;i++){ if (m_pheight[i] <= 10) lendata = lendata + "<td>---cm</td>"; else lendata = lendata + "<td>" + m_pheight[i] + "cm</td>"; } len = lendata.length(); WiFiClient client; char buffer[512],chbuf[16]; Serial.println("Starting connection to server..."); oled_overwritestr(5," Sending.."); if (client.connect("server.name",80)){ oled_overwritestr(5," Sending..."); client.println(F("POST /logger.php HTTP/1.1")); client.println(F("Host: 192.168.0.1:80")); client.println(F("Content-Type: application/x-www-form-urlencoded")); client.print(F("Content-Length: ")); client.println(len); client.println(); client.print(lendata); client.println(); client.stop(); oled_overwritestr(5," Sending... OK"); }else{ oled_overwritestr(5," Sending... NG"); oled_overwritestr(5," Conn Error"); } freezedisplay = true; } int siglevel; void updatedisplay(){ if (freezedisplay) return; display.clearDisplay(); unsigned long currentMillis = millis(); if (currentMillis - lastupdateMillis > 500){ long rssi = WiFi.RSSI(); if (rssi <= -96) siglevel = 1; else if (rssi <= -85) siglevel = 2; else if (rssi <= -75) siglevel = 3; else siglevel = 4; lastupdateMillis = currentMillis; } display.fillRect(115, 2, 17, 11, BLACK); if (siglevel >=1) display.fillRect(117, 12, 2, 2, WHITE); if (siglevel >=2) display.fillRect(120, 10, 2, 4, WHITE); if (siglevel >=3) display.fillRect(123, 8, 2, 6, WHITE); if (siglevel >=4) display.fillRect(126, 6, 2, 8, WHITE); if (m_mode == MODE_MEASURE){ oled_drawtext(7,0,1,s_person[m_person]); if (!m_measure){ display.fillRect(0, 0, 2, 7, WHITE); display.fillRect(3, 0, 2, 7, WHITE); } sprintf(buff,"H: % 2d%c",xangle,(char)247); oled_drawtext(0,55,1,buff); sprintf(buff,"V: % 2d%c",yangle,(char)247); oled_drawtext(63,55,1,buff); if (distance == 999) sprintf(buff,"Dist: --- mm"); else sprintf(buff,"Dist: %4dmm",distance); oled_drawtext(0,45,1,buff); if (xangle > 45 || xangle < -45){ oled_drawtext(30,10,4,"///"); }else if (yangle > 45 || yangle < -45){ oled_drawtext(30,10,4,"///"); }else{ if (distance == 999){ if (m_height == -1 || misscount > 5) //retain previous display for while oled_drawtext(30,10,4,"---"); else{ sprintf(buff,"%3d",(int)m_height/10); oled_drawtext(20,10,4,buff); sprintf(buff,".%d",(int)m_height%10); oled_drawtext(90,10,2,buff); oled_drawtext(95,30,2,"cm"); } }else{ m_height = calcheight(xangle,yangle,distance); sprintf(buff,"%3d",(int)m_height/10); oled_drawtext(20,10,4,buff); sprintf(buff,".%d",(int)m_height%10); oled_drawtext(90,10,2,buff); oled_drawtext(95,30,2,"cm"); } } }else if (m_mode == MODE_SELECT){ oled_drawtext(5,5,1,"Select Person"); sprintf(buff,"%s%8s % 3dcm",m_person == 0?">>":" ",s_person[0],m_pheight[0]); oled_drawtext(15,20,1,buff); sprintf(buff,"%s%8s % 3dcm",m_person == 1?">>":" ",s_person[1],m_pheight[1]); oled_drawtext(15,35,1,buff); sprintf(buff,"%s%8s % 3dcm",m_person == 2?">>":" ",s_person[2],m_pheight[2]); oled_drawtext(15,50,1,buff); }else{ display.drawXBitmap(0,0,pic_cloud,64,32,WHITE); oled_overwritestr(4," Send to Sever"); } display.display(); } void loop() { checkprocessbtn(); if (m_mode == MODE_MEASURE && m_measure){ accelgyro.getAcceleration(&ax, &ay, &az); yangle = atan2(ay, az)*degconvert; xangle = atan2(-ax, az)*degconvert; distance = sensor.readRangeSingleMillimeters(); if (sensor.timeoutOccurred() || distance > 8000){ distance = 999; misscount++; }else misscount = 0; } updatedisplay(); /*char cmd; if (Serial.available()){ cmd = Serial.read(); if (cmd == 'l'){ dist = Serial.parseInt(); Serial.print("set l="); Serial.println(dist); }else if (cmd == 'x'){ xang = Serial.parseInt(); Serial.print("set xang="); Serial.println(xang); }else if (cmd == 'y'){ yang = Serial.parseInt(); Serial.print("set yang="); Serial.println(yang); }else if (cmd == 'r'){ char buff[32]; double res = calcheight(xang,yang,dist); sprintf(buff,"x:%d y:%d l:%d res:",xang,yang,dist); Serial.print(buff); Serial.println(res); } }*/ }
サーバー受信側のPHPスクリプト。単に日付情報付けてテキストに出すだけ。
<?php $t = date('Y/m/d H:i '); $lendata = $_POST['lendata']; if($lendata){ $dataline = "<tr><td>{$t}</td>{$lendata}</tr>\n"; $fp = @fopen("lendata.log","a") or die("Error!!\n"); fputs($fp, $dataline); fclose($fp); } ?>
閲覧用スクリプト。
<?php //HTML文を出力 print <<<EOD <html> <head> <META http-equiv=content-type content="text/html; charset=utf-8"> <title>身長記録</title> <style> td {padding:5px;} .text {background-color:#ddffff; } </style> </head> <BODY> EOD; print("記録<div>"); print("<table border=1><tr class=\"text\">\n<th>日時</th><th>長女</th><th>次女</th><th>三女</th></tr>\n<tr>"); $lendata = file_get_contents('lendata.log'); echo $lendata; print("</tr></table></div></body></html>"); ?>