Компоненты · 11 мин чтения

Датчик цвета 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: в центре 8-выводная микросхема TCS3200, по четырём углам белые светодиоды для подсветки поверхности, с одной стороны пины S0, S1, OE, GND, с другой — VCC, OUT, S2, S3.

Что такое датчик цвета 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 и выставляются из кода перед каждым измерением.

S0S1Масштаб выходной частотыКогда использовать
LOWLOWОтключение (power down)Чтобы усыпить датчик в проектах на батарейках
LOWHIGH2% (типично до 12 kHz)Медленные процессоры, MicroPython и подсчёт через прерывания
HIGHLOW20% (типично до 120 kHz)Лучший вариант для Arduino и pulseIn
HIGHHIGH100% (типично до 600 kHz)Быстрые измерения с аппаратным счётчиком
S2S3Читаемая группа фотодиодов
LOWLOWКрасный фильтр
LOWHIGHСиний фильтр
HIGHLOWБез фильтра (clear, общий свет)
HIGHHIGHЗелёный фильтр
Связь между длительностью импульса и частотой простая: коэффициент заполнения 50%, значит, LOW-часть длится половину периода. По формуле f = 1 / (2 × t) микросекунды от pulseIn переводятся в частоту. Например, если t = 25 µs, то f = 1 / (2 × 0,000025 с) = 20 kHz. При масштабе 20% и включённых светодиодах модуля нормальны значения от 10 до 200 µs.

Подключение TCS3200: Arduino Uno, ESP32 и Pico

Пин модуляArduino UnoESP32Raspberry Pi Pico
VCC5V3V33V3(OUT), пин 36
GNDGNDGNDGND
S0D4GPIO 32GP2
S1D5GPIO 33GP3
S2D6GPIO 25GP4
S3D7GPIO 26GP5
OUTD8GPIO 27GP6
OEGNDGNDGND
Не оставляй пин OE висящим в воздухе. У этого пина активный уровень LOW: если на нём HIGH или он никуда не подключён, выход переходит в высокоимпедансное состояние, и pulseIn всё время возвращает 0. Кроме того, пины ESP32 и Pico работают от 3,3 V: при питании модуля от 5 V пин OUT выдаёт импульсы 5 V и перегружает вход платы. На этих платах питай модуль от 3V3 или поставь на линию OUT делитель напряжения (рассчитать его поможет утилита делителя напряжения) либо преобразователь логических уровней.

Четыре белых светодиода модуля подсвечивают поверхность одинаково при каждом измерении, поэтому результаты остаются стабильнее, даже когда меняется освещение в комнате. На некоторых платах есть отдельный пин LED для включения и выключения светодиодов, а на других светодиоды загораются сразу при подаче питания; посмотри надписи на своей плате. Для лучшего результата оберни датчик чёрной трубкой или картоном, подноси предмет каждый раз на одно и то же расстояние и избегай блестящих, отражающих поверхностей. Расположение пинов твоей платы можно проверить в утилите распиновки.

Шаг за шагом: чтение R, G и B через pulseIn

  1. Подключи модуль по таблице выше; OE соедини с GND, а VCC — с 5V.
  2. Выбери масштаб частоты 20%: S0 в HIGH, S1 в LOW.
  3. Для красного выставь S2 и S3 в LOW и подожди несколько миллисекунд, пока выход установится.
  4. Измерь длительность LOW-части импульса в микросекундах через pulseIn(OUT, LOW); чтобы уменьшить шум, усредни 10 измерений.
  5. Повтори то же для зелёного (S2 HIGH, S3 HIGH) и синего (S2 LOW, S3 HIGH).
  6. Следи за результатами в мониторе порта на скорости 9600 бод и подноси к датчику листы бумаги разных цветов.
C++ (Arduino IDE)
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. Закрепи датчик в неподвижном держателе и задай расстояние до предмета (например, 1,5 см) так, чтобы оно не менялось.
  2. Положи перед датчиком обычный белый лист, несколько раз считай R, G и B кодом выше и запиши средние значения как эталон белого; это самые маленькие значения.
  3. На то же место положи матовый чёрный лист или фетр и запиши показания как эталон чёрного; это самые большие значения.
  4. Впиши эти шесть чисел в константы WHITE_ и BLACK_ в начале кода ниже и загрузи его в плату.
  5. Проверь, что на белой бумаге все три канала показывают около 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. Эти цветовые доли почти не меняются, даже если расстояние и яркость немного сдвигаются. Затем их сравнивают с эталонными долями, один раз измеренными и сохранёнными для каждого цвета, и выбирают ближайшего соседа. Для сравнения достаточно сложить квадраты разностей долей: чем меньше это число, тем больше похожи цвета. Если даже ближайший эталон дальше определённого порога, результат — «неизвестно». Если общего света очень мало, предмет считается чёрным, а если все три канала очень высокие — белым; по долям эти два случая не различить, поэтому их проверяют первыми.

C++ (Arduino IDE)
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» (неизвестно)
Эталонные значения не угадывай, а измеряй: поднеси предмет каждого цвета к датчику 5–10 раз, усредни доли r, g, b из монитора порта и впиши их в массив refs[]. Измеряй при том освещении и на том расстоянии, где будет работать машина. Если показания скачут, помогает усреднение нескольких измерений или медианный фильтр; подробности — в статье о фильтрации данных датчиков.

Машина, которая сортирует предметы по цвету

Самый популярный проект на TCS3200 — машина, которая раскладывает по цветам цветные конфеты, бусины или детали LEGO. Механизм состоит из двух сервоприводов. Верхний сервопривод подачи вращает диск с отверстием ровно под один предмет: сначала забирает предмет из-под трубки бункера, затем подводит его к датчику и после измерения сбрасывает в жёлоб ниже. Нижний сервопривод жёлоба поворачивает наклонный жёлоб и отправляет предмет в контейнер его цвета. Если с сервоприводами работать ещё не приходилось, сначала прочитай статью Что такое сервопривод?.

  1. Установи бункер и трубку чуть шире предметов, чтобы они падали на диск подачи по одному.
  2. Закрепи датчик так, чтобы он видел предмет на диске с расстояния 1–2 см, и закрой его со всех сторон от внешнего света.
  3. Выбери три угла для сервопривода подачи: загрузка, датчик и сброс. Подбери их опытным путём на своём механизме; сервопривод подачи подключи к D9, а сервопривод жёлоба — к D10.
  4. Назначь сервоприводу жёлоба угол для каждого цветового контейнера (например, красный 30°, зелёный 75°, синий 120°, всё остальное 165°).
  5. Калибруй внутри машины, с её собственной подсветкой, и проверь на нескольких предметах каждого цвета.
C++ (Arduino IDE)
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}
Если питать сервоприводы от пина 5V Arduino, при их движении напряжение может просесть, и плата перезагрузится; эта просадка портит и измерения цвета. Питай оба сервопривода от отдельного источника 5 V и соедини его GND с GND Arduino. Не измеряй, пока сервопривод движется; как в коде, добавь короткую паузу после того, как сервопривод встал на место.

TCS3200 и ESP32: измеряем частоту через прерывание

На ESP32 работает тот же код с pulseIn, но есть способ стабильнее: вместо того чтобы засекать импульсы по одному, считать, сколько импульсов приходит за фиксированное время. Прерывание увеличивает счётчик на каждом нарастающем фронте; через 50 мс умножь значение счётчика на 20 — и получится частота в герцах. Этот метод усредняет сотни импульсов, поэтому один испорченный импульс на него не влияет. Процессор ESP32 на 240 MHz легко справляется с десятками тысяч прерываний в секунду при масштабе 20%; если при очень ярком свете счёт становится слишком большим, масштаб можно снизить до 2%. Тот же метод работает и на Arduino Uno, если перенести OUT на D2, который поддерживает прерывания. На ESP32 не забудь питать модуль от пина 3V3.

C++ (Arduino IDE)
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.

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?.

Модуль датчика цвета TCS34725 с I2C
Модуль датчика цвета TCS34725: в центре микросхема датчика цвета с ИК-фильтром, рядом белый светодиод, пины LED, INT, SDA, SCL, 3V3, GND и VIN.
ХарактеристикаTCS3200TCS34725
ВыходЧастота (меандр)I2C, адрес 0x29
Сигнальные проводаS0–S3, OUT и OESDA и SCL (по желанию INT и LED)
РазрешениеЗависит от метода и времени измерения16 бит на канал
ИК-фильтрНетЕсть (на микросхеме)
Настройки3 масштаба частоты и отключение4 уровня усиления, время интегрирования 2,4–614 мс
Питание2,7–5,5 VМикросхема 3,3 V; модули со стабилизатором 3–5 V
ПодсветкаОбычно 4 белых светодиодаОбычно 1 белый светодиод
Нагрузка на процессорЗанят, пока засекает импульсыИзмеряет микросхема, Arduino только читает
Для чего подходитОбучение, простые сортировщики по цветуТочное измерение цвета, цветовой температуры и освещённости в люксах
C++ (Arduino IDE)
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
Блестящие, отражающие поверхности (металл, глянцевый пластик, глянцевая бумага) отбрасывают свет светодиодов прямо обратно в датчик, и цвета кажутся бледнее, чем есть. Если держать датчик не строго перпендикулярно, а под небольшим углом, или работать с матовыми предметами, проблема заметно уменьшается. Если нужно отличать только чёрное от белого, проще и быстрее взять TCRT5000.

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
#11Новичок

Измерение освещённости с LDR

Хочешь увидеть яркость своей комнаты в виде числа? LDR — маленькая деталь, реагирующая на свет, — измеряет освещённость вокруг, а Arduino каждые полсекунды выводит значение на экран компьютера. Закрой

4.6(81)
139
Измерение освещённости модулем LDR на ESP32
#113Новичок

Измерение освещённости модулем LDR на ESP32

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

4.6(36)
55
Работа с RGB-светодиодом
#39Новичок
Premium

Работа с RGB-светодиодом

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

4.9(73)
98
Плавная смена цвета RGB-светодиода
#40Новичок
Premium

Плавная смена цвета RGB-светодиода

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

4.8(58)
76
Смеситель цветов на ESP32 (3 потенциометра + RGB-светодиод + OLED)
#138Продвинутый
Premium

Смеситель цветов на ESP32 (3 потенциометра + RGB-светодиод + OLED)

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

4.7(6)
10
Управление RGB-светодиодом на ESP32
#111Новичок

Управление RGB-светодиодом на ESP32

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

4.6(29)
51
Работа с сервоприводом
#35Новичок
Premium

Работа с сервоприводом

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

4.7(48)
67
Определение чёрной и белой поверхности с TCRT5000
#78Средний
Premium

Определение чёрной и белой поверхности с TCRT5000

С маленьким датчиком, который служит глазом роботов, едущих по линии, можно узнать, чёрный пол или белый. TCRT5000 светит на пол невидимым светом и проверяет, сколько его отражается обратно; на белом

4.7(40)
49