PIC AVR 工作室別館 arduinoの館->TopPage->接続くん->赤外線測距モジュール

赤外線測距モジュールとの接続

シャープの赤外線測距モジュールGP2Y0A21YKをarduinoに接続していろいろ遊んでみよう、というお話です。

ただそれだけだとあまりに面白くないので、このモジュールをarduinoその他のマイコンに接続して使うときに ちょっとだけ使いやすくなるような工夫をしてみました。

実験の概要

このシャープの測距モジュールは、センサーから赤外線を出して物体から反射してくる赤外線の量を計測し、 その反射光の強さを電圧で出力してくれるというモノです。超音波を使うセンサーを自作すると結構大変なのですが、 このセンサーは距離に応じた電圧が出力されるだけなので、扱いは結構簡単です。

データシートに電圧と距離の関係がグラフで図示されているので、AD変換などで拾った電圧値をこのグラフを使って距離に変換することができます。 ただ、この電圧と距離の関係が直線ではなく曲線で表現されているので、対応付けを行うのが少々面倒です。

電圧出力ですから、値の入力についてはarduinoならanalogRead関数を使えばいいので造作無いわけですが、 電圧を人間の目で拾ってグラフを眺めて距離に変換…っていうのはイマイチ過ぎるので、 スケッチ内に変換の計算式を盛り込んで自動的に距離に換算させることにします。

そして、その算出した距離がどの程度正しいのか…といった検証とかやってみたいと思います。

とりあえず、電圧→距離の変換をする関数を書いてみたので、この関数を使えば距離計測が簡単にできると思います。

接続方法

赤外線測距モジュールは3ピンのモジュールで、Vcc、GND、そして電圧出力の3つがそれぞれ割り当たっています。

このモジュールは5V動作なので、Vccは5Vに、GNDはarduinoと共通のGNDに、電圧出力はarduinoのアナログ入力端子に繋いで使います。

通常のarduinoも5V動作なので、VccとGNDはarduinoと共通でよいでしょう。 アナログ入力は今回アナログ0番(PC0端子)を使うことにしました。3.3V動作のarduinoの場合はVccは別配線にする必要がありそうです。 また、参照電圧(AVCC)も5Vではなく3.3Vを基準として計算をする必要があります。

arduinoに登載されているmega168/328PはAD変換の基準電位がGNDなのでGNDをarduinoと共通に繋げばよいわけですが、 マイコンによっては基準電位がGNDではないモノもあるので、その手のマイコンと繋ぐ場合のGND線の取り回しは適宜変えてください。

まぁ、5V版なら以下のように繋げばokです。

なお、モジュールのデータシートを読むとVccとGNDの間にはパスコンを入れるようにと書かれているので、適当にパスコンを入れて下さい。 0.1uFでよいでしょう。

冒頭の写真は、愛用のreduino-nanoに赤外線測距モジュールを繋いでみたところです。赤い線がVcc、黒い線がGND、黄色が電圧出力です。

変換の考え方について

先述のとおり、アナログ入力の電圧値と距離の関係は直線の計算式では表されません。ってことで単純な掛け算割り算とかでは換算できません。 何らかの方法で電圧→距離の読み替え(換算)をする必要があります。

曲線の関係を変換式に表現して変換を行う方法は色々とあります。

多次元方程式を解く方法

まず一番当たり前の方法としは、多次元の方程式で表現することでしょう。

曲線上に数個のデータ点(離散データ)を取り、例えばn個のデータなら(n-1)次の方程式(連続データ)に当てはめて解くわけです。 こうすることによりすべての点を通る1本の曲線がもとまるので、その式に適当な電圧値を入れれば距離に換算されるというわけです。 しかも滑らかな曲線なので、全域で回数無制限に微分が可能になります。

でもこの方法はデータ数が増えれば増えるほど次元数が上がり、計算が大変になっていきます。せいぜい数個が現実的でしょう。

スプライン関数で補完する方法

通常はもっと計算を楽にする補間方法として近似的な方法を用います。例えば「スプライン関数」が有名でしょう。

グラフの曲線上に幾つかの代表点を設けるのは多次元方程式と一緒ですが、このスプライン関数では連続した数個の点の座標を用いて その各該当区間の曲線を代表するような細分化された曲線式をたくさん作ってつなぎ合わせていく方法なのがスプライン関数です。 曲線なので、一応微分が可能になります。

微分が可能ということは、曲線が途中で折れ曲がっていないということを意味しています。 例えば単なる直線的な補間だと点を跨ぐ部分で折れ曲がっていて、その部分で微分値が連続しなくなる…つまり微分不可となる点(特異点)が現れます。 スプライン関数で補完すると滑らかな曲線が求まるので、任意の場所で微分可能とすることができるようになります。

近似値ですので、欠点もあります。スプライン関数は無限回でも微分が可能というわけではなく、有限回の微分しかできません。 逆に何回まで微分可能とするか…といったことなどで計算方法が変わって来ます。 例えば1回微分(傾きや速度を求める)が必要だとか、2回微分(曲がり具合や加速度)が必要だとか…。アプリケーションの要件によって 何回まで微分可能なスプライン関数を求めればよいかが決定されます。

ちなみに微分可能とする回数が増えれば増えるだけ計算も複雑になっていきます。 それでも多次元方程式を延々と解いていくよりはずーーーっと簡単な計算で済みます。 データが100個でも1000個でも10000個でも…。多次元方程式では10000個の変数なんて難しいですからね…

スプライン関数は、多次元方程式のように離散値のデータを滑らかに補完することができるので有用なのですが、 でもなんだかんだで計算が少々面倒です。詳しいことは専門書などご参照ください。 昔学生時代に読んだのは、確かCによるスプライン関数 ←これだったと思うんだけどな…。

今回用いる作戦

ちなみに今回ですが、とりあえず電圧を距離に変換できさえすれば充分だろうと思うのでスプライン関数は用いず、 幾つかの期間に区切った短い直線(一次式)に区切り、1次式で補間することにします。

電圧値自体を基準にして微分(つまり速度、加速度を求める)する必要が無いからです。つまり1回も微分できなくてOKという要件です。

えぇー?おいらは速度や加速度使いたいぞ!そういう人はどうするんじゃい???と思う方もいらっしゃるでしょう。大丈夫。

もし、センサーに近づいたり遠ざかったりする「速度」や「加速度」を知りたいという要件がある場合は、 この補間処理処理のところで考えるのではなく、一旦距離に変換してしまってから、その距離データを一定時間間隔でストックしておいて、 その「距離の変動量」を元に速度や加速度を求める方が現実的でしょう。なので今回はやはり直線補間で行きます。

じゃないと、何を何で微分するのかが不明確になっちゃって、扱いがちょっとめんどくさくなっちゃいますしね…

変換方法について

そういうわけで、各点を直線を使って補間する作戦で行きます。

データシートから、電圧と距離の関係グラフを取り出してきて、その曲線を「ほぼ直線とみなせる」程度に細分化します。

以下の図は私が適当に線分を分断したものです。あまりきちんとした理論根拠に基づいて分割したわけじゃないので、 気になる人は適当に作り直してください。ちなみにx軸(電圧)が横、y軸(距離)が縦になるようにグラフを回転・反転したので文字が変です。 まぁ気にしないで下さい。今回は、約10cm単位を基準にして分割し、それだと粗くなってしまう部分だけ5cm単位で分割しました。

で、まずはこの各点の距離と電圧を一覧表に書き出し、隣り合う上下の差分から傾きと切片を表計算ソフトで計算したのがこれ。 センサー至近(数センチ以下)のグラフは無視してます。

dist(cm)volts(V)a(傾き:cm/V)b(切片:cm)
800.384321-217.916667163.750000
700.430210-158.484848138.181818
600.493308-102.549020110.588235
500.590822-75.79710194.782609
400.722753-58.11111182.000000
300.894837-25.63725552.941176
201.284895-14.77401138.983051
151.623327-7.45014227.094017
102.294455-5.97032023.698630
53.131931**

表の見方ですが、例えば一番上の行を見ると80cmの時に0.384321Vが出力されるはずだよ、ということが書いてあるわけです。 次の行を見ると、70cmなら0.430210Vが出力されるはず、となっています。これら上下で隣り合う2行から差分を取ると傾きが求まります。

(80-70)/(0.384321-0.430210)=-217.916667 となりますね。単位はcm/V。1V変化すると約218cm変化するという意味です。 右下がりなのでマイナスです。電圧が上がると距離が近づく(短くなる)という意味です。 で、この傾きの数値を元に0V時の切片も求めると、163.75cmとなります。

以下こんな風に各分断点の座標を上下に隣り合った同士でy=ax+bの式に当てはめていき、各区間のaとbを求めていきます。 当然xに電圧、yに距離を当てはめていくわけです。

で、この求まったaとbを使うと各区間別の一次式が立てられます。ここまで求まれば、あとはロジックにしてスケッチにそのまま落としていくだけですね。 電圧値によって区間別に計算式を切り替えて計算すればいいわけです。

ちなみに、細かい数値が書いてありますが、これはグラフ中の長さを画面上のドット単位で測っておいて、 そこから表計算ソフトで適当に換算した値であって、厳密な値ではありません。念のため。 まさかこのグラフから1uV単位で読み出すなんて無理ですからね…

スケッチを書いてみる

計算式が求まったので、早速適当なスケッチを書いてみます。スケッチの主要部分はなんと言っても換算処理なので、 それ以外のところは適当にでっち上げます。

とりあえず書き上げてみたスケッチは以下の通り。なお、半角不等号はすべて全角不等号に換えてあるので、適宜半角に戻して使ってください。

サンプルスケッチ

//  SHARP GP2Y0A21 IR distance measure decoder
//    for arduino
//
//                 sketched by m.suga
//                 2009.6.25


/* global definition */

int analogpin0 = 0;  // select the pin for analog0 input



/* functions */

double voltage2distance(double dVolt) {
  
  double dDist;
  
  
  if (dVolt < 0.384321) {
    dDist = 999.0;                        /*out of range*/

  } else if (dVolt < 0.430210) {
    dDist = dVolt * (-217.916667) + 163.750000;      /*80 - 70*/
    
  } else if (dVolt < 0.493308) {
    dDist = dVolt * (-158.484848) + 138.181818;    /*70 - 60*/
      
  } else if (dVolt < 0.590822) {
    dDist = dVolt * (-102.549020) + 110.588235;  /*60 - 50*/
        
  } else if (dVolt < 0.722753) {
    dDist = dVolt * (-75.797101) + 94.782609;  /*50 - 40*/
          
  } else if (dVolt < 0.894837) {
    dDist = dVolt * (-58.111111) + 82.000000;  /*40 - 30*/
            
  } else if (dVolt < 1.284895) {
    dDist = dVolt * (-25.637255) + 52.941176;  /*30 - 20*/
              
  } else if (dVolt < 1.623327) {
    dDist = dVolt * (-14.774011) + 38.983051;  /*20 - 15*/
                
  } else if (dVolt < 2.294455) {
    dDist = dVolt * (-7.450142) + 27.094017;  /*15 - 10*/
                  
  } else if (dVolt < 3.131931) {
    dDist = dVolt * (-5.970320) + 23.698630;  /*10 - 5*/
  }

  else {
    dDist = 0.0;                              /*error*/
  }

  return dDist;
}



void setup() {
  Serial.begin(9600);

  Serial.println("start");

}




void loop() {

  double dDistance;
  int  data;
  double dVoltage;  
  
  data = analogRead(analogpin0);  //read data from analog0 pin
  dVoltage = (double)data / 1024.0 * 5.0;
  dDistance = voltage2distance(dVoltage);
  
  Serial.print((int)dDistance);
  Serial.print(",");

  Serial.println((int)(dVoltage * 1000.0));
  
  delay(100);
  
}

スケッチの簡単な解説

今回の換算処理を行う関数を一つ定義して使ってます。「voltage2distance」という関数です。 電圧(単位はV)を受け取って、距離(単位はcm)を返します。引数、戻り値ともにdouble型です。

電圧が小さすぎる場合はエラー値として距離に999.0cmを、電圧が大きすぎる時もエラー値として0.0cmを返します。

それ以外の電圧値の場合は、if文(とif else節)の連なりの部分で、各電圧値に対応する区間ごとに分けて換算処理を行ってます。 換算処理中に出てくる数値は先ほどの表の数値そのままなので、見比べてみてください。まぁ、大した話じゃありませんね。

あとは、メインルーチン側からこの関数を適当に呼び出して、シリアルでPCに書き出しているだけです。

関数自体は引数を「電圧」にしているので、C言語系の言語なら他のマイコンでもそのまま流用できるでしょうし、 他の言語に移植するのも大した話じゃないかと思います。

なおloop関数内に書いてあるAD変換値から電圧への変換式は5Vの場合を前提にしています。 3.3Vコアのarduinoの場合は適当に読み替えてください。

本当はノイズ対策として、ADコンバータから数回読み出した値の平均を取るなどの方法を取る方が良いかもしれません。

実行した感じ

実行すると、こんな画面がでてきます。

シリアルモニタに表示されている右側の数値が電圧の値を1000倍したもの(つまりmV)で、左側は距離(単位はcm)です。 たとえばこの図の場合は出力電圧が976mVで距離は27cmだという意味です。100m秒(0.1秒)毎に表示されてスクロールしていきます。

それぞれ少数以下切捨てなのであまり正確な数値ではありませんが、どうせそれほど正確な結果は望めないだろうと思うので、 この程度でいいことにします。

改めて実験、実験

実験の観点洗い出し

実験をいきなり始める前に、ザックリと弄って普通に動くことを確認してみました。 で、弄ってみた感じを元にどんな実験を試してみようかなってことを色々考えてみました。

いっぺんにアレコレ全部は出来ないので、とりあえずお題目を挙げておいて、ヒマな時に少しずつ消化して行きたいと思います。

というわけで、まずは実験の観点の洗い出しを。

…とりあえずこんなところかな?ほかに色々思いついたら付け足していきます。

実験結果サマリ

気が向いたところからちょっとずつ実験して、まとめて行こうと思います。

実験済み分

(1)色の違いによる影響

このIR測距センサーは、簡単に言うと放射した赤外線が物体に当って跳ね返ってくる量を測ることで距離を推測する方式です。 可視光線の場合、反射する光の量は素材によって大きく異なります。一般に、白や黄色は反射する絶対量が多く、 赤や青、黒などはあまり光を反射しません。IRセンサーの場合は赤外線なので、こういった色が必ずしも可視光のように影響するとは限りません。

そういうわけで、反射素材のアレコレによってどんな風に変化が生じるかを比較してみます。

実験方法ですが、絶対精度を気にしてもあまり意味が無さそうだし、色によって相対評価ができればいいかなと考えてます。 で、上手い具合に相対評価が出来る方法ってことで考えてみました。何か1つ基準になる色を決めておいて、 距離を一定のまま色だけ変化させると基準色とどのくらい変化するか…今回は、白いコピー用紙を基準にして、 その他の色ではどのくらいの差が生じるのかということでやってみました。

具体的には、色々な色の素材をもってきて、その前面に白いコピー用紙を重ねた状態で距離(正しくは出力電圧)を計測し、 その距離のまま白いコピー用紙だけ取り除くとどのくらい計測距離が変化するかを調べます。およそ30cm程度の距離で計測します。

結果:

グレーっぽい紙:殆ど変わらず

黒っぽい紙:だいたい距離で5%程度の変化がある感じ (ペンタックスK-7のカタログを使いました)

透明のビニール袋:だいたい距離で5%~10%程度の変化がある感じ (少しシワのよったビニールでした)

白い服(木綿):コピー用紙とあまり変わらない感じ

→色の違いがあってもあまり大きな変化は無さそう。せいぜい5~10%程度。データシートのうたい文句と近い感じでした。 ちなみに可視光では、白と18%グレーでは2の2乗~2.5乗倍程度、18%グレーと黒では2の2乗~2.5乗倍程度の差が生じる (4~6倍)んですが、赤外線ではあまり影響が無かった(せいぜい数%程度)ことが面白いところです。

(2)反射角度の違いによる影響

反射させる素材を変えずに、その素材の角度をセンサー側から少し向きをずらしてみるわけです。 もう少し細かく言うと、素材に赤外線が当っている付近の法線がセンサーではなくあさっての方向を向いているような状態(およそ45度)を作り出し、 垂直状態と比べてどのくらい変化するかを調べます。

結果:

大きな変化は無いようです。というか赤外線が放射されている角度がよくわかってないため、 角度に変化をつける際に近いところと遠いところが出来てしまい、そっちの影響の方が大きいような気がしてきた…

まぁ、角度自体はあまり大きな影響がなさげでした。よほどツルツルの素材でなければ…

(3)外乱光による影響

赤外線といえば、テレビやビデオのリモコンも赤外線を使っています。もし測距中にリモコンの操作をしたら距離に影響があるのかを調べてみます。 リモコンから発する赤外線を拾ってしまえば、当然光が強くなるわけだから距離が近いと誤認されるはずです。

方法は…言うまでも無いでしょうが、素材、距離を一定にしたまま測距中にセンサーに向けてビデオのリモコンを照射してみました。

結果:

誤認は認められませんでした。多分、何らかの変調を掛けて発光/受信しているものと思われます。

テレビ等のリモコンの場合、一般には38kHzの変調/復調を掛けることで外乱光の影響を取り除いていますが、 この赤外線モジュールは多分別の周波数帯を使って変調を掛けているものと想像されます。変調/復調っていうのは、 簡単に言うとラジオやテレビのチューニングと一緒で、周波数を限定して受信し、他の周波数の信号が混線しないようにする方法です。 今回の場合の具体的な周波数は調べてませんが…

少なくとも、赤外線LEDは常時発光ではなく点滅(交流的な発光)を用いていて、センサー側も特定周波数だけを拾い、 その振幅だけを取り出して評価に用いているようです。少なくとも38kHzでは無さそうです。

実験未済分

素材の質感による影響

反射素材の大きさ(角度)

イジワルな素材の場合

…  準備中です  …