部品 · 読了まで19分

TCRT5000をArduinoで使うガイド:赤外線センサーの配線、コード、回転計

TCRT5000をArduinoで使う方法です。赤外線LEDとフォトトランジスタ、白黒の検出、AO/DO出力、しきい値、1〜15 mmの検出距離、FC-51との違い、回転数(RPM)のコードを解説します。

TCRT5000は、床の黒いライン、回転する軸の目印、数ミリ先の物体の表面を検出するためにArduinoのプロジェクトで使われる、小さな反射型赤外線センサーです。TCRT5000をArduinoで使うのは簡単です。市販のモジュールを5V、GND、信号ピン1本につなぐだけで、ライブラリは不要、analogRead()かdigitalRead()で十分です。光を出す赤外線LEDと、反射した光を測るフォトトランジスタがひとつのパッケージに入っているので、ライントレースロボット、紙を検出するプリンター、速度を測る回転計に共通する「目」になっています。

この記事では、TCRT5000の中の赤外線LEDとフォトトランジスタがどのように連携するのか、なぜ白と黒の表面をこれほど簡単に見分けられるのか、モジュールのアナログ出力(AO)とデジタル出力(DO)の違い、ポテンショメーターとソフトウェアでのしきい値の設定方法を学びます。さらに、Arduino Uno、ESP32、Raspberry Pi Picoのコードを使って、センサーの1〜15 mmの動作距離、周囲光の影響とそれを取り除く測定方法、FC-51障害物センサーとの違い、TCRT5000で回転計(RPMメーター)を作る方法を、ステップごとに見ていきます。

TCRT5000反射型赤外線センサー:青い赤外線LEDと黒いフォトトランジスタ
TCRT5000:青いレンズの部品が950 nmの赤外線LED、黒いレンズの部品が可視光カットフィルター付きのフォトトランジスタです。2つは同じケースの中に並び、光をさえぎる壁で仕切られています。

TCRT5000とは?どのように動くのか

TCRT5000は、Vishayが開発した反射型光センサーです。ケースの片側には赤外線を出すLED、もう片側には光に反応するフォトトランジスタがあり、どちらも同じ方向、つまりセンサーの前方を向いています。間にあるプラスチックの壁が、LEDの光が直接フォトトランジスタに届くのを防ぎます。光が検出されるのは、前方の表面に当たって跳ね返ってきたときだけです。フォトトランジスタは、ベース領域に当たる光が多いほどコレクタに多くの電流を流すトランジスタです。表面が反射する光が多いほど、コレクタ電流は大きくなります。黒いレンズは可視光カットフィルターで、可視光の大部分をさえぎり、赤外線に近い波長だけを通します。

特徴値(Vishayのデータシート)
構造赤外線LED+フォトトランジスタ、可視光をさえぎるケース
LEDの波長950 nm(目に見えない赤外線)
最適な動作距離2.5 mm
動作範囲0.2〜15 mm(信号がピークの20%以上)
LEDの順方向電圧標準1.25 V、最大1.5 V(60 mA時)
LEDの最大連続電流60 mA
フォトトランジスタVCEO 70 V、最大コレクタ電流100 mA
暗電流標準10 nA、最大200 nA
サイズ10.2 × 5.8 × 7 mm。TCRT5000Lはリードが長い(15 mm)タイプ
動作温度−25〜+85 °C

Arduinoはフォトトランジスタの電流を直接読めません。電流を電圧に変えるため、コレクタに負荷抵抗をつなぎます。市販のモジュールではたいてい10 kΩで、コレクタをVCCにプルアップしています。コレクタの電圧はV = VCC − Ic × Rで決まります。白い表面でフォトトランジスタに約0.4 mAが流れると、10 kΩでの電圧降下は4 Vになり、出力は1 Vまで下がります。黒い表面で電流が20 µAまで減ると、電圧降下はわずか0.2 Vで、出力は4.8 Vのままです。つまりTCRT5000モジュールでは、白で電圧が低く、黒で高くなります。この逆の論理は最初は戸惑いますが、回路のしくみがそうなっているだけです。負荷抵抗を大きくするとセンサーは敏感になりますが遅くなり、小さくすると速くなりますが弱い反射を見分けられなくなります。

TCRT5000はどうやって白と黒を見分けるのか?

白い紙は、当たった赤外線の大部分をあらゆる方向に散らして返します。つや消しの黒い塗料、トナー、テープは光の大部分を吸収して熱に変えます。TCRT5000はこの違いを、フォトトランジスタの電流の大きな差に変えます。ただし、センサーは人の目のように色を見ているわけではありません。センサーにとって大事なのは、表面が950 nmでどれだけ反射するかです。そのため意外なことが起きます。インクジェットの印刷の中には、目には真っ黒に見えても赤外線ではほぼ透明なものがあり、センサーはそれを白と判断します。つやのある黒いビニールテープは、センサーが真正面から見ると、鏡のように光をそのまま跳ね返すことがあります。赤、オレンジ、黄色の表面は白に近い値になることがよくあります。ですから、ラインや床の素材を決める前に、必ずセンサーでテストしてください。下の表は、5 V、高さ約3 mmで測った値の例です。実際の値はセンサーや周囲の環境によって変わることがあります。

表面赤外線の反射AO(Uno、0〜1023)DO(多くのモジュール)
白い紙とても高い約30〜200LOW、状態LEDが点灯
明るい色の木材や机高い約100〜400たいていLOW
つや消しの黒いテープやレーザー印刷とても低い約600〜1000HIGH、状態LEDが消灯
つやのある黒いビニールテープ角度しだい不安定不安定
下に表面がない(2 cm以上)反射なし1000に近いHIGH、黒と同じ
表の最後の行を忘れないでください。前に何もないときも、センサーは黒と判断します。そのため、ライントレースロボットを机から持ち上げると、モーターは「ラインを見つけた」かのように回り続けます。すべてのセンサーが同時に黒を見たらモーターを止めるという簡単なルールを入れておけば、ロボットも机も守れます。

TCRT5000モジュール:アナログ出力(AO)とデジタル出力(DO)

たいていは、TCRT5000単体ではなく市販のモジュールを使います。モジュールの基板には、センサーのほかにLM393コンパレーター、青い調整用ポテンショメーター、赤外線LEDの電流を制限する抵抗、フォトトランジスタの10 kΩの負荷抵抗、2つの小さなLEDが載っています。LEDの1つは電源LED、もう1つはDO出力を示す状態LEDです。AOピンはフォトトランジスタのコレクタに直接つながっていて、先ほど計算した生の電圧を出します。LM393はこの電圧をポテンショメーターのワイパーの電圧と比べ、結果をDOピンに出力します。一般的なモジュールでは、白い表面の上でDOがLOWになって状態LEDが点灯し、黒の上や前に何もないときはHIGHになってLEDが消えます。基板によっては論理が逆なので、まずシリアルモニタで自分のモジュールの動きを確認してください。1チャンネルのモジュールの多くは4ピン(VCC、GND、DO、AO)ですが、DOだけの3ピンのタイプや、3チャンネル・5チャンネルのラインセンサーアレイもあります。モジュール1個の消費電流は、赤外線LEDの抵抗によって5 Vで約15〜40 mAです。

特徴AO(アナログ)DO(デジタル)
わかることどれだけ光が反射しているかしきい値より上か下か
Unoでの読み取りanalogRead():0〜1023digitalRead():HIGHまたはLOW
しきい値コードで設定する基板上のポテンショメーター
白の上低い(約30〜200)LOW
黒の上高い(約600〜1000)HIGH
向いている用途距離の推定、PIDによるライントレース、キャリブレーション割り込みを使った回転数のカウント、単純なオン・オフの判定

動作距離:1〜15 mmの範囲を最大限に生かす

TCRT5000のデータシートによると、信号は2.5 mmでピークになり、0.2〜15 mmの間ではピークの20%以上を保ちます。実際には1〜15 mmで使えますが、距離が離れるほど白と黒の差はすぐに小さくなります。距離と信号の関係も一方向ではありません。2 mm未満では信号がまた下がります。LEDの光の円すいとフォトトランジスタの視野が表面で重なるのは、ある程度離れてからだからです。センサーを表面に押し当てると、白い紙でも暗い値になります。そのため同じアナログ値が1 mmでも5 mmでも出ることがあります。距離を推定したいなら、3 mmより先の一方向の領域だけを使ってください。

距離何が起きる?向いている用途
0〜1 mm光の円すいと視野が重ならず、白でも反応が弱い避ける。センサーを床にこすらせない
2〜3 mm信号がもっとも強く、白と黒の差が最大ライントレース、紙や目印の検出
3〜8 mm信号は下がるが、差はまだはっきりしているロボットの下への安全な取り付け、回転計
8〜15 mm白い表面と光る表面しか検出できない近接検出、物があるかないか
15 mmより先信号が周囲のノイズに埋もれるFC-51やHC-SR04など別のセンサーを選ぶ

TCRT5000で短い距離を測ることもできます。同じ表面ならアナログ値は距離に応じて一定の傾向で変わるので、3、5、8、12 mmなどいくつかの点で値を測って表を作り、その間を直線補間すればよいのです。ただし、この測定は表面の色に左右されます。白い紙でキャリブレーションしたセンサーは、灰色の表面を実際より遠いと判断します。ですからTCRT5000は、物差しではなく、同じ表面の上で「近づいているか、遠ざかっているか」に答える近接センサーと考えてください。センチメートル単位で測るなら、HC-SR04距離センサーのほうがずっと向いています。

TCRT5000モジュールの配線:Arduino Uno、ESP32、Pico

モジュールのピンArduino UnoESP32Raspberry Pi Pico
VCC5V3V33V3(OUT)、36番ピン
GNDGNDGNDGND、38番ピン
DOD2(割り込みピン)GPIO 27GP15、20番ピン
AOA0GPIO 34(ADC1)GP26 / ADC0、31番ピン
ESP32とPicoのピンは3.3 V用です。これらのボードでは、TCRT5000モジュールの電源を5VやVBUSではなく3V3ピンから取ってください。5 Vで動かしたモジュールでは、黒の上でAOとDOの出力が5 V近くになり、入力ピンを壊すおそれがあります。3.3 Vでは赤外線LEDの電流が減るので、検出距離が少し短くなりますが、これは正常です。どうしても5 Vで動かす必要があるなら、AOのラインに分圧回路を入れてください。ボードの全ピンのマップはピン配置ツールで確認できます。

モジュールなしでTCRT5000単体を配線するには?

センサー単体には足が4本あります。青い側の2本が赤外線LEDのアノードとカソード、黒い側の2本がフォトトランジスタのコレクタとエミッタです。いちばん簡単な見分け方は、テスターのダイオードモードです。青い側の2本の足では、一方向だけで約1.0〜1.2 Vが表示されます。そのとき赤いテスト棒が触れている足がアノードです。フォトトランジスタを逆につないでも、10 kΩの抵抗が電流を制限するので壊れません。単に動かないだけです。光を当てても値が変わらなければ、黒い側の2本の足を入れ替えてください。

赤外線LEDの電流は抵抗で制限する必要があります。計算式はR = (VCC − VF) / Iです。5 V、順方向電圧1.2 V、20 mAなら(5 − 1.2) / 0.02 = 190 Ωになります。もっとも近い標準値の220 Ωでは約17 mAになり、Arduinoのピンが余裕をもって供給できる電流です。この計算はLED抵抗ツールでもできます。フォトトランジスタのエミッタをGNDに、コレクタを10 kΩの抵抗を通して5Vにつなぎ、コレクタからA0へ配線します。より遠い表面や暗い表面を見分けたいなら、負荷抵抗を47 kΩに上げてもかまいません。白い表面はすぐに飽和しますが、暗い範囲での小さな差が大きくなります。

TCRT5000の足接続先(Arduino Uno)
アノード(青い側)220 Ωを通して5Vへ。周囲光の測定では5Vの代わりにD7へ
カソード(青い側)GND
コレクタ(黒い側)A0へ。さらに10 kΩを通して5Vへ
エミッタ(黒い側)GND

ステップごとに:Arduinoで白と黒の表面を検出する

  1. モジュールのVCCピンをArduinoの5Vピンに、GNDをGNDにつなぎます。
  2. AOピンをA0に、DOピンをD2につなぎます。
  3. テスト用に、白い紙に幅2 cmのつや消しの黒いテープを貼ります。
  4. センサーを表面から約3 mmの高さで、表面に対して垂直に持つか固定します。
  5. 下のコードを書き込み、シリアルモニタを9600 baudで開きます。
  6. 最初の5秒間に、センサーを黒いテープと白い紙の上で何度か往復させます。コードが最小値と最大値を見つけ、しきい値を自動で計算します。
  7. センサーを黒と白の間で動かします。ボード上のL LEDが黒の上で点灯し、白の上で消えれば成功です。
C++ (Arduino IDE)
1// TCRT5000 ile siyah-beyaz zemin algılama / Black-white surface detection with TCRT5000
2const int SENSOR_AO = A0;      // modülün AO pini / module AO pin
3const int SENSOR_DO = 2;       // modülün DO pini / module DO pin
4const int LED_PIN = 13;        // kart üzerindeki L LED'i / onboard L LED
5const int HYST = 40;           // histerezis payı / hysteresis margin
6
7int threshold = 450;           // varsayılan eşik / default threshold
8bool onBlack = false;
9
10void setup() {
11  Serial.begin(9600);
12  pinMode(SENSOR_DO, INPUT);   // modülde çekme direnci var / the module has its own pull-up
13  pinMode(LED_PIN, OUTPUT);
14
15  Serial.println(F("5 sn: sensoru siyah ve beyazin uzerinden gecir / sweep over black and white"));
16  digitalWrite(LED_PIN, HIGH);                // kalibrasyon sürüyor / calibrating
17  int lo = 1023, hi = 0;
18  unsigned long start = millis();
19  while (millis() - start < 5000) {
20    int v = analogRead(SENSOR_AO);
21    if (v < lo) lo = v;                       // en beyaz / whitest
22    if (v > hi) hi = v;                       // en siyah / blackest
23  }
24  digitalWrite(LED_PIN, LOW);
25  if (hi - lo >= 100) {
26    threshold = (lo + hi) / 2;                // eşik = (beyaz + siyah) / 2 / threshold = (white + black) / 2
27  } else {
28    Serial.println(F("Fark cok az, varsayilan esik / Too little contrast, default threshold"));
29  }
30  Serial.print(F("Beyaz/White: ")); Serial.print(lo);
31  Serial.print(F("  Siyah/Black: ")); Serial.print(hi);
32  Serial.print(F("  Esik/Threshold: ")); Serial.println(threshold);
33}
34
35void loop() {
36  int raw = analogRead(SENSOR_AO);            // beyazda düşük, siyahta yüksek / low on white, high on black
37  bool doBlack = digitalRead(SENSOR_DO) == HIGH;   // çoğu modülde siyahta HIGH / HIGH on black on most modules
38
39  if (!onBlack && raw > threshold + HYST) onBlack = true;    // siyaha geçti / moved onto black
40  if (onBlack && raw < threshold - HYST) onBlack = false;    // beyaza geçti / moved onto white
41
42  digitalWrite(LED_PIN, onBlack ? HIGH : LOW);
43  Serial.print(F("AO: ")); Serial.print(raw);
44  Serial.print(F("  DO: ")); Serial.print(doBlack ? F("SIYAH/BLACK") : F("BEYAZ/WHITE"));
45  Serial.print(F("  Kod/Code: ")); Serial.println(onBlack ? F("SIYAH/BLACK") : F("BEYAZ/WHITE"));
46  delay(100);
47}

コードはしきい値を(最小値 + 最大値) / 2で計算します。白で80、黒で820なら、しきい値は450です。しきい値を1つではなく2つの境界として使い、値が490を超えたら黒に、410を下回ったら白に切り替えます。このヒステリシスのおかげで、センサーがちょうどラインの縁にあっても出力がちらつきません。キャリブレーション中の差が100未満のときは、コードは初期値の450をしきい値にして警告を出します。これはたいてい、センサーが高すぎるか、片方の色の上でしか動かさなかったことを意味します。DOの列には、モジュールのポテンショメーターによる同じ判定が表示されます。両方を並べて見ると、ポテンショメーターの設定を確認するのに役立ちます。

しきい値の設定:ポテンショメーターか、ソフトウェアか?

DO出力を使う場合、しきい値は基板上のポテンショメーターで設定します。ポテンショメーターのワイパーの電圧がコンパレーターの基準になり、AOの電圧がそれを超えた瞬間にDOの状態が切り替わります。うまく設定するには、次のステップに従ってください。

  1. 使うときと同じ高さでセンサーを白い表面の上に置き、状態LEDが点灯するまでポテンショメーターを回します。
  2. センサーを黒いラインの上にずらし、LEDが消えることを確認します。消えなければ、ポテンショメーターを少し戻します。
  3. 白の上ではっきり点灯し、黒の上ではっきり消える両端の位置を見つけ、ポテンショメーターをその中間に合わせます。
  4. もっと細かく設定するなら、白の上と黒の上でAOをテスターで測り、ポテンショメーターのワイパーの電圧を2つの値の中間に合わせます。たとえば0.6 Vと4.2 Vなら、約2.4 Vです。
  5. 調整は必ず、ロボットや装置を実際に動かす場所で、同じ照明のもとで行ってください。
ソフトウェアのしきい値のほうが、たいてい柔軟です。周囲光や電池の電圧が変わったときにキャリブレーションし直せますし、複数のセンサーにそれぞれ別のしきい値を持たせることもできます。DO出力の本当の利点は速さです。コンパレーターは数マイクロ秒で判定し、その出力をそのまま割り込みピンに入れられます。ですから、回転数のカウントのような速い処理にはDOを、ライントレースや距離の推定のような細かい処理にはAOを使いましょう。

周囲光の影響と対策

黒いレンズの可視光カットフィルターは可視光をさえぎりますが、赤外線はさえぎれません。センサー自体が赤外線で動いているからです。太陽光や白熱電球、ハロゲン電球は950 nm付近の強い赤外線を出します。この光がフォトトランジスタに届くと、黒い表面でも白のような値になります。直射日光の下ではフォトトランジスタが完全に飽和し、センサーは何も見分けられなくなることがあります。一方、LEDや蛍光灯の照明は赤外線をほとんど出しません。同じロボットが教室では完璧に動くのに、窓のそばでは迷ってしまうのはこのためです。簡単なものから順に、対策を紹介します。

  • 遮光する:センサーの周りに黒いスカート、熱収縮チューブ、黒いスポンジを付けます。横から入る光をさえぎれます。
  • 低く取り付ける:センサーを表面から2〜4 mmまで近づけます。反射光が強くなり、周囲光の割合が小さくなります。
  • その場でキャリブレーションする:しきい値は必ず実際の環境で設定し、照明が変わるなら起動時にもう一度測ります。
  • 差を測る:センサー単体なら、赤外線LEDをArduinoのピンで駆動し、LEDを消したときと点けたときの2回読み取ります。周囲光はどちらの読み取りでも同じなので、その差はLEDの反射だけになります。
C++ (Arduino IDE)
1// Ortam ışığını çıkaran ölçüm (çıplak TCRT5000) / Ambient-light-cancelling reading (bare TCRT5000)
2const int IR_LED = 7;          // 220 Ω üzerinden IR LED anoduna / to the IR LED anode via 220 Ω
3const int SENSOR = A0;         // kolektör, 10 kΩ ile 5V'a / collector, 10 kΩ pull-up to 5V
4const int SETTLE_US = 500;     // fototransistörün oturma süresi / phototransistor settling time
5
6int readReflection() {
7  digitalWrite(IR_LED, LOW);
8  delayMicroseconds(SETTLE_US);
9  int ambient = analogRead(SENSOR);           // yalnız ortam ışığı / ambient light only
10  digitalWrite(IR_LED, HIGH);
11  delayMicroseconds(SETTLE_US);
12  int lit = analogRead(SENSOR);               // ortam + yansıma / ambient + reflection
13  digitalWrite(IR_LED, LOW);
14  return ambient - lit;                       // ışık arttıkça gerilim düşer / voltage drops as light rises
15}
16
17void setup() {
18  Serial.begin(115200);
19  pinMode(IR_LED, OUTPUT);
20}
21
22void loop() {
23  long sum = 0;
24  for (int i = 0; i < 8; i++) sum += readReflection();   // 8 ölçümün ortalaması / average of 8 readings
25  int reflection = sum / 8;
26  Serial.println(reflection);                 // büyük değer = beyaz ya da yakın / large = white or near
27  delay(50);
28}

フォトトランジスタはすぐには反応しません。10 kΩの負荷抵抗では、新しい値に落ち着くまで数百マイクロ秒かかることがあるので、コードは読み取りの前に毎回500 µs待ちます。値が安定しない場合は、SETTLE_USを1000に上げてください。差が大きいほど、表面は白いか近いということです。ただし、この方法でも太陽には完全には勝てません。周囲光でフォトトランジスタがすでに飽和していると、LEDを点けても何も変わらず、差は0になります。そうなったら、センサーを遮光するしかありません。テレビのリモコンは、38 kHzのパルスで光を送り、受信側がその周波数だけを聞き取ることで、同じ考え方をずっと高速に応用しています。これについては赤外線受信モジュールとリモコンの記事で解説しています。

TCRT5000とFC-51障害物センサーの違いは?

どちらも赤外線LEDと反射光を測る受光部からなり、どちらにもLM393コンパレーターが載っているので、よく混同されます。違いは何のために設計されているかです。TCRT5000は発光部と受光部をひとつのケースにまとめ、数ミリ先の小さな点に向けています。一方、FC-51は別々の赤外線LEDと受光部を並べて使い、数センチ先の障害物から返ってくる光を探します。そのためFC-51は床の細いラインを確実に見分けられず、TCRT5000は10 cm先の壁を見られません。

特徴TCRT5000モジュールFC-51障害物センサー
構造LEDとフォトトランジスタがひとつのケースに入り、間に壁がある別々の赤外線LEDと受光部が横に並ぶ
検出距離約1〜15 mm(最適は2.5 mm)約2〜30 cm(調整可能)
検出範囲狭い点約35°の円すい
出力たいていAO+DODOのみ(障害物があるとLOW)
主な用途ライントレース、回転計、紙や目印の検出障害物を避けるロボット、手や物の検出
周囲光距離が短いぶん影響を受けにくい日光の下では検出距離がはっきり短くなる

TCRT5000で回転数を数える(回転計)

TCRT5000のとても便利なのにあまり知られていない使い道のひとつが、回転の速さを測ることです。Vishayのデータシートでも、用途の一覧のいちばん上にposition sensor for shaft encoders(シャフトエンコーダー用の位置センサー)が挙げられています。考え方は簡単です。黒く塗った円板や軸に白い目印を貼ると、目印がセンサーの前を通るたびにDO出力が1回LOWになります。このパルスを数えれば、2つの方法で速度を計算できます。

  • カウント法:rpm = パルス数 × 60 / (目印の数 × 測定時間〔秒〕)。1秒間に25パルスを数え、円板の目印が1つなら、結果は25 × 60 = 1500 rpmです。高速ではとても正確ですが、1秒の測定時間と目印1つでは分解能が60 rpmになります。
  • 周期法:rpm = 60,000,000 / (2つのパルスの間隔〔マイクロ秒〕 × 目印の数)。2つのパルスの間が40,000 µs(40 ms)なら、結果は60,000,000 / 40,000 = 1500 rpmです。低速でも、パルスのたびに新しく正確な結果が得られます。
  • 目印の数:円板に2つか4つの目印を等間隔に貼ると、カウント法の分解能が上がります。式の目印の数を変えるのを忘れないでください。
  1. 測りたい軸や車輪をつや消しの黒で塗るか、つや消しの黒いテープで覆います。
  2. 約5 × 5 mmの白い紙のラベルを貼ります。目印はセンサーが見ている点より大きくします。
  3. センサーを目印から3〜5 mmの位置に、振動しないように取り付けます。
  4. 軸を手でゆっくり回し、目印が通るたびにDOのLEDが点滅することを確認します。必要ならポテンショメーターを調整します。
  5. DOピンをUnoの割り込みピン(D2かD3)のどちらかにつなぎ、下のコードを書き込みます。
C++ (Arduino IDE)
1// TCRT5000 ile takometre (devir ölçer) / Tachometer with TCRT5000
2const byte SENSOR_DO = 2;                // DO -> D2 (kesme pini / interrupt pin)
3const byte MARKS = 1;                    // diskteki beyaz işaret sayısı / white marks on the disc
4const unsigned long MIN_GAP_US = 500;    // daha sık darbeler titreşimdir / closer pulses are glitches
5
6volatile unsigned long pulseCount = 0;
7volatile unsigned long lastPulseUs = 0;
8volatile unsigned long periodUs = 0;
9
10void onPulse() {
11  unsigned long now = micros();
12  unsigned long gap = now - lastPulseUs;
13  if (gap < MIN_GAP_US) return;          // sınırdaki titreşimi ele / filter edge chatter
14  if (lastPulseUs != 0) periodUs = gap;  // iki darbe arası süre / time between pulses
15  lastPulseUs = now;
16  pulseCount++;
17}
18
19void setup() {
20  Serial.begin(115200);
21  pinMode(SENSOR_DO, INPUT);
22  attachInterrupt(digitalPinToInterrupt(SENSOR_DO), onPulse, FALLING);   // beyaz işarette LOW olur / goes LOW on the white mark
23}
24
25void loop() {
26  static unsigned long lastPrint = 0;
27  if (millis() - lastPrint < 1000) return;   // saniyede bir hesapla / calculate once per second
28  lastPrint = millis();
29
30  noInterrupts();                        // paylaşılan değişkenleri güvenle kopyala / copy shared variables safely
31  unsigned long count = pulseCount;
32  pulseCount = 0;
33  unsigned long period = periodUs;
34  unsigned long last = lastPulseUs;
35  interrupts();
36
37  float rpmCount = count * 60.0 / MARKS;                 // sayma yöntemi / counting method
38  float rpmPeriod = 0;
39  if (period > 0 && micros() - last < 2000000UL) {       // 2 sn darbe yoksa durmuş say / stopped after 2 s without pulses
40    rpmPeriod = 60000000.0 / (period * (float)MARKS);    // periyot yöntemi / period method
41  }
42  Serial.print(F("Sayma/Count: "));
43  Serial.print(rpmCount, 0);
44  Serial.print(F(" rpm   Periyot/Period: "));
45  Serial.print(rpmPeriod, 1);
46  Serial.println(F(" rpm"));
47}

パルスはattachInterrupt()でとらえます。プロセッサーがloop()で何をしていても、DOが下がった瞬間にonPulse()が実行されるので、パルスを取りこぼしません。割り込みルーチンとloop()で共有する変数はvolatileで宣言し、loop()はそれをnoInterrupts()とinterrupts()の間でコピーします。そうしないと、4バイトの数値を読んでいる途中で割り込みが起き、半分だけ更新された値になってしまうことがあります。LM393の出力は、白と黒の境目で何回かチャタリングすることがあります。MIN_GAP_USは、間隔が500 µs未満で届いたパルスを無視してこれを取り除きます。目印が1つなら、このフィルターでも120,000 rpmまで測れるので、ホビー用のモーターでは実質的に制限になりません。割り込みについてはArduinoの割り込みの記事を見てください。

速度がわかっているモーターで装置を確認しましょう。たとえば黄色いTTギアモーターは、6 Vの無負荷で約200 rpmで回ります。測定値がちょうど2倍になるなら、円板に気づいていない2つめの光る部分があります。ねじの頭、金属のハブ、光る縁も目印として数えられてしまいます。

ESP32でTCRT5000を使う

ESP32でも同じ考え方で動きますが、3つの違いに注意してください。1つめに、モジュールの電源は3V3から取ります。2つめに、アナログ入力は12ビットで0〜4095の値になります。AOはADC1のピン(GPIO 32〜39)にだけつないでください。Wi-Fiがオンの間はADC2のピンを読めないからです。3つめに、割り込みルーチンにはIRAM_ATTRを付け、ESP32はコアが2つあるので、共有変数はportMUXのクリティカルセクションで保護します。下のコードは円板の2つの目印を数え、0.5秒ごとに回転数とアナログ値を表示します。ESP32が初めてなら、先にESP32とは?を見てください。

C++ (Arduino IDE)
1// ESP32 + TCRT5000: devir ölçer ve analog okuma / tachometer and analog reading
2const int SENSOR_DO = 27;                // DO -> GPIO 27
3const int SENSOR_AO = 34;                // AO -> GPIO 34 (ADC1)
4const int MARKS = 2;                     // diskteki beyaz işaret sayısı / white marks on the disc
5const uint32_t MIN_GAP_US = 300;         // daha sık darbeler titreşimdir / closer pulses are glitches
6
7volatile uint32_t pulseCount = 0;
8volatile uint32_t lastPulseUs = 0;
9portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
10
11void IRAM_ATTR onPulse() {               // kesme kodu IRAM'de durur / ISR lives in IRAM
12  uint32_t now = micros();
13  portENTER_CRITICAL_ISR(&mux);
14  if (now - lastPulseUs >= MIN_GAP_US) {
15    lastPulseUs = now;
16    pulseCount++;
17  }
18  portEXIT_CRITICAL_ISR(&mux);
19}
20
21void setup() {
22  Serial.begin(115200);
23  pinMode(SENSOR_DO, INPUT);
24  analogReadResolution(12);              // 0-4095
25  attachInterrupt(digitalPinToInterrupt(SENSOR_DO), onPulse, FALLING);
26}
27
28void loop() {
29  static uint32_t lastMs = 0;
30  uint32_t nowMs = millis();
31  if (nowMs - lastMs < 500) return;      // yarım saniyede bir / every half second
32  uint32_t elapsed = nowMs - lastMs;
33  lastMs = nowMs;
34
35  portENTER_CRITICAL(&mux);              // iki çekirdeğe karşı koruma / protection across both cores
36  uint32_t pulses = pulseCount;
37  pulseCount = 0;
38  portEXIT_CRITICAL(&mux);
39
40  float rpm = pulses * 60000.0 / (elapsed * (float)MARKS);   // dev/dk / revolutions per minute
41  int raw = analogRead(SENSOR_AO);       // siyahta yüksek, beyazda düşük / high on black, low on white
42  Serial.printf("RPM: %.0f   AO: %d\n", rpm, raw);
43}

測定時間が0.5秒なので、コードは経過時間をmillis()で正確に測り、式に使います。rpm = パルス数 × 60,000 / (経過時間〔ms〕 × 目印の数)です。Wi-Fi経由でWebページに速度を表示したり、ある値を下回ったらスマホに通知を送ったりできることが、ESP32がこのプロジェクトにもたらす本当の利点です。アナログ値も同時に読んでいるので、円板とセンサーの距離が変わるかどうか、つまり軸がぶれているかどうかも見られます。

Raspberry Pi PicoとMicroPythonでTCRT5000を使う

Picoでは、モジュールの電源を3V3(OUT)ピンから取り、AOをGP26(ADC0)に、DOをGP15につなぎます。read_u16()は0〜65535の値を返し、白の上では低く、黒の上では高くなります。下のコードは表面を検出し、さらにDOラインのパルスを割り込みで数えて回転数も計算します。MicroPythonの割り込みはCほど速くなく、確実に動くのは毎秒数百パルスまでです。とても速いモーターにはArduinoかESP32を選びましょう。セットアップと最初のステップについては、Raspberry Pi Picoとは?を見てください。

MicroPython
1# Raspberry Pi Pico: TCRT5000 ile zemin algılama ve devir sayma
2# Raspberry Pi Pico: surface detection and RPM counting with TCRT5000
3from machine import Pin, ADC
4import machine
5import time
6
7ao = ADC(26)                     # AO -> GP26 (ADC0)
8do = Pin(15, Pin.IN)             # DO -> GP15
9led = Pin("LED", Pin.OUT)        # kart üzerindeki LED / onboard LED
10
11MARKS = 1                        # diskteki beyaz işaret sayısı / white marks on the disc
12MIN_GAP_US = 500                 # daha sık darbeler titreşimdir / closer pulses are glitches
13count = 0
14last_us = time.ticks_us()
15
16def on_pulse(pin):
17    global count, last_us
18    now = time.ticks_us()
19    if time.ticks_diff(now, last_us) < MIN_GAP_US:
20        return                   # sınırdaki titreşimi ele / filter edge chatter
21    last_us = now
22    count += 1
23
24do.irq(trigger=Pin.IRQ_FALLING, handler=on_pulse)   # beyaz işarette DO LOW olur / DO goes LOW on white
25
26while True:
27    time.sleep(1)
28    state = machine.disable_irq()   # sayacı güvenle oku / read the counter safely
29    pulses = count
30    count = 0
31    machine.enable_irq(state)
32
33    raw = ao.read_u16()             # 0-65535: beyazda düşük, siyahta yüksek / low on white, high on black
34    on_white = do.value() == 0      # çoğu modülde beyazda LOW / LOW on white on most modules
35    led.value(on_white)
36    rpm = pulses * 60 / MARKS
37    print("AO:", raw, "Zemin/Surface:", "BEYAZ/WHITE" if on_white else "SIYAH/BLACK", "RPM:", rpm)

TCRT5000のプロジェクトのアイデア

  • ライントレースロボット:2個、3個、5個のセンサーのアレイで黒いラインをたどるロボットです。センサーの配置とPIDの調整はArduinoでライントレースロボットの記事で解説しています。
  • 机の端に気づくロボット:ロボットの前に下向きのTCRT5000を付けます。机の端では信号が「黒」になり、ロボットは落ちる前に止まります。シャーシとモーターについてはArduinoでロボットカーを作る方法のガイドを見てください。
  • モーターの速度表示:回転計のコードをOLEDディスプレイと組み合わせ、回転数を大きな数字で表示します。
  • ホイールエンコーダーと走行距離計:車輪の内側に8〜20個の白黒の区画を貼ってパルスを数え、区画の数で割って車輪の円周を掛ければ、進んだ距離がわかります。向きも知りたい場合は、ロータリーエンコーダーと同じように、2つのセンサーを区画の半分だけずらして配置します。
  • 物の数を数えるカウンター:小さなコンベアの上を通る白い箱や紙を数え、合計をディスプレイに表示します。
  • 用紙切れアラーム:ラベルやプリンター用紙のロールの裏にセンサーを向けます。紙がなくなった瞬間に反射が消え、ブザーが鳴ります。
  • 同じ原理を密閉したもの:LEDとフォトトランジスタを光を通さないひとつのパッケージに封じ込めたフォトカプラとTCRT5000を比べると、光で信号が伝わるしくみがよりよくわかります。

よくある間違いと直し方

症状考えられる原因対処法
AOが1000前後のまま変わらないセンサーが表面から遠すぎる、赤外線LEDが点いていない、またはAOのピンが違うセンサーを3 mmまで近づけ、スマホのカメラで赤外線LEDを確認し、配線を見直す
黒い表面が白と判断される日光や白熱電球の光、つやのあるテープ、赤外線を通すインクセンサーを遮光し、つや消しのテープかレーザー印刷を使い、その場でしきい値を設定する
DOのLEDがまったく変わらないポテンショメーターが端まで回っているLEDが切り替わる位置を見つけ、それに合わせてポテンショメーターを設定する
回転計が実際の2倍の回転数を表示する円板に2つめの光る部分がある、または境目でのチャタリング円板を完全につや消しにし、MIN_GAP_USを上げ、MARKSの値を確認する
ESP32でアナログ値がいつも0か、不安定AOがADC2のピンにつながっていて、Wi-FiがオンAOをGPIO 32〜39のADC1のピンに移す
ロボットを持ち上げるとモーターが回り続ける下に表面がないと、センサーは黒と判断するすべてのセンサーが同時に黒を見たらモーターを止める安全ルールを追加する
赤外線LEDが動いているかは自分の目では見えませんが、スマホのカメラなら見えます。カメラを起動してセンサーの青い側に向けると、画面に紫がかった白い光が見えるはずです。背面カメラに赤外線カットフィルターが付いているスマホもあるので、光が見えなければ前面カメラで試してください。

TCRT5000は、安くて小さく、とても学びの多いセンサーです。部品ひとつで、フォトトランジスタ、コンパレーター、しきい値とヒステリシス、割り込み、周囲光への対処を学べます。まず表面検出のコードで自分のセンサーの値を知り、次に回転計で速度を測り、最後に複数のセンサーをロボットの下に組み合わせてみましょう。光で測るもうひとつの簡単な方法が気になるなら、LDR光センサーの記事も見てみてください。BlueGraysアプリには、TCRT5000で白黒の表面を検出するプロジェクトとTCRT5000で距離を測るプロジェクトがあり、L298NのBluetooth操作カーや障害物を避けるロボットと並んで、ステップごとの回路図とコード付きで紹介しています。

よくある質問

TCRT5000センサーは何に使いますか?

TCRT5000は、赤外線LEDとフォトトランジスタをひとつのケースにまとめた反射型センサーです。数ミリ先の表面が赤外線をどれだけ反射するかを測るので、白と黒を見分けたり、回転する円板の目印を数えたり、すぐ近くの物に気づいたりできます。主にライントレースロボット、回転計、紙の検出に使われます。

TCRT5000の検出距離はどのくらいですか?

TCRT5000が動くのはセンチメートルではなくミリメートルの単位です。データシートによると信号がもっとも強いのは2.5 mmで、センサーは約0.2〜15 mmで使えます。実際にいちばん確実なのは2〜8 mmです。もっと長い距離には、FC-51赤外線障害物センサーかHC-SR04超音波距離センサーを選んでください。

TCRT5000は黒でHIGHとLOWのどちらを出力しますか?

一般的なTCRT5000モジュールでは、白の上でDO出力がLOWになって状態LEDが点灯し、黒の上や前に表面がないときはHIGHになります。AO出力も同じように、白では低い値、黒では高い値になります。基板によっては論理が逆なので、シリアルモニタで自分のモジュールを確認するのがいちばんです。

TCRT5000とFC-51の違いは何ですか?

TCRT5000は発光部と受光部をひとつのケースにまとめた反射型センサーで、約1〜15 mmのとても短い距離で動きます。ライントレースや回転数のカウントに最適です。FC-51は別々の赤外線LEDと受光部を使い、約2〜30 cm先の障害物を検出し、出力はデジタルだけです。簡単に言えば、TCRT5000は床を見て、FC-51は前を見るセンサーです。

TCRT5000で色を見分けられますか?

いいえ。TCRT5000が測るのは950 nm付近の赤外線がどれだけ反射されるかだけで、色はわかりません。赤外線では意外なふるまいをする色もあります。赤や黄色の表面は白に近い値になることが多く、黒いインクジェット印刷の中にはほぼ透明になるものもあります。色を見分けるには、TCS3200やTCS34725などのカラーセンサーを使ってください。

TCRT5000はESP32と3.3 Vで使えますか?

はい。TCRT5000モジュールは3.3〜5 Vで動き、ESP32やRaspberry Pi Picoでは3V3ピンから電源を取るべきです。そうすればAOとDOの出力が3.3 Vを超えることはなく、ボードのピンは安全です。3.3 Vでは赤外線LEDの電流が少なくなるので、検出距離は少し短くなります。ESP32では、AOをADC1のピンにつなぐのを忘れないでください。

全プロジェクトを見る
TCRT5000で床面の白黒を判別
#78中級
プレミアム

TCRT5000で床面の白黒を判別

ライントレースロボットの目となる小さなセンサーで、床面が黒か白かを判別できます。TCRT5000センサーは目に見えない光を床面に当て、戻ってくる光の量を調べます。白い面では緑のLED、黒い面では赤のLEDが点灯します。自分でロボットを作る前に学んでおきたい大切なステップです。

4.7(40)
49
TCRT5000で反射(近接)を読み取る
#77中級
プレミアム

TCRT5000で反射(近接)を読み取る

目に見えない光を出し、その反射量を測る小さなセンサーで、前方の表面を検出できます。物体や明るい色の表面にセンサーを近づけるほど戻ってくる光が増え、Arduinoがその値を0.5秒ごとに画面に数値で表示します。ライントレースロボットや非接触式の水栓には、このようなセンサーが使われています。

4.6(33)
43
LDRで光の強さを測定
#11初級

LDRで光の強さを測定

部屋の明るさを数値で見てみませんか?光に反応する小さな部品、LDRで周囲の光を測定し、Arduinoが0.5秒ごとにその値をコンピューターの画面に表示します。手で覆うと数値が変わる様子を確認できます。自動で点灯する常夜灯などのプロジェクトへの第一歩です。

4.6(81)
139
HC-SR04で距離を測定(シリアルモニタ表示)
#27初級

HC-SR04で距離を測定(シリアルモニタ表示)

HC-SR04センサーは、コウモリが暗闇で道を見つけるのと同じ仕組みで動作します。人には聞こえない音を出し、その反響を捉えて前方の物体までの距離を測ります。Arduinoは0.5秒ごとに距離をセンチメートル単位でコンピューターの画面に表示し、3〜100センチメートルの測定値を示します。駐車センサーや障害物を避けるロボットなどのプロジェクトの基礎になります。

4.6(46)
71
L298Nで作るBluetooth操作の車
#80上級
プレミアム

L298Nで作るBluetooth操作の車

スマートフォンをリモコンにして、自作のロボットカーを操作できます。ArduinoはBluetoothモジュールを通じてスマートフォンのリモコンアプリから指令を受け取り、モータードライバーで車輪を回します。前進、後退、左右や斜めへの移動ができ、速度もスマートフォンから調整できます。停止ボタンを押すと、短いブレーキ動作でモーターがすぐに止まります。

5.0(38)
65
障害物回避ロボット
#81上級
プレミアム

障害物回避ロボット

自分で進む道を見つける、賢い車を作ってみませんか?障害物回避ロボットは、前面の超音波センサーで周囲を常に調べます。障害物を見つけると停止し、サーボモーターで左右を確認して、最も障害物の少ない方向を選んで進みます。自動運転の基礎を学ぶのにぴったりの入門プロジェクトです。

5.0(38)
49
レーザー警報システム
#22中級
プレミアム

レーザー警報システム

映画のような、目に見えない光のフェンスを作ります。向かい側の光センサーにレーザーを当て、人が通って光を遮るとすぐにLEDが点灯し、ブザーが鳴ります。光が再び届くと、LEDが消灯し、ブザーも止まります。ドアや廊下に使える、シンプルながら印象的な警報装置です。

4.8(64)
83
7セグメントディスプレイとボタンで作るカウンター
#43上級
プレミアム

7セグメントディスプレイとボタンで作るカウンター

3つのボタンを使うシンプルなカウンターです。1つ目のボタンを押すと表示の数字が1増え、2つ目を押すと1減り、3つ目を押すと0に戻ります。1桁の7セグメントディスプレイで0から9まで数え、範囲の端に達すると隅の小さな点が0.5秒間点灯して知らせます。スコアボードや順番表示器などのプロジェクトの基礎になります。

4.6(35)
47