勝手な電子工作・・

勝手なオリジナル電子工作に関する記事を書きます

型取りゲージの電子化?(ToFで実験)

f:id:a-tomi:20200830203817j:plain

これは「型取りゲージ」の写真です。複雑な形に合わせて床材やカーペット等をカットするための巧妙なゲージで、型取り定規と言われることもあります。英語ではProfie GaugeとかContour Gaugeとか。

もしこの道具を使わずに、例えば次のような複雑断面の柱に合わせてカーペットを切ろうとすると大変なことになります^^;

f:id:a-tomi:20200830204523j:plain

1mmφのピアノ線のエレメントが自由にズレて型をとる仕掛けで、これを使えば切断材料に相手の形をなぞり書きすることができます。それなりの精度とスムーズな動きが必要なために安価な道具ではありません。

しかし最近では安価なABS樹脂製(中国製)が出回っているようです。少し目が粗い(次の写真のものはエレメントが1.5ミリ幅)とはいっても、材料の革新だなあと勝手に感心します。型をとった状態でロックするメカニズムもなかなか素晴らしい!

f:id:a-tomi:20200830205054j:plain

とはいうものの、現代のことですからデジタル化などできないの?と、ふと思いついてしまいました^^;

仕組みはいくつか考えられますが、手始めにToFセンサー(Time of flight 光が反射して戻るまでの時間で距離を測るセンサー)を使うとどうなるでしょうか?できるような気がしますので、まずはやってみることにします。

ToFセンサーはカメラで近距離を測る用途などにも使われるようになり、ずいぶん安価になりました。たとえば次のToFセンサーVL6180Xのブレークアウトモジュールは海外ネットでは約2ドル強で入手できます。

 

f:id:a-tomi:20200830210212j:plain

真ん中にあるデバイスがVL6180X 本体で、赤外線ビームを出す部分と反射した赤外線を受ける部分があります。使われている赤外線の波長は850nmですので、もちろん目には全く見えません。

このセンサーを型取りゲージと同じ1ミリ単位で正確に水平移動させながら、反射対象までの距離を測っていくことで、同じことができるのではないかと考えました。実験用回路は、毎度手書きのきたないメモですみませんが、次のような簡単なものです。ステッパーの駆動にA4988というモジュールを使い、送りねじ機構で動かします。

f:id:a-tomi:20200830210940j:plain

プログラムではArduinoから測定データをPCへ送って、PC側でリアルタイムのグラフ表示などをしています。Arduino側とPC側の両方の実験プログラムをこの記事の最後の方につけます、とても小さいですが。

実験装置全体は次のように作りました。

f:id:a-tomi:20200831103553j:plain

動かすセンサーを前から見たのが次の写真です。センサーにある丸い窓2つ(赤外線ビーム放出窓と反射光検出窓だろうと思います)が見えています。

f:id:a-tomi:20200831103727j:plain

 

スライドユニットにセンサーをとりつけるために、木片をつけそれにブレッドボードをとりつけてあります。大きさを合わせるため、次の写真のように上のボードを下のサイズへ強引にカットして加工^^

f:id:a-tomi:20200830211500j:plain

ToFセンサーが揺れないよう、ブレークアウトモジュールに足をしっかりつけます。穴のない側にもホットボンドで無理に台と足2ピンだけをつけます。

f:id:a-tomi:20200830211734j:plain

表から見ると次。2つのネジ穴にホットボンドとピンの端が見えています。

f:id:a-tomi:20200830211858j:plain

 

まずはこういう紙型をいじりながら試験。暗い時のほうが結果は良いかと思いますが、かなり明るい状態でも特に問題なさそう。

f:id:a-tomi:20200830211337j:plain

まずは次の型取りができました。なんとなくうまくできてる!かな?

f:id:a-tomi:20200830212243j:plain

それでもよくみると、対象への距離は設定どおりで正確ですが、形が実際より滑らかです。変ですね。

測ったときの断面は下から見るとこれです。不細工ながら、円弧、台形、三角形にしたつもり。

f:id:a-tomi:20200831105148j:plain

手でほぼ正確に測った緑の破線と重ね合わせると次のようになっています。

 

f:id:a-tomi:20200830212513j:plain

台形は丸身を帯び、三角形も丸身を帯びてます(・・? なぜ? ん?

 

原因を調べるため、もっと厳しい直方体の木材や柱を立ててはかってみましたら、ありゃ? 少し差がでるという状況ではありませんね、これは! とくに形の間の深いところはひどく浅くなっています、、、

f:id:a-tomi:20200830212915j:plain

原因をあれこれ考え、ToFの赤外線は勝手に想定していた細いビームではない?!と勘づきます。あちこちで反射している可能性がありそう。

850ナノメーターの赤外線放出の様子を見るのは難しそうですが、この際は勝手な赤外線カメラで観察することにします。そのためにセンサーだけをつけた単体をこしらえて、これでじっくり観察します。

f:id:a-tomi:20200830214541j:plain

使う赤外線カメラは、古いデジカメの撮像面にセットされていたIRカットフィルター(赤外線を遮蔽し可視光だけを通すフィルター)を外して(というよりピンセットで無理にはぎ取って)、代わりにローパスフィルター(可視光を通さず波長780ナノメーター以上の赤外線だけ通すもの)をとり付けたまさしく勝手な赤外線カメラです。

これにより、センサーから出ている赤外線はビームの形どころか、びっくりする程広がっているのがわかりました。

f:id:a-tomi:20200830213856j:plain

これは想定外です!

ひょっとしてこのToFセンサー固有のことなのかなと微かな希望をもって、他のセンサーも調べてみることに。手元にある別のToFセンサーはVL50L0Xで、そちらでも観察することにしました。

こちらはVL6180X よりも数年前の製品です。

f:id:a-tomi:20200830214411j:plain

f:id:a-tomi:20200830214439j:plain

同じ方法で観察したビーム形状が次です。

f:id:a-tomi:20200830214754j:plain

赤外線が弱いようですが、やはり細いビームではなくこちらも光芒がずいぶん広がっています。

他の製品についてもよく調べる必要がありますが、センサーの精度を高める目的から言えば細いビームのもあってよいかな?と考えゆっくり調べることにします。あるとしてもこの目的には高価ではないかな?もしよいのがない場合は、センサーの前に広がりを絞る反射しにくい筒でもつけるのがよいかな?などともあれこれ考え中。

というわけで、すぐできると思ったこの実験、すこしずつ長引きそうです。精度の高いのが完成するまで待つといつになるかわからないので、いったんご紹介することにしました。どなたか良い案を考えたら是非デジタル化してくださいね^^;

 最後に、実験に使ったかんたんな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の大きさで、顕微鏡を低倍率にして見ると次のような形。

f:id:a-tomi:20200909203951j:plain

顕微鏡のIR遮蔽が弱いので850nmの平面Laser光が写っています。上の穴が反射光の検知窓で、穴の間はおよそ3.5mm離れています。ですので、ここに何か細工ができないかなあと考えました。

最初にレンズで集光してみることに。次の状態は何もしない場合の赤外線写真です。

f:id:a-tomi:20200909204819j:plain

この出力窓の前に樹脂製のレンズをおいてみると、だいぶ収束するのがわかります。

f:id:a-tomi:20200909204946j:plain

この写真では高さの位置を出力窓にあわせるために、レンズの下にティッシュペーパーを敷いています。この状態でレンズを前後させるとさらに収束できる位置があるようです。赤外線カメラで見て一番良い位置を確認したのですが写真を撮りそこないました^^;

とはいえ直径が10mmほどのレンズのため、反射が受光窓にもかぶるのでもっとずっと小さいレンズでないといけません。プラスチックレンズは頑張れば切れるかも^^;  モノはためし、幅2.5mmほどの凸レンズを作ってみました。

f:id:a-tomi:20200909205328j:plain

こういう形でも受光窓を避けるにはなんとかなりそうな感じがします。

これは度の強いメガネ(リーディンググラス、f=16.7cm)から、愛用のProxxon「サーキュラーソー」で下の写真のようにエイヤッと切りとったもの。

f:id:a-tomi:20200909205618j:plain

ひどいことをしますが、ヒンジが壊れて不要になった百均のグラスなのでご安心を^^;

 これで早速やってみると、測定結果の距離が極端に小さくでます。何のことはない、出力窓から少し離しただけで、レンズの反射がどうやっても受光窓にはいります。レンズを出力窓にほぼくっつけないとだめそう。焦点距離の相当短いものを使わないと無理そうなのでレンズでの収束はとりあえず断念。

それなら、次はパイプで広がらなくする案。外径2.5mm、穴径1.5mmのアルミパイプを切り出して、木片を支えにしてやってみました。光が漏れないように出力窓にピッタリ付けます。

f:id:a-tomi:20200909210400j:plain

赤外線のカメラで見るとパイプによる集光はバッチリだったのですが。。。測定値が不安定。どうやら測定対象からの反射光がパイプをセットした台で反射しているようです。なぜ短く計測されることがあるのかはわかりませんが、ひょっとして単に往復時間だけでなく位相なども検知してるのかなあ、う~ん ;; 

どっちにしてもパイプが使えるかを確かめるには、パイプ自体の支持方法を工夫しないといけない感じです・・ICに接着してしまえばいいかな?それも気が引ける・・

 

そこでふと気づいたのですが、スリットで広がりを防ぐのはどうだろう?左右だけ広がらなければよいわけなので・・。

しかし、どっこい。そうはいきませんでした。左右の壁で反射して短く検出されてしまいます。それなら反射を防ぐ紙を貼るとどうなるか・・。下のように広げてみてもやっぱり両脇での反射が検出され、短くでます。

f:id:a-tomi:20200909211337j:plain

木製の台が光線の近くにあること自体もまずいかも。

ここまででのところ、測距対象が近くにあるときは正しい距離が得られる場合もありますが、対象が離れると測定値が異常に小さくなるのに気づきました。反射光の強度が小さい場合(測定対象からの反射光が弱いと)センサーは次第にゲインを上げて近くの弱い反射を検出するようにみえます。ゲインは40倍までコントロールできる仕様です。測り方の指定にもよるでしょうが。

他の用途ではどうしているのか?自動掃除機などのLidarに使われているToFセンサーでも光線の性格は同じで、かなり広がっています。ロボットや掃除機が壁にぶつからないようにするためには、それでもとくに問題ないわけです・・・。ポピュラーなこの種のToFセンサーでは正面の距離は正確に測れても、回転時の角度にまつわる分解能は高くはないということでしょうか。

 というわけで、まだ光線が絞れてません。もし別の知恵が出たらこの続きをやろうと思います。ここまでおつきあいいただきありがとうございます (o*。_。)o tks

 

©2020 Akira Tominaga, All rights reserved.