Датчик цвета TCS3200 и Arduino: подключение, калибровка и код
Как работает датчик цвета TCS3200: настройка S0–S3, чтение R, G, B через pulseIn, калибровка по белому и чёрному, сортировка по цвету, код для ESP32, Pico и TCS34725.
Датчик цвета TCS3200 — самый распространённый модуль распознавания цвета в проектах на Arduino: он измеряет цвет поверхности перед собой, раскладывая его на красную, зелёную и синюю составляющие. Свет, который ловит матрица фотодиодов, датчик сразу превращает в частоту: чем больше красного света отражает поверхность, тем выше частота на красном канале. Ни аналогового чтения, ни особого протокола связи здесь нет: значения R, G и B считываются в несколько строк командой Arduino pulseIn(). Машины для сортировки конфет, роботы, распознающие цвета, тележки, которые меняют направление у цветных меток на полу, и лампы, повторяющие цвет поднесённого предмета, — всё это делается на этом датчике.
В этом руководстве пошагово разберём, что находится внутри датчика, зачем нужны пины S0–S3, как подключить его к Arduino Uno, ESP32 и Raspberry Pi Pico, как снять сырые показания, как выполнить калибровку по белому и чёрному и как работает алгоритм, который по показаниям определяет цвет. Затем соберём логику машины, сортирующей предметы по цвету, а в конце сравним TCS3200 с TCS34725 — датчиком, который работает по I2C.

Что такое датчик цвета TCS3200 и как он работает?
На поверхности микросхемы TCS3200, сердца модуля (её старая версия называется TCS230), расположено 64 фотодиода в сетке 8×8. Шестнадцать из них закрыты красными фильтрами, 16 — зелёными, 16 — синими, а оставшиеся 16 без фильтра (clear) и видят общее количество света. Все 16 фотодиодов одного цвета соединены параллельно, а пины S2 и S3 выбирают, какая группа измеряется. Фотодиоды разных цветов перемешаны по всей матрице, поэтому свет, падающий на один угол поверхности, одинаково влияет на все цветовые группы.
Слабый ток выбранной группы фотодиодов поступает на преобразователь ток–частота внутри микросхемы. Эта схема выдаёт меандр с коэффициентом заполнения 50%, и частота сигнала прямо пропорциональна интенсивности света. То есть измеряется не напряжение, а число импульсов в секунду: несколько герц в полной темноте и десятки килогерц рядом с белой поверхностью. Плюс частотного выхода в том, что он устойчив к помехам и подключается к любому цифровому пину Arduino. Если нужно измерить только силу света, проще взять датчик освещённости на фоторезисторе (LDR); TCS3200 делает то же самое отдельно для трёх цветов.
| Характеристика | Значение |
|---|---|
| Микросхема | 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 (плюс ток светодиодов модуля) |
| Управляющие пины | S0, S1 (масштаб частоты), S2, S3 (цветовой фильтр), OE (разрешение выхода, активный LOW) |
| Рекомендуемое расстояние | Около 1–3 см, одинаковое при каждом измерении |
| Подсветка | 4 белых светодиода на модуле |
Пины S0–S3: масштаб частоты и выбор цветового фильтра
Поведение TCS3200 задают четыре управляющих пина. S0 и S1 уменьшают выходную частоту: при 100% импульсы сжимаются до нескольких микросекунд, и точность измерения Arduino Uno оказывается на пределе, а при 2% импульсы становятся длиннее, но за то же время их приходит меньше. S2 и S3 выбирают, какой цветовой фильтр читается. Эти пины подключаются к цифровым выходам Arduino и выставляются из кода перед каждым измерением.
| S0 | S1 | Масштаб выходной частоты | Когда использовать |
|---|---|---|---|
| LOW | LOW | Отключение (power down) | Чтобы усыпить датчик в проектах на батарейках |
| 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 | Без фильтра (clear, общий свет) |
| 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 |
Четыре белых светодиода модуля подсвечивают поверхность одинаково при каждом измерении, поэтому результаты остаются стабильнее, даже когда меняется освещение в комнате. На некоторых платах есть отдельный пин LED для включения и выключения светодиодов, а на других светодиоды загораются сразу при подаче питания; посмотри надписи на своей плате. Для лучшего результата оберни датчик чёрной трубкой или картоном, подноси предмет каждый раз на одно и то же расстояние и избегай блестящих, отражающих поверхностей. Расположение пинов твоей платы можно проверить в утилите распиновки.
Шаг за шагом: чтение R, G и B через pulseIn
- Подключи модуль по таблице выше; OE соедини с GND, а VCC — с 5V.
- Выбери масштаб частоты 20%: S0 в HIGH, S1 в LOW.
- Для красного выставь S2 и S3 в LOW и подожди несколько миллисекунд, пока выход установится.
- Измерь длительность LOW-части импульса в микросекундах через pulseIn(OUT, 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»; самое маленькое число — на красном канале. Таймаут 50 000 µs, переданный в pulseIn, не даёт коду ждать 1 секунду (значение по умолчанию), когда импульса нет; если импульс так и не пойман, функция возвращает 50 000, что означает «очень темно». Если с чтением последовательного вывода есть сложности, загляни в руководство Монитор порта Arduino.
Калибровка по белому и чёрному: переводим сырые значения в 0–255
Сырые значения сами по себе обманчивы: у трёх групп фотодиодов разная чувствительность, а белые светодиоды модуля излучают разные цвета неодинаково. Поэтому даже идеально белый лист даёт на трёх каналах разные числа. Калибровка убирает эти различия и помещает каждый канал в диапазон от 0 (чёрный) до 255 (белый); это та же идея, что и баланс белого в фотоаппарате. Калибруй там, где датчик будет работать на самом деле, при том же освещении и на том же расстоянии: калибровка на столе внутри корпуса машины уже не подойдёт.
- Закрепи датчик в неподвижном держателе и задай расстояние до предмета (например, 1,5 см) так, чтобы оно не менялось.
- Положи перед датчиком обычный белый лист, несколько раз считай R, G и B кодом выше и запиши средние значения как эталон белого; это самые маленькие значения.
- На то же место положи матовый чёрный лист или фетр и запиши показания как эталон чёрного; это самые большие значения.
- Впиши эти шесть чисел в константы WHITE_ и BLACK_ в начале кода ниже и загрузи его в плату.
- Проверь, что на белой бумаге все три канала показывают около 255, а на чёрной — около 0; если нет, повтори измерение при том же освещении и на том же расстоянии.
Осторожно с ошибкой, которая встречается во многих примерах: если переводить длительность импульса прямо в 0–255 через map(), результат будет неверным, ведь свет пропорционален частоте, а не длительности импульса. Правильно сначала перевести длительность в частоту (1/t), а затем масштабировать: уровень = (f − f_чёрн) / (f_бел − f_чёрн) × 255. Разберём пример: на белом получилось 20 µs, на чёрном 200 µs, а предмет дал 40 µs. map() превратит это в 226, а метод через частоту даст 113. Частота предмета ровно в два раза меньше, чем на белом, и после вычитания уровня чёрного это около 44% шкалы, то есть 113; map() же показывает цвета гораздо светлее, чем они есть, и мешает различать тёмные оттенки.
Алгоритм сортировки по цвету: какой это цвет?
Откалиброванные значения R, G, B всё ещё зависят от количества света: если тот же красный предмет немного приблизить к датчику, все три значения вырастут вместе. Чтобы это исправить, делим каждое значение на сумму: r = R / (R + G + B), и так же для g и b. Эти цветовые доли почти не меняются, даже если расстояние и яркость немного сдвигаются. Затем их сравнивают с эталонными долями, один раз измеренными и сохранёнными для каждого цвета, и выбирают ближайшего соседа. Для сравнения достаточно сложить квадраты разностей долей: чем меньше это число, тем больше похожи цвета. Если даже ближайший эталон дальше определённого порога, результат — «неизвестно». Если общего света очень мало, предмет считается чёрным, а если все три канала очень высокие — белым; по долям эти два случая не различить, поэтому их проверяют первыми.
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. Механизм состоит из двух сервоприводов. Верхний сервопривод подачи вращает диск с отверстием ровно под один предмет: сначала забирает предмет из-под трубки бункера, затем подводит его к датчику и после измерения сбрасывает в жёлоб ниже. Нижний сервопривод жёлоба поворачивает наклонный жёлоб и отправляет предмет в контейнер его цвета. Если с сервоприводами работать ещё не приходилось, сначала прочитай статью Что такое сервопривод?.
- Установи бункер и трубку чуть шире предметов, чтобы они падали на диск подачи по одному.
- Закрепи датчик так, чтобы он видел предмет на диске с расстояния 1–2 см, и закрой его со всех сторон от внешнего света.
- Выбери три угла для сервопривода подачи: загрузка, датчик и сброс. Подбери их опытным путём на своём механизме; сервопривод подачи подключи к 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}TCS3200 и ESP32: измеряем частоту через прерывание
На ESP32 работает тот же код с pulseIn, но есть способ стабильнее: вместо того чтобы засекать импульсы по одному, считать, сколько импульсов приходит за фиксированное время. Прерывание увеличивает счётчик на каждом нарастающем фронте; через 50 мс умножь значение счётчика на 20 — и получится частота в герцах. Этот метод усредняет сотни импульсов, поэтому один испорченный импульс на него не влияет. Процессор ESP32 на 240 MHz легко справляется с десятками тысяч прерываний в секунду при масштабе 20%; если при очень ярком свете счёт становится слишком большим, масштаб можно снизить до 2%. Тот же метод работает и на Arduino Uno, если перенести OUT на D2, который поддерживает прерывания. На 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}TCS3200 с Raspberry Pi Pico и MicroPython
На Pico аналог pulseIn из Arduino — функция machine.time_pulse_us(): она возвращает длительность импульса нужного уровня в микросекундах, а при таймауте — отрицательное число. Если вызвать её, когда пин уже находится на этом уровне, она сразу начнёт отсчёт и может измерить половину импульса; поэтому код сначала ждёт окончания HIGH-части, а потом измеряет время LOW. Каждый вызов функции в MicroPython занимает несколько микросекунд, поэтому здесь выбран масштаб 2% (S0=0, S1=1): импульсы становятся в 10 раз длиннее, и влияние этой задержки уменьшается. Калибровочные значения записываются в Hz; при масштабе 2% нормально видеть несколько kHz на белой бумаге и несколько сотен Hz на чёрной. Питай модуль от пина 3V3(OUT) на Pico; если 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 не нужно засекать импульсы. Главное отличие — ИК-фильтр (блокирующий инфракрасный свет) над фотодиодами. У TCS3200 такого фильтра нет; кремниевые фотодиоды чувствительны и к инфракрасному свету, поэтому при освещении, богатом ИК, например солнечном свете или лампах накаливания, показания уплывают. Усиление TCS34725 (1x, 4x, 16x, 60x) и время интегрирования (2,4–614 мс) настраиваются программно, поэтому он работает в широком диапазоне — от очень тусклого до яркого света. Если интересно, как устроена шина I2C, загляни в статью Что такое I2C?.

| Характеристика | TCS3200 | TCS34725 |
|---|---|---|
| Выход | Частота (меандр) | I2C, адрес 0x29 |
| Сигнальные провода | S0–S3, OUT и OE | SDA и SCL (по желанию INT и LED) |
| Разрешение | Зависит от метода и времени измерения | 16 бит на канал |
| ИК-фильтр | Нет | Есть (на микросхеме) |
| Настройки | 3 масштаба частоты и отключение | 4 уровня усиления, время интегрирования 2,4–614 мс |
| Питание | 2,7–5,5 V | Микросхема 3,3 V; модули со стабилизатором 3–5 V |
| Подсветка | Обычно 4 белых светодиода | Обычно 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 через «Менеджер библиотек» (Library Manager); когда IDE спросит о зависимостях, выбери установку всех. На Arduino Uno подключи SDA к A4, а SCL к A5; на ESP32 используй GPIO 21 и 22. getRawData() ждёт окончания времени интегрирования и возвращает сырые значения красного, зелёного, синего и общего света (C); calculateColorTemperature_dn40() даёт цветовую температуру в кельвинах, а calculateLux() — примерный уровень освещённости. Калибровка и логика поиска ближайшего цвета для этого датчика работают точно так же. Если нужен датчик, который кроме цвета распознаёт жесты рукой и приближение, хорошей альтернативой будет APDS-9960.
Идеи проектов с TCS3200
- Машина для сортировки по цвету: Раскладывай конфеты, бусины или детали LEGO по отдельным контейнерам в зависимости от цвета.
- Лампа, повторяющая цвет: Воссоздай цвет предмета, поднесённого к датчику, с помощью RGB-светодиода или ленты WS2812B.
- Робот, читающий цвета: Научи своего робота, следующего по линии, останавливаться у красной метки на полу и ехать дальше у зелёной.
- Робот-манипулятор, распознающий цвета: С помощью робота-манипулятора бери кубики и раскладывай каждый цвет в свой контейнер.
- Говорящий определитель цвета: Озвучивай распознанный цвет заранее записанным звуком через DFPlayer Mini; получится удобный помощник для людей с нарушениями зрения.
- Контроль цвета воды: Следи, как со временем меняется цвет воды в прозрачной ёмкости, и подавай сигнал для аквариума или бака для полива.
Частые ошибки и как их исправить
| Симптом | Вероятная причина | Решение |
|---|---|---|
| pulseIn всегда возвращает 0 или значение таймаута | OE висит в воздухе или на нём HIGH, S0 и S1 оба в LOW (отключение) или OUT не на том пине | Соедини OE с GND, выставь S0 в HIGH, а S1 в LOW, сверь пин OUT с кодом |
| Значения постоянно скачут | Свет в комнате, мерцающая лампа или меняющееся расстояние | Закрой датчик чёрной трубкой, зафиксируй расстояние, усредняй несколько измерений |
| Любой предмет определяется одним и тем же цветом | Нет калибровки; у каналов разная чувствительность | Выполни калибровку по белому и чёрному, заново измерь эталоны |
| При дневном свете цвета уплывают | У TCS3200 нет ИК-фильтра | Измеряй в закрытом корпусе или возьми TCS34725 |
| Уровни залипают на 0 или 255 | Предмет на другом расстоянии, чем при калибровке | Держи то же расстояние, что и при калибровке, при необходимости откалибруй заново |
| ESP32 или Pico ведёт себя странно | Модуль питается от 5 V, OUT выдаёт 5 V | Питай модуль от 3V3 или используй преобразователь уровней |
| Показания сбиваются, когда движется сервопривод | Ток сервопривода просаживает напряжение питания | Питай сервоприводы от отдельного источника 5 V и соедини GND |
TCS3200 — отличный датчик, чтобы разобраться, как свет превращается в частоту, зачем нужна калибровка и как построить простой алгоритм классификации. Перейти на TCS34725, когда понадобится больше точности, тоже легко: изученная калибровка и логика поиска ближайшего цвета переносятся без изменений. В приложении BlueGrays есть проекты «Плавная смена цвета RGB-светодиода», «Смеситель цветов на ESP32», «Измерение освещённости с LDR» и «Работа с сервоприводом» со схемами и пошаговым кодом; объедини с ними датчик цвета и собери собственную машину.
Частые вопросы
С какого расстояния датчик цвета TCS3200 распознаёт цвет?
Самые стабильные результаты получаются примерно на 1–3 см, потому что свет светодиодов модуля быстро слабеет с расстоянием. Важнее самого расстояния — держать его одинаковым при каждом измерении, и калибровать нужно на нём же. Для определения цвета больших поверхностей издалека этот датчик не подходит.
Чем отличаются TCS230 и TCS3200?
TCS230 — более старый датчик цвета того же производителя, а TCS3200 — его улучшенная версия. В обоих матрица фотодиодов 8×8, частотный выход и одинаковая логика управления S0–S3, поэтому подключение и код общие. В большинстве модулей, которые продаются сегодня, стоит TCS3200.
Почему pulseIn возвращает 0 с TCS3200?
pulseIn возвращает 0, когда не видит импульса за заданный таймаут. Самые частые причины: пин OE не соединён с GND, S0 и S1 оба оставлены в LOW (датчик уходит в режим отключения) или OUT подключён не к тому пину, что указан в коде. Если перед датчиком полная темнота, частота падает до нескольких герц, и за короткий таймаут импульс не ловится.
Работает ли TCS3200 с ESP32 и Raspberry Pi Pico?
Да. TCS3200 работает от 2,7–5,5 V, поэтому модуль можно питать от пина 3V3 платы, и тогда пин OUT тоже выдаёт импульсы 3,3 V. Если питать модуль от 5 V, на линию OUT нужен делитель напряжения или преобразователь уровней. При измерениях на MicroPython на Pico точнее работает масштаб частоты 2%.
Что лучше: TCS3200 или TCS34725?
Для точного измерения цвета лучше TCS34725: у него есть ИК-фильтр, разрешение 16 бит на канал и регулируемое усиление, а подключается он всего двумя проводами I2C. TCS3200 — распространённый и вполне достаточный вариант, чтобы освоить измерение частоты и калибровку и собрать простые сортировщики по цвету.
Сколько цветов может различить TCS3200?
При контролируемом освещении и фиксированном расстоянии TCS3200 надёжно различает 6–8 явно разных цветов: красный, оранжевый, жёлтый, зелёный, синий, фиолетовый, чёрный и белый. Различить близкие оттенки, например светло- и тёмно-зелёный, тоже можно, но для этого нужна более тщательная калибровка, много эталонных измерений для каждого оттенка и строгий порог.
Что собрать после прочтения
Все проекты
Измерение освещённости с LDR
Хочешь увидеть яркость своей комнаты в виде числа? LDR — маленькая деталь, реагирующая на свет, — измеряет освещённость вокруг, а Arduino каждые полсекунды выводит значение на экран компьютера. Закрой

Измерение освещённости модулем LDR на ESP32
Наблюдение в мониторе порта в реальном времени за сырым значением ADC модуля LDR и относительным процентом освещённости, вычисленным по опорным значениям темноты и света. Этот процент — не измерение в люксах. Учит логике калибровки модуля. Идеально выполнить его перед проектом 10, потому что он нужен для определения порога.

Работа с RGB-светодиодом
В одном светодиоде прячутся три цвета: RGB-светодиод загорается красным, затем зелёным, затем синим, каждый цвет горит полсекунды и полсекунды не горит, а потом цикл начинается снова. Нужны всего один

Плавная смена цвета RGB-светодиода
Один RGB-светодиод медленно переходит от красного к зелёному, от зелёного к синему и обратно к красному, без остановки прокручивая радугу. Каждый переход длится около двух с половиной секунд, а жёлтый

Смеситель цветов на ESP32 (3 потенциометра + RGB-светодиод + OLED)
Умная лампа для смешивания цветов на ESP32: вращая три потенциометра, можно получить любой цвет! Когда крутишь потенциометры красного, зелёного и синего, RGB-светодиод с общим катодом меняет цвет в реальном времени. OLED-дисплей SSD1306 показывает значения каждого цвета в диапазоне 0–255, полосы заполнения и веб-код цвета HEX (например, #FF8040). Мини-студия цвета, которая учит теории цвета RGB, чтению АЦП, регулировке яркости через PWM и дизайну экрана I2C.

Управление RGB-светодиодом на ESP32
Бесконечное число цветов одним RGB-светодиодом вместо 3 отдельных. Как в Arduino, радужный переход получается заданием значений R/G/B через analogWrite(). Теория цвета (например, красный + зелёный = жёлтый) видна на практике.

Работа с сервоприводом
Рычаг маленького сервопривода по очереди поворачивается в три положения: до упора в начало, ровно на середину и до упора в конец, задерживаясь на каждой остановке на две секунды. Arduino сообщает мото

Определение чёрной и белой поверхности с TCRT5000
С маленьким датчиком, который служит глазом роботов, едущих по линии, можно узнать, чёрный пол или белый. TCRT5000 светит на пол невидимым светом и проверяет, сколько его отражается обратно; на белом
Другие статьи
Приложение BlueGrays
Собери эту схему шаг за шагом в телефоне
Схема подключения, код и список деталей на одном экране. 200+ проектов на Arduino, ESP32 и Pico готовы к сборке.
Наведи камеру телефона
Наведи камеру на код — откроется магазин приложений на телефоне.
или открыть магазин

