公開日:2019/03/23
title.png

4th Season


#4-9 フォトインタラプタを作る

 フォトインタラプタと呼ばれるセンサ(別ウインドウが開いて検索します)が市販されています。発光素子と、受光素子が対になっていて、その間を何かが遮ったことを検知するセンサです。
 今回は、普通のLEDとフォトレジスタ(CdSセル)を使って、フォトインタラプタを手作りしてみます。

治工具を作る

 まずは、治工具を作ります。LEDとCdSセルの中心軸を合わせて、正対させる必要があります。

ar49_jig1.png

 身の回りにある材料で、書けなくなったボールペンの不透明な軸部分を使って、上図の様な治工具を作成しました。小さなLEDとCdSセルを中に挿入できて、そんなに気を遣わなくても正対させることができる、適切な太さのパイプと言うことで、ボールペンの軸がちょうどでした。
 ボールペンの軸そのままでは、光を遮る部分が無いので、途中に鋸で切り込みを入れ、スリットを作成します。鋸で完全に切り離してしまうと、LEDとCdSセルを正対させるのが困難になりますので、切り離さずに少し残して、つながったままにしておくのがミソです。

 受光素子として使用するCdSセル側には、周りの光が差し込まないようにする必要があるので、少し長めに軸を残します。LEDの側は、光が逃げにくい(拡散しにくい)様に少し覆われるくらいの長さを残します。

ar49_jig2.jpg

 上図の様に、筆者が作成した治工具は、全長10.5cmです。左右からLEDとCdSセルを挿入すると、入り口から3〜4[cm]くらい内部に入り込むので、実際のLEDとCdSセルの距離は、〜4cm程度となっています。スリットは、鋸の歯1枚分程度なので、2〜3[mm]ほどです。薄い金属の板程度であれば、スリットに差し込んで光を遮ることが可能となっています。

フォトインタラプタの回路設計

 回路としては、LEDの発光回路と、CdSセルの抵抗変化を測定する回路を組み合わせただけです。回路図を以下に示します。

ar49_schem.png

 まず、LEDをなるべく明るく光らせたいと考えました。その方が、CdSセルの抵抗変化量が大きくなるので。ただし、Arduinoのポートでドライブしたいので、40mA(絶対最大定格)以下となるように電流制限抵抗を330Ωとしました。ArduinoのVoh≧4.2V(@Ioh=20mA)を考え合わせると、ざっくり、If≒10mAくらいな計算です。
 LEDのVf次第では、もっと電流値Ifは小さいかも?ですが、まあ、光らせた感じ、結構明るかったので、良し、としました。

※If≒10mAを目標にすると、Voh≧4.2V(@Ioh=20mA)ですので、Ioh=10mAではVoh≒4.6V程度と見積もりました。回路はR2とLEDがシリーズで、LEDの電圧をVfとすると、Voh=R2×If+Vfの関係式が成立します。Vf≒1.5V程度とすると、R2≒310Ωとなります。LEDのVf=2Vくらいあるかもしれませんが、その場合はもう少し抵抗を小さくできます。

 CdSセルのシリーズ抵抗は、CdSセルの抵抗値がほぼ0Ωになった場合に流れる貫通電流として、許容できる値(〜0.5mA)から適切に決めました。ここはあんまりシビアに考えなくて良いかと。
 Arduinoのアナログ端子A0で測定した電位は、VccをR3とR1(CdSセル)で按分するので、CdSセルの抵抗値R1は以下の式で算出できます(「#2-9 抵抗値を測定する」参照)。

  R1=R3×A0/(Vcc−A0)

 LEDの選択については、手元に赤、青、緑、黄、白のLEDがあったのですが、同じ電流制限抵抗値で光らせてみたところ、緑と白のLEDが一番明るく見えたので、筆者は白のLEDを使うことにしました。

※後ほど調べるCdSセルのデータシートによると、感度の中心波長が540nm(緑)辺りにあるみたいなので、緑のLEDでも良かったのですが、白の方が、もっと広い範囲の波長を含んでいるので、ピーク感度以外の波長でも反応する可能性があると考え、白にしました。

CdSセルの応答時間を知る

 フォトレジスタ(CdSセル)の抵抗値は、明るいと抵抗小、暗いと抵抗大となる特徴があります。「#3-5 フォトレジスタとサーミスタ」で使用してみた時の印象は、「結構反応が速い」でした。蛍光灯の光で照らされている時に、手をかざして影にすると、すぐに抵抗値が変化した記憶があります。
 今回は、LEDを点灯してから、どのくらいの時間を待つと抵抗値が安定するのか、正確な時間を知っておく必要があるので、LED OFF状態からONにした時のCdSセルに掛かる電圧値をLogiscopeのデジタルオシロスコープ機能で測定してみます。

 今回の完成版プログラムをリンクしておきます。

  完成版プログラム:photo_intrpt.ino

 Arduino UNO等にコンパイルして転送しておいてください。シリアルモニタから、以下のコマンドを受け付けるようになっています。

 UNOでphoto_intrpt.inoを実行するので、Logiscopeを実行するためにもう1機Arduinoが必要です。Logiscopeのデジタルオシロスコープ機能を使うので、AVR系のArduinoを準備します。筆者は、Arduino Micro互換機を使いました。UNOとMicroのGNDを接続し、もう一本、UNOのA0端子と同じ信号線をMicroのA0端子にも接続してください。

  実験サンプル:【 】内はシリアルモニタ/Logiscopeからの送信データ
  【osc,7,-4.9,5,0】 …… MicroのLogiscope

  【led,255】 …… UNOのシリアルモニタ

※【osc】コマンドに渡すチャンネル番号(第1パラメータ)は、ATmega搭載AD変換器のチャンネル番号です。Micro (ATmega32U4)のA0端子は、ADC7ですので、【osc】コマンドの第一パラメータには"7"を渡す必要があります。端子とAD変換器のチャンネルとの対応は、「#3-3 I2C接続 LCD表示モジュール」で確認できます。

ar49_stabil.png

 上の様な波形が観測できるはずです。CRの放電電圧波形に似ていますが、CdSセルにLEDの光が当たった時の抵抗変化に伴う、CdSセルの電位変化波形です。LED OFF時に5Vに張り付いていた電位が、LED ONにしたことによって、3V弱辺りまで低下しています。
 さすがに、msオーダで測定すると、安定するまでにそこそこ時間がかかっているようですね。
 右端(13msくらい)でもまだ、電圧の低下が落ち着いていないようですので、完全に安定するには、15〜20[ms]くらいは要するのではないでしょうか?確実に判定しようと思うなら、最終到達電圧値の95%程度に変化するまでの時間を待ちたいところですが、電位の変化量もかなり大きいので、もっと短い時間で判定しても、十分精度の高い測定ができるかもしれませんね。
 判定時間として許容できる待ち時間と、判定精度とのトレードオフになりますが、今回は特に測定時間に制約が無いので、15ms程度待って、電圧(従って抵抗値)が安定してから測定することにしようと思います。

  const uint32 msec_led2cds=15;  // led to cds wait time [ms]

 高速に判定する必要がある場合には、測定波形から適切な待ち時間を決め、必要に応じて変更して使ってください。

光が遮られたことを判定する

 前節の結果だけを見ると、LEDを光らせっぱなしにしておいて、例えば、CdSセルの抵抗値が40kΩ以下だった場合は「光が届いている→遮られていない」、と判定し、40kΩ以上だった場合に「光が届かなかった→遮られた」と判定すれば良い様に思えます。

※前節の波形から、CdSセルの電位は5→3[V]程度に低下していることがわかります。そこで、中間の4Vで判定しようとします。CdSセルの電位が4Vだった時、その抵抗値はRcds=R3×A0/(5V−A0)と計算できます。R3=10kΩ、A0=4Vを代入すると、Rcds=40kΩが閾値。

 しかし、そうでしょうか?周囲環境の明るさによっても、CdSセルの抵抗値は変わってしまいます。今回はたまたま、LEDが消灯していた場合にCdSセルの電位は5Vに張り付いていましたが、周囲がもっと明るくて、治工具の内部に光が差し込むような状況だった場合、周囲の光だけで4V(従って40kΩ)を下回ってしまう場合も、十分考えられます。
 治工具を密閉して周囲の光が差し込まないようにできれば、そんな心配はいらないのですが、そもそも、スリットを設けて、光を遮る/遮らない状態を作り出せなければ、フォトインタラプタとして機能しません。スリットがあれば、どうしてもそこから光が侵入してしまいます。

 従って、閾値の数値を決め打ちで判定するのは危険です。

ar49_judge.png

 そこで、上図に示したような手順を踏むことにします。まず、LEDをOFF(消灯)した状態で、CdSセルの抵抗値R0を測定します。次にLEDをON(点灯)してから、安定時間(msec_led2cds)待った後で、もう一度CdSセルの抵抗値R0’を測定します。
 R0>R0’なら、「光は遮られていない」と判定することにします。

 この手順なら、多少、周囲環境光の影響を受けたとしても、少なくとも、LED 1個分は明るくなるはずなので、その分の電位変化が観測可能なはずです。
 一方、光が遮られていた場合、R0’≒R0が観測されます。R0’がほんの少しでもR0より小さいと、R0>R0’の条件は満たしてしまうため、最終的にdelta=R0−R0’が、どの程度の値で判定するか?判定値を決定する必要があります。

明るさとCdSセル抵抗値の関係

 CdSセルのデータシートを検索(別ウインドウが開きます)してみたところ、CdSセルの抵抗値は、横軸に明るさ(照度、ルクス)の対数を取った時に、負の傾きをもった直線になることが分かりました。この特性がわかっていれば、3点の異なる明るさに対するCdSセルの抵抗値を測定すると、各点における相対的な照度を確定することができます。

ar49_luxr.png

※ここの認識が誤っていました。本ページ最後の「後日のアップデート」で訂正してありますので、ご参照ください。m(1__)m

 まず、上図の黄色い点、3点に注目してください。

 予め測定したら、以下の様に、Cのソースコードに定数として埋め込みます。

  const float cds_Roff=6870.;
  const float cds_Ron=4971.;
  const float cds_Rlit=917.;


※上の値は、筆者環境での測定値です。特に、RoffとRonについては、LEDとCdSセル間の距離などにも左右されるパラメータですので、各自、測定する必要があります。

 Roff、Ron、Rlitが直線に乗っているものとします。また、Roff測定時の照度をLoff=1、Rlit測定時の照度をLlit=10、と仮定します。抵抗値をy、照度をxと置くと、上図の直線は以下の方程式で表すことができます。

  y=−(Roff−Rlit)×log(x)+Roff

 この時、Ron測定時の照度Lonは、1(Loff)〜10(Llit)の間の数値で計算可能です。

  log(Lon)=(Roff−Ron)/(Roff−Rlit)

 つまり、LED 1個分の照度Lledは、Lled=Lon−Loffとなります。

 ここまで来たら、後は簡単。今度は、黄緑の点に注目です。前節の方法でR0とR0’を測定したら、以下の手順で判定します。

 上の図で、赤い⇔で示したLED 1個分の照度Lledが、Roff基準の場合と、R0基準の場合で長さが異なって見えますが、横軸が対数軸だからです。
 周囲光が明るくなればなるほど、LED 1個分の照度の見た目の幅が狭くなり、判定がシビアになります。周辺の明るさが一定以上に達すると、LED 1個分の照度では判定不可能となってしまうでしょう。スリットはなるべく細くして、周囲の光がCdSセルに差し込まないように工夫することが必要となります。

動作確認

 フォトインタラプタの判定関数を示しておきます。前節までに解説した手順通りにインプリしてあります。

  bool cds_photoi (void)
  {
    // check photo interrupter sensor status
    // return: true=not interrupted / false=interrupted
    int anlg0;
    int anlg1;
    float cds_L0;

    // measure led-off state
    digitalWrite (pin_LED, LOW);
    delay (msec_led2cds);
    anlg0=analogRead (ana_CDS);
    // measure led-on state
    digitalWrite (pin_LED, HIGH);
    delay (msec_led2cds);
    anlg1=analogRead (ana_CDS);

    // led off
    digitalWrite (pin_LED, LOW);

    // judge
    cds_R0=cds_volt2ohm (anlg0);
    cds_R0d=cds_volt2ohm (anlg1);
    cds_L0=cds_ohm2ilm (cds_R0);
    cds_Rth=cds_ilm2ohm (cds_L0 + cds_Lled/2.);

    return (!( cds_Rth>cds_R0d ));
  }


 シリアルモニタで受け付けている、【phi】コマンドで上の関数を呼び出すようになっています。

  実行サンプル:【 】内はシリアルモニタからの送信データ
  【phi】 …… スリットを開放した状態で実行
  OK: phi=false (r0=10230000 r0'=6151 rth=8452)

  【phi】 …… スリットを遮蔽した状態で実行
  OK: phi=true (r0=10230000 r0'=9212 rth=8452)

ar49_intrpt.jpg

 実行時に光が遮られているかどうか?判定して、判定値を返します。trueで「遮蔽されている」、falseで「遮蔽されていない」ことを意味します。判定に使用したCdSセルの抵抗値(R0、R0’)や閾値(Rth)も表示されますので、ご確認ください。

※紙片などで遮蔽する場合、光が透過したり、丸い治工具と直線的な遮蔽物の隙間から光が漏れたりして、正しく判定できないケースがあります。うまく、遮蔽物の形を治工具の形に合わせるなど、工夫してください。
※あるいは、抵抗値(cds_Roffとcds_Ron)を測定する時、LEDのOFF/ON状態で測定するのではなく、LEDはON(点灯)し続けておいて、スリットを遮蔽した状態の抵抗をcds_Roff、スリットを解放した状態の抵抗をcds_Ronとした方が、精度良く判定できるかも、です。


 今回作成したシステムだと、数ミリのスリットが遮蔽されているかどうか?がわかるだけなので、用途としては、ドアや蓋等が閉まっている(/開いている)ことの検知等となります。


後日のアップデート

 CdSセルの抵抗値と照度の関係を表した以下図が間違っていました。訂正します。

ar49_luxr.png

 ↑誤り / 訂正↓

ar49_luxr2.png

 縦軸の抵抗値も、対数軸になるのが正解みたいです。従って、計算式も以下の様に訂正します。

  log(Lon)=(Roff−Ron)/(Roff−Rlit)
    ↓
  log(Lon)=(log(Roff)−log(Ron))/(log(Roff)−log(Rlit))

 本来はプログラム(photo_intrpt.ino)の方も修正して、評価し直すべきなのですが、間違った計算のままでも、「フォトインタラプタ」としての動作は確認できているし、計算式を訂正したところで、劇的にパフォーマンスが良くなる、と言う事でもないため、そのまま放置します。


btn_arduino.png

btn_prev.png btn_next.png

home