El sensor de color TCS3414, el TCS3404, como en general toda la familia TCS330 y TCS341, para las que es de aplicación buena parte de lo expuesto en este texto, están formados por una matriz de fotodiodos con filtros de color que permiten detectar los componentes rojo, azul y verde de la luz así como un nivel neutro (si filtro, o blanco) con una resolución de 16 bits. Su explotación es muy sencilla al comunicarse con el microcontrolador mediante el bus I2C.
Aunque siempre es conveniente promediar los resultados, al disponer de cuatro fotodiodos para cada color, es aceptable realizar un única medición del color, mucho más rápido, para obtener un valor admisible. El TCS3414 puede configurarse para utilizar diferentes tiempos de integración (que resultan en diferentes tiempos de lectura) lo que permite ganar en precisión así como apreciar valores intermedios en condiciones de poca iluminación.
La compañía AMS dispone de sensores de color más avanzados pero, con el TCS3414, consiguió una gran popularidad por su equilibrio entre eficacia y sencillez de implementación que posteriormente se consolidó por su precio, haciendo que hasta hoy sea un sensor de color presente en aplicaciones muy diferentes, aunque el dispositivo cabecera de la serie actual, el TCS34000, disponga de nuevas prestaciones, como las activación-desactivación del filtro infrarrojo o la disposición circular de sus fotodiodos permita medir la luz desde cualquier ángulo.
El comportamiento del TCS3414 ante las diferentes longitudes de onda es compatible con la visión humana. La principal diferencia entre su sensibilidad y la de los conos y bastones de nuestra retina es su respuesta en buena parte de la zona infrarroja del espectro; para resolver esto utiliza un filtro infrarrojo, para ser más precisos, un filtro a la luz que empieza entorno a una longitud de onda de 650 nm (la visión humana abarca aproximadamente desde los 400 nm hasta los 700 nm).
El segundo inconveniente puede ser el desfase entre la sensibilidad de los filtros del TCS3414 y los diferentes fotorreceptores del ojo humano. En la siguiente imagen puede verse la distribución normalizada (como porcentaje) del TCS3414 (líneas) frente a la visión humana (áreas rellenas). Como puede verse, el pico y la distribución del filtro verde es equiparable al del fotorreceptor (cono) del color verde. La sensibilidad que resulta del filtro azul está ubicada y distribuida en un rango de longitud de onda similar a la del fotorreceptor del azul aunque es más elevada; sin embargo, esto no representa un problema debido a las peculiaridades de interpretación, seguramente del procesado, de los conos del azul. El inconveniente que es necesario vigilar con más cuidado deriva de la notable diferencia entre la sensibilidad al rojo del TCS3414 y de la visión humana.
Tanto por la sensibilidad diferencial, como por lo que las condiciones concretas de aplicación del sensor pueden producir en las medidas, conviene hacer una serie de mediciones de prueba para una posterior corrección de los valores (calibración) al uso que se le pretenda dar. Esta especie de calibrado, del que se habla más adelante desde el punto de vista del código, permite, además relativizar las medidas, conocer los rangos en los que se encuentran en el contexto de iluminación en el que funciona el dispositivo que se diseña y con el medio objetivo de la medida.
Como el encapsulado del TCS3414 es un poco incómodo de incorporar a un circuito para hacer pruebas, una opción muy práctica, también por precio, es utilizar módulos que lo incorporen y permitan integrarlo en medidas de patillas DIP. Otra ventaja de utilizar módulos es que añaden los componentes pasivos y en algunas ocasiones, como es el caso del que puede verse en la fotografía de abajo, otros componentes que simplifican su uso, como la alimentación a 5 V (además de la original a 3,3 V), iluminación controlada para las pruebas o una carcasa para evitar iluminación exterior.
La implementación a nivel de hardware del TCS3414 es muy sencilla, solamente necesita las resistencias pull-up del bus I2C. Si se decide utilizar la señal de interrupción también es necesario disponer una resistencia pull-up en la línea correspondiente.
La dirección del sensor de color TCS3414 en el bus I2C es fija, no hay patillas con las que modificarla, así que, salvo que se utilice hardware extra, siempre es 0x39
.
Aunque el TCS3414 dispone de un condensador interno para filtrar interferencias, en condiciones de ruido más desfavorables también se puede incorporar un condensador de 100 nF e incluso otro más de 10 µF. Para ser más versátiles, en la mayoría de los módulos suelen incorporarse ya que es sencillo a la vez que da seguridad de un funcionamiento más estable.
El TCS3414 permite optar entre un sincronismo interno o un sincronismo externo, que se conecta a la patilla SYNC (rotulada SYS en el módulo de la imagen de arriba), para sincronizar el ciclo de integración.
La alimentación nominal del sensor de color TCS3414 es de 3V3, aunque puede funcionar en un rango de alimentación comprendido entre 2V7 y 3V6.
Al diseñar el dispositivo que usará un sensor de color TCS3414 hay que tener en cuenta que puede no solo detecta la luz que incide perpendicularmente sino que es capaz de detectar un 45 % en un ángulo de 60°, un 70 % a 40° y el 90 % a 30°. La detección no es igual en todas las partes del sensor ya que los fotodiodos están dispuestos en una matriz de 8×2, estas cifras corresponden a la dirección del dispositivo que contiene más fotodiodos, en la perpendicular la distribución creciente es más suave y hasta llegar a 30° entre un 5 % y un 10 % menor.
Como el sensor TCS3414 mide la luz que incide sobre él, debe controlarse la que refleja o atraviesa (cuando sea transparente) el objeto o el medio cuyo color se trata de monitorizar. Es práctico disponer algún tipo de cierre que aísle al sensor de la luz ambiental permitiendo solamente que le llegue la luz que atraviesa o refleja el objeto medido.
Software para controlar el TCS3414 por I2C
La explotación del TCS3414 se realiza accediendo a sus registros de operación por medio del bus I2C. Estos accesos pueden realizarse en cualquier situación, por ejemplo (antes) para configurarlo o para enviar una orden, o después de un aviso del dispositivo de que se alcanza determinado umbral máximo o mínimo generando una interrupción.
La trama de comunicaciones I2C con el sensor TCS3414 están compuestas por un bit de inicio, siete bits con la dirección (de esclavo) del dispositivo, un bit de escritura, un bit de confirmación (acknowledge) enviado por el TCS3414 como respuesta, un byte de datos, otro bit de confirmación (acknowledge) desde el dispositivo y un bit de parada. Para hacer más sencillo el proceso, en los ejemplos se utiliza la librería Wire de Arduino para comunicaciones I2C que permite realizar las comunicaciones de manera transparente.
Para distinguir que el dato enviado al TCS3414 es una orden se precede de un bit MSB (bit más significativo) a nivel alto. Las once operaciones que el sensor TCS3414 es capaz de ejecutar se codifican con los últimos cuatro bits LSB (bits menos significativos) y el quinto bit da acceso a los registros en los que se almacenan los valores medidos por los fotodiodos con los diferentes filtros.
Antes de ver en detalle los diferentes registros de operación del TCS3414, el siguiente ejemplo mínimo permite repasar el proceso de comunicación con el dispositivo para solicitar un dato. En este caso se lee el identificador de un sensor TCS34XX para saber de qué tipo de dispositivo se trata. El byte del identificador está formado por un bit de familia (el último del nibble más significativo) y un valor de revisión el los cuatro bits menos significativos.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
// Lectura del identificador del TCS34 como ejemplo mínimo de acceso a sus registros #define DIRECCION_TCS34 0x39 // Dirección del sensor TCS34 en el bus I2C #define ENVIAR_ORDEN 0B10000000 // Bit de indicación de operación (orden). Como alternativa más rápida se puede utilizar en todas órdenes pero se separa para aclarar mejor el funcionamiento) #define LEER_IDENTIFICADOR 0x04 // Operación de lectura del identificador (versión del integrado) #define FIN_FAMILIA_TCS340X 0B00001111 // Último identificador asociado a la familia TCS340 (TCS3404) #define REVISION_IDENTIFICADOR 0B00001111 // Bits utilizados por el identificador para almacenar la revisión del TCS34 #include <Wire.h> byte indentificador_familia; // Identificador del integrado TCS34 void setup() { Serial.begin(9600); // Iniciar las comunicaciones serie para mostrar el resultado en la consola Wire.begin(); // Iniciar las comunicaciones I2C Wire.beginTransmission(DIRECCION_TCS34); // Iniciar las comunicaciones con el TCS34 Wire.write(ENVIAR_ORDEN|LEER_IDENTIFICADOR); Wire.endTransmission(); // Liberar las comunicaciones Wire.beginTransmission(DIRECCION_TCS34); // Iniciar las comunicaciones con el TCS34 Wire.requestFrom(DIRECCION_TCS34,1); while(!Serial); // Esperar el inicio de las comunicaciones serie. Solamente es necesario en Arduino Leonardo if(Wire.available()==1) // Si se ha recibido un byte { indentificador_familia=Wire.read(); if(indentificador_familia>FIN_FAMILIA_TCS340X) { Serial.print("Sensor de la familia TCS341 (TCS3413, TCS3414, TCS3415 o TCS3416)"); } else { Serial.print("Sensor de la familia TCS340 (TCS3404)"); } indentificador_familia&=REVISION_IDENTIFICADOR; // La revisión está en los últimos 4 bits if(indentificador_familia>0) { Serial.print(" Revisión "); Serial.print(indentificador_familia); } } else { Serial.println("No se ha podido leer el identificador del TCS34"); } Wire.endTransmission(); // Liberar las comunicaciones } void loop() { } |
Para simplificar el código anterior se da por cierto que llegarán datos del dispositivo y solamente se verifica que llegan en la cantidad correcta. Lo más ortodoxo sería esperar la recepción de los datos del bus I2C (aunque frecuentemente no se haga cuando se escribe un programa para Arduino).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
// Lectura del identificador del TCS34 como ejemplo mínimo de acceso a sus registros #define DIRECCION_TCS34 0x39 // Dirección del sensor TCS34 en el bus I2C #define ENVIAR_ORDEN 0B10000000 // Bit de indicación de operación (orden). Como alternativa más rápida se puede utilizar en todas órdenes pero se separa para aclarar mejor el funcionamiento) #define LEER_IDENTIFICADOR 0x04 // Operación de lectura del identificador (versión del integrado) #define FIN_FAMILIA_TCS340X 0B00001111 // Último identificador asociado a la familia TCS340 (TCS3404) #define REVISION_IDENTIFICADOR 0B00001111 // Bits utilizados por el identificador para almacenar la revisión del TCS34 #define TIMEOUT_I2C 20 // Esperar 20 ms antes de renunciar a leer el bus I2C #include <Wire.h> byte indentificador_familia; // Identificador del integrado TCS34 unsigned long cronometro_i2c; // Cronómetro para controlar el tiempo de espera máximo al leer el bus I2C void setup() { Serial.begin(9600); // Iniciar las comunicaciones serie para mostrar el resultado en la consola Wire.begin(); // Iniciar las comunicaciones I2C Wire.beginTransmission(DIRECCION_TCS34); // Iniciar las comunicaciones con el TCS34 Wire.write(ENVIAR_ORDEN|LEER_IDENTIFICADOR); Wire.endTransmission(); // Liberar las comunicaciones Wire.beginTransmission(DIRECCION_TCS34); // Iniciar las comunicaciones con el TCS34 Wire.requestFrom(DIRECCION_TCS34,1); while(!Serial); // Esperar el inicio de las comunicaciones serie. Solamente es necesario en Arduino Leonardo cronometro_i2c=millis(); while(Wire.available()<1&&(unsigned long)(millis()-cronometro_i2c)>TIMEOUT_I2C); // Esperar a que haya un byte disponible o pase el tiempo hasta desistir if(Wire.available()==1) // Si se ha recibido un byte { indentificador_familia=Wire.read(); if(indentificador_familia>FIN_FAMILIA_TCS340X) { Serial.print("Sensor de la familia TCS341 (TCS3413, TCS3414, TCS3415 o TCS3416)"); } else { Serial.print("Sensor de la familia TCS340 (TCS3404)"); } indentificador_familia&=REVISION_IDENTIFICADOR; // La revisión está en los últimos 4 bits if(indentificador_familia>0) { Serial.print(" Revisión "); Serial.print(indentificador_familia); } } else { Serial.println("No se ha podido leer el identificador del TCS34"); } Wire.endTransmission(); // Liberar las comunicaciones } void loop() { } |
Como es habitual, el proceso consiste en iniciar la librería Wire, acceder al dispositivo por su dirección, enviar una orden (y un dato, si procede) y recibir datos (si fuera el caso). Una hipotética función (hipotética porque quedan algunos detalles por aclarar) que realizara estas operaciones podría ser como la del código de ejemplo de abajo al que ya se ha añadido la espera al bus I2C.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// Función para leer un byte de un registro del TCS3414 #define DIRECCION_TCS34 0x39 // Dirección del sensor TCS34 en el bus I2C #define ENVIAR_ORDEN 0B10000000 // Bit de indicación de operación (orden). Como alternativa más rápida se puede utilizar en todas órdenes pero se separa para aclarar mejor el funcionamiento) #define TIMEOUT_I2C 20 // Esperar 20 ms antes de renunciar a leer el bus I2C byte leer_registro_TCS3414(byte registro) { unsigned long cronometro_i2c; // Cronómetro para controlar el tiempo de espera máximo al leer el bus I2C byte resultado=0; // Para poder terminar las comunicaciones dentro de la función hay que almacenar el resultado después de leerlo Wire.beginTransmission(DIRECCION_TCS3414); // Acceder a la dirección del I2C del TCS3414 Wire.write(ENVIAR_ORDEN|registro); // Acceder al registro Wire.endTransmission(); // Liberar las comunicaciones I2C Wire.beginTransmission(DIRECCION_TCS3414); // Acceder a la dirección del I2C del TCS3414 (Como norma general conviene liberar las comunicaciones I2C a cada operación) Wire.requestFrom(DIRECCION_TCS3414,1); // Pedir un byte cronometro_i2c=millis(); while(Wire.available()<1&&(unsigned long)(millis()-cronometro_i2c)>TIMEOUT_I2C); // Esperar a que haya un byte disponible o pase el tiempo hasta desistir if(Wire.available()==1) // Si se ha recibido un byte { resultado=Wire.read(); // Almacenar el byte que se ha cargado desde el TCS3414 } Wire.endTransmission(); // Liberar las comunicaciones I2C return resultado; // Devolver el byte cargado del TCS3414 } |
Códigos de operación del sensor TCS3414
Para utilizar el TCS3414 se almacenan o se leen sus registros. Para configurar el funcionamiento existen determinados campos (grupos de bits) dentro de los registros de configuración e igualmente, en los resultados (como se ha visto con el identificador del integrado) se pueden devolver varios valores disponiéndolos en diferentes bits. Esto hace más sencillo entender y recordar los distintos valores y estructura la organización del código con el que se programa el TCS3414.
Con un ánimo más didáctico que exhaustivo, a continuación se ven los diferentes registros a la vez que se construye un documento de cabecera con las correspondientes constantes que se pueden utilizar para programarlo. Los nombres de los registros y de los valores de los campos persiguen describir el funcionamiento, así que no son ni el nombre (que puede encontrarse en la hoja de datos del TCS4314 del fabricante) ni una traducción del mismo, aunque se incluye como referencia.
3 4 5 6 7 8 9 10 11 12 13 14 15 |
#define CONTROL 0x00 // Operación de control de las funciones básicas del TCS34 (al ser cero se puede omitir, pero se incluye para aclarar mejor el funcionamiento) #define CAMBIAR_TIEMPO 0x01 // Operación de configuración del cambio de tiempo #define ESTABLECER_INTERRUPCION 0x02 // Control de las interrupciones #define ORIGEN_INTERRUPCION 0x03 // Establecer el origen de la interrupción (el color o la combinación que lanza la interrupción) #define LEER_IDENTIFICADOR 0x04 // Operación de lectura del identificador (versión del integrado) #define CAMBIAR_GANANCIA_Y_DIVISOR 0x07 // Operación de configuración de la ganancia y el divisor (prescaler) #define UMBRAL_MINIMO_INTERRUPCION 0x08 // Establecer el byte bajo del umbral mínimo de interrupción #define UMBRAL_MAXIMO_INTERRUPCION 0x0A // Establecer el byte bajo del umbral máximo de interrupción #define LEER_BLOQUE 0x0F // Leer bloque de bytes (leer todos los datos) #define ROJO 0x12 // Operar sobre los fotodiodos que detectan el color rojo #define VERDE 0x10 // Operar sobre los fotodiodos que detectan el color verde #define AZUL 0x14 // Operar sobre los fotodiodos que detectan el color azul #define BLANCO 0x16 // Operar sobre los fotodiodos sin filtrar (luz/blanco) |
La técnica que he utilizado para componer los registros implica la elección del valor de cada campo y una operación OR
de todos los campos para componer el registro, por eso, a las anteriores definiciones (registros) hay que añadir las de sus correspondientes campos.
Funciones básicas
El formato de las órdenes o, usando la nomenclatura del fabricante, el registro de orden (aunque no se almacene) permite especificar la dirección de un registro al que se accede, el número de bytes que se desean recibir (cuando proceda) y, lo más importante, si en la comunicación hay involucrada una orden.
El bit de orden es el más significativo (bit 7), los dos siguiente indican la longitud de la transacción (incluyendo la cancelación o borrado) y los cinco últimos la dirección del registro al que se accede.
El campo con la longitud de la transacción (bits 5 y 6) permite elegir entre solicitar un byte, dos bytes (una palabra o word) y un bloque de bytes. Como se ha dicho, en este mismo campo se puede solicitar la cancelación de una operación pendiente; a efectos del funcionamiento del TCS3414, la cancelación de un proceso de interrupción, por eso, para aportar más claridad al código, he dado dos nombres al mismo valor.
17 18 19 20 21 22 |
#define ENVIAR_ORDEN 0B10000000 // Bit de indicación de operación (orden). Como alternativa más rápida se puede utilizar en todas órdenes pero se separa para aclarar mejor el funcionamiento) #define LEER_UN_BYTE 0B00000000 // Leer los datos entregados por el TCS34 de byte en byte (las lecturas son de dos bytes) #define LEER_DOS_BYTES 0B00100000 // Leer una palabra (word) formada por dos bytes #define LEER_BLOQUE 0B01000000 // Leer todos los datos (bloque) #define CANCELAR 0B01100000 // Cancelar la operación en curso #define CANCELAR_INTERRUPCION 0B01100000 |
El registro de control del TCS3414 se encuentra en la dirección 0x00 y permite activar («encender») el dispositivo (bit menos significativo) y la integración o ADC (bit 1). Además, el bit 4, de solo-lectura, permite saber si la conversión (ADC) se ha realizado correctamente.
23 24 25 |
#define ENCENDER 0B00000001 // Iniciar el funcionamiento del TCS34 (bit del registro de control de operaciones básicas) #define ACTIVAR_INTEGRACION 0B00000010 // Iniciar integración (bit del registro de control básico para activar la conversión analógica a digital de la lectura dentro del TCS34) #define INTEGRACION_CORRECTA 0B00010000 // Integración realizada correctamente (bit de solo-lectura del registro de control) |
Establecer el tiempo de integración
En el TCS3414 se puede optar por el sincronismo interno del que dispone o vincularlo a un sincronismo externo conectándolo a la patilla SYNC del sensor. Por medio del sincronismo externo se pueden controlar «manualmente» los ciclos de integración. Si se utiliza el sincronismo interno se puede usar uno de los tres tiempos de integración disponibles que permiten o una lectura más rápida, aunque menos precisa, o una más lenta pero con más resolución, por ejemplo para condiciones de menor iluminación.
En el registro de control de tiempo (TIMING, conforme a la nomenclatura del fabricante) se encuentra en la dirección 0x01 y permite configurar el tipo de sincronismo y la manera en la que funciona así como el tiempo de integración prefijado
27 28 29 |
#define INTEGRACION_12_MS 0B00000000 // Establecer el tiempo de integración en 12 ms (que se traduce en unos 12 ms de tiempo de lectura) #define INTEGRACION_100_MS 0B00000001 // Establecer el tiempo de integración en 100 ms #define INTEGRACION_400_MS 0B00000010 // Establecer el tiempo de integración en 400 ms |
Configuración de las interrupciones
El registro INTERRUPT en la dirección 0x02 sirve para establecer cuándo se genera una interrupción (en cada ciclo de conversión analógica-digital, a cierto umbral, a cada valor o a cierto tiempo) así como activar o desactivar las interrupciones o cancelar un ciclo ADC.
31 32 33 34 35 36 37 |
#define PARAR_INTEGRACION 0B01000000 // Detiene la integración cuando se produce una interrupción (para dar lugar a tratarla) #define DESACTIVAR_INTERRUPCION 0B00000000 #define INTERRUPCION_POR_NIVEL 0B00100000 #define INTERRUPCION_CICLO_ADC 0B00000000 #define INTERRUPCION_UMBRAL 0B00000001 // Cada ciclo de conversión analógica-digital lanza una interrupción #define INTERRUPCION_100_ms 0B00000010 // Se genera una interrupción cada 100 milisegundos #define INTERRUPCION_1_s 0B00000011 // Se genera una interrupción cada segundo |
Para establecer el origen (canal) de la interrupción se utiliza el registro INT SOURCE que se encuentra en la dirección 0x03 del TCS3414 y del que se utilizan los dos bits menos significativos.
39 40 41 42 |
#define ORIGEN_ROJO 0B00000001 #define ORIGEN_VERDE 0B00000000 #define ORIGEN_AZUL 0B00000010 #define ORIGEN_BLANCO 0B00000011 |
Identificar el tipo de sensor
En el registro ID en la dirección 0x04 se encuentran almacenados dos códigos en los dos nibbles con los que se identifica, como se veía en el primero de los ejemplos, el tipo de integrado de la familia TCS34 (TCS3414 en este caso) y el número de revisión del mismo. Para hacer más sencilla la lectura del código se crean dos bytes iguales que definen cada campo, la familia y la revisión.
44 45 |
#define FIN_FAMILIA_TCS340X 0B00001111 // Último identificador asociado a la familia TCS340 (TCS3404) #define REVISION_IDENTIFICADOR 0B00001111 // Bits utilizados por el identificador para almacenar la revisión del TCS34 |
Configurar la ganancia (multiplicador) y el divisor (prescaler)
En la dirección 0x07 del sensor TCS3414 se encuentra el registro GAIN con el que se configura el multiplicador y el divisor del valor calculado por la conversión analógica-digital realizada por el dispositivo.
La ganancia permite aumentar los valores devueltos por la ADC y poder apreciar mejor medidas pequeñas ampliándolas. Como es lógico esto puede introducir cierto nivel de error al amplificar también el ruido.
47 48 49 50 |
#define GANANCIA_1X 0B00000000 // Configurar la ganancia (multiplicador) al valor base (multiplicar por uno) #define GANANCIA_4X 0B00010000 // Configurar la ganancia a cuatro veces su valor base #define GANANCIA_16X 0B00100000 // Configurar la ganancia a 16 veces su valor base #define GANANCIA_64X 0B00110000 // Configurar la ganancia a 64 veces su valor base |
Al contrario, cuando los valores sean demasiado grandes puede saturarse la lectura (sobrepasar el valor máximo) perdiendo definición en las medidas más altas, en las condiciones de mayor luminosidad. El divisor (prescaler) sirve para corregir la saturación disminuyendo el resultado devuelto por la ADC al multiplicarlo por cierto valor.
52 53 54 55 56 57 58 |
#define DIVISOR_1 0B00000000 // Configurar el divisor (prescaler) a su valor base (dividir entre uno) #define DIVISOR_2 0B00000001 // Configurar el divisor al doble de su valor base (dividir entre dos) #define DIVISOR_4 0B00000010 // Configurar el divisor a cuatro veces su valor base (dividir entre cuatro) #define DIVISOR_8 0B00000011 // Configurar el divisor a 8 veces su valor base (dividir entre 8) #define DIVISOR_16 0B00000100 // Configurar el divisor a 16 veces su valor base #define DIVISOR_32 0B00000101 // Configurar el divisor a 32 veces su valor base #define DIVISOR_64 0B00000110 // Configurar el divisor a 64 veces su valor base |
Configurar los umbrales de valor que activa la interrupción
El TCS3414 es capaz de lanzar una interrupción cuando se alcanza cierto valor máximo o mínimo. Como la resolución de los valores medidos por el sensor es de 16 bits, se utilizan cuatro registros de un byte, dos (seguidos) para el valor máximo dos para el mínimo. Al estar seguidos los bytes bajo y alto (y por ese orden) solamente es necesario definir uno de ellos.
Los registros THRESH en las direcciones 0x08 a 0x0b son los encargados de almacenar los valores máximo (HIGH_THRESH) y mínimo (LOW_THRESH) con un byte para la parte alta del valor (HIGH_BYTE) y otro para la baja (LOW_BYTE) resultando en cuatro direcciones:
LOW_THRESH_LOW_BYTE
en la dirección 0x08LOW_THRESH_HIGH_BYTE
en 0x09HIGH_THRESH_LOW_BYTE
en 0x0aHIGH_THRESH_HIGH_BYTE
en 0x0b
Para que se utilicen estos umbrales de valor es necesario también configurar las interrupciones del TCS3414 además de establecer los valores de los registros.
Leer los valores de color medidos por el sensor TCS3414
El TCS3414 se encarga de realizar la lectura y la conversión a un valor digital de 16 bits de la intensidad de luz detectada por los cuatro juegos de cuatro fotodiodos sin necesidad de elegir individualmente cada grupo de filtros. El resultado es almacenado en cuatro registros consecutivos de un byte (cada valor necesita dos bytes) que pueden ser recuperados individualmente, por palabras (word) para cargar el valor completo de un color, o como un bloque con toda la información, útil para almacenarlo externamente en un proceso de monitorización continuo. Como ya se ha visto, para elegir la forma en la que se accede a los valores almacenados en los registros hay que establecer el campo del formato de lectura en el registro de operación del TCS3414 y luego acceder al registro correspondiente a la dirección del color que se va a leer y que puede verse en la siguiente lista (atención, no sigue el orden que se espera)
GREEN_LOW
en la dirección 0x10GREEN_HIGH
en 0x11RED_LOW
en 0x12RED_HIGH
en 0x13BLUE_LOW
en 0x14BLUE_HIGH
en 0x15CLEAR_LOW
en 0x16CLEAR_HIGH
en 0x17
Software de medida de color con el sensor TCS3414
Ahora que se ha descrito con más detalle la estructura y funcionamiento de los registros del TCS3414 se puede definir una nueva función de lectura de los colores medidos que cargue los dos bytes que forman cada valor. Para asegurarse de que el modo de lectura es word, el argumento de la función solamente espera el primer byte de dirección del canal, el que almacena la parte menos significativa.
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
unsigned int leer_canal(byte canal) { unsigned long cronometro_i2c; unsigned int resultado=0; Wire.beginTransmission(DIRECCION_TCS34); Wire.write(ENVIAR_ORDEN|LEER_DOS_BYTES|canal); Wire.endTransmission(); Wire.beginTransmission(DIRECCION_TCS34); Wire.requestFrom(DIRECCION_TCS34,2); cronometro_i2c=millis(); while(Wire.available()<2&&(unsigned long)(millis()-cronometro_i2c)>TIMEOUT_I2C); // Esperar a que haya dos bytes disponibles o pase el tiempo hasta desistir if(Wire.available()==2) // Si hay dos bytes disponibles { resultado=Wire.read(); // Byte menos significativo resultado|=(unsigned int)(Wire.read())<<8; // Byte más significativo, rotar ocho bits y sumar (OR) al otro byte } Wire.endTransmission(); return resultado; } |
Las direcciones de los registros y los diferentes valores de los campos se pueden agrupar en un documento de cabecera para incluirlo en el código de Arduino cuando se trabaje con el TCS3414.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
#define DIRECCION_TCS34 0x39 // Dirección del sensor TCS34 en el bus I2C #define CONTROL 0x00 // Operación de control de las funciones básicas del TCS34 (al ser cero se puede omitir, pero se incluye para aclarar mejor el funcionamiento) #define CAMBIAR_TIEMPO 0x01 // Operación de configuración del cambio de tiempo #define ESTABLECER_INTERRUPCION 0x02 // Control de las interrupciones #define ORIGEN_INTERRUPCION 0x03 // Establecer el origen de la interrupción (el color o la combinación que lanza la interrupción) #define LEER_IDENTIFICADOR 0x04 // Operación de lectura del identificador (versión del integrado) #define CAMBIAR_GANANCIA_Y_DIVISOR 0x07 // Operación de configuración de la ganancia y el divisor (prescaler) #define UMBRAL_MINIMO_INTERRUPCION 0x08 // Establecer el byte bajo del umbral mínimo de interrupción #define UMBRAL_MAXIMO_INTERRUPCION 0x0A // Establecer el byte bajo del umbral máximo de interrupción #define LEER_BLOQUE 0x0F // Leer bloque de bytes (leer todos los datos) #define ROJO 0x12 // Operar sobre los fotodiodos que detectan el color rojo #define VERDE 0x10 // Operar sobre los fotodiodos que detectan el color verde #define AZUL 0x14 // Operar sobre los fotodiodos que detectan el color azul #define BLANCO 0x16 // Operar sobre los fotodiodos sin filtrar (luz/blanco) #define ENVIAR_ORDEN 0B10000000 // Bit de indicación de operación (orden). Como alternativa más rápida se puede utilizar en todas órdenes pero se separa para aclarar mejor el funcionamiento) #define LEER_UN_BYTE 0B00000000 // Leer los datos entregados por el TCS34 de byte en byte (las lecturas son de dos bytes) #define LEER_DOS_BYTES 0B00100000 // Leer una palabra (word) formada por dos bytes #define LEER_BLOQUE 0B01000000 // Leer todos los datos (bloque) #define CANCELAR 0B01100000 // Cancelar la operación en curso #define CANCELAR_INTERRUPCION 0B01100000 #define ENCENDER 0B00000001 // Iniciar el funcionamiento del TCS34 (bit del registro de control de operaciones básicas) #define ACTIVAR_INTEGRACION 0B00000010 // Iniciar integración (bit del registro de control básico para activar la conversión analógica a digital de la lectura dentro del TCS34) #define INTEGRACION_CORRECTA 0B00010000 // Integración realizada correctamente (bit de solo-lectura del registro de control) #define INTEGRACION_12_MS 0B00000000 // Establecer el tiempo de integración en 12 ms (que se traduce en unos 12 ms de tiempo de lectura) #define INTEGRACION_100_MS 0B00000001 // Establecer el tiempo de integración en 100 ms #define INTEGRACION_400_MS 0B00000010 // Establecer el tiempo de integración en 400 ms #define PARAR_INTEGRACION 0B01000000 // Detiene la integración cuando se produce una interrupción (para dar lugar a tratarla) #define DESACTIVAR_INTERRUPCION 0B00000000 #define INTERRUPCION_POR_NIVEL 0B00100000 #define INTERRUPCION_CICLO_ADC 0B00000000 #define INTERRUPCION_UMBRAL 0B00000001 // Cada ciclo de conversión analógica-digital lanza una interrupción #define INTERRUPCION_100_ms 0B00000010 // Se genera una interrupción cada 100 milisegundos #define INTERRUPCION_1_s 0B00000011 // Se genera una interrupción cada segundo #define ORIGEN_ROJO 0B00000001 #define ORIGEN_VERDE 0B00000000 #define ORIGEN_AZUL 0B00000010 #define ORIGEN_BLANCO 0B00000011 #define FIN_FAMILIA_TCS340X 0B00001111 // Último identificador asociado a la familia TCS340 (TCS3404) #define REVISION_IDENTIFICADOR 0B00001111 // Bits utilizados por el identificador para almacenar la revisión del TCS34 #define GANANCIA_1X 0B00000000 // Configurar la ganancia (multiplicador) al valor base (multiplicar por uno) #define GANANCIA_4X 0B00010000 // Configurar la ganancia a cuatro veces su valor base #define GANANCIA_16X 0B00100000 // Configurar la ganancia a 16 veces su valor base #define GANANCIA_64X 0B00110000 // Configurar la ganancia a 64 veces su valor base #define DIVISOR_1 0B00000000 // Configurar el divisor (prescaler) a su valor base (dividir entre uno) #define DIVISOR_2 0B00000001 // Configurar el divisor al doble de su valor base (dividir entre dos) #define DIVISOR_4 0B00000010 // Configurar el divisor a cuatro veces su valor base (dividir entre cuatro) #define DIVISOR_8 0B00000011 // Configurar el divisor a 8 veces su valor base (dividir entre 8) #define DIVISOR_16 0B00000100 // Configurar el divisor a 16 veces su valor base #define DIVISOR_32 0B00000101 // Configurar el divisor a 32 veces su valor base #define DIVISOR_64 0B00000110 // Configurar el divisor a 64 veces su valor base #define CANTIDAD_CANALES 4 // Tipos diferentes de sensores (filtros) #define CANTIDAD_TIEMPOS 3 // Cantidad de tiempos de integración |
Si el tiempo de integración es variable, o se establece externamente (por ejemplo leyendo una serie de micro-interruptores), lo más conveniente es activar el código de lectura del color con una interrupción. También se puede leer a cada ciclo del programa el bit ADC_VALID del registro de control. Cuando se conozca el tiempo de integración se puede esperar a que haya transcurrido, comprobando en el ciclo de programa o, solo en el caso en el que el microcontrolador no realice otras tareas, usar delay()
. El siguiente código muestra un ejemplo básico de lectura de las medidas de los colores. Como el tiempo entre presentaciones de la lectura es muy superior al de integración, solo se verifica el primero y no el segundo; el código para verificar que ha transcurrido el tiempo de integración sería similar.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
#define TIMEOUT_I2C 20 // Esperar 20 ms antes de renunciar a leer el bus I2C #define PIN_APAGAR_LED 5 // Pin con la señal de control de la iluminación del módulo (no está relacionado con el TCS34) #define ESPERA_MEDIDA 5000 // Se toman medidas de color cada cinco segundos (5000 ms) #include "TCS3414.h" // Cargar las constantes del TCS3414 #include <Wire.h> // Cargar la librería I2C de Arduino unsigned long cronometro_medida=0; // Tiempo transcurrido entre medidas. A cero para empezar a medir en el primer ciclo String nombre_color[CANTIDAD_CANALES]={"Rojo","Verde","Azul","Blanco"}; // Nombres de los componentes de color para mostrar en la consola byte canal_color[CANTIDAD_CANALES]={ROJO,VERDE,AZUL,BLANCO}; // Operaciones de lectura de los TCS34 para cada canal de color void setup() { pinMode(PIN_APAGAR_LED,OUTPUT); // Configurar el pin de control de los LED como salida digitalWrite(PIN_APAGAR_LED,LOW); // Encender la iluminación del módulo del TCS34 Serial.begin(9600); // Preparar las comunicaciones serie para ver los resultados en la consola while(!Serial); // Esperar el inicio de las comunicaciones serie. Solamente es necesario en Arduino Leonardo Wire.begin(); // Inicializar la librería de comunicaciones I2C de Arduino Wire.beginTransmission(DIRECCION_TCS34); // Iniciar las comunicaciones con el TCS34 Wire.write(ENVIAR_ORDEN|CONTROL); // Avisar al TCS34 de que se va a enviar una orden de control Wire.write(ENCENDER|ACTIVAR_INTEGRACION); // Encender y preparar la integración Wire.endTransmission(); // Liberar las comunicaciones Wire.beginTransmission(DIRECCION_TCS34); // Iniciar las comunicaciones con el TCS34 Wire.write(ENVIAR_ORDEN|CAMBIAR_GANANCIA_Y_DIVISOR); // Preparar la ganancia y el divisor Wire.write(GANANCIA_1X|DIVISOR_1); // Ganancia a uno y divisor también a uno (en realidad no es necesario porque es la configuración por defecto) Wire.endTransmission(); // Liberar las comunicaciones Wire.beginTransmission(DIRECCION_TCS34); // Iniciar las comunicaciones con el TCS34 Wire.write(ENVIAR_ORDEN|CAMBIAR_TIEMPO); Wire.write(INTEGRACION_100_MS); // Usar un tiempo de integración de 100 ms (punto intermedio entre velocidad y precisión/iluminación) Wire.endTransmission(); // Liberar las comunicaciones delay(3000); // Esperar para asegurarse de que se ha estabilizado el sensor } void loop() { if((unsigned long)(millis()-cronometro_medida)>ESPERA_MEDIDA) { cronometro_medida=millis(); for(byte numero_canal=0;numero_canal<CANTIDAD_CANALES;numero_canal++) { Serial.print(nombre_color[numero_canal]); Serial.print(": "); Serial.println(leer_canal(canal_color[numero_canal])); } Serial.println(); } } unsigned int leer_canal(byte canal) { unsigned long cronometro_i2c; unsigned int resultado=0; Wire.beginTransmission(DIRECCION_TCS34); Wire.write(ENVIAR_ORDEN|LEER_DOS_BYTES|canal); Wire.endTransmission(); Wire.beginTransmission(DIRECCION_TCS34); Wire.requestFrom(DIRECCION_TCS34,2); cronometro_i2c=millis(); while(Wire.available()<2&&(unsigned long)(millis()-cronometro_i2c)>TIMEOUT_I2C); // Esperar a que haya dos bytes disponibles o pase el tiempo hasta desistir if(Wire.available()==2) // Si hay dos bytes disponibles { resultado=Wire.read(); // Byte menos significativo resultado|=(unsigned int)(Wire.read())<<8; // Byte más significativo, rotar un byte y sumar (OR) al otro byte } Wire.endTransmission(); return resultado; } |
Los valores que se obtienen del sensor TCS3414 deben posprocesarse conforme al contexto para corregir posibles desviaciones. El siguiente código de ejemplo monitoriza el valor de una muestra de color blanco de referencia para hacer una especie de balance de blancos con el que establecer los valores máximos así como para calcular el coeficiente diferencial entre los componentes. Posteriormente, por ejemplo usando las funciones map()
y constrain()
de Arduino, se convertirían las medidas obtenidas al rango que el uso del dispositivo requiera.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
#define ESPERA_ESTABILIZACION_SENSOR 350 #define TIMEOUT_I2C 20 // Esperar 20 ms antes de renunciar a leer el bus I2C #define PIN_APAGAR_LED 5 // Pin con la señal de control de la iluminación del módulo (no está relacionado con el TCS34) #include "TCS3414.h" #include <Wire.h> byte indentificador_familia; // Identificador del integrado TCS34 unsigned long cronometro_i2c; // Cronómetro para controlar el tiempo de espera máximo al leer el bus I2C byte numero_canal; // Número del sensor que se está leyendo (rojo, verde, azul o blanco) unsigned long numero_valor_medido=0; // Contador de las lecturas realizadas (para mostrarlo y para calcular la media) unsigned long ultimo_valor_medido=30000; // Número máximo de medidas (cada tiempo de integración cuenta 1) unsigned int valor_menor; // Valor menor de la ronda de lectura para calcular el coeficiente de corrección con respecto a él (que será uno) unsigned int valor_color[CANTIDAD_CANALES]; // Vector con los valores de los colores de la ronda de lectura en curso float valor_medio[CANTIDAD_TIEMPOS][CANTIDAD_CANALES]={{0,0,0,0},{0,0,0,0},{0,0,0,0}}; // Valor intermedio (ponderando los iniciales) de los colores en función de los diferentes tiempos de integración (lectura) unsigned int valor_maximo[CANTIDAD_TIEMPOS][CANTIDAD_CANALES]={{0,0,0,0},{0,0,0,0},{0,0,0,0}}; // Valor máximo de los colores obtenido con los distintos tiempos de integración. Sirve para estimar el tipo de dato (que será unsigned int incluso en el menor) y para hacer una estimación de la componente del color en un rango (por ejemplo para calcular la componente del color de 0 a 255) float coeficiente_color[CANTIDAD_CANALES]={0,0,0,0}; // Factor de corrección de cada componente de color (una especie de balance de blancos) String nombre_color[CANTIDAD_CANALES]={"Rojo","Verde","Azul","Blanco"}; // Nombres de los componentes de color para mostrar en la consola byte canal_color[CANTIDAD_CANALES]={ROJO,VERDE,AZUL,BLANCO}; // Operaciones de lectura de los TCS34 para cada canal de color byte numero_tiempo; // Contador de tiempos de integración (o número de tiempo de integración en curso) byte tiempo_integracion[CANTIDAD_TIEMPOS]={INTEGRACION_12_MS,INTEGRACION_100_MS,INTEGRACION_400_MS}; // Valores del registro del tiempo de integración unsigned int espera_integracion[CANTIDAD_TIEMPOS]={15,125,500}; // El tiempo que tarda más un coeficiente de seguridad (estimado) del 25% void setup() { pinMode(PIN_APAGAR_LED,OUTPUT); digitalWrite(PIN_APAGAR_LED,LOW); Serial.begin(9600); Wire.begin(); Wire.beginTransmission(DIRECCION_TCS34); // Iniciar las comunicaciones con el TCS34 Wire.write(ENVIAR_ORDEN|CONTROL); // Avisar al TCS34 de que se va a enviar una orden de control Wire.write(ENCENDER|ACTIVAR_INTEGRACION); // Encender y preparar la integración Wire.endTransmission(); // Liberar las comunicaciones Wire.beginTransmission(DIRECCION_TCS34); // Iniciar las comunicaciones con el TCS34 Wire.write(ENVIAR_ORDEN|CAMBIAR_GANANCIA_Y_DIVISOR); Wire.write(GANANCIA_1X|DIVISOR_1); Wire.endTransmission(); // Liberar las comunicaciones Wire.beginTransmission(DIRECCION_TCS34); // Iniciar las comunicaciones con el TCS34 Wire.write(ENVIAR_ORDEN|LEER_IDENTIFICADOR); Wire.endTransmission(); // Liberar las comunicaciones Wire.beginTransmission(DIRECCION_TCS34); // Iniciar las comunicaciones con el TCS34 Wire.requestFrom(DIRECCION_TCS34,1); while(!Serial); // Esperar el inicio de las comunicaciones serie en Leonardo cronometro_i2c=millis(); while(Wire.available()<1&&(unsigned long)(millis()-cronometro_i2c)>TIMEOUT_I2C); // Esperar a que haya un byte disponible o pase el tiempo hasta desistir if(Wire.available()==1) { indentificador_familia=Wire.read(); if(indentificador_familia>FIN_FAMILIA_TCS340X) { Serial.print("Sensor de la familia TCS341 (TCS3413, TCS3414, TCS3415 o TCS3416)"); } else { Serial.print("Sensor de la familia TCS340 (TCS3404)"); } indentificador_familia&=REVISION_IDENTIFICADOR; // La revisión está en los últimos 4 bits if(indentificador_familia>0) { Serial.print(" Revisión "); Serial.print(indentificador_familia); } } else { Serial.println("No se ha podido leer el identificador del TCS34"); } Serial.println("\n\n"); // Dos líneas en blanco Wire.endTransmission(); // Liberar las comunicaciones delay(ESPERA_ESTABILIZACION_SENSOR); // Esperar para asegurarse de que se ha estabilizado el sensor } void loop() { if(numero_valor_medido<ultimo_valor_medido) { numero_tiempo=numero_valor_medido%CANTIDAD_TIEMPOS; numero_valor_medido++; Serial.print("Número medida: "); Serial.print(numero_valor_medido); Serial.print(" Tiempo: "); Serial.print(espera_integracion[numero_tiempo]); Serial.println(" ms"); Wire.beginTransmission(DIRECCION_TCS34); // Iniciar las comunicaciones con el TCS34 Wire.write(ENVIAR_ORDEN|CAMBIAR_TIEMPO); Wire.write(tiempo_integracion[numero_tiempo]); Wire.endTransmission(); // Liberar las comunicaciones delay(max(ESPERA_ESTABILIZACION_SENSOR,espera_integracion[numero_tiempo])); // Esperar para asegurarse de que se ha estabilizado el sensor y/o que ha pasado el tiempo de lectura valor_menor=-1; // Necesita que el tipo de dato de valor_menor sea unsigned for(numero_canal=0;numero_canal<CANTIDAD_CANALES;numero_canal++) { Wire.beginTransmission(DIRECCION_TCS34); Wire.write(ENVIAR_ORDEN|LEER_DOS_BYTES|canal_color[numero_canal]); Wire.endTransmission(); Wire.beginTransmission(DIRECCION_TCS34); Wire.requestFrom(DIRECCION_TCS34,2); cronometro_i2c=millis(); while(Wire.available()<2&&(unsigned long)(millis()-cronometro_i2c)>TIMEOUT_I2C); // Esperar a que haya dos bytes disponibles o pase el tiempo hasta desistir if(Wire.available()==2) // Si hay dos bytes disponibles { valor_color[numero_canal]=Wire.read(); valor_color[numero_canal]|=(unsigned int)(Wire.read())<<8; } else { valor_color[numero_canal]=1; } Wire.endTransmission(); if(numero_canal<CANTIDAD_CANALES-1) { if(valor_color[numero_canal]<valor_menor) { valor_menor=valor_color[numero_canal]; } if(valor_color[numero_canal]>valor_maximo[numero_tiempo][numero_canal]) { valor_maximo[numero_tiempo][numero_canal]=valor_color[numero_canal]; } valor_medio[numero_tiempo][numero_canal]= ( valor_medio[numero_tiempo][numero_canal] *(numero_valor_medido-1) +valor_color[numero_canal] ) /(float)numero_valor_medido; } } for(numero_canal=0;numero_canal<CANTIDAD_CANALES;numero_canal++) { coeficiente_color[numero_canal]= ( coeficiente_color[numero_canal] *(numero_valor_medido-1) +(float)valor_menor/(float)valor_color[numero_canal] ) /(float)numero_valor_medido; Serial.print(nombre_color[numero_canal]+" Actual: "); Serial.print(valor_color[numero_canal]); if(numero_canal<CANTIDAD_CANALES-1) { Serial.print(", Medio: "); Serial.print(valor_medio[numero_tiempo][numero_canal]); Serial.print(", Máximo: "); Serial.print(valor_maximo[numero_tiempo][numero_canal]); Serial.print(", Corrección: "); Serial.print(coeficiente_color[numero_canal]); } Serial.println(); } Serial.println(); } } |
Aunque el ejemplo anterior está profusamente comentado es interesante aclarar algún extremo. Como la finalidad del programa es calcular los valores con los que establecer el rango de medida (esa especie de balance de blancos de la que hablaba más arriba) se ha utilizado delay() (y hay que insistir en que solamente en esos casos es una buena opción) en la línea 89 con dos finalidades. Por una parte para asegurarse que el sensor se ha estabilizado después de realizar el cambio de tiempo de integración; tarda algo más de 250 ms asi que, por seguridad, se le da un margen suficiente esperando 350 ms. Por otra parte, como se prueban todos los tiempos de integración (con su correspondiente margen de seguridad), el de 400 ms es más desfavorable que el de estabilización después de cambio de integración, así que se elige siempre el mayor de los dos posibles.
Como puede verse en las siguientes capturas de pantalla de la salida de consola del ejemplo anterior, desde el principio la medida es relativamente correcta. Entorno a las mil medidas se alcanzan más o menos las medidas finales y apenas hay cambio.
Se promedian por igual todos los tiempos de integración, lo que es discutible ya que parece que el de 12 ms es menos preciso y debería tener menor peso en el total pero, por la cantidad de medidas, parece que el valor se estabiliza.
El coeficiente de corrección que se ha obtenido multiplicaría a la medida obtenida en el canal correspondiente para determinar el valor final que se utilizaría.
El valor máximo, y el mínimo, si fuera el caso, sirve para establecer un rango con el que trabajar que incluso podría determinar el tipo de dato utilizado (tipo byte
o como máximo tipo unsigned int
, ya que la resolución es de 16 bits)
También es importante tener en cuenta que en la mayoría de circunstancias el color negro no corresponde con el valor cero sino con cierto nivel mínimo que puede ser relevante considerar como lo es en general el contexto de medida.
Javier
Hola Víctor,
Enhorabuena por tu web, hay artículos muy interesante aunque quizás con un nivel muy elevado para los que nos estamos iniciando como afición en la electrónica y la programación de los entornos arduino.
Te escribo porque estoy interesado en realizar una especie de colorímetro con arduino y ando buscando información en la web. He visto que tienes dos artículos dedicados a sensores de color este dedicado al TCS3414 y otro al TCS3200. El primero tiene 16 fotodiodos y el segundo 64. Me gustaría saber cuál consideras mejor de los dos y más preciso para tomar valores de color lo más preciso posible. A mí me parece más interesante el TCS3414 por la estructura que trae y que permite apoyarlo sobre la superficie a medir, pero por otra parte veo que tiene menos fotodiodos (yo he comprado un modelo similar al que muestras en el artículo y otro de Seeedstudio).
Hablas de que realiza medidas en 16bits (si no me equivoco esto implica una precisión de 65536 diferentes valores a medir por color), pero los valores RGB usado en fotografía, monitores, etc van del 0 al 255 (algo así como 8 bits de profundidad de color, que en los 3 canales conjunto da los famosos más de 16 millones de colores). De hecho, en las medidas que aparecen en los ejemplos de tu artículo muestra valores mayores de 255 (ej 1120, 1365, etc). Supongo que para pasarlos a los valores estandar de RGB usados en fotografía habría que hacer un mapeado como comentas y una calibración con valores de referencia, cierto?
Por otra parte en cierto momento hablas de realizar un documento de cabecera, para usar el sketch que has realizado, supongo que será alguna especie de librería. He copiado el sketch que has desarrollado para probarlo con los sensores que tengo y me da error (creo que me falta la librería que haces referencia en #include «TCS3414.h»), te agradecería que la incluyeras en el hilo si te es posible.
Como te he comentado al principio tu web es muy interesante, pero el nivel es en general bastante elevado, sería interesante algunos artículos de nivel más básico para ir entrando en el mundo de la electrónica y la programación de arduino, estaría muy bien que hicieras un artículo sobre cómo hacer librerías para arduino, tipo de cómunicación I2C, serial, wifi… para neofitos..
Un saludo
Javier
Víctor Ventura
Hola, Javier.
Gracias por tus elogios.
Me consta que hay lectores del blog a los que los artículos les parecen demasiado complejos y visitantes que los consideran demasiado superficiales. Eso quiere decir que me estoy acercando al punto medio 🙂
Lo más complejo es conseguir que el tema de fondo (el color, en el caso de este artículo) tenga rigor y sea práctico para los lectores interesados en programación y/o en electrónica que es el público-objetivo de los textos. No me queda clara la aplicación que quieres darle al sensor, a lo mejor sabiéndola podemos aportarte (yo mismo o algún lector) más información sobre cómo resolverlo y podría ser un interesante ejemplo de aplicación.
Tu sugerencia sobre escribir también artículos, digamos «de base», me parece interesante y ya entra en mis planes; de hecho, algo he intentado ya.
Decir que una cosa es mejor-peor que otra es delicado porque depende de las circunstancias. Si tengo que elegir entre susto o muerte me quedo con el TCS3414 (por las comunicaciones y la estabilidad de las medidas). Ahora estoy trabajando con el TCS3472 y me parece aún mejor (más preciso y estable) y tiene menos fotodiodos (doce, dispuestos en una matriz de 3×4). Seguramente publicaré un artículo en un par de semanas y trataré las interrupciones, que en este casi no he hablado de ellas.
En efecto, el TCS3414 tiene una resolución de 16 bits lo que implica valores entre 0 y 65535, si es que todo ese rango de valores sirviera para tu aplicación pero quieres utilizarlo con un modelo de 8 bits tendrías que convertirlo (perdiendo mucha resolución) multiplicando por 255 y dividiendo entre 65535 (también puedes usar
map()
). La clave está en que muy raramente utilizarás todo el rango ni aún con los tiempos de integración más largos (400 ms) sino que quedará en unos pocos miles en el mejor de los casos (apenas mil en la mayoría de las ocasiones). Para poder sacar partido de un sensor de este tipo primero tienes que resolver una fase previa para conocer cómo es el contexto en el que va a trabajar, una especie de calibración en la que buscar los valores máximos, mínimos y las desviaciones de color con la que obtener coeficientes y límites para convertir las lecturas a valores útiles para unas circunstancias y un uso y concreto.Por ejemplo, muchas veces se mide (por transparencia) el color de un líquido para saber si se mantiene en el margen de cierto tono, lo que permite saber (suponer) si un producto mantiene cierta calidad (previamente se ha estudiado la correlación entre esa calidad y el color). Es decir, solamente se persigue medir un único color de referencia con una luz concreta controlada (que no dañe a la muestra).
El documento de cabecera no es una librería, es la definición de las constantes que se utilizan (que yo he utilizado) para programar el TCS3414, por ejemplo la dirección el el bus I2C, la dirección de los registros, los bits de cada campo… cosas como
#define DIRECCION_TCS34 0x39
, que hacen más legibles los programas además de ahorrar un poco de trabajo. Utilizar el TCS3414 es tan sencillo que no creo que merezca la pena hacer una librería salvo para sistemas muy grandes, no para un Arduino de una serie pequeña. Puedes copiar y pegar el código pero, por si te resulta más sencillo, también puedes descargar la cabecera con las constantes para el TCS3414 e incluir el documento en la carpeta de tu programa para Arduino.Saludos y gracias por visitar polaridad.es
Javier
Hola Victor,
Gracias por tus aclaraciones, me he descargado la cabecera que enlazas pero no he tenido tiempo de ver si me funciona (ni siquiera la he descomprimido). Mi idea es hacer una especie de colorímetro digital DIY, algo como los colorímetro triestimulo konica-minolta CR-400, pero DIY y bajo costo. Estos aparatos valen para medir el color de la superficie de objetos variados y te dan los valores RGB de 0 a 255 para cada uno de los canales (puedes medir en otros modos de color diferentes al RGB, como el Lab, HCB, etc). Pero para ello, imagino que habría que calibrar el sensor con una carta de colores, no sé si tienes experiencia al respecto o me puedes guiar hacia alguna web donde pueda informarme.
He intentado usar el sensor de SeeedStudio «Grove I2C Color Sensor» que usa el mismo sensor que el que tu muestras en el artículo, usando su librería y demás (aquí el enlace: http://www.seeedstudio.com/wiki/Grove_-_I2C_Color_Sensor), y me da valores de 0 a 225, pero no estoy seguro de que esos valores sean fiables con respecto al valor real de los colores medidos. Al usar un sensor como el que muestras en el artículo los valores medidos sobre el mismo objeto diferían bastante (supongo que debido a los led de iluminación y demás) por lo que creo que es necesario reallizar una calibración.
Un saludo y gracias
Javier
Víctor Ventura
Hola, Javier.
Muy interesante tu proyecto.
Evidentemente existe una gran diferencia de sensibilidad entre el producto en el que te inspiras y el sensor del que se habla en este artículo, y por tanto habrá una gra diferencia de resultados, al menos para la aplicación concreta que buscas.
Una vez obtenido un código de color válido en un modelo, existen fórmulas para expresarlo conforme a otros modelos de color, especialmente las de la CIE. En la página que enlazas hay alguna pista al respecto.
Si me permites opinar sobre tu proyecto (por las pruebas que yo he hecho), creo que es mejor utilizar una cámara: un sensor de imagen mejor que uno de color. Aunque la sensibilidad es menor, el calibrado es más sencillo y la conversión más directa. Además, si el tiempo de muestreo no es crítico, puedes promediar unos pocos fotogramas para conseguir una medida mejor. Aunque las más «golosas» son las que entregan JPEG o MJPEG o como mínimo disponen de una memoria FIFO, he sufrido problemas por no tener presente que lo que entregan es lo que había en el sensor unos milisegundos atrás; por lo demás, resuelven el (terrible) problema de la transmisión de datos UART a un microcontrolador.
Suerte con tu proyecto. Por favor, cuéntanos cómo te va.
Saludos y gracias por participar en polaridad.es
David
¿Para qué sirven los pines SYS y LED? Gracias
Víctor Ventura
Hola, David.
En el módulo de la foto SYS está mal rotulado, debería poner SYNC; es la entrada de sincronismo (la referencia para control externo).
La entrada LED, presente en muchos módulos, sirve para encender o apagar la iluminación incluida en el módulo (no tiene nada que ver con el TCS3414). En el módulo de la foto, la patilla LED activa (enciende) a nivel bajo un par de LED que hay en la placa. En otros módulos puede activar la iluminación a nivel alto o hasta puede ser la entrada alimentación de los LED; en estos casos mucho cuidado con graduarla con PWM, porque la medida podría verse afectada; lo mejor es establecer la iluminación de forma analógica, no con un pulso.
Gracias por visitar polaridad.es
Javier
Hola Victor,
He intentado de nuevo utilizar tu skecht pero me sigue dando error, algo así como : » Error compilación en tarjeta Arduino/Genuino Uno», pero al cargar otros skech no me parace.
El archivo con la cabecera que enlazaste lo baje y como era un archivo con extension .h que mi IDE arduino no queria abrir al no tener extensión .in,. le cambié la extensión lo abrí y copie el contenido en otro skecth donde tenía el skecth (vaya lio!!) último que aparece en el artículo, pero me sigue dando el error que comento.
Podrías enlazarme un skecth que contenga todo, tanto las cabecera como el código que hace funcionar el sensor, a ver si consigo hacerlo funcionar con mi sensor.
Gracias de antemano
Javier.
Víctor Ventura
Hola, Javier.
Me pillas con el cerebro semi-paralizado. Sólo he alcanzado a entender que no te funciona y que si te puedo mandar un sketch completo.
De momento, aquí puedes descargar el ejemplo completo (el documento .ino y el documento .h, no he hecho más que copiar y pegar lo que hay en el artículo) En cuanto me vuelva a funcionar el cerebro, releo el mensaje a ver si te puedo contestar algo más inteligente 🙂
Saludos.
Javier
Hola Victor,
Ahora sí que me funciona con el nuevo zip que has enlazado. No sé por qué antes no funcionaba antes.
Muchas gracias. Seguro que volveré a preguntarte alguna otra cosa sobre el tema, ya que la salida del puerto serie con el tiempo, valores actuales, medio, máximo y correción tengo que verlo mejor.
Muchas gracias de nuevo. Te agradezco tu rápida respuesta, espero no agobiarte con tantas dudas, me puedes contestar cuando puda yo voy poco a poco haciendo pruebas, para mí es un hobby que lo dejo y retomo de tiempo en tiempo.
Saludos
Javier
Víctor Ventura
🙂
Diego Frutos Angosto
Hola Victor,
Estoy realizando un proyecto para medir la cantidad de suciedad es limpiada en muestras de telas blancas. Para ello, estoy utilizando el sensor TCS3414. Estoy trabajando con el diodo sin filtro o neutro para ver la cantidad de luminosidad que se refleja. El problema que tengo es que para poder calibrar el sensor en función de cuál sea mi máx. de suciedad(negro) y el blanco de la muestra, necesito saber cual es la curva característica del sensor (si es lineal o no), para poder mapear o no el rango de valores.
¿Sabes algo al respecto?
Es decir, sabes donde puedo encontrar la curva característica? He estado mirando el Datasheet y la curva que te dan de respuesta normalizada (%) frente a longitud de onda no se si es la curva característica del sensor.
Por otro lado, cuando te habla de CS package y FN package ¿Qué diferencia hay?
Un saludo y gracias de antemano.
Diego Frutos Angosto
Por otro lado,
Arriba cuando muestras la gráfica de sensibilidad en función de la longitud de onda, ese tanto por ciento de sensibilidad, ¿Es la resolución que te da el sensor, es decir, el 100% sería los 65335 valores de salida?
Javier
Hola Victor,
Vuelvo a la carga con el proyectillo del colorímetro que te comentaba arriba. He visto que has realizado un artículo sobre el sensor TCS34725.
Me gustaría preguntarte varias cosas:
– Conoces alguna comparativa de los tres sensores de coolor con los que has trabajado (u otros más que conozca) en relación a cuál ofrece mejor resultados o la calidad de los datos obtenidos?
– Conoces si en algún lugar se pueden vender los sensores TCS34725 y TCS3200 con el encapsulado que aparece al principio de este artículo (CJMCU-115)?
– Y para finalizar, el tema de la calibración, has realizado alguna prueba para realizar la calibración o comprobar la calibración de los resultados obtenidos por estos sensores. He visto una web donde hablan sobre cómo calibrarlo el TCS3200. Aquí te la dejo por si te interesa: https://arduinoplusplus.wordpress.com/2015/07/15/tcs230tcs3200-sensor-calibration/
Un saludo
Javier
Emilio
Hola Victor.
He visto algunos artículos tuyos y no sé si puedes indicarme como generar varios colores con un diodo RGB y arduino, pero los colores deben tener cada uno cierta longitud de onda.
Se me ocurre hacerlo con dos circuitos, el primero capturar los parámeros de longitud de onda con un TCS3414, para configurar después los colores necesarios a emitir con el segundo circuito, pero no encuentro información sobre «longitudes de onda» a capturar primero y a emitir después.
Saludos