El TCS3472, de la compañía AMS, es un sensor de color formado por una matriz de 4×3 fotodiodos nueve de los cuales están dotados de filtros de color capaces de detectar la luz que incide en ellos separándola conforme a un modelo de color RGB y la iluminación sin filtrar en el caso de los tres restantes.
Su funcionamiento es similar en muchos aspectos al funcionamiento de la familia de sensores de color TCS330 y TCS341 por lo que puede tomarse como referencia lo que se explicó en el artículo sobre el sensor de color I2C TCS3414. Aunque el TCS3472 tiene una matriz de 12 fotodiodos es un dispositivo más avanzado que el TCS3414 con una matriz de 16 fotodiodos, por ejemplo, el margen dinámico del TCS3414 es de 1M:1 mientras que el del TCS3472 es de 3,8M:1, casi cuatro veces mayor. Por lo demás, comparten una resolución de 16 bits por canal, un bus I2C rápido, generación de una señal de interrupción o protección contra interferencias eléctricas y parpadeo de la iluminación. A diferencia del TCS3414, el TCS3472 no tiene entrada de señal SYNC.
Aunque con un rango dinámico mayor, la sensibilidad relativa al color del TCS3472 en función de la longitud de onda es similar a la del TCS3414: en general situada en la zona del espectro visible por el ojo humano, poco sensible a la luz ultravioleta y casi no detectan luz infrarroja gracias a un filtro que la atenúa y que, en realidad empieza en la parte visible, entorno a los 650 nm, como puede verse en la imagen anterior. Especialmente en el color rojo, la distribución de la sensibilidad no es totalmente proporcional a la humana; por eso y por la importante influencia de la iluminación con la que se realiza la medida será necesario realizar una especie de calibrado en el que se establezcan las condiciones iniciales de medida para establecer una posible corrección de los valores obtenidos.
Muchos de los módulos que existen en el mercado con los que realizar pruebas disponen de iluminación LED que se puede controlar desde del MCU, activándola o determinando su intensidad con PWM. En el segundo caso es importante sincronizar con los tiempos de medida ya que la protección contra parpadeos del TCS3472 está pensado para corregir la fluctuación de la corriente alterna y un pulso arbitrario podría falsear la medida de la iluminación en función de su distribución.
Aunque la iluminación LED de los módulos es muy práctica para las pruebas, es importante realizar el calibrado en las condiciones de lumínicas del montaje definitivo tanto en temperatura de color (longitud de onda) como en intensidad o disponer mandos hardware o software para poder calibrar in situ y en tiempo de ejecución el dispositivo.
La integración del TCS3472 en un circuito es muy sencilla, casi no necesita otros componentes. En caso de que existan importantes interferencias se puede disponer el clásico filtro de 10 µF y 100 nF en la alimentación, aunque normalmente no es necesario ya que, como se ha dicho, el TCS3472 incluye una protección interna. El bus I2C y la señal de interrupción necesitan sus correspondientes resistencias pull-up de, por ejemplo, 10 KΩ.
Actualmente, los sensores de la familia TCS3472 se fabrican en dos versiones: los TCS34725, que se alimentan a 3V3 y los TCS34727, que se alimentan a 1V8. Para simplificar su conexión a la mayoría de los microcontroladores, los módulos que lo integran, como el de la foto de arriba, incluyen regulación de tensión para poder conectarlos directamente también a 5 V. El antiguo TCS34721 corresponde con el actual TCS34725 y el TCS34723 con el TCS34727.
Software para controlar el TCS3472 por I2C
Utilizar el TCS3472 es muy sencillo, basta con acceder a sus registros, usando el bus I2C, para almacenar la configuración conforme a la que se desea que funcione o para leer los valores de color medidos. Aunque en diferentes direcciones, la configuración de los registros es similar a la de los sensores de color I2C de la familia TCS34 que se puede tomar como referencia.
Para repasar la forma de acceder al TCS3472 por medio del bus I2C desde Arduino tomemos como ejemplo el siguiente código que lee el identificador que se encuentra en la dirección 0x12.
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 |
#define DIRECCION_TCS3472 0x29 // Dirección del sensor TCS3472 en el bus I2C #define LEER_IDENTIFICADOR 0x12 // Leer el identificador del dispositivo #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 IDENTIFICADOR_TCS34725 0x44 // Código del identificador de los sensores TCS34721 o TCS34725 (3V3) #define IDENTIFICADOR_TCS34727 0x4D // Código del identificador de los sensores TCS34723 o TCS34727 (1V8) #include <Wire.h> void setup() { Serial.begin(9600); while(!Serial); // Esperar el inicio de las comunicaciones serie en Leonardo Wire.begin(); Wire.beginTransmission(DIRECCION_TCS3472); // Iniciar las comunicaciones con el TCS3472 Wire.write(ENVIAR_ORDEN|LEER_IDENTIFICADOR); Wire.endTransmission(); // Liberar las comunicaciones y vaciar la cola de datos Wire.beginTransmission(DIRECCION_TCS3472); // Iniciar las comunicaciones con el TCS3472 Wire.requestFrom(DIRECCION_TCS3472,1); Serial.print("En la dirección 0x"); Serial.print(DIRECCION_TCS3472,HEX); Serial.print(" hay conectado un "); switch(Wire.read()) { case IDENTIFICADOR_TCS34725: Serial.println("TCS34721 o un TCS34725"); break; case IDENTIFICADOR_TCS34727: Serial.println("TCS34723 o un TCS34727"); break; default: Serial.println("componente desconocido"); } Wire.endTransmission(); // Liberar las comunicaciones y finalizar el envío de datos } void loop() { } |
Como puede verse en las directivas #define
del principio del código anterior, el TCS3472 (un TCS34725 en mi caso) se encuentra en la dirección I2C 0x29. El registro de configuración está en la dirección 0x12 y devuelve uno de dos valores: 0x44 para el TCS34721 y el TCS34725 o 0x4D para el TCS34723 y el TCS34727.
El «registro» de operación (que no tiene dirección) tiene el bit más significativo a nivel alto para indicar que se trata de una orden. Para que sea más sencillo leerlo, se hace una operación OR con la orden que se solicita al TCS3472 y el valor del «registro» de operación, que en este caso solo usa el bit número 7.
Como he conectado un TCS34725 al bus I2C de un Arduino Leonardo, la salida que muestra en la consola serie el programa anterior corresponde con la captura de pantalla de abajo.
El procedimiento para acceder al TCS3472 es el habitual cuando se usa el bus I2C:
- Incluir la librería en el código con
#include <Wire.h>
- Inicializar la librería con
Wire.begin();
- Acceder al TCS3472 pasando la dirección 0x29 a
Wire.beginTransmission
- Enviar los códigos de operación con
Wire.write
- Si se van a leer datos desde el TCS3472 se usa
Wire.requestFrom
pasando como primer argumento la dirección del dispositivo y como segundo el número de bytes - Si se esperan datos,
Wire.available()
informa de los que hay disponibles - Los datos se leen con
Wire.read()
- Aunque no sea imprescindible, es una buena práctica liberar el bus con
Wire.endTransmission();
y vaciar la cola de datos cuando termine una operación. Después habrá que restablecer las comunicaciones conWire.beginTransmission
. De esta forma se puede acceder a otros dispositivos que usen el mismo bus I2C. En el código anterior se podrían haber eliminado las líneas 17 y 18 pero se han incluido como ejemplo para ilustrar esta recomendación.
El dato del identificador (un byte) se ha leído directamente con Wire.read()
después de solicitarlo con Wire.requestFrom
. Esta fórmula seguramente funcione sin problemas en buena parte de las ocasiones pero lo más correcto es esperar, consultando con Wire.available()
, a que estén disponibles los datos que se han solicitado. Para evitar que un error produzca una espera infinita debe imponerse también un tiempo de espera (timeout) máximo.
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 |
#define DIRECCION_TCS3472 0x29 // Dirección del sensor TCS3472 en el bus I2C #define LEER_IDENTIFICADOR 0x12 // Leer el identificador del dispositivo #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 IDENTIFICADOR_TCS34725 0x44 // Código del identificador de los sensores TCS34721 o TCS34725 (3V3) #define IDENTIFICADOR_TCS34727 0x4D // Código del identificador de los sensores TCS34723 o TCS34727 (1V8) #define TIMEOUT_I2C 20 // Esperar 20 ms antes de renunciar a leer el bus I2C #include <Wire.h> unsigned long cronometro_i2c; void setup() { Serial.begin(9600); while(!Serial); // Esperar el inicio de las comunicaciones serie en Leonardo Wire.begin(); Wire.beginTransmission(DIRECCION_TCS3472); // Iniciar las comunicaciones con el TCS3472 Wire.write(ENVIAR_ORDEN|LEER_IDENTIFICADOR); Wire.endTransmission(); // Liberar las comunicaciones y finalizar el envío Wire.beginTransmission(DIRECCION_TCS3472); // Iniciar las comunicaciones con el TCS3472 Wire.requestFrom(DIRECCION_TCS3472,1); 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 hay un byte disponible { Serial.print("En la dirección 0x"); Serial.print(DIRECCION_TCS3472,HEX); Serial.print(" hay conectado un "); switch(Wire.read()) { case IDENTIFICADOR_TCS34725: Serial.println("TCS34721 o un TCS34725"); break; case IDENTIFICADOR_TCS34727: // TCS34723 o TCS34727 Serial.println("TCS34723 o un TCS34727"); break; default: Serial.println("componente desconocido"); } } else // Si no hay un byte disponible (la espera ha terminado porque ha pasado el tiempo, no porque ha llegado el dato) { Serial.println("Sensor desconocido. No se ha recibido el identificador."); } Wire.endTransmission(); // Liberar las comunicaciones y vaciar la cola de datos que se envían al TCS3472 } void loop() { } |
En el código anterior se han resaltado las líneas con las que se implementa la espera de datos desde el bus I2C. En la línea 8 se define una constante para la espera máxima, en la 12 un cronómetro para calcular, usando millis()
en la 24, el tiempo que ha transcurrido y en la línea 25 se detiene el programa hasta que llegue el byte que se esperan. Para verificar que se ha salido del bucle de espera porque han llegado los datos, en la línea 26 se vuelven a comprobar y se muestra el resultado o un mensaje de error entre las líneas 43 a 46 según la cantidad de datos sea correcta o no.
La otra alternativa es iniciar la solicitud de datos con Wire.requestFrom()
y realizar la lectura a la llegada de la información con una función que se habrá configurado con Wire.onReceive()
. Es este caso, para no encerrar en la interrupción el código, lo aconsejable es utilizar una variable indicador que se active a la llegada de datos; cuando esta variable esté activada el programa leerá los datos.
Medida básica de color con el sensor TCS3472
La forma más sencilla, y más usada, de obtener el color con el sensor TCS3472 consiste, simplemente, en configurar el dispositivo y leer las dos direcciones de los dos registros que almacenan cada canal. La forma de hacerlo es muy parecida a lo que se explicó en el artículo sobre el sensor de color I2C TCS3432 que puede tomarse como referencia.
La preparación mínima antes de realizar la lectura debe incluir la activación del dispositivo y la activación de la conversión analógica-digital. En principio, después de solicitar la lectura de los registros de color, hay que esperar el tiempo de integración que corresponda aunque, en función de cómo sea el programa que realiza la lectura, es muy probable que ese tiempo quede incluido en otras operaciones y podrá sustituirse por una verificación de que ha transcurrido o un cálculo para asegurarse de que la parte del código que se ejecuta en la circunstancia más favorable incluye al menos el tiempo de integración.
Para simplificar la lectura, el siguiente código para Arduino omite la configuración y usa un valor de integración muy alto. Cuando el programa que rige el microcontrolador se limita a leer el sensor es admisible usar delay
para forzar la espera pero si hay otras tareas que ejecutar lo correcto será verificar que el tiempo de integración ha transcurrido en lugar de detener el programa. En la mayoría de los casos el programa estará funcionando hasta que un evento (la muestra ha llegado de forma estable al sensor) indique que debe empezar la lectura y si hay intervención humana, debe implementarse algún tipo de aviso (como un sonido o un indicador luminoso) para avisar de que se ha terminado la lectura o proceder, quizá solamente esperar, con la siguiente lectura.
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 |
#define DIRECCION_TCS3472 0x29 // Dirección del sensor TCS3472 en el bus I2C #define ACTIVAR 0x00 // Registro de activación #define PRIMER_COLOR 0x14 // Direcciones del primer byte de los cuatro canales de color (RGB+L) #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 ACTIVAR_TCS3472 0B00000001 // Bit de activación del (reloj del) TCS3472 #define ACTIVAR_ADC 0B00000010 // Bit de activación de la conversión analógica digital del color #define TIMEOUT_I2C 20 // Esperar 20 ms antes de renunciar a leer el bus I2C #define ESPERA_INTEGRACION 615 // Esperar la conversión AD del color. Como no se ha establecido (para simplificar) se supone el máximo para WTIME 0xFF (2.4ms) (256-0)*2.4ms=614.4 Bastaría con esta espera para todos los canales pero como se lee de uno en uno, por seguridad, hay que esperar cada vez #define ESPERA_ENTRE_LECTURAS 3000 // Mostrar el color cada tres segundos #include <Wire.h> String nombre_color[4]={"Luz","Rojo","Verde","Azul"}; unsigned long cronometro_i2c; // Cronómetro para medir el tiempo que lleva esperando la lectura del bus I2C para comprobar si se ha superado el tiempo máximo unsigned int valor_color; // Resultado de unir los dos bytes de cada color unsigned char numero_color; // Número de color que se está leyendo void setup() { Serial.begin(9600); while(!Serial); // Esperar el inicio de las comunicaciones serie en Leonardo Wire.begin(); // Inicializar la librería Wire para las comunicaciones I2C Wire.beginTransmission(DIRECCION_TCS3472); // Comunicar con el dispositivo en la dirección DIRECCION_TCS3472 Wire.write(ACTIVAR|ENVIAR_ORDEN); // Enviar la orden de activación Wire.write(ACTIVAR_TCS3472|ACTIVAR_ADC); // Activar el dispositivo y la integración Wire.endTransmission(); // Liberar el bus I2C y vaciar la cola de datos delay(250); // Espera (arbitraria) de estabilización } void loop() { for(numero_color=0;numero_color<4;numero_color++) { valor_color=0; Wire.beginTransmission(DIRECCION_TCS3472); Wire.write((PRIMER_COLOR+2*numero_color)|ENVIAR_ORDEN); // Modo de lectura de bytes del primer byte (bajo) del color que corresponda Wire.endTransmission(); delay(ESPERA_INTEGRACION); Wire.beginTransmission(DIRECCION_TCS3472); Wire.requestFrom(DIRECCION_TCS3472,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=Wire.read(); // Byte menos significativo valor_color|=(unsigned int)(Wire.read())<<8; // Byte más significativo, rotar un byte y sumar (OR) al otro byte Serial.print(nombre_color[numero_color]); Serial.print(": "); Serial.println(valor_color); } else // Si no han llegado dos bytes se ha producido un error al leer el dispositivo I2C { Serial.print("Error al leer el canal "); Serial.println(nombre_color[numero_color]); } } Serial.println(); delay(ESPERA_ENTRE_LECTURAS); } |
En la línea 25 del código anterior se activa el dispositivo y la integración enviando como código el resultado de una operación OR de los bis de ambas operaciones. Como se vio en el primer ejemplo, para enviar una orden al TCS3472 hay que usar el registro correspondiente con el bit del modo operación activo, como en la línea 24 del ejemplo.
En la línea 36 del código anterior se solicita al TCS3472 el valor de un canal. Como los canales están ordenados, solamente es necesario conocer la dirección del primero y bastará con ir incrementándola en dos bytes (cada color se expresa como un valor de 16 bits)
Antes de solicitar los dos bytes del valor del canal en la línea 40, se debe esperar (línea 38) el tiempo que se haya configurado para la integración. Para calcular el valor del color con los dos bytes de cada canal basta con rotar el del segundo byte (el más significativo) los 8 bits que corresponden a su posición en el entero sin signo, como puede verse en las líneas 45 y 46 del código de arriba.
Configuración del sensor TCS3472
Del sensor de color TCS3472, dicho a grandes rasgos, los parámetros que se pueden configurar son la ganancia, el tiempo de integración y los valores de interrupción. Para aclarar un poco más estos conceptos y no alargar demasiado este texto, se puede consultar, como he recomendado antes, el artículo sobre la familia de sensores de color I2C TCS340 y TCS341, que puede servir como referencia al ser su funcionamiento muy parecido al del TCS3472.
Para configurar la ganancia se utilizan los dos bits menos significativos del registro de control, que se encuentra en la dirección 0x0F del TCS3472. Los cuatro posibles multiplicadores para la ganancia que se pueden elegir son 1, 4, 16 y 60 que corresponden, por ese orden, con los cuatro valores que los dos bits pueden representar.
Como es lógico, para elegir la ganancia adecuada habrá que conocer el contexto en el que se realiza la medida, para lo que será necesario realizar pruebas (una especie de calibración) con la iluminación y los patrones de color máximo y mínimo que se muestrean, añadiendo, además, unos márgenes de seguridad en los valores para no recortar los más extremos. Con esa misma «calibración» se podrán establecer coeficientes para corregir posibles errores producidos, principalmente, por el color de la iluminación.
El registro para configurar el tiempo de integración se encuentra en la dirección 0x01 y se establece determinando los ciclos de integración a intervalos de 2,4 ms. El valor que se almacena en el registro se resta de 256 y se multiplica por 2,4. Por ejemplo, si se almacena 246 el tiempo resultante sería 256-246=10 ciclos de integración que, multiplicados por 2,4 ms resultan 24 ms.
El TCS3472 puede generar una interrupción para avisar de dos tipos de eventos: que la lectura de la iluminación se sale de un rango máximo y mínimo o que el ciclo de lectura ha terminado. Esta segunda opción permite muestrear el valor solamente cuando la integración haya terminado y evita que el programa tenga que esperar deteniéndose.
Para configurar la interrupción se utiliza el registro de activación, en la dirección 0x00, cuyo bit número uno controla la generación de una interrupción al concluir la conversión analógica-digital y el registro de persistencia, en la dirección 0x0C. El segundo registro, cuando vale cero, utiliza la interrupción para avisar del fin de la integración o para establecer el número de veces que debe rebasarse el rango de iluminación. Los valores máximos y mínimos del umbral de iluminación se almacena en los registros que hay en las direcciones 0x04-0x05 y 0x06-0x07.
Lectura del sensor de color TCS3472 usando las interrupciones
La función del programa que se llama cuando se genera la interrupción debe contener el mínimo de código necesario para realizar la lectura por lo que lo habitual, como decía al principio, es cambiar el estado de una variable indicador de estado (flag) y ceder a alguna parte del bucle principal la gestión (por supuesto, a su vez, podrá delegarla a otras funciones).
Es importante desactivar las interrupciones mientras se realiza la lectura o, en general, hasta que el programa esté listo para gestionar la siguiente interrupción. Por claridad y para ilustrar la forma habitual de hacerlo, en el siguiente código de ejemplo se utiliza el método genérico, detachInterrupt()
, aunque no es imprescindible, ya el sensor de color TCS3472 no generará una nueva interrupción. Para que se vuelvan a generar interrupciones habrá que borrar la actual usando el registro de comando con los bits de tipo (5 y 6) y los de modo de borrado (1 y 2) activos.
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 |
#define DIRECCION_TCS3472 0x29 // Dirección del sensor TCS3472 en el bus I2C #define ACTIVAR 0x00 // Registro de activación #define PRIMER_COLOR 0x14 // Dirección del primer byte (luz sin filtrar) de los cuatro canales de color (RGB+L) #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 ELIMINAR_INTERRUPCIONES 0B01100110 // Borrar las interrupciones pendientes #define ACTIVAR_INTERRUPCION 0B00010000 // Bit de activación de la interrupción #define ACTIVAR_TCS3472 0B00000001 // Bit de activación del (reloj del) TCS3472 #define ACTIVAR_ADC 0B00000010 // Bit de activación de la conversión analógica digital del color #define CAMBIAR_TIEMPO_INTEGRACION 0x01 // Establecer el tiempo de integración (256-tiempo)*2.4ms #define MAXIMO_TIEMPO_INTEGRACION 0 // (256-0)*2.4ms=614.4ms (máximo) #define CAMBIAR_GANANCIA 0x0F // Los bits menos significativos del registro de control (0x0F) sirven para establecer la ganancia #define GANANCIA_1X 0B00000000 // El mismo valor que se lea #define GANANCIA_4X 0B00000001 // Cuatro veces el valor leído #define GANANCIA_16X 0B00000010 // 16 veces el valor leído #define GANANCIA_60X 0B00000011 // 60 veces el valor leído #define ESTABLECER_INTERRUPCION 0x0C // Establecer cómo generar la interrupción #define INTERRUPCION_INTEGRACION 0B00000000 // Valor del registro 0x0C que hace que se genere una interrupción cuando termina el proceso de integración #define TIMEOUT_I2C 20 // Esperar 20 ms antes de renunciar a leer el bus I2C #define ESPERA_ESTABILIZACION 50 // Milisegundos de espera para dar tiempo a estabilizar el TCS3472. Es un valor arbitrario (superior al máximo probado) En principio, el mínimo necesario después de la activación del dispositivo es menor de 10 ms y los otros solo se incluyen por seguridad. #define PIN_INTERRUPCION 2 #include <Wire.h> String nombre_color[4]={"Luz","Rojo","Verde","Azul"}; // Nombres de los canales de color (en el mismo orden que los registros) unsigned long cronometro_i2c; // Cronómetro para medir el tiempo que lleva esperando la lectura del bus I2C para comprobar si se ha superado el tiempo máximo unsigned int valor_color; // Resultado de unir los dos bytes de cada color unsigned char numero_color; // Número de color que se está leyendo bool lectura_disponible=false; // Indicador que se activa cuando la interrupción indica que la integración está lista byte interrupcion=digitalPinToInterrupt(PIN_INTERRUPCION); // Número de interrupción utilizada (en función del pin al que se conecta) unsigned long cronometro_integracion; // Cronómetro para medir (verificar) lo que tarda en realizarse la lectura void setup() { Serial.begin(9600); Wire.begin(); // Inicializar la librería Wire para las comunicaciones I2C Wire.beginTransmission(DIRECCION_TCS3472); // Comunicar con el dispositivo en la dirección DIRECCION_TCS3472 Wire.write(ACTIVAR|ENVIAR_ORDEN); // Enviar la orden de activación Wire.write(ACTIVAR_TCS3472|ACTIVAR_ADC); // Activar el dispositivo y la integración Wire.endTransmission(); // Liberar el bus I2C delay(ESPERA_ESTABILIZACION); // Espera (arbitraria) de estabilización Wire.beginTransmission(DIRECCION_TCS3472); // Comunicar con el dispositivo en la dirección DIRECCION_TCS3472 Wire.write(ACTIVAR|ENVIAR_ORDEN); // Enviar la orden de activación Wire.write(ACTIVAR_TCS3472|ACTIVAR_ADC|ACTIVAR_INTERRUPCION); // Activar el dispositivo, la integración y la interrupción Wire.endTransmission(); // Liberar el bus I2C delay(ESPERA_ESTABILIZACION); // Espera (arbitraria) de estabilización Wire.beginTransmission(DIRECCION_TCS3472); Wire.write(ESTABLECER_INTERRUPCION|ENVIAR_ORDEN); // Configurar la interrupción… Wire.write(INTERRUPCION_INTEGRACION); // …para generarla cuando se termine la conversión analógica digital del color Wire.endTransmission(); // Liberar el bus I2C delay(ESPERA_ESTABILIZACION); // Espera (arbitraria) de estabilización Wire.beginTransmission(DIRECCION_TCS3472); Wire.write(CAMBIAR_TIEMPO_INTEGRACION|ENVIAR_ORDEN); // Configurar el tiempo de integración… Wire.write(MAXIMO_TIEMPO_INTEGRACION); // …al máximo posible Wire.endTransmission(); // Liberar el bus I2C delay(ESPERA_ESTABILIZACION); // Espera (arbitraria) de estabilización Wire.beginTransmission(DIRECCION_TCS3472); Wire.write(CAMBIAR_GANANCIA|ENVIAR_ORDEN); // Configurar la ganancia… Wire.write(GANANCIA_4X); // …a cuatro veces el valor normal Wire.endTransmission(); // Liberar el bus I2C delay(ESPERA_ESTABILIZACION); // Espera (arbitraria) de estabilización cronometro_integracion=millis(); attachInterrupt(interrupcion,leer_color,FALLING); // Asignar la interrupción al pin que se ha conectado el TCS3472 } void loop() { if(lectura_disponible) { detachInterrupt(interrupcion); // Desactivar la interrupción (esta es la forma genérica pero, en realidad, el TCS3472 no generará una nueva interrupción hasta no borrar la actual) Serial.println(millis()-cronometro_integracion); // ¿Cuánto ha tardado la integración? cronometro_integracion=millis(); // Preparar la medida de la próxima integración for(numero_color=0;numero_color<4;numero_color++) // Leer todos los registros de color { valor_color=0; Wire.beginTransmission(DIRECCION_TCS3472); Wire.write((PRIMER_COLOR+2*numero_color)|ENVIAR_ORDEN); // Modo de lectura de bytes del primer byte (bajo) del color que corresponda Wire.endTransmission(); Wire.beginTransmission(DIRECCION_TCS3472); Wire.requestFrom(DIRECCION_TCS3472,2); // Pedir los dos bytes que determinan el valor del color en curso 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=Wire.read(); // Byte menos significativo valor_color|=(unsigned int)(Wire.read())<<8; // Byte más significativo, rotar un byte y sumar (OR) al otro byte Serial.print(nombre_color[numero_color]); Serial.print(": "); Serial.println(valor_color); } else // Si no han llegado dos bytes se ha producido un error al leer el dispositivo I2C { Serial.print("Error al leer el canal "); Serial.println(nombre_color[numero_color]); } } Serial.println(); Wire.beginTransmission(DIRECCION_TCS3472); Wire.write(ELIMINAR_INTERRUPCIONES|ENVIAR_ORDEN); // Borrar la interrupción actual para permitir que se genere otra al terminar la integración Wire.endTransmission(); lectura_disponible=false; attachInterrupt(interrupcion,leer_color,FALLING); // Volver a asignar la interrupción que se desactivó con detachInterrupt() } } void leer_color() { lectura_disponible=true; // indicar que ha terminado la integración } |
En la primera parte del código del ejemplo anterior se definen las direcciones de los registros y de las configuraciones de los bits correspondientes. De esta forma es más sencillo comprender la finalidad de cada operación que se realiza con ellos.
Después de dar nombre a los registros y a los bits de sus distintas configuraciones, se carga la librería Wire de Arduino con la que se gestionan las comunicaciones I2C con el sensor de color TCS3472.
Entre las líneas 24 y 30 se declaran y, en su caso, se inicializan las variables. Respecto del código del apartado anterior, en este ejemplo se ha añadido una variable que almacena el número de interrupción calculado a partir del pin al que se conecta. Otra diferencia de este programa es el cronómetro que se utiliza para monitorizar (solo para verificar) cuánto tarda en realizarse la integración.
En el bloque setup()
se activa el dispositivo y la integración y posteriormente la interrupción. Para asegurar que la segunda operación se realiza cuando la primera se ha concluido se espera un intervalo arbitrario. En principio bastaría con un ciclo de 2,4 ms pero, como se ejecuta solo una vez y por seguridad, se usa un valor sobradamente alto (50 ms). Aunque solamente es imprescindible esperar a esa operación, se ha añadido una espera a cada cambio de configuración ya que supone poco retraso y se produce una única vez en el programa y da seguridad de que el cambio de configuración ha tenido efecto.
También en el bloque setup()
, se ha configurado el tiempo de integración más alto posible para que se pueda apreciar en las medidas, que se toman en milisegundos, y así verificar que está funcionando en el rango previsto. Como ejemplo, también se ha configurado un valor para la ganancia de 4X que sirve para ver que el valor incrementa sensiblemente.
La principal diferencia que se ha introducido en el bloque loop()
es realizar la lectura solo si la interrupción ha activado el indicador lectura_disponible
. Como se decía al principio, mientras se procesa la lectura se desactiva la interrupción con detachInterrupt()
(aunque no sea necesario en este caso) y se vuelve a establecer al terminar la lectura. Para reiniciar las interrupciones del TCS3472, y esto sí es imprescindible, se restablece el registro de persistencia en la dirección 0x0C como explicaba en el apartado anterior.
Jonatan
Donde se puede conseguir este sensor
Víctor Ventura
Hola, Jonatan.
Como escribo desde España y creo que tú estás bastante lejos de aquí, no puedo recomendarte un distribuidor local, que sería lo deseable, pero puedes conseguirlo en tiendas internacionales. El TCS34725, que es el más usado de la familia de los TCS3472, puedes encontrarlo fácilmente, por ejemplo en:
Gracias por visitar polaridad.es