これは「型取りゲージ」の写真です。複雑な形に合わせて床材やカーペット等をカットするための巧妙なゲージで、型取り定規と言われることもあります。英語ではProfie GaugeとかContour Gaugeとか。
もしこの道具を使わずに、例えば次のような複雑断面の柱に合わせてカーペットを切ろうとすると大変なことになります^^;
1mmφのピアノ線のエレメントが自由にズレて型をとる仕掛けで、これを使えば切断材料に相手の形をなぞり書きすることができます。それなりの精度とスムーズな動きが必要なために安価な道具ではありません。
しかし最近では安価なABS樹脂製(中国製)が出回っているようです。少し目が粗い(次の写真のものはエレメントが1.5ミリ幅)とはいっても、材料の革新だなあと勝手に感心します。型をとった状態でロックするメカニズムもなかなか素晴らしい!
とはいうものの、現代のことですからデジタル化などできないの?と、ふと思いついてしまいました^^;
仕組みはいくつか考えられますが、手始めにToFセンサー(Time of flight 光が反射して戻るまでの時間で距離を測るセンサー)を使うとどうなるでしょうか?できるような気がしますので、まずはやってみることにします。
ToFセンサーはカメラで近距離を測る用途などにも使われるようになり、ずいぶん安価になりました。たとえば次のToFセンサーVL6180Xのブレークアウトモジュールは海外ネットでは約2ドル強で入手できます。
真ん中にあるデバイスがVL6180X 本体で、赤外線ビームを出す部分と反射した赤外線を受ける部分があります。使われている赤外線の波長は850nmですので、もちろん目には全く見えません。
このセンサーを型取りゲージと同じ1ミリ単位で正確に水平移動させながら、反射対象までの距離を測っていくことで、同じことができるのではないかと考えました。実験用回路は、毎度手書きのきたないメモですみませんが、次のような簡単なものです。ステッパーの駆動にA4988というモジュールを使い、送りねじ機構で動かします。
プログラムではArduinoから測定データをPCへ送って、PC側でリアルタイムのグラフ表示などをしています。Arduino側とPC側の両方の実験プログラムをこの記事の最後の方につけます、とても小さいですが。
実験装置全体は次のように作りました。
動かすセンサーを前から見たのが次の写真です。センサーにある丸い窓2つ(赤外線ビーム放出窓と反射光検出窓だろうと思います)が見えています。
スライドユニットにセンサーをとりつけるために、木片をつけそれにブレッドボードをとりつけてあります。大きさを合わせるため、次の写真のように上のボードを下のサイズへ強引にカットして加工^^
ToFセンサーが揺れないよう、ブレークアウトモジュールに足をしっかりつけます。穴のない側にもホットボンドで無理に台と足2ピンだけをつけます。
表から見ると次。2つのネジ穴にホットボンドとピンの端が見えています。
まずはこういう紙型をいじりながら試験。暗い時のほうが結果は良いかと思いますが、かなり明るい状態でも特に問題なさそう。
まずは次の型取りができました。なんとなくうまくできてる!かな?
それでもよくみると、対象への距離は設定どおりで正確ですが、形が実際より滑らかです。変ですね。
測ったときの断面は下から見るとこれです。不細工ながら、円弧、台形、三角形にしたつもり。
手でほぼ正確に測った緑の破線と重ね合わせると次のようになっています。
台形は丸身を帯び、三角形も丸身を帯びてます(・・? なぜ? ん?
原因を調べるため、もっと厳しい直方体の木材や柱を立ててはかってみましたら、ありゃ? 少し差がでるという状況ではありませんね、これは! とくに形の間の深いところはひどく浅くなっています、、、
原因をあれこれ考え、ToFの赤外線は勝手に想定していた細いビームではない?!と勘づきます。あちこちで反射している可能性がありそう。
850ナノメーターの赤外線放出の様子を見るのは難しそうですが、この際は勝手な赤外線カメラで観察することにします。そのためにセンサーだけをつけた単体をこしらえて、これでじっくり観察します。
使う赤外線カメラは、古いデジカメの撮像面にセットされていたIRカットフィルター(赤外線を遮蔽し可視光だけを通すフィルター)を外して(というよりピンセットで無理にはぎ取って)、代わりにローパスフィルター(可視光を通さず波長780ナノメーター以上の赤外線だけ通すもの)をとり付けたまさしく勝手な赤外線カメラです。
これにより、センサーから出ている赤外線はビームの形どころか、びっくりする程広がっているのがわかりました。
これは想定外です!
ひょっとしてこのToFセンサー固有のことなのかなと微かな希望をもって、他のセンサーも調べてみることに。手元にある別のToFセンサーはVL50L0Xで、そちらでも観察することにしました。
こちらはVL6180X よりも数年前の製品です。
同じ方法で観察したビーム形状が次です。
赤外線が弱いようですが、やはり細いビームではなくこちらも光芒がずいぶん広がっています。
他の製品についてもよく調べる必要がありますが、センサーの精度を高める目的から言えば細いビームのもあってよいかな?と考えゆっくり調べることにします。あるとしてもこの目的には高価ではないかな?もしよいのがない場合は、センサーの前に広がりを絞る反射しにくい筒でもつけるのがよいかな?などともあれこれ考え中。
というわけで、すぐできると思ったこの実験、すこしずつ長引きそうです。精度の高いのが完成するまで待つといつになるかわからないので、いったんご紹介することにしました。どなたか良い案を考えたら是非デジタル化してくださいね^^;
最後に、実験に使ったかんたんなArduinoスケッチとPC側のプログラム(例によって簡単なSmall Basicで書いてます)をつけておきます。
まずArduinoのスケッチ。
/**********************************************************
* Profile gauge with VL6180X to Xfer data to Small Basic
* Initial version V00 Aug.29,2020 Akira Tominaga
***********************************************************/
#include "Wire.h"
#include "VL6180X.h"
VL6180X ToF;
#define Dir 3 // A4988 stepper direction
#define Step 4 // A4988 Step
#define Nreset 5 // A4988 !reset
#define Nsleep 6 // A4988 !sleep
#define Led 13 // LED
uint8_t nTimes = 32; // number of ToF measures for average
void setup() { // ***** Arduino Setup *****
float Kyori;
float Tot;
float Ave;
uint16_t iAve;
pinMode(Nsleep, OUTPUT);
sSleep(); // sleep A4988
pinMode(Led, OUTPUT);
digitalWrite(Led, LOW);
Serial.begin(9600);
Wire.begin();
bLED(2, 300, 100); // show I am awake
ToF.init();
ToF.configureDefault();
ToF.setTimeout(200);
pinMode(Dir, OUTPUT);
pinMode(Step, OUTPUT);
digitalWrite(Step, LOW);
sWake(); // wake A4988 up
pinMode(Nreset, OUTPUT);
sReset(); // home at current position
Repeat:
while (Serial.available() < 1) {} // wait for Smallbasic ready
String s = Serial.readString(); // read Smallbasic message
delay(1000);
// move stepper to left end
sN(1600); // X-1600 times (X-80 mm)negative
sSleep(); // stop and sleep to avoid heat
sWake(); // and wake stepper
sReset(); // reset at Left end
delay(100); //
bLED(3, 300, 100); // blink 3 times for start..
/*******************************************
Measurement
*******************************************/
#define xMax 160
for (uint8_t j = 0; j < xMax; j++) {
sP(20); // X + 1mm = 20 cycles
sSleep(); // stop and sleep to avoid heat
Tot = 0;
for (uint8_t i = 0; i < nTimes; i++) {
Try:
Kyori = ToF.readRangeSingleMillimeters();
if (ToF.timeoutOccurred()) {
// Serial.println("TO");
goto Try;
}
Tot = Tot + Kyori;
}
Ave = (float)(Tot / nTimes);
iAve = Ave * 10.00 + 0.50;
uint8_t yh = iAve >> 8; // get y-high byte
uint8_t yl = iAve - 256 * yh; // get y-low byte
Serial.write(yh);
Serial.write(yl);
} // end of For loop
bLED(5, 300, 100); // end..
// return stepper to center
sReset(); // Reset at right end
sN(1600); // X - 1600 times (X-80 mm)
sSleep(); // sleep to avoid heat
bLED(100, 20, 300); // blink LED 100 times
goto Repeat; // and repeat again
}
void loop() { // ***** Arduino Loop *****
} // never comes here
/**************************************
User defined functions
**************************************/
#define tmStp 2000 // stepper phase timing in μS
// *** bLED() *** Blink LED
void bLED(int nTimes, int onLen, int offLen) {
for (int m = 0; m < nTimes; m++) {
digitalWrite(Led, HIGH);
delay(onLen);
digitalWrite(Led, LOW);
delay(offLen);
}
}
// *** sP(n-rotations) *** move stepper positive
void sP(int nRot) {
sWake(); // wake-up stepper
digitalWrite(Dir, LOW);
mvS(nRot, tmStp);
}
// *** sN(n-rotations) *** move steppe negative
void sN (int nRot) {
sWake(); // wake-up stepper
digitalWrite(Dir, HIGH);
mvS(nRot, tmStp);
}
// *** mvS(nRot, ndelay) *** move stepper
void mvS(int nRot, long nDelay) {
for (int l = 0; l < nRot; l++) {
digitalWrite(Step, HIGH);
delayMicroseconds(nDelay);
digitalWrite(Step, LOW);
delayMicroseconds(nDelay);
}
}
// *** sReset() *** Reset stepper
void sReset() {
digitalWrite(Nreset, LOW);
delay(1);
digitalWrite(Nreset, HIGH);
delay(1);
}
// *** sSleep() *** Sleep stepper to avoid heat
void sSleep(void) {
digitalWrite(Nsleep, LOW);
}
// *** sWake() *** Wake up stepper
void sWake(void) {
digitalWrite(Nsleep, HIGH);
delay(1); // wait for charge pump readiness
}
// end of program
次はPC側のプログラムです。
myTitle="Receiving ToF data from Arduino Aug.29, 2020 (c)Akira Tominaga"
TextWindow.Title=myTitle
TextWindow.Write("Opening CommPort..")
connectPort()
' ***** Define Graphics Window *****
gw = 854
gh = 480
margin = 50
xMax=160 ' max X = 160mm
yMax=100 ' max Y =100mm
yMaxX10=yMax*10
sp =10 ' space between lines (mm)
GraphicsWindow.Width = gw+margin
GraphicsWindow.Height = gh+margin
GraphicsWindow.Title=myTitle
GraphicsWindow.BackgroundColor="Black"
GraphicsWindow.PenWidth=3
GraphicsWindow.FontName="Arrial"
GraphicsWindow.FontSize=15
GraphicsWindow.BrushColor="White"
' ***** Y scale and horizontal lines *****
For xx=0 To xMax
X=gw*(xx/160)+margin/2 ' Set x position X
For yy=0 To yMaxX10 Step sp*10
Y=gh*(yy/(yMaxX10))+Margin/2 'Set y position Y
GraphicsWindow.SetPixel(X,Y,"White")
If xx=0 Then
X=margin*0.1 'set scale position
Y=gh*(yy/yMaxX10)+Margin*0.4 'adjust scale position
GraphicsWindow.DrawText(X,Y,yy/10) ' draw Y scale
EndIf
EndFor
EndFor
' ***** X scale and vertical lines *****
For yy=0 To yMaxX10
For xx=0 To xMax Step sp
X=gw*(xx/xMax)+margin/2 ' Set x position X
Y=gh*(yy/yMaxX10)+margin/2 'Set y position Y
GraphicsWindow.SetPixel(X,Y,"White")
If yy=0 Then
X=gw*(xx/xMax)+margin*0.3
Y=margin*0.1 'adjust scake position
GraphicsWindow.DrawText(X,Y,xx) ' draw X scale
EndIf
EndFor
EndFor
' ***** Draw a graph, receiving data from UNO ToF *****
GraphicsWindow.PenWidth=3
GraphicsWindow.PenColor="Yellow"
'EOT=254*256 ' define 0xFExx as End of Transmission from Arduino
LDCommPort.TXString("SB") ' tell to UNO "SB is ready"
For xx=0 to xMax
yh=LDCommPort.RXByte()
yl=LDCommPort.RXByte()
yy=256*yh+yl
TextWindow.Write("yy=")
TextWindow.WriteLine(yy)
X=gw*(xx/xMax)+margin/2 ' Set x position X
TextWindow.Write("X=")
TextWindow.writeLine(X)
Y=gh*(yy/yMaxX10)+Margin/2 ' Set y position Y
TextWindow.Write("Y=")
TextWindow.writeLine(Y)
If xx>0 Then
GraphicsWindow.DrawLine(xs, ys,X,Y)
EndIf
xs=X ' save X
ys=Y ' save Y
EndFor
LDCommPort.ClosePort()
Sound.PlayBellRing()
' ***** Subroutines *****
Sub connectPort
for i=1 to 16
portName="COM"+i
status=LDCommPort.OpenPort(portName,9600)
If status="SUCCESS" Then
Goto Out
EndIf
EndFor
Out:
TextWindow.Write("Connected to ")
TextWindow.WriteLine(portName)
If status="CONNECTIONFAILED" Then
ComFailed:
Goto ComFailed ' stop here
EndIf
EndSub
2020.9.9追記
精度がなんとかならないか・・、その後も少しずつですが実験したので以下に追加します。
このセンサー自体は2.8mmx4.8mmx1.0mmの大きさで、顕微鏡を低倍率にして見ると次のような形。
顕微鏡のIR遮蔽が弱いので850nmの平面Laser光が写っています。上の穴が反射光の検知窓で、穴の間はおよそ3.5mm離れています。ですので、ここに何か細工ができないかなあと考えました。
最初にレンズで集光してみることに。次の状態は何もしない場合の赤外線写真です。
この出力窓の前に樹脂製のレンズをおいてみると、だいぶ収束するのがわかります。
この写真では高さの位置を出力窓にあわせるために、レンズの下にティッシュペーパーを敷いています。この状態でレンズを前後させるとさらに収束できる位置があるようです。赤外線カメラで見て一番良い位置を確認したのですが写真を撮りそこないました^^;
とはいえ直径が10mmほどのレンズのため、反射が受光窓にもかぶるのでもっとずっと小さいレンズでないといけません。プラスチックレンズは頑張れば切れるかも^^; モノはためし、幅2.5mmほどの凸レンズを作ってみました。
こういう形でも受光窓を避けるにはなんとかなりそうな感じがします。
これは度の強いメガネ(リーディンググラス、f=16.7cm)から、愛用のProxxon「サーキュラーソー」で下の写真のようにエイヤッと切りとったもの。
ひどいことをしますが、ヒンジが壊れて不要になった百均のグラスなのでご安心を^^;
これで早速やってみると、測定結果の距離が極端に小さくでます。何のことはない、出力窓から少し離しただけで、レンズの反射がどうやっても受光窓にはいります。レンズを出力窓にほぼくっつけないとだめそう。焦点距離の相当短いものを使わないと無理そうなのでレンズでの収束はとりあえず断念。
それなら、次はパイプで広がらなくする案。外径2.5mm、穴径1.5mmのアルミパイプを切り出して、木片を支えにしてやってみました。光が漏れないように出力窓にピッタリ付けます。
赤外線のカメラで見るとパイプによる集光はバッチリだったのですが。。。測定値が不安定。どうやら測定対象からの反射光がパイプをセットした台で反射しているようです。なぜ短く計測されることがあるのかはわかりませんが、ひょっとして単に往復時間だけでなく位相なども検知してるのかなあ、う~ん ;;
どっちにしてもパイプが使えるかを確かめるには、パイプ自体の支持方法を工夫しないといけない感じです・・ICに接着してしまえばいいかな?それも気が引ける・・
そこでふと気づいたのですが、スリットで広がりを防ぐのはどうだろう?左右だけ広がらなければよいわけなので・・。
しかし、どっこい。そうはいきませんでした。左右の壁で反射して短く検出されてしまいます。それなら反射を防ぐ紙を貼るとどうなるか・・。下のように広げてみてもやっぱり両脇での反射が検出され、短くでます。
木製の台が光線の近くにあること自体もまずいかも。
ここまででのところ、測距対象が近くにあるときは正しい距離が得られる場合もありますが、対象が離れると測定値が異常に小さくなるのに気づきました。反射光の強度が小さい場合(測定対象からの反射光が弱いと)センサーは次第にゲインを上げて近くの弱い反射を検出するようにみえます。ゲインは40倍までコントロールできる仕様です。測り方の指定にもよるでしょうが。
他の用途ではどうしているのか?自動掃除機などのLidarに使われているToFセンサーでも光線の性格は同じで、かなり広がっています。ロボットや掃除機が壁にぶつからないようにするためには、それでもとくに問題ないわけです・・・。ポピュラーなこの種のToFセンサーでは正面の距離は正確に測れても、回転時の角度にまつわる分解能は高くはないということでしょうか。
というわけで、まだ光線が絞れてません。もし別の知恵が出たらこの続きをやろうと思います。ここまでおつきあいいただきありがとうございます (o*。_。)o tks
©2020 Akira Tominaga, All rights reserved.