Датчик света и цвета TCS3200 + Arduino Mega = что-то получается

Возникала у меня, по служебной необходимости, потребность в измерении цвета полупрозрачных предметов. И не просто на глазок, а с конкретными циферками, пусть и не ложащимися в метрологические сертификаты. Готовые промышленные приборы стоят каких-то очень смешных денег, пока не понимаешь, что ценник указан в иностранной валюте, да и гарантии нет никакой, что они будут измерять то, что нужно. Бытовые колориметры, например, для калибровки мониторов, тоже не панацея. Ведь они для калибровки, а не для измерения. И неизвестно каким образом они будут вообще выдавать результаты.

TCS3200, colormeter, измеритель цвета, в корпусе, дисплей

Окончательный вид измерителя цвета на основе TCS3200 в корпусе.

Совершенно случайно, на глаза попался плата для Arduino с чипом TCS3200. Ознакомившись с описанием, я понял, что сенсор, установленный на плате — выполняет как раз ту самую работу, которую и необходимо автоматизировать. Сам чип TCS3200 производится некой компанией AMS, которая в спецификации на сенсор называется TAOS. Но не суть, суть, что компания занимается, в основном, разработкой как раз таких вот недорогих решений по «колометрии на чипе».

TCS3200, сенсор, color, sensor, проводники, микросхема, плата

Сенсор TCS3200 крупным планом. Если приглядеться, то видны не только тончайшие проводники но и отдельные группы фотодиодов.

Чип TCS3200 состоит из 64 фотодиодов, 16 из которых не покрыты цветными фильтрами, а остальные затенены по 16 штук с применением синего, зеленого и красного светофильтров. Что это значит? Это значит, что чип способен измерить отдельно каждый из RGB-цветов, плюс замерить общий световой поток. Помимо TCS3200 в аналогичных платах может встречаться чип немного попроще — TCS3210. У TCS3210, в отличии от TCS3200, всего 24 фотодиода, соответственно на каждый из цветов, включая просто белый свет, приходится по 6 отдельных фотодиодов. Другими словами, чувствительность и точность измерений у TCS3200 должна быть выше, чем у его собрата TCS3210.

Изначально, плату с TCS3200 для Arduino начала изготавливать DFRobot. Но позже эстафету подхватили и другие производители. Как раз в моем случае производитель оказался настолько скромный, что на плате разместил только свой логотип, который, лично мне мало о чем говорит. Итак, конструктивно плата с чипом содержит стабилизатор напряжения, благодаря чему плата может работать как с системами с напряжением в 5 В, так и с менее прожорливыми 3.3 вольтовыми платами. По краям платы выведено четыре белых светодиода, которые при подключении освещают исследуемый объект, а чип измеряет его цвет в отраженном свете. В условиях, когда мне необходимо проводить измерения на просвет — светодиоды подсветки мне ни к чему, поэтому я их даже и не стал подключать (подключается подсветка путем подключения вывода LED на плюс питания), а позже и вообще отпаял от платы.

Некоторые пользователи сообщают о негативном опыте работы с платой. По их мнению, точность измерения цвета — пальцем в небо. Однако, данная проблема возникает, в том числе, из-за засветки сенсора светодиодами подсветки. Если вам требуется подсветка, то есть вы хотите проводить измерения в отраженном свете, то следует установить на плату, вокруг сенсора, барьер в виде непрозрачной трубки, который будет защищать сенсор от фоновой засветки светодиодами подсветки.

Идем далее. Помимо питания, плата с сенсором подключается к Arduino посредством еще пяти выводов: S0, S1, S2, S3 и Out. Выводы… Хотя начнем с того, каким образом сенсор передает информацию микроконтроллеру. Для обмена данными в сторону Arduino предусмотрен всего один выход Out. На нем формируется сигнал квадратной формы с заполнением 50%. Чем больше света попадает на фотодиоды, тем выше частота передаваемого сигнала. Частоты начинаются от 10 кГц и заканчиваются где-то за 600 кГц. Поскольку с такой тактовкой справится далеко не каждый из микроконтроллеров, то производитель предусмотрел понижающие коэффициенты для частоты на выходе Out, которые задаются через входы S0 и S1. Если на оба входа подать низкое напряжение, то устройство перейдет в режим энергосбережения и никаких измерений производиться не будет. Тем не менее, доступны режимы масштабирования частоты (в сторону уменьшения):

таблица, мастшабирование, S0, S1, датчик, свет, цвет

Комбинации высокого и низкого напряжения на входах S0 и S1 и результирующие значения коэффициента масштабирования.

Коэффициент масштабирования определяет сколько импульсов будет усреднено на стороне сенсора, прежде чем будет дан усреднённый частотный импульс на выход Out. Поскольку Arduino не является системой реального времени, то следует применять коэффициент масштабирования с размерностью 2%. В этом случае при достаточно длительном периоде измерения можно быть уверенным, что данные с сенсора не пропадут.

TCS3200, плата, датчик, выводы, LED, OUT

Плата сенсора TCS3200

Каким же образом производитель предлагает проводить измерения частоты? Во-первых, для каждого типа фотодиодов сенсора, измерение проводятся по отдельности. Во-вторых, для измерения под Arduino, доступно, по факту, всего два варианта: измерение длительности импульса и подсчет количества импульсов за определенный период времени. Измерение длительности импульса работает очень быстро, особенно при высокой освещенности. Для измерения достаточно поймать всего один импульс, чтобы рассчитать общую частоту (тут не следует забывать, что заполнение высоким уровнем в импульсе идет всего 50%). При измерении через количество импульсов производится подсчет количества импульсов за определенный период времени. И чем дольше этот период, тем точнее будет измерение. Чуть позже пройдемся по способам измерения немного подробнее, а пока перейдем к другому оборудованию.

TCS3200, плата, датчик, сенсор, pass

Обратная сторона платы TCS3200 с логотипом производителя

Поскольку мой, создаваемый прибор, должен работать стационарно, без компьютера, то информацию он должен выводить на дисплей. В качестве устройства вывода, я традиционно выбрал 20х4 дисплей на HD44780 совместимом чипсете. Arduino отлично работает с подобными устройствами при помощи стандартной библиотеки LiquidCrystal. Для подключения я, как и рекомендуется, использовал только половину из пинов шины данных. На скорость подобный усеченный вариант подключения практически не влияет, а шлейф проводов заметно меньше.

Помимо дисплея с пользователем взаимодействует RGB-светодиод, пять тактовых кнопок и пьезоэлектрический динамик. В результате количество пинов для подключения всего навесного оборудования вышло за пределы допустимого для Arduino Uno и пришлось взять Arduino Mega R3 на чипе 2560. Не спорю, что можно было пойти путем самурая и вообще повесить все кнопки с резисторами на один аналоговый пин, но я сенсей, а не самурай.

arduino, плата, mega, мега, power, analog-in

Arduino Mega. Made in Italy. Nature Product.

Arduino Mega — штука замечательная, на ней разведено столько портов ввода-вывода, что заполнить их очень не просто, даже если как следует постараться. Раньше у меня не было опыта работы с оригинальным Arduino Mega и после краткого знакомства могу сказать, что плата очень и очень удачная. В комплекте вместе с платой идет еще и пластиковая подставка, позволяющая не ловить «коротыши» дном платы, и несколько наклеек. Особенно разуют наклейки. Из минусов могу назвать традиционный разъем USB Type B и непомерную стоимость. Аналоги Mega от китайских товарищей куда более доступны. Что же, перейдем к выводам платы сенсора.

таблица, S2, S3, цвет, датчик цвета, сенсор, входы

Комбинации высокого и низкого напряжений на входах S2 и S3, соответствующие типы фотодиодов.

Выбор, какая из сборок фотодиодов будет передавать свою частоту, осуществляется через подачу высокого или низкого напряжений на выводы S2 и S3 согласно таблице выше. Перед проведением измерений необходимо установить соответствующий коэффициент масштабирования, затем переключиться на нужный фотодиод. И можно проводить измерения.

Ребята из DFRobot предлагают использовать следующий код, основанный на использовании прерывания:

int s0 = 3, s1 = 4, s2 = 5, s3 = 6;
int out = 2;
int flag = 0;
byte counter = 0;
byte countR = 0, countG = 0, countB = 0;
void setup()
{
  Serial.begin(115200);
  pinMode(s0, OUTPUT);
  pinMode(s1, OUTPUT);
  pinMode(s2, OUTPUT);
  pinMode(s3, OUTPUT);
}
void TCS()
{
  flag = 0;
  digitalWrite(s1, HIGH);
  digitalWrite(s0, HIGH);
  digitalWrite(s2, LOW);
  digitalWrite(s3, LOW);
  attachInterrupt(0, ISR_INTO, LOW);
  timer0_init();
}
void ISR_INTO()
{
  counter++;
}
void timer0_init(void)
{
  TCCR2A = 0x00;
  TCCR2B = 0x07; //the clock frequency source 1024 points
  TCNT2 = 100;   //10 ms overflow again
  TIMSK2 = 0x01; //allow interrupt
}
int i = 0;
ISR(TIMER2_OVF_vect)//the timer 2, 10ms interrupt overflow again. Internal overflow interrupt executive function
{
  TCNT2 = 100;
  flag++;
  if (flag == 1)
  {
    countR = counter;
    Serial.print("red=");
    Serial.println(countR, DEC);
    digitalWrite(s2, HIGH);
    digitalWrite(s3, HIGH);
  }
  else if (flag == 2)
  {
    countG = counter;
    Serial.print("green=");
    Serial.println(countG, DEC);
    digitalWrite(s2, LOW);
    digitalWrite(s3, HIGH);
  }
  else if (flag == 3)
  {
    countB = counter;
    Serial.print("blue=");
    Serial.println(countB, DEC);
    Serial.println("\n");
    digitalWrite(s2, LOW);
    digitalWrite(s3, LOW);
  }
  else if (flag == 4)
  {
    flag = 0;
  }
  counter = 0;
}
void loop()
{
  TCS();
  while (1);
}

Однако, по мнению некоторых исследователей, использование прерывания, особенно при высокой освещенности, может приводить к серьезным искажением результатам (при том, что сам сенсор дает погрешность всего 0,2% при частоте 50 кГц). Дескать прерывания дергают кому только не лень, а тут будут пропускаться импульсы. Вообще, код, приведенный выше, написан так безобразно, словно его писал какой-то житель Индии, а не квалифицированный программист. Нет в нем красоты, а только хуки, про которые сам потом забудешь через полтора года. Поэтому, пользователи на форуме Амперки переписал этот ужас в нечто более удобоваримое:

int s0 = 3, s1 = 4, s2 = 8, s3 = 9;
volatile int counter = 0;
int cR = 0, cG = 0, cB = 0;
void setup() {
  // put your setup code here, to run once:
  pinMode(s0, OUTPUT);
  pinMode(s1, OUTPUT);
  pinMode(s2, OUTPUT);
  pinMode(s3, OUTPUT);
  digitalWrite(s1, HIGH);
  digitalWrite(s0, HIGH);
  Serial.begin(9600);
  attachInterrupt(0, INT_0, CHANGE);
}
void loop() {
  readColor();
  Serial.print("red=");
  Serial.println(cR, DEC);
  Serial.print("green=");
  Serial.println(cG, DEC);
  Serial.print("blue=");
  Serial.println(cB, DEC);
  Serial.println(" ");
  delay(1000);
}
void readColor () {
  digitalWrite(s2, LOW);  // вкл красный фильтр
  digitalWrite(s3, LOW);
  cR = delay10();
  digitalWrite(s2, HIGH); // вкл зеленый фильтр
  digitalWrite(s3, HIGH);
  cG = delay10();
  digitalWrite(s2, LOW);  // вкл синий фильтр
  digitalWrite(s3, HIGH);
  cB = delay10();
}
int delay10 () {
  counter = 0;
  delay(20);
  return counter;
}
void INT_0()
{
  counter++;
}

Смысл тут ровно тот же, только написано красивее. Вместо прерываний, кстати, если наловчиться, можно использовать обычный DigitalRead. Альтернативным же методом считывания частоты получается еще интереснее, нежели через прерывания:

int getModule(int a, int b) {
  if (a > b) {
    return a - b;
  } else {
    return b - a;
  }
}

int getColorValue(int iVal) {
  // iVal: 0-red, 1-green, 2-blue, 3-white
  RunningMedian rmFrequency = RunningMedian(measures);
  switch (iVal) {
    case 0:
      digitalWrite(S2, LOW);
      digitalWrite(S3, LOW);
      break;
    case 1:
      digitalWrite(S2, HIGH);
      digitalWrite(S3, HIGH);
      break;
    case 2:
      digitalWrite(S2, LOW);
      digitalWrite(S3, HIGH);
      break;
    case 3:
      digitalWrite(S2, HIGH);
      digitalWrite(S3, LOW);
      break;
  }
  for (int i = 0; i <= measures; i++) {
    rmFrequency.add(pulseIn(sensorOut, LOW));
  }
  return rmFrequency.getAverage();
}

void mesOfColors(int iType) {
  // iType: 0 - calibration; 1 - measurment
  int lRed;
  int lGreen;
  int lBlue;
  int lWhite;
  switch (iType) {
    case 0:
      Serial.print("Calibration: ");
      Serial.print("R = ");
      Serial.print(cRed);
      Serial.print(" G = ");
      Serial.print(cGreen);
      Serial.print(" B = ");
      Serial.print(cBlue);
      Serial.print(" White = ");
      Serial.println(cWhite);
      break;
    case 1:
      Serial.print("Measurment: ");
      break;
    default:
      Serial.print("SomeShit: ");
  }
  switch (iType) {
    case 0:
      cRed = getColorValue(0);
      cGreen = getColorValue(1);
      cBlue = getColorValue(2);
      cWhite = getColorValue(3);
      break;
    case 1:
      lRed = getColorValue(0);
      lGreen = getColorValue(1);
      lBlue = getColorValue(2);
      lWhite = getColorValue(3);
      break;
  }
  Serial.print("R = ");
  Serial.print(cRed);
  Serial.print(" G = ");
  Serial.print(cGreen);
  Serial.print(" B = ");
  Serial.print(cBlue);
  Serial.print(" White = ");
  Serial.println(cWhite);
}

Этот код уже мой. Он базируется на таком интересном методе, доступном в Arduino, как pulseIn. Данный метод вычисляет длительность импульса на указанном пине. Помимо прочего можно выбрать какой тип сигнала ожидать, высокий или низкий. А в качестве опционального параметра можно задать значение таймаута, вдруг никакого импульса не будет вовсе.

Мой код далеко не оптимален и тут есть куда еще стремиться в плане уменьшения. Функция mesOfColors выполняет два типа измерений: калибровочное и простое. Калибровочное можно использовать как опорное значение, когда требуется сравнить текущее измерение, с тем, что было произведено ранее. Для повышения точности результата я провожу несколько измерений, значение задается глобальной переменной measures. А для хранения и усреднения полученных значений я применяю объект RunningMedian одноименной библиотеки. Объект оказался очень удобным и значительно сокращает время написания кода. Рекомендую поподробнее ознакомиться с данным классом.

Мой вариант подсчета через pulseIn работает, но имеет и существенный недостаток. При недостаточном освещении и при большом количестве измерений сигнала сам процесс измерений может занимать несколько секунд. Исправить ситуацию тут можно проводя предварительный анализ освещенности и если он очень низкий, то просто постараться изменить коэффициент масштабирования частоты, либо сократить количество итераций измерений.

Прошу обратить внимания, что ни один из вышеприведенных кодов не выдает настоящие значения RGB или же освещенность в люксах или же светимость объекта в люменах. Кстати, а ведь освещенность можно рассчитать, ведь известны размеры фотодиодов, площадь самого сенсора. Мой код выдает лишь усредненную длительность импульса в микросекундах, а основанные на прерываниях — количество импульсов за установленную единицу времени. Судя по записям в «интернетах», многие пользователи просто не понимают какие же значения они получают с сенсора. Доходит до того, что они рекомендуют не использовать TCS3200, а взять что-то более интересное, например, такое. А самые «продвинутые» предлагают воспользоваться сенсором от AdaFruit на чипе TCS34725 все от той же китайской компании. Преимуществом решения от AdaFruit можно считать использование инфракрасного фильтра на сенсоре и применение протокола передачи I2C. Действительно, электронные светочувствительные ячейки подвержены засветке ИК. В качественной фото и видеотехнике в обязательном порядке применяется фильтр, отсекающий инфракрасный диапазон. А применение I2C позволяет избежать мучений с подсчетом импульсов или с определением их длительности. Но, судя по примерам, что выложены на сайте AdaFruit они тоже не разобрались с системой измерения применяемой в китайской компании, так как значения цвета у них получилось под 14 тысяч Кельвинов. Это уже даже не знаю, что за диапазон такой, но явно не видимый человеческим глазом. Да и фотодиодов у TCS34725 значительно меньше, чем у TCS3200.

скриншот, ада, фрут, адафрут, датчик, цвет, опеределние

Скриншот сайта AdaFruit со скриншотом вывода данных с их сенсора. 14 тысяч кельвинов? 12 тысяч люкс?

Глядя на все это безобразие, пользователь с форума «Умный дом» решил пойти и ушел куда дальше. Он собрал измеритель цвета и освещенности используя сразу два датчика, применил более точный расчет, с приведением к данным из спецификации, при расчетах же используется корректировка по цветам на основе замеров фотодиодов без фильтра. Код у него получился такой:

#include "arhat.h"
#define tcsOut    47  // T5in   => выход обоих датчиков out 
#define tcsS2     44  // T5outC => управление цветом обоих датчиков 
#define tcsS3     45  // T5outB => управление цветом обоих датчиков 
#define tcsOE1    46  // T5outA => !OE for TCS3200 1-й датчик 
#define tcsOE2    48  // T5icp  => !OE for TCS3200 2-й датчик 
#define tcsTimer   5
#define TCS_MAX_MEASURES 2          // [1,2,4,8,..] для автозамены деления сдвигами 
//#define TCS_WAIT         8          // мсек на разовый подсчет частоты. Итого fmin=125hz (1.5лк), замер 4 цветов = 31.25Гц
#define TCS_WAIT         4          // мсек на разовый подсчет частоты. Итого fmin=250hz (4лк), замер 4 цветов = 62.5Гц 
//#define TCS_WAIT        16          // мсек на разовый подсчет частоты. Итого fmin=62.5hz (0.9лк), замер 4 цветов = 15.6Гц
//#define TCS_WAIT        32          // мсек на разовый подсчет частоты. Итого fmin=31.25hz (0.4лк), замер 4 цветов = 7.8Гц
#define TCS_MAX_WAIT    4*TCS_WAIT  // время, к которому приводим подсчитанные величины для повышения качества в темноте 
#define TCS_CONTRAST     8          // делитель для повышения контраста цвета (1/2,1/4,1/8,1/16..) 
#define TCS_WHITE    0       // порядок цветов в рез. массиве 
#define TCS_GREEN    1
#define TCS_RED      2
#define TCS_BLUE     3
#define TCS_NOCOLOR  4    // замер завершен, данные действительны. 
#define TCS_START    5      // начать новый замер. 
int      tcsColors[4];       // итоговые и усредненные данные замера
int      tcsTemp[4];         // внутреннее хранение данных при усреднениях и замерах
uint16_t tcsCount;           // номер завершенного замера
uint8_t  tcsColor;           // текущий измеряемый цвет в попытке
uint8_t  tcsMeasure;         // номер текущей усредняемой попытки замера
uint8_t  tcsWait;            // текущая длительность одного замера частоты от датчика
uint8_t  tcsIsBright;        // Пересвет?
/**
   Настройка таймера и выводов на работу с датчиком
*/
void tcsSetup()
{
  // обе ноги разрешения датчиков на выход и запрет работы датчикам
  pinModeOut(tcsOE1);
  pinModeOut(tcsOE2);
  pinOutHigh(tcsOE1);
  pinOutHigh(tcsOE2);
  timerControl(tcsTimer, A) = 0;  // нормальный режим счета, счет по спадающесу(6),нарастающему(7) фронту на входе T5
  timerControl(tcsTimer, B) = 6;  // нормальный режим счета, все выходы счетчика отключены.
  pinModeIn(tcsOut);              // счет импульсов счетчиком T5
  // ноги управления цветом на выход и режим s2=1,s3=0 -- анализ белого
  pinModeOut(tcsS2);
  pinModeOut(tcsS3);
  pinOut(tcsS2, HIGH);
  pinOut(tcsS3, LOW);
  tcsWait = TCS_WAIT;
  // дополнение: контроль перезагрузок из-за сбоев питания:
  // в случае авто-перезагрузки моргаем светодиодом и ждем 2сек ничего не делая:
  pinModeOut(pinLed);
  delay(1000);
  for (uint8_t i = 5; i > 0; i--) {
    pinOutHigh(pinLed);
    delay(150);
    pinOutLow(pinLed);
    delay(300);
  }
  delay(1000);
}
/**
   настройка заданного датчика на чтение данных о цвете с задержками по 1мксек (даташит)
*/
void tcsPrepare(uint8_t num)
{
  switch (num) {
    case 1:
      pinOut(tcsOE2, HIGH);
      delayMicro8(5);
      pinOut(tcsOE1, LOW);
      break;
    case 2:
      pinOut(tcsOE1, HIGH);
      delayMicro8(5);
      pinOut(tcsOE2, LOW);
      break;
  }
  delayMicro8(5);
  tcsColor = TCS_START;
}
/**
   переключение датчика на текущий цвет и сброс таймера для нового замера
*/
void tcsNextColor() {
  uint8_t color = tcsColor + 2; // изменяем порядок цветов: первым опрашиваем общий канал!
  if ( color & 2 ) {
    pinOutHigh(tcsS2);
  } else {
    pinOutLow(tcsS2);
  }
  if ( color & 1 ) {
    pinOutHigh(tcsS3);
  } else {
    pinOutLow(tcsS3);
  }
  timerCount(tcsTimer) = 0;
}
/**
   попытка замера цвета датчиком
*/
void tcsRun(uint8_t num)
{
  if ( tcsColor == TCS_NOCOLOR ) return; // не запущен замер/останов, выход.
  if ( tcsColor == TCS_START ) {
    // начало очередного замера
    tcsColor = 0;
    tcsMeasure = 0;
    for (uint8_t c = 0; c < 4; c++) tcsTemp = 0; } else { // замер: приводим текущий замер к эфф. времени uint16_t counter = timerCount(tcsTimer); tcsTemp[tcsColor] += counter * (TCS_MAX_WAIT / tcsWait); if ( tcsColor == 0 ) { // первым замеряли яркостный канал: смотрим надо ли менять время замеров (удерживаем точность 0.5%): tcsIsBright = 0; if ( counter > 590 * tcsWait ) {
        tcsIsBright = 1;  // Очень ярко! Частота выше 600кГц !!!
      }
      else if ( counter < 150 ) {
        tcsWait = TCS_WAIT * 4;  // совсем темно, макс. длительность.
      }
      else if ( counter < 300 ) { tcsWait = TCS_WAIT * 2; // темновато, удвоенная длительность. } else if ( tcsWait > 1 ) {
        tcsWait /= 2;  // пробуем сократить время замера
      }
    }
    if ( (++tcsColor) == TCS_NOCOLOR ) {
      if ( tcsMeasure >= TCS_MAX_MEASURES - 1 ) {
        // Завершены все попытки: восстанавливаем баланс белого (R=0.18%,G=0.41%,B=-0.05%) средней обратной матрицей в разложении по 1/2..
        int      minVal, maxVal;
        int      tg = tcsTemp[1] / 2, tr = tcsTemp[2] / 2, tb = tcsTemp[3] / 2;
        tcsColors[0] = tcsTemp[0];
        tcsColors[1] = tcsTemp[1] + tg;
        tcsColors[2] = tcsTemp[2];
        tcsColors[3] = tcsTemp[3] + tb;
        tg >>= 1; tr >>= 1; tb >>= 1;   // 1/4
        tcsColors[1] -= (tb + tb + tb);
        tcsColors[2] -= tg;
        tcsColors[3] -= tg;
        tg >>= 1; tr >>= 1; tb >>= 1;   // 1/8
        tcsColors[1] += tg;
        tcsColors[3] -= tr;
        tg >>= 1; tr >>= 1; tb >>= 1;   // 1/16
        tcsColors[2] += tr - tg;
        tcsColors[3] -= tg - tr;
        tg >>= 1; tb >>= 1;             // 1/32
        tcsColors[1] += tg;
        tcsColors[3] += tb - tg;
        tcsColors[2] += tb + tg / 4;    // +1/128tg (0.18%), иначе большая ошибка в красном (1.02%)!
        // , усредняем и масштабируем частоту в люксы и сохраняем:
        tg = tcsColors[0] / TCS_MAX_MEASURES;
        tcsColors[0] = tg + tg / 4;
        minVal = 32767; maxVal = 0;
        tg = tcsColors[1] / TCS_MAX_MEASURES;
        if ( minVal > tg ) minVal = tg;
        if ( maxVal < tg ) maxVal = tg; tr = tcsColors[2] / TCS_MAX_MEASURES; if ( minVal > tr ) minVal = tr;
        if ( maxVal < tr ) maxVal = tr; tb = tcsColors[3] / TCS_MAX_MEASURES; if ( minVal > tb ) minVal = tb;
        if ( maxVal < tb ) maxVal = tb;
        // вычисляем степень повышения контраста:
        maxVal -= minVal;
        if ( maxVal < minVal ) maxVal = TCS_CONTRAST; else if ( maxVal > 2 * minVal ) maxVal = TCS_CONTRAST * 4;
        else                         maxVal = TCS_CONTRAST * 2;
        // , коррекция баланса и повышение контрастности цветов:
        tg = tg + tg   - tg / 8  + tg / maxVal - minVal / maxVal;
        tr = tr + tr / 2 + tr / 32 + tr / maxVal - minVal / maxVal;
        tb = tb - tb / 8 - tb / 16 + tb / maxVal - minVal / maxVal;
        // убираем отрицательные значения из-за ошибок округления:
        tcsColors[1] = (tg > 0 ? tg : 0);
        tcsColors[2] = (tr > 0 ? tr : 0);
        tcsColors[3] = (tb > 0 ? tb : 0);
        // конец работы, новый номер замера.
        tcsCount++;
        return;
      } else {
        tcsMeasure++;
        tcsColor = 0;
      }
    }
  }
  tcsNextColor();
}
void setup() {
  Serial.begin(115200);
  tcsSetup();
  tcsPrepare(1);
}
void loop() {
  everyOVF(tcsWait, tcsRun(1) );
  if ( tcsColor == TCS_NOCOLOR ) {
    // замер завершен.
    Serial.print(", count=");
    Serial.print(tcsCount, DEC);
    Serial.print(", W=");
    Serial.print(tcsColors[TCS_WHITE], DEC);
    Serial.print(", R=");
    Serial.print(tcsColors[TCS_RED], DEC);
    Serial.print(", G=");
    Serial.print(tcsColors[TCS_GREEN], DEC);
    Serial.print(", B=");
    Serial.print(tcsColors[TCS_BLUE], DEC);
    Serial.print(", time=");
    Serial.print(tcsWait, DEC);
    Serial.print(", isBright? ");
    Serial.print(tcsIsBright, DEC);
    Serial.println("");
    tcsColor = TCS_START;
  }
}

Код достаточно интересный, но на практике в лоб его применить нельзя. Дело не только в том, что автор использовал два сенсора сразу, а по большей части в том, что у него применена авторская же библиотека, заменяющая некоторые функции Wire. Несмотря на все мои старания, мне так и не удалось запустить данный код, поскольку компилироваться авторская библиотека отказалась напрочь, а перелопачивать код обратно на Wire мне не очень хотелось. Тем не менее, автор заявляет, что у него получилось достичь достаточного уровня точности. Хотелось бы в это верить, тем более, что точность измерений немного зависит от температуры сенсора, освещенности, а также от напряжения питания, да и как проверить точность результатов без другого калиброванного прибора за смешные деньги?

А что дальше? Как из частот получить RGB-значения, хотя бы отдаленно напоминающие реальные? К сожалению, спецификация к сенсору не дает четкого ответа на этот вопрос. Вполне в духе китайских товарищей, которые внедрили в PDF-документ графики, но получить точные значения из них нельзя, даже если распечатать на А3 и промерить с линейкой. Значит, нужно идти каким-то другим путем.

Первое, что приходит в голову, так это банальное приведение значений получаемой частоты с сенсора к значениям RGB через функцию map. Измеряются значения при максимальной и минимальной засветках. Затем все это приводится к стандартным 8-ми битным градациям. Немного усложнить и уточнить схему можно при наличии другого прибора, при помощи которого можно откалибровать сенсор TCS3200 немного точнее. Например, если измерить значения минимума и максимума, сравнить их с реальными показаниями, то результат будет куда вернее.

#define MIN_RED 5000.0
#define MAX_RED 16400.0
#define MIN_GREEN 7000.0
#define MAX_GREEN 11000.0
#define MIN_BLUE 6000.0
#define MAX_BLUE 10000.0
Output_Color = (255.0/(MAX_RED-MIN_RED))*(FreqColor-MIN_RED); //MAPEAR FUNCIÓN
Output_Color = (255.0/(MAX_GREEN-MIN_GREEN))*(FreqColor-MIN_GREEN);  //MAPEAR FUNCIÓN
Output_Color = (255.0/(MAX_RED-MIN_BLUE))*(FreqColor-MIN_BLUE);  //MAPEAR FUNCIÓN
//Constrain Value to MaxRange
if (Output_Color > 255) Output_Color = 255;
if (Output_Color < 0) Output_Color = 0;

Вот такой вариант конвертации в RGB предлагает пользователь GitHub. Насколько точны первоначальные значения, забитые в коде, остается только догадываться. Да и нет ли ошибки при конвертации синего цвета? Вообще, если покопаться, то на GitHub-е можно найти множество различных попыток конвертации частоты в числовые значения. Интересный код предлагает другой пользователь GitHub Frank Milburn:

// constants - adjust if getting poor readings
const int Rc = 100;                      // Clear Relative Responsivity 
const int Rr = 99;                       // Red Relative Responsivity 
const int Rg = 65;                       // Green Relative Responsivity 
const int Rb = 70;                       // Blue Relative Responsivity
// some cuts are here
  int cPulse = pulseIn(outPin, LOW);     // read clear
  int Ac = cPulse * Rc;
  int rPulse = pulseIn(outPin, LOW);     // read red    
  int Ar = rPulse * Rr;                  // adjust reading for responsiveness
  int Cr = Ar - Ac;                      // correct for clear reading
  int gPulse = pulseIn(outPin, LOW);     // read green
  int Ag = gPulse * Rg;                  // adjust reading for responsiveness                  
  int Cg = Ag - Ac;                      // correct for clear reading
  int bPulse = pulseIn(outPin, LOW);     // read blue
  int Ab = bPulse * Rb;                  // adjust reading for responsiveness
  int Cb = Ab - Ac;    
//-------------- Find relative order of colors and scale to 255 ------------------
//-------------- Algorithym is empirical                        ------------------
  int r;
  int g;
  int b;
  if (Cr < Cg && Cg < Cb)                //   RGB is color order
  {
    r = 255;
    g = 128 * Cr / Cg;
    b = 16 * Cr / Cb;
  }
  else if (Cr < Cb && Cb < Cg)           //   RBG is color order
  {
    r = 255;
    b = 128 * Cr / Cb;
    g = 16 * Cr / Cg;
  }
  else if (Cg < Cr && Cr < Cb)           //   GRB is color order
  {
    g = 255;
    r = 128 * Cg / Cr;
    b = 16 * Cg / Cb;
  }
  else if (Cg < Cb && Cb < Cr)           //   GBR is color order
  {
    g = 255;
    b = 128 * Cg / Cb;
    r = 16 * Cg / Cr;
  }  
  else if (Cb < Cr && Cr < Cg)           //   BRG is color order
  {
    b = 255;
    r = 128 * Cb / Cr;
    g = 16 * Cb / Cg;
  }  
  else // (Cb < Cg && Cg < Cr)           //   BGR is color order
  {
    b = 255;
    g = 128 * Cb / Cg;
    r = 32 * Cb / Cr;
  }  
  if (r < 0) r = 0;
  if (g < 0) g = 0;
  if (b < 0) b = 0;

Алгоритм Фрэнка базируется на предположении различной чувствительности цветных сенсоров, о чем заявляется, в очень неявной форме и в спецификации на сенсор. Самая низкая чувствительность у голубого сенсора, а за сто процентов принимается значение фотодиодов без фильтров вообще. Следует учитывать, что в алгоритме, помимо всего прочего, проводится коррекция считанной частоты по отношению к частоте фотодиодов без фильтров.

Интересный вариант получения значений частоты нашел у пользователей на оригинальном форуме Arduino:

int lastDigitalRead = HIGH;
    for(int j=0; j<20000; j++)
    {
      int digitalReadValue = digitalRead(OUT);
      if (lastDigitalRead == LOW && digitalReadValue == HIGH) 
      {
        count++;
      }
      counter++;
      lastDigitalRead = digitalReadValue;
    }
    scaleFactor[i] = 255.0/count;

Совершенно без заморочек в коде считывается количество символов тем самым методом DigitalRead. А затем происходит примитивная конвертация к значениям RGB. Но в данном коде, помимо упрощенной системы конвертации, не используется подсчет времени на 20000 итераций цикла, а без него можно лишь только очень приблизительно понять частоту, да и то, такое возможно лишь на простых системах, типа Arduino Uno, где нет никаких существенных фоновых процессов, а на чем-то более продвинутом, например, ESP32 точность показаний будет плавать в ощутимых пределах.

Очень интересным, в плане получения значений с сенсора оказался вариант использования от MajicDesign. Подсчет импульсов ведется при помощи сторонней библиотеки FreqCount. А библиотека не простая. Подсчет импульсов ведется строго на определенном порту. Почему сделано так? Потому что не все порты Arduino одинаково полезны. Но особое внимание следует уделить тому, как происходит процесс вычисления значений RGB. Автор предполагает, что зависимость линейная и стоит предварительно откалибровать минимальные и максимальные значения частоты:

void MD_TCS230::RGBTransformation(void)
// Exploiting linear relationship to remap the range 
{
    int32_t    x;
    for (uint8_t i=0; i<RGB_SIZE; i++)
    {
        x = (_Fo.value[i] - _Fd.value[i]) * 255;
        x /= (_Fw.value[i] - _Fd.value[i]);
        // copy results back into the global structures
        if (x < 0) _rgb.value[i] = 0;         else if (x > 255) _rgb.value[i] = 255;
        else _rgb.value[i] = x;
    }
}

Актуальный вариант трансформации частоты в RGB, а ниже еще один, более сложный, но дающий результат примерно такой же, как и актуальный:

void RGBTransformation(void)
// complex transformation that does not seem to be 
// much better than the simpler one above
{
    float v;            // the results
    float inRGB[3];        // values read
    const float    T[3][3] = { // transformation matrix
    { 0.7659,  0.7052, -0.4990 },
    {-0.3140,  1.3283, -0.1367 },
    { 0.0609, -0.4739,  1.0326 }};
    // set up the inRGB array with the values read, adjusted by the 
    // calibration parameters
    for (uint8_t i=0; i<RGB_SIZE; i++)
    {
        inRGB[i] = (float)_Fo.value[i] - (float)_Fd.value[i];
        inRGB[i] /= (((float)_Fw.value[i] - (float)_Fd.value[i]) / 255.0);
        DUMP("\ninRGB[", i); 
        DUMP("] = ", inRGB[i]);
    }
    // Carry out the matrix multiplication
    // [outRGB] = [T] . [inRGB]
    for (uint8_t i = 0; i < 3; i++)
    {
        v = 0.0;
        for (uint8_t j = 0; j < 3; j++)
        {
            v += T[i][j] * inRGB[j];
        }
        DUMP("\nTransformed result for ", (char)SR[i].type);
        DUMP(" = ", v);
        // copy results back into the global structures
        if (v < 0.0) _rgb.value[i] = 0;         else if (v > 255.0) _rgb.value[i] = 255;
        else _rgb.value[i] = trunc(v + 0.5);
    }
}

Как видим, в сложном варианте присутствует некая матрица трансформации. И в обоих вариантах происходит «подрезка» значений RGB при пересвете или же в слишком темном окружении. Что, в общем-то не так-то уж и плохо. В коде MajicDesign основные параметры объявляются так:

sensorData    _Fd;        // Dark calibration data
sensorData    _Fw;        // White balance calibration data
sensorData    _Fo;        // current raw sensor reading
_Fd.value[i] = 6000L;    // just typical numbers read by a sensor to ensure...
_Fw.value[i] = 55000L;    // ... something sensible is returned with no calibration

И здесь тоже получается, что калибровочные значения забиваются некими значениями по умолчанию, хотя и их можно подкорректировать по результатам конкретных калибровочных измерений.

И что же делать? Очевидно, что получить точные значения цветов без качественной калибровки весьма затруднительно. Тут я имею ввиду, что получить конкретные значения в модели RGB, которые будут «биться» с реальностью, не очень-то и просто. Но мне это и не требуется. Моя задача получить цвет, то есть соотношение красного, зеленого и синего, а не конкретные их значения с учетом яркости. При желании их тоже можно получить, проведя экстраполяцию с использованием показаний сенсора без цветных фильтров. Получится эдакое псевдо LAB-пространство, где цвет и яркость хранятся по отдельности.

измерение, результат, RGB, датчик, сенсор, измерение, определение, цевт, RGB

Значения цветных фотодиодов и фотодиодов без фильтров на выборке из десяти значений, стандартное отклонение и отклонение от среднего выраженное в процентах. Измерения через pulseIn.

Дополнительно я провел сравнительные испытания двух методов измерения частоты: через pulseIn (в таблице вверху) и через прерывания (таблица ниже).

interrupt, table, color, sensor, rgb

Значения цветных фотодиодов и фотодиодов без фильтров на выборке из десяти значений, стандартное отклонение и отклонение от среднего выраженное в процентах. Измерение через прерывания.

Как видно из сравнения, метод подсчета импульсов оказался менее точным. Разброс в показаниях у него достигает аж семи процентов. А вот различия в показаниях при использовании метода проверки через прерывания показали расхождение результатов только для определения частоты импульсов для фотодиодов без фильтров. Но и тут колебания от среднего составляют менее процента. По этой причине метод определения при помощи прерываний признаем более совершенным. Кстати вот он:

volatile unsigned long intCount;
long getColorValueInterupt(){
  intCount=0;
  attachInterrupt(2, INT_2, FALLING);
  delay(measuresInt);
  detachInterrupt(2);
  return (intCount*(1000/measuresInt));
}

void INT_2(){
  intCount++;
}

Я использую прерывание на пине 21 Arduino Mega и сразу же вычисляю частоту в герцах (с учетом коэффициента масштабирования). Суть метода тривиальна: регистрируем функцию INT_2() в качестве функции, которая будет вызываться в случае прерывания, в ней просто увеличиваем счетчик intCount на единицу. Длительность подсчета задаем через delay(measuresInt). Во время приостановки программы через delay будут обрабатываться прерывания, которые и будут увеличивать значение переменной. Все очень просто. Впрочем, но на этом приключения с сенсором не заканчиваются. Идем дальше.

TCS3200, таблица, значения, показания, чувствительность

Таблица из спецификации на сенсор TCS3200 с типовыми значениями показаний различных фотодиодов.

Многие из пользователей фапают пристально изучают таблицу из спецификации, что я привел выше. Она немного больше, чем на картинке, но для целей использования датчика достаточно только приведенных выше значений. Именно по этой таблице можно откалибровать сенсор так, чтобы он показывал очень точные и очень реальные значения. Но обо все по порядку. Данные в таблице приведены для отключенного, т.е. работающего на 100% мощности, коэффициента частоты, напряжения 5 В и комнатной температуре. Другими словами — идеальные условия.

В первой группе, для fo указаны значения частоты фотодиодов без фильтра и с тремя фильтрами при облучении их светом со строго определенной длинной волны и мощностью. Всего дано три варианта: 470nm для синего света, 524nm зеленого и 640nm красного. Именно такие частоты обычно присутствуют у цветных светодиодов, более того, спектральные диаграммы цветных полупроводниковых светодиодов обычно узенькие и всё их излучение сконцентрировано в нешироком диапазоне длин волн, как раз на указанных пиках. Помимо длинны волны для каждого из типов указывается и мощность светового потока попадающего на фотодиоды сенсора. Мощность указана в микроваттах на квадратный сантиметр. Если изловчиться, посчитать площадь белых фотодиодов, мощность обычного светодиода (а там уже милливатты), расстояния между светодиодом и сенсором, то можно откалибровать показания фотодиодов без фильтров (для желающих, в примечании даны точные параметры светодиодов для калибровки). В соответствующей колонке даны минимальные, типовые и максимальные значения частоты в кГц (со 100% частотой, разумеется). И если у вашего конкретного сенсора будет расхождение, то можно ввести математическое устранение данной погрешности. Кстати, в скобках тут приведены значения для микросхемы TCS3210, у нее меньше фотодиодов, соответственно меньше и частота, существенно меньше.

Если откалибровать показания сенсора для фотодиодов без цветных фильтров, то одновременно можно провести калибровку и цветных фотодиодов. В таблице даны лишь минимальная и максимальная границы для каждого из цветов. Границы указаны в процентах частоты по отношению к частоте фотодиодов без фильтров. Разброс значений составляет порядка 20%. Что уже не мало, но задача калибровщика — провести калибровку и понять, сколько конкретно в процентах от «белого» показывают цветные светодиоды. В последствии, результаты так же можно будет корректировать на полученные значения.

Вторая группа значений, маркируется Re и именуется чувствительностью к облучению. Величина измеряется в Гц/см2 и обратно пропорциональна fo. Опять же значение приводится для фотодиодов без фильтров, а цветные даны в процентах к частоте «белого» света.

TCS3200, таблица, чувствительность, свет, длина волны, цвет

Диаграмма из спецификации на TCS3200 в отношении частотного диапазона и чувствительности различных типов фотодиодов.

Внимательные исследователи обратят внимание на то, что цветные светодиоды показывают определенные проценты при облучении другими цветами. Если посмотреть на диаграмму чувствительности, то отчётливо видно, что у синего и зеленого светодиодов есть практически идентичные пики, и достаточно большие, за пределами границ видимого света. Красный фотодиод имеет наибольший диапазон и интегральную чувствительность. Он так же чувствителен в ИК, но и более чувствителен, чем зеленый и синий, к видимому свету соответствующих диапазонов. На отметке, примерно в 900 нм, уже в области инфракрасного, невидимого глазу теплового излучения, линии всех четырех типов фотодиодов — совпадают. Из этого можно сделать вывод, что красный фотодиод более чувствительный, а все фотодиоды очень сильно подвержены засветке ИК. Чувствительность красного светодиода хорошо подтверждается при измерении темноты, он дает 100% излучения всех цветных фотодиодов. Ну и конечно, нужно устанавливать дополнительный фильтр ИК, без него рассчитывать на нормальные результаты не стоит. Любой нагретый предмет будет давать соответствующую засветку.

Что же сделать, чтобы получить более адекватные результаты от сенсора? Подводя итоги, хочется отметить, что в настоящий момент у меня всего несколько вариантов того, как можно улучшить показания собранного прибора:

  • Поставить ИК-фильтр, т.е. стеклышко не пропускающее инфракрасный диапазон. Без него результаты получаются чумачечие. И математической калибровкой их улучшить будет весьма проблематично. ИК-фильтр, как я уже упоминал выше, можно раздобыть сняв его с матрицы цифрового фотоаппарата. По размеру он как раз подойдет, чтобы покрыть весь сенсор. Еще одним источником ИК-фильтра могут служить китайские видеокамеры наблюдения с автоматическим режимом день/ночь. Там такой фильтр механическим путем закрывает матрицу, когда света достаточно. В конце концов его можно купить. При покупке следует учитывать, что под ИК-фильтрами подразумевается два вида фильтров: те, которые не пропускают видимый свет и пропускают ИК, и соответственно те, которые пропускают видимый свет и не пропускают ИК. Нужные вторые.
  • Откалибровать показания фотодиодов, пользуясь методикой с цветными фотодиодами, описанной выше. Да, выполнить все условия для калибровки — тяжело. Но, а что делать?
  • Проводить калибровку по источнику подсветки. Каждый источник света имеет свою температуру, свой спектр, свое соотношение красного, зеленого и синего цветов. По сути, модель RGB как раз и подразумевает излучение на каждой из длин волн, благодаря которой можно получить, в том числе, и белый свет. Перед измерениями, прибор калибруется на источнике света. С учетом полученных пропорций на цветных светодиодах, можно проводить коррекцию полученных измерений при последующих измерениях.
  • Добавить отсечку по пересвету и недосвету. При очень ярком источнике света фотодиоды начинают давать идентичную картинку, т.е. соотношения у всех фотодиодов становятся идентичными. При очень низком, наоборот, красный забивает показания всех остальных.

В целом, я могу отметить, что, несмотря на цирк с генерацией и подсчетом частоты, а также «слепой» спецификации, сенсор TCS3200 вполне можно использовать в качестве измерительного прибора. Точность и погрешность у него не прецизионны, особенно с учетом применения Arduino, но в целом к использованию продукт годен и весьма.

Upd.: В процессе доработки измерителя цвета я установил инфракрасный фильтр от камеры видеонаблюдения. Данный фильтр встроен в корпус для объектива камеры и имеет привод, позволяющий включать его или отключать. Привод отлично работает как от 3.3 вольта, так и от 5 вольт. Включение и отключение фильтра осуществляется путем подачи напряжения на его контакты, меняя полярность соответственно меняется и функция. Для Arduino Mega я подключил выводы непосредственно к двум цифровым пинам, на один из которых и подаю HIGH длительностью в 100 мс. Если требуется переключить его состояние, то импульс HIGH подается уже на другой пин.

IR, ИК, фильтр, filter, камера, наблюдение, IP-Cam, surveliance

Блок инфракрасного фильтра камеры видеонаблюдения

Наличие инфракрасного фильтра дает существенный прирост в точности определения цветов. Так, по белому листу бумаги без фильтра сенсор возвращается процентные соотношения RGB%: 54-24-20, а с фильтром уже RGB%: 46-32-21. Из значений видно, что доля составляющая для красного цвета уменьшилась, ведь данный тип фотодиодов имеет большую интегральную чувствительность в плане инфракрасного диапазона.

IR, ИК, TCS3200, TSC3200, фильтр, filter, крепление, датчик, цвет, сенсор

Плата сенсора TCS3200 отлично подошла к блоку ИК-фильтра от камеры наблюдения. Отверстия крепления совпали идеально и плата села как родная.

Для получения еще большей точности, я ввел калибровку по источнику света. При калибровке вычисляются процентные значения всех типов цветных фотодиодов, затем вычисляется среднее значение из суммы показаний всех трех типов цветных фотосенсоров и рассчитывается корректировочный процент для каждого из типов. Допустим, при калибровке у нас RGB%: 54-24-20. Среднее значение составит (54+24+20)/3=32. Вычисляем поправочные коэффициенты для каждого из типов сенсоров: Ra%=54-32=22, Ga%=24-32=-8, Ba%=20-32=-12. При последующем измерении вычитаем из полученного значения поправочный коэффициент. Таким образом, если после калибровку в тех же условиях провести измерение, то результат будет RGBa%: 33-33-33. Другими словами, калибровка позволяет настроить баланс белого прибора на конкретный источник света. Приведу пример измерения при проведенной выше калибровке. Получаем с сенсора RGB%: 50-29-20, после применения коэффициентов результат будет уже RGBa%: 28-37-32.

Следует обратить внимание, что саму калибровку следует проводить на источнике света с которым в дальнейшем будут производиться измерения. Если поменять источник или использовать отраженный свет от этого источника, то следует проводить повторную калибровку. Если же требуется проводить калибровку для отраженного света, то в качестве мишени для измерения следует взять серый или белый лист бумаги или иного матового материала.

Небольшое замечание по измерению в процентах. Как я уже упоминал выше RGB% это соотношение значений каждого из цветов. Сумма этих соотношений не обязана быть всегда 100%. Из-за погрешностей и округлений она обычно колеблется в районе 98-99%. При желании можно делать нормализацию RGB% для получения суммы в 100% всегда.

Для обеспечения еще немного более точных показаний пришлось ввести ограничения на очень яркий свет и на его недостаточность. Основная отсечка осуществляется по значению для фотодиодов без фильтров. При показателе масштабирования 2% максимальная частота у меня задана на уровне 100.000 циклов, что составляет величину порядка 20.000 люкс. Минимум дан на отсечке в 20 циклов, что примерно соответствует значению около 10 люкс. Дополнительно проверяются значения и с цветных фотодиодов, если все три типа фотодиодов вернули 0 (ноль циклов), значит света слишком мало.



Датчик света и цвета TCS3200 + Arduino Mega = что-то получается: Один комментарий

  1. Arhat109

    Случайно нашел Ваш обзор, пояснения:

    1. "сторонная" библиотека нормально компилируется и запускается на ИДЕ версии 1.6.4. В старших версиях разработчики добавили правки, исключающие подстановку таких библаотек (нельзя заменить файл Arduino.h).

    2. Приведенный код - это пример работы с БИБЛИОТЕКОЙ с этим сенсором, в которой можно использовать ДО двух сенсоров одновременно. Замерять можно каждым по отдельности, и в самых разных режимах: "датчик освещенности", "датчик цвета", "колориметр" и т.д. там все описано. В примере, кстати, использован ОДИН датчик, а не два.

    3. В этой библиотеке как раз применена специально пересчитанная обратная матрица цветности согласно приведенной таблице из даташита. Матрица реализована на целочисленной арифметике (восстановление баланса белого) для ускорения получения результатов, точность которых составляет около 1.5%.

    4. Там же есть блок восстановления белого по особенностям светодиодов освещения, которые не совсем "белые" на самом деле.

    5. Метод замера - аналог pulseIn() но на 5-м таймере Ардуино Мега аппаратно (режим захвата таймера). Он такой вообще один у Ардуино Мег, в отличии от нормально распаянного кристалла, поэтому там применены только "эти" ноги Ардуино.

    Спасибо за попытку "понять код", вышло забавно.

    С уважением, автор.

Добавить комментарий