El objetivo de esta primera práctica que vamos a realizar con Arduino, será la construcción de un prototipo a modo de toma de contacto. Esto nos permitirá ilustrar la resolución de una serie de obstáculos que han de considerarse en la consecución de nuestro objetivo de desarrollar un Controlador de Acuarios por Ordenador.
En el vídeo, de cinco minutos y medio, las explicaciones son demasiado concisas porque no interesaba otra cosa que mostrar el prototipo y el principal requisito era hacer un vídeo muy cortito que sirva para mostrar el prototipo funcionando.
En este artículo vamos a explicarlo todo con mayor detalle. Quizás sea recomendable volver a ver el vídeo una vez que se termine de asimilar el presente artículo para poder interpretar correctamente todo lo que aparece en él.
Este es un prototipo sencillo pero completito. No es lo más indicado tomarlo como primer ejercicio con Arduino. Por ello, y a pesar de que iremos avanzando poco a poco, lo más adecuado para los que no tienen experiencia con a Arduino, sería practicar con los ejemplos más básicos, tales como hacer parpadear un LED. Véase Tutorial: Comenzando con Arduino
El esquema de la electrónica que hemos conectado al Arduino podéis apreciarlo en la imagen siguiente:
1) Iluminación:
Al implementar un rudimentario sistema de control horario pretendemos superar con las limitaciones de los temporizadores tradicionales de acuarios que controlan un solo circuito eléctrico para la iluminación. Algunos temporizadores programables permiten un tipo de flexibilidad que permiten definir comportamientos diferentes en función de los días de la semana y cosas así, pero los peces, no entiende de días de la semana. A lo que estos sí son sensibles la mayoría de los seres vivos es al fotoperiodo (relación noche/día que tienen una variación estacional). Los seres vivos, incluidos los peces, sincronizan su ciclo reproductivo para aprovechar los patrones estacionales usando el fotoperiodo como indicador para desencadenar sus comportamientos estacionales. También son sensibles a la forma gradual en que se pasa del día a la noche y viceversa. Por ejemplo: algunos cíclidos que cuidan su prole necesitan acomodar a los alevines recogiéndoles en su nido, y un apagado brusco de la luz les puede sorprender en mal momento con parte de los alevines dispersados de forma nada conveniente para pasar la noche.
Estos dos factores, fotoperiodo y las transiciones crepusculares factores son especialmente importantes para la cría de determinadas especies y no se tienen en cuenta en la mayoría de los acuarios domésticos.
Para esta primera prueba estableceremos un programa para controlar tres tipos de iluminación diferentes que llamaremos iluminación nocturna (luz azul), iluminación crepuscular (blanco cálido) e iluminación diurna (luz blanca intensa + blanco cálido). Para esta primera prueba se han establecido unos horarios fijos:
- Luz azul : de 22:00 a 8:00
- Luz crepuscular: de 8:00 a 22:00
- Luz diurna : de 9:30 a 20:30
Como la verificación de todo ello en un periodo de 24 horas resultaría poco cómoda, para esta demo, el ciclo horario de iluminación se ejecutará 5000 veces más rápido de lo normal. Es decir, el día de 24 horas en esta demo durará poco más de 17 segundos.
2) Calibración de sensores:
También vamos a probar las capacidades de Arduino para la lectura de sensores. Vamos a hacer que un sensor de luz con una simple fotoresitencia LDR, se pueda auto ajustar a partir de lecturas de referencia mínima y máxima, para así provocar una respuesta de iluminación variable en un LED dentro de los rangos de iluminación deseados.
3) Concurrencia:
Programaremos todo ello de forma que las tareas automatizadas por Arduino se gestionen de forma simultánea sin introducir retrasos las unas en las otras. Todas las funciones que se activan desde la función loop(), estarán diseñadas para ser muy breves con objeto de no ir acumulando los posibles retrasos en las diferentes tareas. Para que esto quede claro, haremos que una de estas tareas concurrentes haga parpadear un LED a una velocidad constante de un ciclo por segundo, con independencia de las restantes tareas.
4) Recepción de comandos por puerto serie:
Por último, implementaremos la posibilidad de aceptar comandos muy sencillos a través del puerto serie.
Nuestra base de tiempos funcionará usando el contador de milisegundos de Arduino. Este se va incrementando desde cero a partir del momento en el que arranca. Para esta demo eso nos basta, pero para un caso real hay que considerar que antes de que pasen 50 días este contador se desbordaría y recomenzaría en cero. Esto es un problema que tendremos que tratar en futuras versiones. Iremos resolviendo uno a uno los diferentes problemas hasta alcanzar una versión plenamente funcional para automatizar acuarios reales.
Código fuente del programa:
El código fuente que para finalizar se muestra a continuación, incluye abundantes comentarios:
/********************************************************* (C) Antonio Castro Snurmacher 2013 Bajo licencia Creative Commons (BY-NC-SA) Reconocimiento - NoComercial - CompartirIgual (by-nc-sa) CAO_Proto01 ----------- 1) Un led parpadeara de forma continua indicando con ello que el programa está funcionando y que otras tareas pueden realizarse de forma concurrente sin afectarse sensible- mente unas a otras. 2) Existira un sensor de luz y un LED asociado al mismo. Al inicio parpadeara indicando que necesita ser calibrado. Mientras esto ocurra hay que someter al sensor a su valor minimo de luz y a su valor maximo para que estos sean registrados como tales. Transcurrido el tiempo de calibra- ción, el LED dejará de parpadear y lucira variando en in- tensidad dependiendo de la luz recibida en el sensor dentro del rango maximo y mínimo registrado. Un pulsador permitira recalibrar el sensor a voluntad. 3) Al inicio las trazas estan activas y la inicializacion de los horarios se envia al puerto serie. Este prototipo define tres horarios de iluminación para: Luz_noctura, luz_crespuscular y luz_diurna. 4) El programa escucha por la entrada del puerto serie, para identificar tres comandos diferentes: "INISENS", "TRAZAON", y "TRAZAOFF". NOTA: Algunas funciones han tomado la idea en fragmentos de codigo fuente de dominio publico para Arduino. Vease: http://arduino.cc/es/Tutorial/Calibration *********************************************************/ const int PinBlink_1Hz=13; // salida digital const int PinLuzNocturna=12; // salida digilal const int PinLuzCrepuscu=11; // salida digital const int PinLuzDiurna=10; // salida digital const int PinLedSensor = 5; // salida digital PWM const int PinPulsador =2; // entrada digital para // el pulsador. // Entrada analogica para el sensor const int sensorPin = 3; // Variables boolean H_LuzNocturna[24][60], H_LuzCrepuscu[24][60], H_LuzDiurna[24][60]; int sensorValue = 0; // the sensor value int sensorMin = 1023; // minimum sensor value int sensorMax = 0; // maximum sensor value boolean SensorPreparado = false; // 5 segundos para calibrar el Sensor const unsigned long TimeOutSetingSens1= 5000L; // Si vale menos de TimeOutSetingSens1 se activara la // calibración del sensor y se desactivara la lectura del // mismo. unsigned long IniSensorAt = 0L; char CadAux[333]; boolean DebugOn=false; /********************************************************* Identifica una cadena de caracteres. (Optimizable) *********************************************************/ boolean Tok(char *buff, char *tok){ int i=0; sprintf(CadAux,"buff=(%s), tok=(%s)\n", buff, tok); Debug(CadAux, 0); while (tok[i]!='\0'){ if (tok[i]!=buff[i]) return false; i++; } return true; } /********************************************************* No lee hasta que se hace return (la consola no envia los datos hasta entonces) y lee todos los caracteres disponibles en el buffer de entrada. La funcion leera todos los caracteres disponibles de un tiron, o en su caso, si llegan mas de, MaxBuffIn caracteres, vaciara el buffer de entrada y retornara error. (Hay que dejar pausa entre comandos. SerialGetCommand no recibira comandos si vienen apelotonados). Despues de leer el comando lo procesara o devolverá error. ********************************************************/ void SerialGetCommand() { // Maxima longitud del buffer de recepcion. int MaxBuffIn=128; unsigned i=0; char sIn; char BUFF[MaxBuffIn]; // Si no hay nada por leer retornar sin mas dilación if (Serial.available()==0){ return; } // Ya hay algo pero puede que aun no esten disponibles // todos los caracteres enviados. Por eso forzaremos // una espera de 50 milisegundos. (Optimizable) delay(50); if (Serial.available()>=MaxBuffIn){ // Vaciar Buffer de entrada (demasiados caracteres). while (Serial.available()){ Serial.read(); } Serial.print("ERROR: Comando muy largo\n"); return; } // Queremos que la cadena se rellene hasta // MaxBuffIn-2 para que en el carácter MaxBuffIn-1 // podamos meter el terminador \0 --MaxBuffIn; while (Serial.available() && i=TimeOutSetingSens1) return; // sobrepasado el tiempo del setup Blink_KPeriod(PinLedSensor, 1000/8); // Parpadeo a 8Hz sensorValue = analogRead(sensorPin); if (sensorValue > sensorMax) { sensorMax = sensorValue; } if (sensorValue < sensorMin) { sensorMin = sensorValue; } sprintf(CadAux, "** CalibraSensor() min=%d, max=%d\n", sensorMin, sensorMax); Debug(CadAux, 200); } /********************************************************* Lee el valor del sensor y provoca la intensidad de brillo en el LED que corresponda. *********************************************************/ void TaskSensor() { // Para sacar una traza la primera vez que entre // una vez calibrado el sensor. if (!SensorPreparado){ sprintf(CadAux, "\n** OK *** sensorMin=%d sensorMax=%d *****\n", sensorMin, sensorMax); Debug(CadAux, 0); } sensorValue = analogRead(sensorPin); sensorValue = map(sensorValue, sensorMin, sensorMax, 0, 255); sensorValue = constrain(sensorValue, 0, 255); analogWrite(PinLedSensor, sensorValue); SensorPreparado= true; } /********************************************************* Comprueba el pulsador y en su caso resetea el sensor para forzar el recalibrado. Si es el turno de recalibrar proceder a ello y sino proceder con la tarea asociada al sensor. (LED de intensidad variable) ********************************************************/ void Sensor() { int p; p=digitalRead(PinPulsador); if (p==HIGH){ // Si pulsador pulsado--> recalibrar SetupSensor(); delay(500); } // Hasta que no pasen "TimeOutSetingSens1" milisegs no termina la autocalibración if ((millis()-IniSensorAt)=FromM) HR[h][m]=Val; if (h>FromH & h overflow un poco antes de los 50 días. ********************************************************/ void ProcFotoPeriodo(boolean HR[24][60], int Pin){ unsigned long s, h, m, d; // Velocidad real del reloj (dias de 24 horas) // s = millis() / 1000; // Multiplicamos la velocidad del reloj * 5000 // (dias de 17 segundos con 280 milis) s = millis() * 5; m = s / 60; s %= 60; h = m / 60; m %= 60; d = h / 24; h %= 24; if (HR[h][m]){ digitalWrite(Pin, HIGH); } else{ digitalWrite(Pin, LOW); } } /********************************************************* Hace parpadear un led con un periodo (=1/frec.) determinado. ********************************************************/ void Blink_KPeriod(int Pin, int KPeriod){ if ((millis()/KPeriod) % 2){ digitalWrite(Pin, HIGH); } else{ digitalWrite(Pin, LOW); } } /****************************************************/ /**** INICIALIZACIÓN DE LAS TAREAS AL ARRANCAR ******/ /****************************************************/ void setup(){ DebugOn=true; // Activar trazas. if (DebugOn){ Serial.begin(9600); Debug("\n\n\nEstamos haciendo setup()\n\n", 2000); } pinMode(PinBlink_1Hz, OUTPUT); pinMode(PinLuzNocturna, OUTPUT); pinMode(PinLuzCrepuscu, OUTPUT); pinMode(PinLuzDiurna, OUTPUT); // EStablecer horarios iniciando matrices. SetHorario(H_LuzNocturna, 0, 0,23,59, true); SetHorario(H_LuzNocturna, 8, 0,22, 0, false); SetHorario(H_LuzCrepuscu, 0, 0,23,59, false); SetHorario(H_LuzCrepuscu, 8, 0,22, 0, true); SetHorario(H_LuzDiurna, 0, 0,23,59, false); SetHorario(H_LuzDiurna, 9,30,20,30, true); /************* TRAZAS HORARIAS *************/ if (DebugOn){ Debug("\n\nHorario de luz nocturna\n", 0); MostrarHorario(H_LuzNocturna); Debug("\n\nHorario de luz crepuscular\n", 0); MostrarHorario(H_LuzCrepuscu); Debug("\n\nHorario de luz diurna\n", 0); MostrarHorario(H_LuzDiurna); } /***********************************************/ SetupSensor(); CalibraSensor(); } /****************************************************/ /******* BUCLE PRINCIPAL DE CONTROL DE TAREAS *******/ /****************************************************/ void loop(){ ProcFotoPeriodo(H_LuzNocturna, PinLuzNocturna); ProcFotoPeriodo(H_LuzCrepuscu, PinLuzCrepuscu); ProcFotoPeriodo(H_LuzDiurna, PinLuzDiurna); // Parpadeo a 1Hz Blink_KPeriod(PinBlink_1Hz, 1000); Sensor(); SerialGetCommand(); }
Manuel
Hola Antonio, acabo de empezar con mis primeros diseños sencillos en Arduino y me interesa el CAO que has desarrollado. Entiendo bastantes cosas del código escrito pero veo que aún estoy un poco verde.
He cogido tú código y al compilarlo me da error en: while (tok[i]!=»){
Me podrías decir como solucionarlo?
Saludos,
Manuel
Antonio Castro
Mi código no contiene ese error porque también a mí me daría error. No me comentas que programa has bajado, ni en que linea da el error ni que dice el error aunque esto último no es necesario porque eso tiene que dar error de sintaxis.
Este tipo de asistencia la ofrezco desde
Alula Arduino para principiantes (subforo de acuariofiliamadrid.org)
Andrés
Hola, Antonio. Me encanta el proyecto y te doy la enhorabuena por ello.
No sé si desde que lo publicaste has llegado a hacer alguna mejora o nueva revisión. El caso es que no sé si el esquema esta o no publicado para poderlo hacer. Sólo quiero que cuando el ph baje de un nivel ajustable, un relé corte la alimentación de la electroválvula de 220 v. del sistema de CO2. A un determinado ciclo de histéresis el CO2 volvería a saltar.
Muy agradecido de antemano por la respuesta y un cordial saludo.
Los parámetros que se visualizan en pantalla, en la línea que hace scroll entiendo no sean del acuario exactamente, ¿no?