jueves, 27 de junio de 2013

Termostato controlador para invernadero


Termostato para invernadero


Este proyecto consiste en un sistema de apertura y cierre automáticos para regular la temperatura de un invernadero. 

La medición de la temperatura se realiza mediante un sensor DHT11, y el control lo realiza un arduino. Para permitir un manejo sencillo de los parámetros, se ha incorporado un display LCD de 84x48 pixels. Las ventanas del invernadero se controlan con dos motores de apertura de puertas. Todo el conjunto debía soportar humedad así que se utilizó una caja totalmente estanca y los agujeros para el display y los botones serán cubiertos por un plástico termo adhesivo de los que se usan para plastificar papel que cubrirá a demás una máscara impresa con instrucciones de uso. 

Caja estanca


El display:

Para poder configurar parámetros de temperatura, humedad, y otros más se hace necesaria una pantalla donde mostrar información y permitir cambios. Una opción atractiva y barata es usar un display de Nokia 5110. Son muy baratos, se consiguen fácil y permiten mostrar datos gráficos en una matriz de 84x48 pixels. Hay bibliotecas para Arduino disponibles para escribir textos y mostrar líneas, círculos, etc.

PCD8544

Botonera:

Para interactuar con el programa utilizaremos tres pulsadores. Buscamos que la cantidad sea la mínima necesaria porque todo el dispositivo tiene que ser resistente a la humedad. Eso significa que los botones tiene que ser presionados desde el exterior a través de agujeros en la tapa pero cubiertos completamente. Para reducir la dificultad era preferible que tuviera la menor cantidad posible de agujeros y por lo tanto de botones. 


Panel de display y teclado:

Los botones y el display conformarán un sólo palel. Una placa experimental será suficiente para las necesidades y ponemos un conector para conectar y desconectar los cables necesarios:





Montaje de la placa de control:

El primer paso es mecir y marcar la posición de los botones y la pantalla. Una vez seguros de la posición se puede proceder a hacer los cortes y agujeros.

Dado que los pulsadores quedarán al ras del frente, los agujeros deberán ser de un tamaño adecuado para que el dedo pueda presionarlos. Unos 10mm son suficientes. 




Una vez presentada la placa en la posición correcta, se puede medir el tamaño de los separadores necesarios. En nuestro caso los haremos utilizando trozos de pegamento de plástico como separadores.








Cableado de pulsadores y display:


Display, pulsadores y conector soldados


Agregados cables que van al conecto

Todos los conectores están fabricados utilizando tiras de pines de las que se venden de a 50, cortando la cantidad necesaria y soldando cables finos. Para evitar cortocircuitos los cables se aislan con plástico termocontraíble.



Con esto ya podemos probar y verificar que funciona la pantalla y el teclado:

Display funcionando

Para evitar la filtración de humedad después se pegará sobre la superficie un plástico termoadherente de los que se usan para plastificado de papeles, pero antes habrá que poner una máscara impresa para darle algo de estilo.

Circuitos:

Placa de display y teclado:


La placa que contiene los botones y el display es muy sencilla. Llevamos todos los cables y hacemos las conexiones correspondientes del otro lado: los ocho del display más tres de los botones. Por las dudas dejaremos algunas patitas extra para uso futuro.

Foto de la placa terminada
Placa display + teclado
 
Necesitamos usar un mínimo de 4 patitas para controlar este display. Como en este proyecto no necesitaremos entradas analógicas, usaremos 4 de ellas como salidas digitales para controlarlo: A0, A1, A2 y A4. A demás controlaremos la retroiluminación del display con otra patita más, la A5. Esto nos deja la A6 por si en el futuro necesitáramos una entrada analógica. Hay que recordar que este display funciona con 3,3V así que la conexión de VCC del display irá a la salida de 3,3V del Arduino. Por suerte el circuito tolera 5V en las entradas así que no necesitamos ningún chip adicional para adaptar niveles.

Los pulsadores irán conectadas a las entradas digitales 2, 3 y 4 y activaremos en ellas los pullups para no necesitar agregar resistencias, lo cual significa que la otra patita de los pulsadores deberá ir a masa, y leeremos un valor bajo cuando esté pulsado y alto cuando no lo esté. Para activar los pullups basta con configurar la patita como entrada, pinMode(keypins[0],INPUT) y enviar un valor alto como si fuera una salida, digitalWrite(keypins[0],HIGH). Esto activará una resistencia interna de 20K a VCC que tiene el microcontrolador del Arduino.


Control de los motores:


El sistema manejará dos ventanas, cada una que se abre y cierra con su propio motor. A demás ambos motores tienen cada uno un par de sensores de fin de carrera compuestos por un reed switch y un imán. Con el objeto de no cargar demasiado la fuente de alimentación, los motores nunca se encenderán al mismo tiempo.

Inversor de dirección:

Para manejar un motor se pueden utilizar dos relays. Con el siguiente circuito se consigue encendido, apagado y dirección:

Sin embargo dado que tenemos que manejar dos motores, no hay más remedio que utilizar al menos un relay más que seleccione qué motor vamos a controlar. Dado que no necesitamos que los motores estén en funcionamiento al mismo tiempo nunca, bastará con un único relay extra que haga la selección.


El conexionado final quedaría así:



En Ebay podemos conseguir fácilmente placas de 4 relays a muy buen precio así que nos sobrará uno por si queremos agregar algo en el futuro.
Placa de 4 relays con optoacopladores

El circuito completo inversor y selector de dos motores con la placa de cuatro relays queda así:


Conexionado de los relays para formar un inversor más un selector


Hay que tomar en cuenta que esta placa de relays que lleva optoacopladores, se activan con valores bajos lo cual significa que hay que enviar un cero (LOW) para que el relay se encienda y un uno para que se apague. Basta con tener esto en cuenta en el software para no tener problemas. Para manejar la placa desde el Arduino necesitaremos tres salidas digitales. Usaremos las patas 5,6, y 7.

Dado que en ningún caso habrá más de dos relays activados, podemos conectar la alimentación a la salida de 5V del Arduino y no necesitamos una fuente externa.


Los motores


La apertura y cierre se hace con motores de 12 V especiales para apertura de puertas como se ve en la foto:

Motor de apertura/cierre

Sensores de fin de carrera


Para detectar la posición de abierta y cerrada de las ventanas utilizaremos sensores magnéticos (reed switch) del tipo que se instalan en las alarmas.

Un reed switch no es más que un par de placas metálicas, una magnética y otra no, que se unen cuando se acerca un imán.


Los que vienen para usar como sensores de apertura en alarmas tienen son básicamente lo mismo sólo que vienen con una caja plástica y su imán correspondiente. Necesitaremos cuatro (dos para cada ventana) y los conectaremos a cuatro entradas digitales del Arduino (8,9,10 y 11). Para evitar el uso de resistencias externas activaremos los pullups internos. 
Reed switch con su imán
Los sensores se conectarán mediante cables del largo suficiente, y para poder desconectarlos fácilmente usaremos un conector RJ45 que irá pegado dentro de la caja estanca.

Sensor de temperatura y humedad


Para medir temperatura y humedad utilizaremos un circuito DHT11. Son muy baratos pero tienen el inconveniente de no poder medir temperaturas bajo cero. Existe como alternativa el DHT22 que es mucho más preciso y sí mide negativos pero en este caso no será necesario. Hay una biblioteca para Arduino capaz de manejar cualquiera de estos dos chips así que su uso se hace sencillo, aunque hay que tener en cuenta que cuando la comunicación falla (porque está desconectado por ejemplo), los intentos de lectura frenan el programa durante aproximadamente un segundo cada vez, así que el programa se pone muy lento.

DHT11
Este es el único caso en que necesitaremos algún componente extra: una resistencia de 4K7 entre la pata de datos y VCC. Podríamos haber usado el pullup interno del Arduino pero es de 20K, que podría haber funcionado pero es mucho más alto que lo recomendado así que simplemente soldamos una resistencia de 4K7 directametne sobre las patitas del sensor en el momento de conectar el cable. En la otra punta usaremos otro conector RJ45 para poder desconectarlo si hace falta.

Circuito completo:

En el siguiente diagrama se puede observar el conexionado completo del Arduino con el resto de los componentes. 

Diagrama completo de todas las conexiones


Las asignaciones de patitas del Arduino están todas declaradas con #define al comienzo del programa así que es sencill0 cambiarlas si fuera necesario.

En la siguiente fotografía se observa la tapa de la caja estanca, donde se fijaron todas las placas de circuitos. Los cables que salen corresponden a las entradas de alimentación, salidas para motores y los conectores de los reed switch y el sensor de temperatura.

Vista con las placas conectadas


A continuación el detalle de la tapa y el resto de la caja donde se fijaron las dos fuentes (una para el Arduino y otra para los motores) y una bornera para las entradas de 220 y salidas para los motores.





Funcionamiento del software:

En el estado normal de funcionamiento, la pantalla del controlador mostrará en números grandes los valores actuales de temperatura y humedad, y en letras más chicas las mediciones máximas y mínimas de dichos valores. La luz de la pantalla permanece apagada pero al presionar cualquier botón se enciende y permanece así por un mínimo de un minuto.

Pantalla principal
En este estado, la función de los tres botones son: APERTURA MANUAL, MENÚ y CIERRE MANUAL. Cuando se solicita apertura o cierre manual el sistema deja de abrir y cerrar automáticamente las ventanas. A demás, durante la apertura o cierre, el botón opuesto detiene el motor.

Modo de control manual de apertura y cierre.

Pulsando el botón central obtenemos un menú con diferentes alternativas. En este momento los botones izquierdo y derecho pasan a usarse para moverse arriba y abajo dentro del menú.

Menú

En este menú tenemos las siguientes opciones:


  1. Volver a la pantalla inicial
  2. Limpiar valores de temperatura y humedad máxima y mínima
  3. Control manual del motor 1
  4. Control manual del motor 2
  5. Ajuste de temperatura programada
  6. Ajuste de humedad máxima programada
  7. Ajuste de histéresis de temperatura máxima
  8. Ajuste de tolerancia de temperatura para exceso de humedad
  9. Tiempo máximo de apertura
  10. Tiempo máximo de cierre

Las funciones de control manual (3 y 4) de motores están pensadas para realizar ajustes durante la instalación. En estos modos los botones izquierdo y derecho activan el motor en una dirección y la otra mientras están presionados. El botón central vuelve al menú. A demás se muestra en pantalla el estado de los sensores de fin de carrera. De esta forma se puede llevar manualmente cada ventana a su posición de abierta o cerrada, y mover los sensores magnéticos a las posiciones adecuadas.
Control manual de motores
La opción 5 permite ajustar la temperatura igual que en cualquier termostato normal. En este modo los botones de izquierda y derecha permiten subir y bajar el valor y el central guarda y vuelve al menú.
Ajuste de temperatura programada
La opción 6 sirve para el ajuste de humedad máxima admisible. Superada esta humedad, el sistema reducirá gradualmente la temperatura programada en función de la diferencia entre la humedad actual y el 100% de manera que las ventanas se abran a temperaturas menores y así se permita la salida del aire húmedo.
Ajuste de humedad máxima
Este ajuste se complementa con la opción 8, que llamamos TH e indica cuánto se permitirá bajar la temperatura en caso de que haya demasiada humedad. La respuesta sigue la siguiente función:

La temperatura efectiva baja al superar la humedad máxima
Se puede observar que cuando la humedad es menor que la máxima permitida, la temperatura efectiva concuerda con la programada. Sin embargo al sobrepasar el límite de humedad, la temperatura efectiva disminuye hasta un máximo de TH grados menos cuando la humedad es del 100%.


Con la opción 7 permite ajustar la histéresis de la temperatura. Este valor sirve para controlar con qué frecuencia se abrirán y cerrarán las ventanas. Una histéresis baja permite un control más preciso de la temperatura pero a costa de abrir y cerrar con más frecuencia. La histéresis indica cuánto se ñpermite a la temperatura real alejarse del valor programado.
Ajuste de histéresis de temperatura
Por ejemplo, si se ha programado 20 grados con una histéresis de 1 grado, significa que la temperatura oscilará entre 19 y 21. La siguiente gráfica ilustra este comportamiento:


Apertura y cierre en función de la temperatura

Las opciones 9 y 10 permiten ajustar el tiempo máximo de apertura y de cierre. Estos valores permiten poner un límite al tiempo que estarán funcionando los motores para el caso en que haya alguna falla en los mecanismos o en los sensores.

Ajuste de tiempo máximo de apertura

El software

Manejo del display PCD8544

Existen en internet varias bibliotecas para manejar el display PCD8544 (el de Nokia 3310 y 5110). Yo he elegido la que distribuyen en Adfruit porque a demás del acceso básico incluye una segunda biblioteca para manejo de gráficas.
Para comenzar lo primero es incluir las bibliotecas correspondientes:


#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>

El siguiente paso es crear el objeto que maneja el display. Como parámetros pasaremos las patitas donde hemos conectado el display:

Adafruit_PCD8544 display = Adafruit_PCD8544(A0, A1, A2, A3);

Y con esto ya estamos listos para usarlo. En la función setup() inicializamos el display de la siguiente forma: 

  display.begin();
  display.setContrast(50);

  display.clearDisplay();
  display.display();

No hay que olvidar hacer un llamado a la fucnión display() después de terminar de hacer modificaciones porque de lo contrario los datos no se envían a la pantalla. El resto es utilizar las diferentes funciones que proveen las bibliotecas como drawRoundRect() para dibujar rectángulos, 

Para dibujar los diferentes íconos que se muestran en la parte inferior de la pantalla se utiliza la función drawBitmap(), que recibe coordenadas, el puntero al buffer que contiene los datos, los valores de ancho y alto, y el color (blanco o negro). La información para mostrar se indica en forma de bits, así que yo he optado por definir los datos directamente en binario en el fuente para así poder hacer retoques con facilidad. He resaltado los "1" para que se note mejor el dibujo.

const unsigned char PROGMEM MANO[]={
 B00000110,B00000000
,B00001001,B00000000
,B00001001,B00000000
,B00001001,B00000000
,B00001001,B00000000
,B00001001,B10000000
,B00001001,B01110000
,B00001001,B01001100
,B00001001,B01001010
,B11101001,B01001001
,B10011000,B00000001
,B01001000,B00000001
,B00101000,B00000001
,B00100000,B00000001
,B00010000,B00000001
,B00010000,B00000010
,B00001000,B00000010
,B00001000,B00000010
,B00000100,B00000100
,B00000100,B00000100
,B00000111,B11111100
};

Para mostrar el ícono en pantalla se hace por ejemplo así:

display.drawBitmap(34, 18,  MANO, 16, 20, BLACK);

Nótese la directiva "PROGMEM" al definir la constante. Esto indica al compilador que los datos deberán ponerse en la memoria flash donde va el programa pero no se copiará a RAM, de esta forma no gastamos la memoria sin necesidad.

Manejo del teclado (antirebote) 

Leer la botonera no tiene mucha ciencia, pero existe un par de inconvenientes en usos como este. El primero es el rebote de contactos. Cuando se cierra un contacto eléctrico, la unión no es instantánea sino que en el momento del cierre hay entrecortes como si hubiéramos presionado y soltado muchas veces muy rápido. Si el circuito es suficientemente rápido puede detectarse como muchas pulsaciones.

Captura de alta velocidad del momento en que se presiona un  pulsador.
Se nota el efecto llamado "rebote de contacto".


Para evitar ese problema basta con hacer un retardo apenas se detecta el primer cierre del contacto, pero hay que tener en cuenta que es necesario recordar el estado anterior para poder saber si el pulsador se está cerrando o abriendo. Durante esa espera ocurrirán todos los rebotes y los ignoraremos. El segundo reto que tenemos en esta aplicación es la repetición automática. Tenemos casos como la selección de temperatura donde queremos poder mantener presionado un botón y que vaya repitiéndose automáticamente como si presionáramos muchas veces. El truco es verificar si entre dos lecuras diferentes la tecla se ha mantenido presionada y calcular el tiempo que ha pasado entre el último cambio de estado y el momento actual. Si pasa de un determinado valor (un segundo por ejemplo) responder el valor repetidamente. La función usada en este programa quedó así:

byte getkey(int repeat){

static byte oldkey=-1;
static unsigned long oldtime=0;
static unsigned long keydelaytime=0;
static byte repitiendo=0;

int k;

if (digitalRead(keypins[0])==0 && digitalRead(keypins[1])==0 && digitalRead(keypins[2])==0) asm("jmp 0x0000");

for (k = 0; k < sizeof(keypins); k++) {
    if (digitalRead(keypins[k])==LOW){
        if (k!=oldkey){
           oldkey=k;
           oldtime=millis();
           keydelaytime=oldtime;
           repitiendo=0;
           luz(1);
           delay(10);
           return k+1;
           }

        if (repeat && millis()-oldtime>(repitiendo?100:1000)) {
           if (millis()-keydelaytime<90) return 0;
           keydelaytime=millis();
           oldtime=millis();
           repitiendo=1;
           luz(1);
           delay(10);
           return k+1;
           }
        return 0;
        }
    }

if (k >= sizeof(keypins) ){

    if (millis()-oldtime>90){
       oldkey=-1;

       }
    }
repitiendo=0;
return 0;
}

Manejo del sensor DHT11

Este no tiene ninguna complicación. Sólo hay que incluir la biblioteca que lo maneja:

#include <DHT11.h>

Después crear el objeto:

dht11 DHT11;

y después sólo leer con 

DHT11.read(DHT11PIN);

La función completa que hace la lectura queda así:


void leer_sensores(){

int chk = DHT11.read(DHT11PIN);

if (chk==DHTLIB_OK){
   temperatura=DHT11.temperature*10;
   humedad=DHT11.humidity;

   tmax=max(temperatura,tmax);
   tmin=min(temperatura,tmin);

   hmax=max(humedad,hmax);
   hmin=min(humedad,hmin);
   }
}


Grabación de la configuración en la EEPROM

Todos los datos de configuraciones tienen que mantenerse incluso si se apaga el equipo. Por lo tanto tenemos que usar la memoria EEPROM del Atmega. Para comenzar definimos en qué lugar de la memoria vamos a guardar esta ifnormación con un #define al comienzo del programa. Esto nos permitirá cambiar fácilmente la ubicación en el caso de que queramos reutilizar parte del código en algún otro proyecto que use la EEPROM para otra cosa. Teniendo esto sólo necesitamos crear un par de funciones, una para guardar y otra para leer. Para poder usar las funcines de EEPROM de las bibliotecas de Arduino, hay que agregar al comienzo.


#include <EEPROM.h>


El procedimiento para grabar es muy sencillo. Simlemente usamos EEPROM.write para grabar byte a byte los datos que tenemos en memoria y que queremos mantener.


void grabar_configuracion(){

EEPROM.write(EEPROM_POS+0,prog_t & 0xFF);
EEPROM.write(EEPROM_POS+1,(prog_t & 0xFF00) >> 8);

EEPROM.write(EEPROM_POS+2,prog_h);
EEPROM.write(EEPROM_POS+3,prog_hist_t);
EEPROM.write(EEPROM_POS+4,prog_th);
EEPROM.write(EEPROM_POS+5,prog_tiempo_a);
EEPROM.write(EEPROM_POS+6,prog_tiempo_c);

}

La lectura de la información se hace con EEPROM.read pero en este caso tendremos una complicación adicional: la primera vez que se ejecute el programa seguramente la EEPROM tendrá valores inválidos, así que después de leer los datos nos aseguramos de que están dentro de los límites razonables y si no es así, asignamos algo por defecto.

void cargar_configuracion(){

prog_t=EEPROM.read(EEPROM_POS+0)+256*EEPROM.read(EEPROM_POS+1);
if (prog_t<1) prog_t=1;
if (prog_t>700) prog_t=500;

prog_h=EEPROM.read(EEPROM_POS+2);
if (prog_h<0) prog_h=0;
if (prog_h>100) prog_h=100;

prog_hist_t=EEPROM.read(EEPROM_POS+3);
if (prog_hist_t<1) prog_hist_t=1;
if (prog_hist_t>700) prog_hist_t=100;

prog_th=EEPROM.read(EEPROM_POS+4);
if (prog_th<0) prog_th=0;
if (prog_th>100) prog_th=100;

prog_tiempo_a=EEPROM.read(EEPROM_POS+5);
if (prog_tiempo_a<0) prog_tiempo_a=0;
if (prog_tiempo_a>60) prog_tiempo_a=60;
prog_tiempo_c=EEPROM.read(EEPROM_POS+6);
if (prog_tiempo_c<0) prog_tiempo_c=0;
if (prog_tiempo_c>60) prog_tiempo_c=60;

}


Presentación final

A continuación una foto y un video del resultado final.

Termostato instalado


Motores funcionando


Descargas:


Bibliotecas utilizadas: