ArduinoでTCS3200カラーセンサー:配線、キャリブレーション、コード
TCS3200カラーセンサーの仕組みを解説します。S0〜S3の設定、pulseInでのR・G・Bの読み取り、白と黒のキャリブレーション、色の仕分け、ESP32・Pico・TCS34725のコードを紹介します。
TCS3200カラーセンサーは、Arduinoのプロジェクトで最も広く使われている色検出モジュールです。前にある面の色を、赤・緑・青の成分に分けて測ります。フォトダイオードアレイが受けた光を、そのまま周波数に変換するのが特徴で、面が反射する赤い光が多いほど、赤チャンネルの周波数が高くなります。アナログ読み取りや特別な通信プロトコルは必要なく、ArduinoのpulseIn()命令を使えば数行でR・G・Bの値を読み取れます。お菓子の仕分け機、色を認識するロボット、床の色付きの目印で向きを変える車、前にかざした物の色をまねるランプなどは、どれもこのセンサーで作られています。
このガイドでは、センサーの内部構造、S0〜S3ピンの役割、Arduino Uno・ESP32・Raspberry Pi Picoへの配線、生の値の読み取り、白と黒のキャリブレーションの方法、そして読み取った値がどの色に当たるかを判定するアルゴリズムをステップごとに紹介します。続いて物を色ごとに仕分ける装置のロジックを組み立て、最後にI2Cで動くセンサーTCS34725と比較します。

TCS3200カラーセンサーとは?仕組みは?
モジュールの中心にあるTCS3200チップ(旧版はTCS230と呼ばれます)の表面には、8×8の格子状に並んだ64個のフォトダイオードがあります。そのうち16個は赤のフィルター、16個は緑、16個は青のフィルターの下にあり、残りの16個はフィルターなし(クリア)で光の総量を見ます。各色の16個のフォトダイオードは並列につながっていて、S2ピンとS3ピンでどのグループを測るかを選びます。色の異なるフォトダイオードがアレイ全体に入り交じって配置されているため、面の一角に当たった光はすべての色のグループに同じように影響します。
選ばれたフォトダイオードのグループが生み出す小さな電流は、チップ内部の電流-周波数変換回路に送られます。この回路はデューティ比50%の矩形波を生成し、その周波数は光の強さに正比例します。つまり測っているのは電圧ではなく、1秒あたりのパルスの数です。真っ暗な中では数ヘルツ、白い面に近づけると数十キロヘルツになります。周波数出力の良いところは、ノイズに強く、Arduinoのどのデジタルピンにもつなげることです。光の強さだけを測りたいなら、LDR光センサーのほうが簡単です。TCS3200は同じことを3つの色ごとに別々に行います。
| 項目 | 値 |
|---|---|
| チップ | TCS3200(旧版TCS230、ピン互換) |
| フォトダイオードアレイ | 8×8 = 64:赤16、緑16、青16、クリア16 |
| 出力 | デューティ比50%の矩形波、周波数は光に比例 |
| フルスケール周波数(標準) | 600 kHz(100%)、120 kHz(20%)、12 kHz(2%) |
| 電源電圧 | 2.7〜5.5 V |
| チップの電流 | 標準1.4 mA(モジュールのLEDの電流は別途) |
| 制御ピン | S0、S1(周波数スケーリング)、S2、S3(カラーフィルター)、OE(出力イネーブル、アクティブLOW) |
| 推奨距離 | 約1〜3 cm、毎回の読み取りで同じ距離 |
| 照明 | モジュール上の白色LED 4個 |
S0〜S3ピン:周波数スケーリングとカラーフィルターの選択
TCS3200の動作は4本の制御ピンで決まります。S0とS1は出力周波数を下げる倍率を決めます。100%ではパルスが数マイクロ秒まで短くなり、Arduino Unoの測定精度の限界に近づきます。一方、2%ではパルスが長くなる代わりに、同じ時間に届くパルスの数が減ります。S2とS3は、どのカラーフィルターを読むかを選びます。これらのピンはArduinoのデジタル出力につなぎ、読み取りのたびにコードから設定します。
| S0 | S1 | 出力周波数のスケーリング | 使いどころ |
|---|---|---|---|
| LOW | LOW | パワーダウン | 電池で動くプロジェクトでセンサーを休止させる |
| LOW | HIGH | 2%(標準で最大12 kHz) | 低速なプロセッサー、MicroPython、割り込みでのカウント |
| HIGH | LOW | 20%(標準で最大120 kHz) | ArduinoとpulseInに最適な設定 |
| HIGH | HIGH | 100%(標準で最大600 kHz) | ハードウェアカウンターを使った高速な測定 |
| S2 | S3 | 読み取るフォトダイオードのグループ |
|---|---|---|
| LOW | LOW | 赤のフィルター |
| LOW | HIGH | 青のフィルター |
| HIGH | LOW | フィルターなし(クリア、光の総量) |
| HIGH | HIGH | 緑のフィルター |
TCS3200の配線:Arduino Uno、ESP32、Pico
| モジュールのピン | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| VCC | 5V | 3V3 | 3V3(OUT)、36番ピン |
| GND | GND | GND | GND |
| S0 | D4 | GPIO 32 | GP2 |
| S1 | D5 | GPIO 33 | GP3 |
| S2 | D6 | GPIO 25 | GP4 |
| S3 | D7 | GPIO 26 | GP5 |
| OUT | D8 | GPIO 27 | GP6 |
| OE | GND | GND | GND |
モジュール上の4個の白色LEDは、読み取りのたびに面を同じように照らすので、部屋の明るさが変わっても結果が安定しやすくなります。LEDを点灯・消灯するためのLEDピンが別にあるボードもあれば、電源を入れるとすぐにLEDが点くボードもあります。自分のボードの表示を確認してください。最良の結果を得るには、センサーを黒いチューブや厚紙で囲い、物を毎回同じ距離に置き、光沢のある反射しやすい面は避けましょう。ボードのピンの並びはピン配置ツールで確認できます。
ステップごとに:pulseInでR・G・Bを読み取る
- 上の表のとおりにモジュールを配線します。OEはGNDに、VCCは5Vにつなぎます。
- S0をHIGH、S1をLOWにして、20%の周波数スケーリングを選びます。
- 赤を読むにはS2とS3をLOWにし、出力が安定するまで数ミリ秒待ちます。
- pulseIn(OUT, LOW)でパルスのLOWの部分の長さをマイクロ秒単位で測ります。ノイズを減らすため、10回の読み取りを平均します。
- 緑(S2 HIGH、S3 HIGH)と青(S2 LOW、S3 HIGH)も同じように測ります。
- 9600ボーでシリアルモニタを開いて結果を見ながら、いろいろな色の紙をセンサーの前にかざします。
1// TCS3200 ile ham R, G, B okuma / Raw R, G, B reading with the TCS3200
2const int S0 = 4;
3const int S1 = 5;
4const int S2 = 6;
5const int S3 = 7;
6const int OUT_PIN = 8;
7
8// Filtreyi seçer, 10 darbenin ortalama LOW süresini döndürür (µs) / Selects a filter, returns the average LOW time of 10 pulses (µs)
9unsigned long readPulse(bool s2, bool s3) {
10 digitalWrite(S2, s2 ? HIGH : LOW);
11 digitalWrite(S3, s3 ? HIGH : LOW);
12 delay(5); // çıkışın oturmasını bekle / let the output settle
13 unsigned long total = 0;
14 int count = 0;
15 for (int i = 0; i < 10; i++) {
16 unsigned long t = pulseIn(OUT_PIN, LOW, 50000UL); // 50 ms zaman aşımı / 50 ms timeout
17 if (t > 0) {
18 total += t;
19 count++;
20 }
21 }
22 return count > 0 ? total / count : 50000UL; // darbe yoksa çok karanlık / no pulse means very dark
23}
24
25void setup() {
26 pinMode(S0, OUTPUT);
27 pinMode(S1, OUTPUT);
28 pinMode(S2, OUTPUT);
29 pinMode(S3, OUTPUT);
30 pinMode(OUT_PIN, INPUT);
31 digitalWrite(S0, HIGH); // S0=HIGH, S1=LOW: %20 ölçek / 20% scaling
32 digitalWrite(S1, LOW);
33 Serial.begin(9600);
34}
35
36void loop() {
37 unsigned long r = readPulse(false, false); // kırmızı filtre / red filter
38 unsigned long g = readPulse(true, true); // yeşil filtre / green filter
39 unsigned long b = readPulse(false, true); // mavi filtre / blue filter
40 Serial.print("R: ");
41 Serial.print(r);
42 Serial.print(" G: ");
43 Serial.print(g);
44 Serial.print(" B: ");
45 Serial.println(b);
46 delay(500);
47}このコードで最も大事なのは、読み取った数値が光に反比例することです。値が小さいほど、その色の光が多く届いています。赤い紙をかざすと「R: 24 G: 68 B: 52」のような出力になり、いちばん小さい数値が赤チャンネルに出ます。pulseInに渡している50,000 µsのタイムアウトは、パルスが来ないときにコードが初期設定の1秒間待ち続けるのを防ぎます。パルスがまったく捕まらなければ関数は50,000を返し、これは「とても暗い」という意味です。シリアル出力の読み取りでつまずいたら、Arduinoのシリアルモニタのガイドを見てください。
白と黒のキャリブレーション:生の値を0〜255に変換する
生の値だけでは判断を誤ります。3つのフォトダイオードのグループは感度が同じではなく、モジュールの白色LEDもすべての色を均等に出しているわけではないからです。そのため、真っ白な紙でも3つのチャンネルで異なる数値になります。キャリブレーションはこうした差をなくし、各チャンネルを0(黒)〜255(白)の範囲に収めます。カメラのホワイトバランスと同じ考え方です。キャリブレーションは、センサーを実際に使う場所で、同じ光、同じ距離で行ってください。机の上で行ったキャリブレーションは、装置のケースの中では通用しません。
- センサーを固定したホルダーに取り付け、測定距離(例:1.5 cm)を変わらないように決めます。
- 無地の白い紙を前に置き、上のコードでR・G・Bの値を何回か読み取って、その平均を白の基準値として記録します。これが最も小さい値です。
- 同じ位置につや消しの黒い紙かフェルトを置き、読み取った値を黒の基準値として記録します。これが最も大きい値です。
- この6つの数値を下のコードの先頭にあるWHITE_とBLACK_の定数に入力し、書き込みます。
- 白い紙で3つのチャンネルすべてが255近く、黒で0近くになるか確認します。ならない場合は、同じ光と距離で測定をやり直します。
多くの例で見られる間違いに注意してください。map()でパルス幅をそのまま0〜255に変換しても正しい結果にはなりません。光はパルス幅ではなく周波数に比例するからです。正しい方法は、まずパルス幅を周波数(1/t)に変換してからスケーリングすることです:レベル = (f − f_黒) / (f_白 − f_黒) × 255。例で見てみましょう。白で20 µs、黒で200 µsを測り、物では40 µsになったとします。map()ではこれが226になりますが、周波数を使う方法では113になります。物の周波数はちょうど白の半分で、黒のレベルを差し引くとスケールの約44%、つまり113に当たります。一方map()は色を実際よりずっと明るく見せ、暗い色合いを見分けにくくしてしまいます。
色の分類アルゴリズム:この読み取り値は何色?
キャリブレーション済みのR・G・Bの値も、まだ光の量に左右されます。同じ赤い物がセンサーに少し近づくだけで、3つの値がそろって上がります。これを補正するため、それぞれの値を合計で割ります:r = R / (R + G + B)、gとbも同様です。こうした色の比率は、距離や明るさが少し変わってもほとんど一定のままです。あとは、色ごとに一度測って保存しておいた基準の比率と比べ、最近傍を選びます。比較には比率の差の二乗を足し合わせるだけで十分で、この数値が小さいほど色が似ています。いちばん近い基準でも一定のしきい値より離れていれば、結果は「不明」です。光の総量がとても少なければ黒、3つのチャンネルすべてがとても高ければ白とみなします。この2つのケースは比率では区別できないため、最初にチェックします。
1// TCS3200 ile kalibrasyonlu renk tanıma / Calibrated color recognition with the TCS3200
2const int S0 = 4, S1 = 5, S2 = 6, S3 = 7, OUT_PIN = 8;
3
4// Kalibrasyonda ölçtüğün süreler (µs), kendi değerlerinle değiştir / Pulse widths from your calibration (µs), use your own
5const float WHITE_R = 16, WHITE_G = 20, WHITE_B = 18; // beyaz kağıt / white paper
6const float BLACK_R = 140, BLACK_G = 170, BLACK_B = 150; // siyah kağıt / black paper
7
8struct ColorRef {
9 const char *name;
10 float r, g, b; // renk oranları, toplamı 1 / color ratios, sum is 1
11};
12
13// Örnek referanslar, kendi ölçümlerinle değiştir / Sample references, replace with your own measurements
14ColorRef refs[] = {
15 {"Kirmizi / Red", 0.58, 0.20, 0.22},
16 {"Turuncu / Orange", 0.55, 0.30, 0.15},
17 {"Sari / Yellow", 0.42, 0.40, 0.18},
18 {"Yesil / Green", 0.24, 0.46, 0.30},
19 {"Mavi / Blue", 0.18, 0.30, 0.52},
20 {"Mor / Purple", 0.40, 0.20, 0.40}
21};
22const int REF_COUNT = sizeof(refs) / sizeof(refs[0]);
23const float MAX_DIST = 0.02; // bundan uzaksa bilinmiyor / farther than this is unknown
24
25unsigned long readPulse(bool s2, bool s3) {
26 digitalWrite(S2, s2 ? HIGH : LOW);
27 digitalWrite(S3, s3 ? HIGH : LOW);
28 delay(5);
29 unsigned long total = 0;
30 int count = 0;
31 for (int i = 0; i < 10; i++) {
32 unsigned long t = pulseIn(OUT_PIN, LOW, 50000UL);
33 if (t > 0) {
34 total += t;
35 count++;
36 }
37 }
38 return count > 0 ? total / count : 50000UL;
39}
40
41// Süreyi frekansa çevirip 0-255 arasına ölçekler / Converts the pulse width to frequency and scales it to 0-255
42float toLevel(unsigned long t, float tWhite, float tBlack) {
43 float f = 1.0 / t; // ışık frekansla orantılı / light is proportional to frequency
44 float fWhite = 1.0 / tWhite;
45 float fBlack = 1.0 / tBlack;
46 float level = (f - fBlack) / (fWhite - fBlack) * 255.0;
47 return constrain(level, 0.0, 255.0);
48}
49
50// En yakın referans rengi bulur / Finds the nearest reference color
51const char *classify(float R, float G, float B) {
52 float sum = R + G + B;
53 if (sum < 90) return "Siyah / Black"; // çok az ışık / very little light
54 if (R > 200 && G > 200 && B > 200) return "Beyaz / White";
55 float r = R / sum, g = G / sum, b = B / sum; // parlaklıktan bağımsız oranlar / brightness-independent ratios
56 int best = 0;
57 float bestDist = 1e9;
58 for (int i = 0; i < REF_COUNT; i++) {
59 float d = sq(r - refs[i].r) + sq(g - refs[i].g) + sq(b - refs[i].b); // uzaklığın karesi / squared distance
60 if (d < bestDist) {
61 bestDist = d;
62 best = i;
63 }
64 }
65 if (bestDist > MAX_DIST) return "Bilinmiyor / Unknown";
66 return refs[best].name;
67}
68
69void setup() {
70 pinMode(S0, OUTPUT);
71 pinMode(S1, OUTPUT);
72 pinMode(S2, OUTPUT);
73 pinMode(S3, OUTPUT);
74 pinMode(OUT_PIN, INPUT);
75 digitalWrite(S0, HIGH); // %20 ölçek / 20% scaling
76 digitalWrite(S1, LOW);
77 Serial.begin(9600);
78}
79
80void loop() {
81 float R = toLevel(readPulse(false, false), WHITE_R, BLACK_R);
82 float G = toLevel(readPulse(true, true), WHITE_G, BLACK_G);
83 float B = toLevel(readPulse(false, true), WHITE_B, BLACK_B);
84 float sum = R + G + B;
85 Serial.print("R=");
86 Serial.print(R, 0);
87 Serial.print(" G=");
88 Serial.print(G, 0);
89 Serial.print(" B=");
90 Serial.print(B, 0);
91 if (sum > 0) { // referans için oranlar / ratios for your references
92 Serial.print(" r=");
93 Serial.print(R / sum, 2);
94 Serial.print(" g=");
95 Serial.print(G / sum, 2);
96 Serial.print(" b=");
97 Serial.print(B / sum, 2);
98 }
99 Serial.print(" -> ");
100 Serial.println(classify(R, G, B));
101 delay(500);
102}| コードの部分 | 役割 |
|---|---|
| WHITE_*とBLACK_* | キャリブレーションで測った白と黒のパルス幅(µs) |
| toLevel()(レベル変換) | パルス幅を周波数に変換し、0〜255にスケーリングする |
| refs[](基準値) | 色ごとの基準のr、g、bの比率。自分で測った値に置き換える |
| classify()(分類) | まず黒と白を分け、次にいちばん近い基準を探す |
| MAX_DIST | この距離より離れた読み取り値は「Unknown」(不明)になる |
物を色ごとに仕分ける装置を作る
TCS3200で最も人気のあるプロジェクトは、色付きのお菓子、ビーズ、LEGOのピースを色ごとに仕分ける装置です。仕組みは2つのサーボでできています。上の送りサーボは、物がちょうど1個入る穴の開いた円盤を回します。まずホッパーのチューブの下から物を1個受け取り、それをセンサーの前に運び、読み取りが終わったら下のシュートに落とします。下のシュートサーボは斜めのシュートを回して、物をその色の容器に送ります。サーボを初めて使うなら、まずサーボモーターとは?を読んでください。
- 物が1個ずつ送り円盤の上に落ちるように、ホッパーと、物より少し太いチューブを取り付けます。
- センサーが円盤の上の物を1〜2 cmの距離から見られるように取り付け、外の光が入らないように囲います。
- 送りサーボの角度を3つ決めます。受け取り、センサー、落下です。自分の機構で試しながら見つけてください。送りサーボはD9に、シュートサーボはD10につなぎます。
- シュートサーボに、色の容器ごとの角度を割り当てます(例:赤30°、緑75°、青120°、それ以外165°)。
- キャリブレーションは装置の中で、装置自身の照明を使って行い、各色の物をいくつか使ってテストします。
1// TCS3200 ve iki servoyla renk ayırıcı / Color sorter with a TCS3200 and two servos
2#include <Servo.h>
3
4const int S0 = 4, S1 = 5, S2 = 6, S3 = 7, OUT_PIN = 8;
5const int FEEDER_PIN = 9; // besleme servosu / feeder servo
6const int CHUTE_PIN = 10; // yönlendirme servosu / chute servo
7
8const int LOAD_ANGLE = 160; // huninin altı / under the hopper
9const int SENSOR_ANGLE = 95; // sensörün önü / in front of the sensor
10const int DROP_ANGLE = 30; // bırakma deliği / drop hole
11
12// Kalibrasyon süreleri (µs) / Calibration pulse widths (µs)
13const float WHITE_R = 16, WHITE_G = 20, WHITE_B = 18;
14const float BLACK_R = 140, BLACK_G = 170, BLACK_B = 150;
15
16Servo feeder;
17Servo chute;
18
19unsigned long readPulse(bool s2, bool s3) {
20 digitalWrite(S2, s2 ? HIGH : LOW);
21 digitalWrite(S3, s3 ? HIGH : LOW);
22 delay(5);
23 unsigned long total = 0;
24 int count = 0;
25 for (int i = 0; i < 10; i++) {
26 unsigned long t = pulseIn(OUT_PIN, LOW, 50000UL);
27 if (t > 0) {
28 total += t;
29 count++;
30 }
31 }
32 return count > 0 ? total / count : 50000UL;
33}
34
35float toLevel(unsigned long t, float tWhite, float tBlack) {
36 float f = 1.0 / t;
37 float level = (f - 1.0 / tBlack) / (1.0 / tWhite - 1.0 / tBlack) * 255.0;
38 return constrain(level, 0.0, 255.0);
39}
40
41// Baskın kanala göre kutu açısını seçer / Picks the bin angle from the dominant channel
42int binAngle(float R, float G, float B) {
43 if (R + G + B < 90) return 165; // çok koyu: diğer kutusu / too dark: "other" bin
44 if (R > G * 1.3 && R > B * 1.3) return 30; // kırmızı kutusu / red bin
45 if (G > R * 1.3 && G > B * 1.3) return 75; // yeşil kutusu / green bin
46 if (B > R * 1.3 && B > G * 1.3) return 120; // mavi kutusu / blue bin
47 return 165; // karar verilemedi / undecided
48}
49
50void setup() {
51 pinMode(S0, OUTPUT);
52 pinMode(S1, OUTPUT);
53 pinMode(S2, OUTPUT);
54 pinMode(S3, OUTPUT);
55 pinMode(OUT_PIN, INPUT);
56 digitalWrite(S0, HIGH); // %20 ölçek / 20% scaling
57 digitalWrite(S1, LOW);
58 feeder.attach(FEEDER_PIN);
59 chute.attach(CHUTE_PIN);
60 feeder.write(LOAD_ANGLE);
61 delay(500);
62}
63
64void loop() {
65 feeder.write(LOAD_ANGLE); // huniden bir nesne al / take one object from the hopper
66 delay(600);
67 feeder.write(SENSOR_ANGLE); // sensörün önüne getir / bring it to the sensor
68 delay(600); // servo dursun, titreşim geçsin / let the servo settle
69 float R = toLevel(readPulse(false, false), WHITE_R, BLACK_R);
70 float G = toLevel(readPulse(true, true), WHITE_G, BLACK_G);
71 float B = toLevel(readPulse(false, true), WHITE_B, BLACK_B);
72 chute.write(binAngle(R, G, B)); // kanalı doğru kutuya çevir / aim the chute at the right bin
73 delay(400);
74 feeder.write(DROP_ANGLE); // nesneyi kanala bırak / drop the object into the chute
75 delay(600);
76}ESP32でTCS3200:割り込みで周波数を測る
同じpulseInのコードはESP32でも動きますが、もっと安定した方法があります。パルスを1つずつ計るのではなく、一定時間に届くパルスの数を数えるのです。割り込みで立ち上がりエッジのたびにカウンターを1つ増やし、50 ms後のカウントに20を掛ければ、ヘルツ単位の周波数になります。この方法は数百個のパルスを平均するので、1つの不良なパルスに影響されません。ESP32の240 MHzのプロセッサーなら、20%のスケーリングで1秒間に数万回の割り込みも楽にこなせます。とても明るい光でカウントが大きくなりすぎる場合は、スケーリングを2%に下げられます。OUTを割り込みに対応したD2に移せば、Arduino Unoでも同じ方法が使えます。ESP32では、モジュールを3V3ピンから給電するのを忘れないでください。
1// ESP32: kesmeyle TCS3200 frekans ölçümü / ESP32: TCS3200 frequency measurement with an interrupt
2const int S0 = 32, S1 = 33, S2 = 25, S3 = 26, OUT_PIN = 27;
3
4volatile uint32_t pulseCount = 0;
5
6void IRAM_ATTR onPulse() {
7 pulseCount++; // her yükselen kenarda say / count every rising edge
8}
9
10// Seçilen filtrenin frekansını Hz olarak döndürür / Returns the frequency of the selected filter in Hz
11uint32_t readHz(bool s2, bool s3) {
12 digitalWrite(S2, s2 ? HIGH : LOW);
13 digitalWrite(S3, s3 ? HIGH : LOW);
14 delay(5); // çıkış otursun / let the output settle
15 pulseCount = 0;
16 delay(50); // 50 ms boyunca say / count for 50 ms
17 return pulseCount * 20; // 50 ms x 20 = 1 saniye / 1 second
18}
19
20void setup() {
21 Serial.begin(115200);
22 pinMode(S0, OUTPUT);
23 pinMode(S1, OUTPUT);
24 pinMode(S2, OUTPUT);
25 pinMode(S3, OUTPUT);
26 pinMode(OUT_PIN, INPUT);
27 digitalWrite(S0, HIGH); // %20 ölçek / 20% scaling
28 digitalWrite(S1, LOW);
29 attachInterrupt(digitalPinToInterrupt(OUT_PIN), onPulse, RISING);
30}
31
32void loop() {
33 uint32_t r = readHz(false, false); // kırmızı / red
34 uint32_t g = readHz(true, true); // yeşil / green
35 uint32_t b = readHz(false, true); // mavi / blue
36 Serial.print("R: ");
37 Serial.print(r);
38 Serial.print(" Hz G: ");
39 Serial.print(g);
40 Serial.print(" Hz B: ");
41 Serial.print(b);
42 Serial.println(" Hz");
43 delay(300);
44}Raspberry Pi PicoとMicroPythonでTCS3200
PicoでArduinoのpulseInに当たるのはmachine.time_pulse_us()関数です。指定したレベルのパルスの長さをマイクロ秒で返し、タイムアウトしたときは負の数を返します。ピンがすでにそのレベルにあるときに呼び出すと、すぐに計測を始めてしまい、パルスの半分だけを測ることがあります。そのため、コードではまずHIGHの部分が終わるのを待ってから、LOWの時間を測っています。MicroPythonでは関数呼び出しのたびに数マイクロ秒かかるので、ここでは2%のスケーリング(S0=0、S1=1)を選びました。パルスが10倍長くなり、その遅れの影響が小さくなります。キャリブレーション値はHz単位で書いています。2%のスケーリングでは、白い紙で数kHz、黒で数百Hzになるのが普通です。モジュールはPicoの3V3(OUT)ピンから給電してください。Picoを初めて使うなら、Raspberry Pi Picoとは?でMicroPythonのインストール方法を説明しています。
1# Pico + TCS3200: süreden frekansa ve 0-255 seviyesine / Pico + TCS3200: from pulse width to frequency and a 0-255 level
2from machine import Pin, time_pulse_us
3import time
4
5s0 = Pin(2, Pin.OUT)
6s1 = Pin(3, Pin.OUT)
7s2 = Pin(4, Pin.OUT)
8s3 = Pin(5, Pin.OUT)
9out = Pin(6, Pin.IN)
10
11s0.value(0) # S0=0, S1=1: %2 ölçek / 2% scaling
12s1.value(1)
13
14FILTERS = {"R": (0, 0), "G": (1, 1), "B": (0, 1)} # S2, S3 değerleri / S2, S3 values
15WHITE_HZ = {"R": 3125, "G": 2500, "B": 2778} # beyaz kağıtta Hz / Hz on white paper
16BLACK_HZ = {"R": 357, "G": 294, "B": 333} # siyah kağıtta Hz / Hz on black paper
17
18def read_hz(channel, samples=10):
19 s2.value(FILTERS[channel][0])
20 s3.value(FILTERS[channel][1])
21 time.sleep_ms(5) # çıkış otursun / let the output settle
22 total = 0
23 count = 0
24 for _ in range(samples):
25 time_pulse_us(out, 1, 50000) # HIGH bitsin, LOW başında ol / wait for HIGH to end
26 t = time_pulse_us(out, 0, 50000) # LOW süresi, µs / LOW time, µs
27 if t > 0: # negatif = zaman aşımı / negative = timeout
28 total += t
29 count += 1
30 if count == 0:
31 return 0
32 return 1000000 / (2 * total / count) # f = 1 / (2t)
33
34def level(channel, hz):
35 v = (hz - BLACK_HZ[channel]) / (WHITE_HZ[channel] - BLACK_HZ[channel]) * 255
36 return max(0, min(255, int(v)))
37
38while True:
39 r = level("R", read_hz("R"))
40 g = level("G", read_hz("G"))
41 b = level("B", read_hz("B"))
42 print("R:", r, "G:", g, "B:", b)
43 time.sleep(0.5)TCS3200とTCS34725、どちらを選ぶ?
TCS34725は、同じ仕事をI2Cで行う新しいカラーセンサーです。測定をチップの内部で行い、結果をチャンネルごとに16ビットの数値で返すので、Arduinoがパルスを計る必要がありません。最も大きな違いは、フォトダイオードの上にある赤外線(IR)カットフィルターです。TCS3200にはこのフィルターがありません。シリコンのフォトダイオードは赤外線にも反応するので、太陽光や白熱電球のような赤外線の多い光では読み取り値がずれます。TCS34725はゲイン(1x、4x、16x、60x)と積分時間(2.4〜614 ms)をソフトウェアで設定できるため、とても暗い光から明るい光まで広い範囲で使えます。I2Cの仕組みが気になる場合は、I2Cとは?の記事を見てください。

| 項目 | TCS3200 | TCS34725 |
|---|---|---|
| 出力 | 周波数(矩形波) | I2C、アドレス0x29 |
| 信号線 | S0〜S3、OUT、OE | SDAとSCL(INTとLEDは任意) |
| 分解能 | 測定方法と時間による | チャンネルごとに16ビット |
| IRフィルター | なし | あり(チップ上) |
| 設定 | 3段階の周波数スケーリングとパワーダウン | 4段階のゲイン、2.4〜614 msの積分時間 |
| 電源 | 2.7〜5.5 V | チップは3.3 V、レギュレーター付きのモジュールは3〜5 V |
| 照明 | たいてい白色LED 4個 | たいてい白色LED 1個 |
| プロセッサーの負荷 | パルスを計る間は手がふさがる | チップが測定し、Arduinoは読むだけ |
| 向いている用途 | 学習、シンプルな色の仕分け機 | 正確な色、色温度、照度の測定 |
1// TCS34725 ile ham RGBC, renk sıcaklığı ve lüks / Raw RGBC, color temperature and lux with the TCS34725
2#include <Wire.h>
3#include <Adafruit_TCS34725.h>
4
5// 50 ms entegrasyon süresi, 4x kazanç / 50 ms integration time, 4x gain
6Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_50MS, TCS34725_GAIN_4X);
7
8void setup() {
9 Serial.begin(9600);
10 if (!tcs.begin()) { // 0x29 adresinde ara / look for it at 0x29
11 Serial.println("TCS34725 bulunamadi / not found");
12 while (true) {
13 delay(1000);
14 }
15 }
16}
17
18void loop() {
19 uint16_t r, g, b, c;
20 tcs.getRawData(&r, &g, &b, &c); // 16 bit ham değerler / 16-bit raw values
21 uint16_t kelvin = tcs.calculateColorTemperature_dn40(r, g, b, c);
22 uint16_t lux = tcs.calculateLux(r, g, b);
23 Serial.print("R: ");
24 Serial.print(r);
25 Serial.print(" G: ");
26 Serial.print(g);
27 Serial.print(" B: ");
28 Serial.print(b);
29 Serial.print(" C: ");
30 Serial.print(c);
31 Serial.print(" K: ");
32 Serial.print(kelvin);
33 Serial.print(" Lux: ");
34 Serial.println(lux);
35 delay(500);
36}コードを書き込む前に、ライブラリマネージャからAdafruit TCS34725ライブラリをインストールしてください。依存ライブラリについて聞かれたら、「全てをインストール」を選びます。Arduino UnoではSDAをA4、SCLをA5に、ESP32ではGPIO 21と22を使います。getRawData()は積分時間だけ待ってから、赤・緑・青とクリア(C)の生の値を返します。calculateColorTemperature_dn40()はケルビン単位の色温度を、calculateLux()はおおよその明るさを返します。キャリブレーションと最も近い色を選ぶロジックは、このセンサーにもそのまま使えます。色だけでなく手のジェスチャーや近接も検出できるセンサーが欲しいなら、APDS-9960が良い選択肢です。
TCS3200を使ったプロジェクトのアイデア
- 色の仕分け機:お菓子、ビーズ、LEGOのピースを色ごとに別々の容器に仕分けます。
- 色をまねるランプ:センサーの前にかざした物の色を、RGB LEDやWS2812Bテープで再現します。
- 色を読むロボット:ライントレースロボットに、床の赤い目印で止まり、緑の目印で進むように教えます。
- 色を認識するロボットアーム:ロボットアームでキューブをつかみ、色ごとにそれぞれの容器に落とします。
- しゃべる色検出器:検出した色を、DFPlayer Miniにあらかじめ録音した音声で知らせます。目の不自由な人の役に立つ補助具になります。
- 水の色モニター:透明な容器に入れた水の色の変化を時間とともに記録し、水槽や水やり用タンクのために警告を出します。
よくある間違いと対策
| 症状 | 考えられる原因 | 対策 |
|---|---|---|
| pulseInが常に0かタイムアウト値を返す | OEがフローティング状態かHIGH、S0とS1が両方LOW(パワーダウン)、またはOUTが違うピンにつながっている | OEをGNDにつなぎ、S0をHIGH、S1をLOWにして、OUTのピンをコードと照らし合わせる |
| 値が飛び続ける | 部屋の光、ちらつく照明、変わる距離 | センサーを黒いチューブで覆い、距離を固定し、複数回の読み取りを平均する |
| どの物も同じ色と判定される | キャリブレーションしていない。チャンネルごとに感度が違う | 白と黒でキャリブレーションし、基準値を測り直す |
| 日光の下で色がずれる | TCS3200にはIRフィルターがない | 閉じた箱の中で測るか、TCS34725を使う |
| レベルが0か255に張り付く | 物がキャリブレーション時と違う距離にある | キャリブレーション時と同じ距離を保ち、必要ならキャリブレーションし直す |
| ESP32やPicoの動作がおかしい | モジュールを5 Vで給電していて、OUTが5 Vを出している | モジュールを3V3から給電するか、レベル変換モジュールを使う |
| サーボが動くと読み取り値が狂う | サーボの電流で電源電圧が下がる | サーボには別の5 V電源を使い、GNDをつなぐ |
TCS3200は、光が周波数に変わる仕組み、キャリブレーションが大切な理由、シンプルな分類アルゴリズムの作り方を学ぶのにぴったりのセンサーです。もっと精度が必要になったときにTCS34725へ移るのも簡単です。学んだキャリブレーションと最も近い色を選ぶロジックが、そのまま使えるからです。BlueGraysアプリには、RGB LEDで滑らかに色を変える、ESP32の調色ライト、LDRで光の強さを測定、サーボモーターの使い方といったプロジェクトが回路図とステップごとのコード付きでそろっています。カラーセンサーと組み合わせれば、自分だけの装置を作れます。
よくある質問
TCS3200カラーセンサーはどのくらいの距離まで色を検出できますか?
最も安定した結果が出るのは約1〜3 cmです。モジュールのLEDの光は、距離が離れるとすぐに弱くなるからです。距離そのものより大事なのは、毎回の読み取りで同じ距離を保つことで、キャリブレーションもその距離で行ってください。このセンサーは、離れた場所にある大きな面の色を読むのには向いていません。
TCS230とTCS3200の違いは何ですか?
TCS230は同じメーカーの旧型のカラーセンサーで、TCS3200はその改良版です。どちらも8×8のフォトダイオードアレイ、周波数出力、同じS0〜S3の制御ロジックを使うので、配線もコードも共通です。現在売られているモジュールのほとんどはTCS3200を搭載しています。
TCS3200でpulseInが0を返すのはなぜですか?
pulseInは、指定したタイムアウト時間内にパルスを検出できないと0を返します。最も多い原因は、OEピンがGNDにつながっていない、S0とS1が両方LOWのまま(センサーがパワーダウンモードになる)、OUTがコードとは別のピンにつながっている、の3つです。センサーの前が真っ暗だと周波数が数ヘルツまで下がり、短いタイムアウトではパルスを捕まえられません。
TCS3200はESP32やRaspberry Pi Picoで使えますか?
はい。TCS3200は2.7〜5.5 Vで動作するので、モジュールをボードの3V3ピンから給電でき、そうすればOUTピンも3.3 Vのパルスを出します。モジュールを5 Vで給電する場合は、OUTラインに分圧回路かレベル変換モジュールが必要です。PicoでMicroPythonを使って測る場合は、2%の周波数スケーリングのほうが正確な結果になります。
TCS3200とTCS34725はどちらが優れていますか?
正確な色の測定ならTCS34725が優れています。赤外線カットフィルター、チャンネルごとに16ビットの分解能、調整可能なゲインを備え、I2Cの2本の線だけでつながります。TCS3200は、周波数の測定やキャリブレーションを学んだり、シンプルな色の仕分け機を作ったりするための、よく使われる十分な選択肢です。
TCS3200はいくつの色を見分けられますか?
照明を管理し、距離を固定すれば、TCS3200は赤、オレンジ、黄、緑、青、紫、黒、白のようにはっきり異なる6〜8色を確実に見分けられます。明るい緑と暗い緑のような近い色合いを見分けることもできますが、より丁寧なキャリブレーション、色合いごとの多くの基準値、厳しいしきい値が必要になります。
この記事を読んだ後に作れるプロジェクト
全プロジェクトを見る
LDRで光の強さを測定
部屋の明るさを数値で見てみませんか?光に反応する小さな部品、LDRで周囲の光を測定し、Arduinoが0.5秒ごとにその値をコンピューターの画面に表示します。手で覆うと数値が変わる様子を確認できます。自動で点灯する常夜灯などのプロジェクトへの第一歩です。

ESP32とLDRモジュールで光の強さを読み取る
LDRモジュールの生のADC値と、暗い状態・明るい状態の基準値から計算した相対的な明るさの割合を、シリアルモニタでリアルタイムに確認します。この割合はルクスの測定値ではありません。モジュールの校正の仕組みを学べます。しきい値の決定に使うため、プロジェクト10の前に取り組むのが適しています。

RGB LEDの使い方
1つのLEDに3つの色が入っています。RGB LEDが赤、緑、青の順に、それぞれ0.5秒点灯して0.5秒消灯し、このサイクルを繰り返します。ArduinoにRGB LEDを1つと抵抗を3つ接続するだけです。色とりどりの光を使うプロジェクトを、手軽に始められます。

RGB LEDで滑らかに色を変える
1つのRGB LEDが赤から緑、緑から青、そして再び赤へとゆっくり変わり、虹色のサイクルを繰り返します。1回の色の変化には約2.5秒かかり、色が混ざることで、その間に黄色、ターコイズ、紫が自然に現れます。光が滑らかに暗くなったり明るくなったりする様子を、自分の目で確かめられます。

ESP32の調色ライト(可変抵抗3個 + RGB LED + OLED)
ESP32と3個の可変抵抗で、好みの色を作れるスマート調色ライトです。赤・緑・青の可変抵抗を回すと、カソードコモンのRGB LEDの色がリアルタイムに変わります。SSD1306 OLEDディスプレイには、各色の0~255の値、色の強さを示すバー、#FF8040などのWeb用HEXカラーコードをリアルタイムで表示します。小さなプロ仕様のカラースタジオとして、RGBの色の仕組み、ADCによるアナログ値の読み取り、PWMによる明るさの制御、I2Cディスプレイの表示設計を1つのプロジェクトで学べます。

ESP32でRGB LEDを制御
3個の個別のLEDの代わりに、1個のRGB LEDで無数の色を作ります。Arduinoと同じようにanalogWrite()でR/G/Bの値を調整し、虹色に変化させます。赤と緑を混ぜると黄色になるなど、色の仕組みを実際に確認できます。

サーボモーターの使い方
小型サーボモーターのアームが、一方の端、中央、もう一方の端の3つの位置へ順番に回り、それぞれの位置で2秒間止まります。Arduinoが保持する角度をモーターに指示すると、モーターはその位置まで動いて止まります。ロボットアームや自動開閉するふたが動く仕組みを学べます。

TCRT5000で床面の白黒を判別
ライントレースロボットの目となる小さなセンサーで、床面が黒か白かを判別できます。TCRT5000センサーは目に見えない光を床面に当て、戻ってくる光の量を調べます。白い面では緑のLED、黒い面では赤のLEDが点灯します。自分でロボットを作る前に学んでおきたい大切なステップです。
ほかの記事
BlueGraysアプリ
この回路をスマホでステップごとに作る
回路図、コード、部品リストを1つの画面で確認できます。Arduino、ESP32、Picoのプロジェクトを200件以上用意しています。

