Colorímetro con TCS230 y arduino
Introducción:
Esta página explica como interconectar un sensor de color de bajo coste y fácil adquisición con un microcontrolador atmega 1280 (arduino), usando además un display de 16X2 para así diseñar un colorímetro de fácil transporte y estudiar su viabilidad, sensibilidad, etc...
El datasheet del sensor se puede encontrar en diferentes páginas como esta.
Un resumen de como funciona el sensor TCS230:
Este sensor convierte el valor de uW/cm^2 en frecuencia en cada sensor que tiene.
Está compuesto por 4 sensores, uno blanco sin filtro, otro azul, verde y uno rojo. Para seleccionar cada color hay que configurarlo mediante 2 patas conectadas a +5V para 1 o a 0V para 0.
También hay una configuración para la frecuencia de respuesta, que va desde 10 hasta 600KHz. A velocidades muy lentas si el color es muy oscuro, puede tardar mucho en realizar un numero grande de lecturas, en cambio si se selecciona una frecuencia muy elevada, es posible que el microcontrolador no pueda procesarlo a tiempo.
Que cálculos debe realizar el microcontrolador:
Para tener una buena precisión, se va a contar 10 veces el ciclo de subida de la frecuencia de salida del sensor, y tomando el tiempo dividido por 10 tendremos el periodo. La inversa es la frecuencia.
Luego para mejorar la precisión se va a tomar 10 veces esa lectura y se hará la media, guardandola como referencia.
Este proceso se repite para el resto de colores (excepto el blanco).
Una vez finalizado, muestra en pantalla que debes cambiar la muestra patrón por el color que quieres comprara y se apreta un botón.
Entonces se repite todos los pasos anteriores pero guardando en otra variable.
Una vez finalizado todo, se hacen los cálculos para mostrar en % con dos decimales, la diferencia de color en cada sensor respecto el patrón o primer color tomado.
Diseño del algoritmo:
Se ha diseñado usando "Grafcet", o sea diagramas paso a paso, de esta forma es mucho más fácil de entender y de explicar el código.
Cada paso realiza unas determinadas funciones hasta que se cumple una condición que hará saltar al paso siguiente, y de esta forma nunca se puede repetir por error algún trozo de código.
Después de tener los pasos definidos, se pasa a código C y se programa usando la plataforma de programación básica de arduino 0022.
Código en C:
/*
LiquidCrystal Library - Hello World
Demonstrates the use a 16x2 LCD display. The LiquidCrystal
library works with all LCD displays that are compatible with the
Hitachi HD44780 driver. There are many of them out there, and you
can usually tell them by the 16-pin interface.
The circuit:
* LCD RS pin to digital pin 8
* LCD Enable pin to digital pin 9
* LCD D4 pin to digital pin 4
* LCD D5 pin to digital pin 5
* LCD D6 pin to digital pin 6
* LCD D7 pin to digital pin 7
* LCD R/W pin to ground
* 10K resistor:
* ends to +5V and ground
* wiper to LCD VO pin (pin 3)
Library originally added 18 Apr 2008
by David A. Mellis
library modified 5 Jul 2009
by Limor Fried (http://www.ladyada.net)
example added 9 Jul 2009
by Tom Igoe
modified 22 Nov 2010
by Tom Igoe
*/
// include the library code:
#include <LiquidCrystal.h>
int akey = 0; // Valor entrada teclado analógico
int key_val[5] ={0, 142, 327, 503, 740 }; //"Right Key OK ", "Up Key OK ", "Down Key OK ", "Left Key OK ", "Select Key OK"
int pinput = 2;
int S2 = 11;
int S3 = 12;
int i=0;
float periodo = 0;
float frecuencia = 0;
float tiempoactual = 0;
int media = 0;
float valorazul = 0;
float valorverde = 0;
float valorrojo = 0;
float valorazul2 = 0;
float valorverde2 = 0;
float valorrojo2 = 0;
int paso = 0;
int s; // Variables para For
int muestra = 10; // Valor para hacer media, mientras más es más preciso, pero tarda mucho más (0 incluido).
int nmuestra = 9; // Numero de ciclos para saber la frecuencia, a más es más fiable, aunque arduino tiene un tiempo mínimo de 4uS u 8uS según modelo (0 incluido).
float azul[11], verde[11], rojo[11]; // IMPORTANTE: Poner el tamaño del array igual al valor de "muestra" + 1
/* Bits de configuración del sensor, se descarga el sensor blanco y se usarán los de diferentes colores
S2 | S3 | Sensor
----------------------
L L Red
L H Blue
H L Clear (no filter
H H Green
*/
// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
void setup() {
// set up the LCD's number of columns and rows:
lcd.begin(16, 2);
pinMode(pinput, INPUT); // Pin entrada frecuencia sensor color
pinMode(S2, OUTPUT); // Pin S2 de configuración
pinMode(S3, OUTPUT); // Pin S3 de configuración
Serial.begin(57600); // Para debug, comunicación serie con PC
Serial.println("Sensor color:");
Serial.println("Blanco | Azul | verde | rojo");
}
void loop() {
if(paso == 0){
lcd.setCursor(0,0);
lcd.print("Colorimetro ->Enter");
lcd.setCursor(0,1);
lcd.print(analogRead(akey));
lcd.print(" ");
if(analogRead(akey)==key_val[4]){
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Capturando color...");
paso++;
delay(200);
}
}
// ---------------------- Para blanco --------------------------
if(paso == 1){
/* --------->> Parte para el sensore blanco
attachInterrupt(0, cuenta, RISING); // Activa la interrupción cuando hay un cambio de estado de subida
if(i>=nmuestra){
i=0;
detachInterrupt(0); // Desactiva la interrupción, mientras hace los cálculos y envia a pantalla
periodo = micros() - tiempoactual; // Periodo en milisegundos
blanco[media] = (nmuestra+1)*1000000 / (periodo);
Serial.println(media);
Serial.print(blanco[media]); Serial.print(" ");
lcd.setCursor(0, 0);
lcd.print(frecuencia); lcd.print(" ");
paso++;
}
*/
paso++; // Este paso se quedó dentro del IF anulado
}
// ------------------------ Para azul ---------------------------
if(paso == 2){
attachInterrupt(0, cuenta, RISING); // Activa la interrupción cuando hay un cambio de estado de subida
if(i>=nmuestra){ // Cuando ha contado "nmuestra" entonces procede a lo siguiente:
i=0; // Reinicia el contador para el próximo color
detachInterrupt(0); // Desactiva la interrupción, mientras hace los cálculos y envia a pantalla
periodo = micros() - tiempoactual; // Periodo en microsegundos que ha tardado en contar los "nmuestra" veces (contando el 0)
azul[media] = (nmuestra+1)*1000000 / (periodo); // Pasa el periodo a frecuencia.
Serial.print(azul[media]); Serial.print(" "); // Imprime por serial para comprobaciones
lcd.print(azul[media]); lcd.print(" "); // Imprime por LCD aunque va muy rápido, se puede omitir
paso++; // incrementa el paso para el siguiente y el actual no se repetirá más.
lcd.setCursor(0, 1); // Se posiciona al inicio del LCD
}
}
// ------------------------ Para verde ---------------------------
if(paso == 3){
attachInterrupt(0, cuenta, RISING); // Activa la interrupción cuando hay un cambio de estado de subida
if(i>=nmuestra){
i=0;
detachInterrupt(0); // Desactiva la interrupción, mientras hace los cálculos y envia a pantalla
periodo = micros() - tiempoactual; // Periodo en microsegundos
verde[media] = (nmuestra+1)*1000000 / (periodo);
Serial.print(verde[media]); Serial.print(" ");
lcd.print(verde[media]); lcd.print(" ");
paso++;
}
}
// ------------------------ Para rojo ---------------------------
if(paso == 4){
attachInterrupt(0, cuenta, RISING); // Activa la interrupción cuando hay un cambio de estado de subida
if(i>=nmuestra){
i=0;
detachInterrupt(0); // Desactiva la interrupción, mientras hace los cálculos y envia a pantalla
periodo = micros() - tiempoactual; // Periodo en microsegundos
rojo[media] = (nmuestra+1)*1000000 / (periodo);
Serial.print(rojo[media]); Serial.println(" ");
lcd.print(rojo[media]); lcd.print(" ");
paso++;
}
}
if(paso == 5){
// Media de 10 tomas de muestras
lcd.clear();
lcd.setCursor(0,1);
lcd.print("muestra ");
lcd.print(media);
if(media >=muestra ){
paso = 6;
}else{
paso = 1;
media++;
}
}
if(paso == 6){
Serial.println("Valores de lectura para hacer media:");
for(s=0;s<media+1;s++){
Serial.println(s);
// valorblanco = valorblanco + blanco[s];
valorazul = valorazul + azul[s];
valorverde = valorverde + verde[s];
valorrojo = valorrojo + rojo[s];
}
// Serial.print("Total valorblanco = ");Serial.println(valorblanco);
// valorblanco = valorblanco / (media+1);
Serial.println("-------- Calculo para comprobación: -----------");
Serial.print("valorblanco = valorblanco / (media+1)");
Serial.print(valorblanco); Serial.print(" = "); Serial.print(valorblanco); Serial.print(" / "); Serial.println(media+1);
Serial.println("-------------------------------------------------");
Serial.print("Total valorazul = ");Serial.println(valorazul);
valorazul = valorazul / (media+1);
Serial.print("Total valorverde = ");Serial.println(valorverde);
valorverde = valorverde / (media+1);
Serial.print("Total valorrojo = ");Serial.println(valorrojo);
valorrojo = valorrojo / (media+1);
Serial.println("Valores de calibración: ");
Serial.print(valorblanco);
Serial.print(" ");
Serial.print(valorazul);
Serial.print(" ");
Serial.print(valorverde);
Serial.print(" ");
Serial.println(valorrojo);
Serial.println("-----------------------------------------------------------");
paso++;
}
if(paso == 7){
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Segunda muestra ->");
lcd.setCursor(0,1);
lcd.print("Presione enter...");
if(analogRead(akey)==key_val[4]){
paso++;
media=0;
Serial.print("Valor media puesta a cero =");Serial.println(media);
}
delay(200);
}
// ------------------------------------ Segunda muestra ---------------------------------------------------------
if(paso == 8){
/*
attachInterrupt(0, cuenta, RISING); // Activa la interrupción cuando hay un cambio de estado de subida
if(i>=nmuestra){
i=0;
detachInterrupt(0); // Desactiva la interrupción, mientras hace los cálculos y envia a pantalla
periodo = micros() - tiempoactual; // Periodo en milisegundos
blanco2[media] = (nmuestra+1)*1000000 / (periodo);
Serial.println(media);
Serial.print(blanco2[media]); Serial.print(" ");
lcd.setCursor(0, 0);
lcd.print(blanco2[media]); lcd.print(" ");
paso++;
}
*/
paso++; // Este paso se quedó dentro del IF anulado
}
// ------------------------ Para azul ---------------------------
if(paso == 9){
attachInterrupt(0, cuenta, RISING); // Activa la interrupción cuando hay un cambio de estado de subida
if(i>=nmuestra){
i=0;
detachInterrupt(0); // Desactiva la interrupción, mientras hace los cálculos y envia a pantalla
periodo = micros() - tiempoactual; // Periodo en milisegundos
azul[media] = (nmuestra+1)*1000000 / (periodo);
Serial.print(azul[media]); Serial.print(" ");
lcd.print(azul[media]); lcd.print(" ");
paso++;
lcd.setCursor(0, 1);
}
}
// ------------------------ Para verde ---------------------------
if(paso == 10){
attachInterrupt(0, cuenta, RISING); // Activa la interrupción cuando hay un cambio de estado de subida
if(i>=nmuestra){
i=0;
detachInterrupt(0); // Desactiva la interrupción, mientras hace los cálculos y envia a pantalla
periodo = micros() - tiempoactual; // Periodo en milisegundos
verde[media] = (nmuestra+1)*1000000 / (periodo);
Serial.print(verde[media]); Serial.print(" ");
lcd.print(verde[media]); lcd.print(" ");
paso++;
}
}
// ------------------------ Para rojo ---------------------------
if(paso == 11){
attachInterrupt(0, cuenta, RISING); // Activa la interrupción cuando hay un cambio de estado de subida
if(i>=nmuestra){
i=0;
detachInterrupt(0); // Desactiva la interrupción, mientras hace los cálculos y envia a pantalla
periodo = micros() - tiempoactual; // Periodo en milisegundos
rojo[media] = (nmuestra+1)*1000000 / (periodo);
Serial.print(rojo[media]); Serial.println(" ");
lcd.print(rojo[media]); lcd.print(" ");
paso++;
}
}
if(paso == 12){
// Media de 10 tomas de muestras
lcd.clear();
lcd.setCursor(0,1);
lcd.print("muestra ");
lcd.print(media);
if(media >=muestra ){
paso = 13;
}else{
paso = 8;
media++;
}
}
if(paso == 13){
for(s=0;s<media+1;s++){
Serial.println(s);
// valorblanco2 = valorblanco2 + blanco2[s];
valorazul2 = valorazul2 + azul[s];
valorverde2 = valorverde2 + verde[s];
valorrojo2 = valorrojo2 + rojo[s];
}
Serial.print("Valor media final , comprobar para operación correcta: "); Serial.println(media);
valorblanco2 = valorblanco2 / (media+1);
Serial.println("-------- Calculo para comprobación: -----------");
Serial.print("valorblanco2 = valorblanco2 / (media+1)");
Serial.print(valorblanco); Serial.print(" = "); Serial.print(valorblanco); Serial.print(" / "); Serial.println(media+1);
Serial.println("-------------------------------------------------");
valorazul2 = valorazul2 / (media+1);
valorverde2 = valorverde2 / (media+1);
valorrojo2 = valorrojo2 / (media+1);
Serial.println("Valores de resultado: ");
Serial.print(valorblanco2);
Serial.print(" ");
Serial.print(valorazul2);
Serial.print(" ");
Serial.print(valorverde2);
Serial.print(" ");
Serial.println(valorrojo2);
Serial.println("---------------------------");
Serial.println("Diferencia en % respecto calibracion:");
Serial.print("Blanco: ");
Serial.println((valorblanco2 / valorblanco)*100);
Serial.print("Azul: ");
Serial.println((valorazul2 / valorazul)*100);
Serial.print("Verde: ");
Serial.println((valorverde2 / valorverde)*100);
Serial.print("Rojo: ");
Serial.println((valorrojo2 / valorrojo)*100);
lcd.clear();
lcd.setCursor(0,0);
lcd.print("B,G,R:");
lcd.print((valorazul2 / valorazul)*100);
lcd.setCursor(0,1);
lcd.print((valorverde2 / valorverde)*100);
lcd.print(" ");
lcd.print((valorrojo2 / valorrojo)*100);
paso++;
}
if(paso == 14){
switch(analogRead(akey)>1000){
//No hace nada hasta que apreta el botón
delay(100);
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Press Enter 2a mu");
lcd.setCursor(0,1);
lcd.print("abajo para reiniciar");
}
if(analogRead(akey)==key_val[4]){ // Si presiona enter, vuelve a ver el segundo color para comparar igual con el primero. Pone a 0 los valores del segundo color
paso=8;
valorblanco2=0;
valorazul2=0;
valorverde2=0;
valorrojo2=0;
media= 0;
}else if(analogRead(akey)==key_val[2]){ // Si presiona abajo, reinicia contadores a 0 y empieza de 0
paso = 0;
valorblanco=0;
valorazul=0;
valorverde=0;
valorrojo=0;
valorblanco2=0;
valorazul2=0;
valorverde2=0;
valorrojo2=0;
media = 0;
}
}
}
void cuenta(){ // Función configuración de los bits para seleccionar los 4 sensores, va asociado al paso
if(i==0){
switch(paso){
case 1: // Para blanco
digitalWrite(S2, HIGH);
digitalWrite(S3, LOW);
break;
case 2: // Para azúl
digitalWrite(S2,LOW);
digitalWrite(S3,HIGH);
break;
case 3: // Para verde
digitalWrite(S2,HIGH);
digitalWrite(S3,HIGH);
break;
case 4: // Para rojo
digitalWrite(S2, LOW);
digitalWrite(S3, LOW);
break;
case 8: // Para blanco
digitalWrite(S2, HIGH);
digitalWrite(S3, LOW);
break;
case 9: // Para azúl
digitalWrite(S2,LOW);
digitalWrite(S3,HIGH);
break;
case 10: // Para verde
digitalWrite(S2,HIGH);
digitalWrite(S3,HIGH);
break;
case 11: // Para rojo
digitalWrite(S2, LOW);
digitalWrite(S3, LOW);
break;
}
tiempoactual=micros(); // Guarda el inicio del contador de tiempo de microsegundos.
}
i++;
}
El código es algo largo, se puede resumir quitando lineas de depuración por serie.
Para descargar el código, lo teneis en este enlace.
Pruebas de funcionamiento:
El sensor con este código tiene una variabilidada aproximada de +/- 0.5% por color.
Para capturar colores sobre pinturas satinadas, el propio reflejo de la luz que va perpendicular al área del sensor, hace que capte mucha luz por reflejo directo y eso hace perder resolución en las lecturas.
Para capturar el color hay que hacerle un soporte para que siempre haya la misma distancia desde el sensor hasta el color y así se puede comprarar correctamente los colores. Pero el sensor viene sin soporte y hay que modificarlo.
Una vista del montaje a falta de modificar la iluminación y hacerle un soporte o caja de transporte.

Conclusiones y mejoras:
El sensor tiene buena precisión y se puede mejorar más ampliando el tamaño de las muestras para realizar la media.
El problema del reflejo de la propia fuente de luz es un punto imprescindible por mejorar, pero se precisa modificar la placa o añadiendo una fuente de luz externa con un ángulo suficiente para que no haya reflejo directo.
El otro problema de la variabilidad por diferente intensidad de luz (sea por acercar o alejar la muestra) se puede apreciar hasta en la misma muestra de color (tomada dos veces), por lo que la siguiente mejora será sobre la suma del total de colores, tomar la proporción en % y mostrarla en pantalla. De esta forma aunque haya más o menos intensidad de luz, la proporción se debería conservar y reducir los errores.
Tiempo invertido:
Inicio del proyecto 03-11-2011 y finaliza hasta este código el 29-11-2011.
Cabe decir que por el trabajo y la Universidad no he podido dedicarle mucho tiempo, la suma de horas invertidas ha sido de unas 6 en total.
Personalmente:
Un colorímetro es un aparato bastante caro, y los baratos pueden no tener resolución suficiente para según que muestras de color.
El coste de la placa Arduino ha sido inferior a 25 euros, y el sensor de 10 euros. Un precio ridículo en comparación al coste de uno comercial (más de 1500 euros, hasta 3000 euros), el único secreto puede ser el código que lleve implementado en el microcontrolador y la presentación hacia el público, además de las rigurosas normas para trabajar en ambientes industriales.
Realizando este proyecto, además de poder ayudar a cualquiera que trabaje con pinturas (planchista, pintores, artistas, etc..) me ha permitido aprender más sobre interconexión de sensores a la plataforma de hardware libre como Arduino.
También pretendo ayudar a mi padre Alfonso Torres Toral que trabaja en una planchistería Torsol S.L. de Figueres y usando un colorímetro que puede cuantificar el color, se puede mejorar los procesos de pintura, aunque por la experiencia que tiene no le es imprescindible.
Dudas y comentarios a alfonatr@hotmail.com
Volver al index |