26 nov. 2018

Mejorando el extractor de humedad Arduino - Elimina la humedad de un local


Hace tiempo compartí con vosotros cómo reduje considerablemente la humedad de un local subterráneo; tras 7 años funcionando el Arduino ha dejado de funcionar, así que aproveché a cambiar el sistema y añadir una económica pantalla OLED de 0,96" además de cambiar el motor de ventilación por uno de mayor caudal y más silencioso, y comparto con vosotros el código actualizado y mejorado de funcionamiento (configurables los parámetros a nuestro gusto, ahorro de energía y con función de salvado a EEPROM de datos de horas aunque esto último no está muy depurado).


Os recordaré de qué se trataba; mediante un arduino, dos sensores de humedad/temperatura DHT11 (uno en la pared de la calle y otro en la zona a ventilar), activamos un relé para conectar el motor sólo cuando en el exterior hay menos humedad que en el interior (un 5% de diferencia al menos) para alargar la vida del motor y menor consumo.

Este motor, económico por 30€, tiene dos velocidades, aunque apenas se nota la diferencia, he añadido un relé para activar la velocidad máxima sólo en caso de que la humedad interna sea un 20% o más que la exterior.

Esta pantalla OLED sólo cuesta unos 3€, es muy duradera y de bajo consumo y nos permite monitorizar en 4 líneas toda la información esencial en caso de duda de funcionamiento. De todas formas, como no se va a consultar apenas, para aumentar su durabilidad sólo se enciende 10 segundos cada minuto indicando la temperatura y humedad de ambos sensores (el externo e interno), con el mensaje "saving oled" durante tres segundos avisando de ello.


He utilizado la librería Adafruit, suficientemente rápida para texto, y he disfrutado tanto de la mejora que seguramente utilice este tipo de pantallas en otros proyectos (sobre todo con la rápida y potente librería u8gLib).


También he cambiado el transformador por uno conmutado de menor consumo que los de cobre.


Y el código es como sigue; aseguraos de tener instaladas las librerías del principio para que no dé errores de compilación. Espero que os sea útil, si lo examináis un poco veréis que en realidad es bastante sencillo; el relé es de los alimentados externamente (con los 9V del transformador) con lo que se activa con los 5V de salida digital del Arduino.

 /*Programa test para sensor DHT11 de humedad y temperatura
cleaned by sucotronic
Modificado por Regata para tallerarduino.wordpress.com
Este programa está preparado para activar el relé sólo si merece la pena, evitando consumo innecesario de energía y alargando la vida del ventilador;
en este caso en las horas del día más calurosas (se supone que también hay menos humedad en la calle), pero menor de XºC,
los valores más razonables de delta_temp y delta_hum es activarlo cuando tenemos entre un 5 y un 10% más de humedad en el interior (ajustar los valores personalizables
un poco según la situación), y siempre que la temperatura externa sea mayor (pero menor de XºC) activo el ventilador al menos media hora. Ringmaster 2018

Requirements: Sketch->Include->Manage Libraries:
  1. Adafruit SSD1306     -- by Adafruit Version 1.0.1
  2. Adafruit GFX Library -- by Adafruit Version 1.0.2
  3. TimerOne             -- by Jesse Tane et al. Version 1.1.0
*/

#include <Adafruit_SSD1306.h>
#include <Adafruit_GFX.h>
#include <Wire.h>
#include <SPI.h>
#include <TimerOne.h>  
#include <EEPROMex.h> //Para usar la memoria EEPROM
int direccionInicial=0; //Byte inicial EEPROM

//OLED SETUP
#define OLED_RESET 10             
Adafruit_SSD1306 display(OLED_RESET);   // Setup the OLED

#include "DHT.h";
// Uncomment whatever type you're using!
#define DHTTYPE DHT11   // DHT 11
//#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321
//#define DHTTYPE DHT21   // DHT 21 (AM2301)

//Librerías para dormir al procesador y ahorrar energía cuando no hace nada
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/wdt.h>
#include <Time.h> //Manejo reloj interno Arduino

// Initialize DHT sensor.
// Note that older versions of this library took an optional third parameter to
// tweak the timings for faster processors.  This parameter is no longer needed
// as the current DHT reading algorithm adjusts itself to work on faster procs.
DHT DHTINT(8, DHTTYPE); //Sensor interior va al pin DIGITAL 2; indica aquí el que tú uses
DHT DHTEXT(2, DHTTYPE); //Sensor exterior va al pin DIGITAL 8; indica aquí el que tú uses

int relePin = 4; //Personalizable; Salida digital para el relé
int relePin2 = 6;
int errorPin = 11; //Personalizable; Para activar el LED de error, yo uso el 11 digital
int onPin = 12; //Personalizable; Para indicar "en marcha"; pin 12 digital
int onRele = 9; //Personalizable; Para indicar que el relé está activado
int tempint; //Variables para guardar los valores y poder comparar
int tempext;
float humint;
float humext;
float voltajeDHT11=4; //Voltaje real alimentación sensores (El valor de humedad con DHT11 varía progresivamente con el voltaje).
int delta_temp = 6; //Personalizable; Se activará cuando la temperatura exterior sea al menos este valor mayor (y parará cuando ya no haya esa diferencia)
int delta_hum = 8; //Personalizable; Se activará el relé cuando la humedad exterior sea al menos este valor mayor (y parará cuando ya no haya esa diferencia). Podemos utilizar estos valores también para "calibrar" los sensores.
boolean data_error; //Guardamos el error en esta variable
boolean rele_act = false; //Guardamos aquí si en el loop anterior se activó el relé
long counter=0;
long tiempo = 15; //Personalizable; tiempo en minutos mínimo que debe activarse el relé, independientemente si baja la humedad o no; evitamos que esté arrancando y parando contínuamente.
int tempmax = 30; //Personalizable; tope temperatura interior máxima a la que activo el relé, por si no queremos calentar demasiado el local. Indicar 99 para desactivarlo
int difTempMax = 10; //Si la temperatura exterior es muy baja, máxima diferencia entre interior y exterior
int humMax = 20; //Si la diferencia supera este valor se activa el segundo relé (para motor de dos velocidades)
int temp;

long hrsRele; //Horas funcionamiento relé (motor)
long hrsTotal; //Hrs funcionamiento Arduino
int ctrArduino=0; //Contador minutos Arduino
long minutosMotor=0; 


void setup() {
  Serial.begin(115200);
  Serial.println("Inicio");

  //Preparación EEPROM
  // start reading from position memBase (address 0) of the EEPROM. Set maximumSize to EEPROMSizeUno 
  // Writes before membase or beyond EEPROMSizeUno will only give errors when _EEPROMEX_DEBUG is set
  EEPROM.setMemPool(20, EEPROMSizeUno);
  
  // Set maximum allowed writes to maxAllowedWrites. 
  // More writes will only give errors when _EEPROMEX_DEBUG is set
  EEPROM.setMaxAllowedWrites(10000);
  //Coger la dirección inicial siempre en el mismo momento y en el mismo orden
  direccionInicial = EEPROM.getAddress(4); //Primera dirección Long disponible a partir de la cual guardar

  //Reseteamos a 0 la zona de EEPROM necesaria durante la primera ejecución
  if (EEPROM.readLong(direccionInicial+45)!= 4011983) { //Si se ha reseteado ya, no lo volvemos a hacer
    for(int i=0; i<5; i++) {               
         EEPROM.writeLong(direccionInicial +(i*4),0);
          }
    EEPROM.writeLong(direccionInicial+45,4011983);
    Serial.println("Borrado de EEPROM terminado");
  }

  hrsRele = EEPROM.readLong(direccionInicial); //Cogemos los valores guardados
  hrsTotal= EEPROM.readLong(direccionInicial+4);
    
  // Begin the OLED screen
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);               
  
  //OLED pantalla inicial
  display.setRotation(0);                       // 2 = upside down; 0 = rightside down
  OpeningScreen();
  delay(1000); 
  display.setTextSize(1);
  
  //Líneas de configuración del WatchDog Timer
  /*** Setup the WDT ***/

  /* Clear the reset flag. */
  MCUSR &= ~(1 << WDRF);

  /* In order to change WDE or the prescaler, we need to
     set WDCE (This will allow updates for 4 clock cycles).
  */
  WDTCSR |= (1 << WDCE) | (1 << WDE);

  /* set new watchdog timeout prescaler value */
  //WDP3 - WDP2 - WPD1 - WDP0 - time
  // 0      0      0      0      16 ms
  // 0      0      0      1      32 ms
  // 0      0      1      0      64 ms
  // 0      0      1      1      0.125 s
  // 0      1      0      0      0.25 s
  // 0      1      0      1      0.5 s
  // 0      1      1      0      1.0 s
  // 0      1      1      1      2.0 s
  // 1      0      0      0      4.0 s
  // 1      0      0      1      8.0 s

  WDTCSR = 1 << WDP2 | 1 << WDP0; /* 0.5 seconds */
  //WDTCSR = 1 << WDP2 | 1 << WDP1; /* 1.0 seconds */
  //WDTCSR = 1<<WDP2 | 1<<WDP1 | 1<<WDP0; /* 2.0 seconds */
  //WDTCSR = 1<<WDP3; /* 4.0 seconds */
  //WDTCSR = 1<<WDP0 | 1<<WDP3; /* 8.0 seconds */

  /* Enable the WD interrupt (note no reset). */
  WDTCSR |= _BV(WDIE);

  pinMode(relePin, OUTPUT); // sets the digital pin as output
  pinMode(relePin2, OUTPUT);
  pinMode(onRele, OUTPUT);

  Serial.println("Initialisation complete.");

}

void loop() {

  data_error = false; //reseteamos el byte de comprobación
  //  digitalWrite(relePin,HIGH);
  //  delay(500);
  //  digitalWrite(relePin2,HIGH);
  //  delay(500);
  

  // Reading temperature or humidity takes about 250 milliseconds!
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  humint = DHTINT.readHumidity()* (5/voltajeDHT11);
  // Read temperature as Celsius (the default)
  tempint = DHTINT.readTemperature();
  // Read temperature as Fahrenheit (isFahrenheit = true)
  //float f = DHTINT.readTemperature(true);

  // Check if any reads failed and exit early (to try again).
  if (isnan(humint) || isnan(tempint)) { // || isnan(f)) {
    Serial.println("Failed to read from int sensor!");
    data_error = true; //activamos el LED de error
    //return;
  }

  // Compute heat index in Fahrenheit (the default)
  //float hif = dht.computeHeatIndex(f, h);
  // Compute heat index in Celsius (isFahreheit = false)
  //float hic = dht.computeHeatIndex(t, h, false);

  humext = DHTEXT.readHumidity()* (5/voltajeDHT11);
  // Read temperature as Celsius (the default)
  tempext = DHTEXT.readTemperature();
  // Read temperature as Fahrenheit (isFahrenheit = true)
  //float f = DHTINT.readTemperature(true);

  // Check if any reads failed and exit early (to try again).
  if (isnan(humext) || isnan(tempext)) { // || isnan(f)) {
    Serial.println("Failed to read from ext sensor!");
    data_error = true; //activamos el LED de error
    //return;
  }
  
  if (data_error == true) //al menos uno de los sensores no funciona; mensaje de error y apagado de ventilador y reseteo contadores (como si se reseteara el programa)

  {

    display.setTextSize(2);
    display.setCursor(0, 0);
    display.clearDisplay();
    display.print(F("ERROR \n   SENSOR"));
    display.display();
    display.setTextSize(1);
    for (int i = 0; i <= 5; i++) { 
      enterSleep(); //Se muestra 5 segundos.
    }
    display.clearDisplay(); //Borra pantalla
    display.display();
  }

  else

  {
    //Comparamos valores para activar o desactivar el relé

    if (rele_act) { //si el relé está activado comprobamos cada x tiempo si merece la pena que siga en marcha
      if (counter > 0) {
          --counter; //le quitamos 1 al contador sólo en caso de que sea >0
        }
    }
    if (counter==0) { 
        //Serial.println("comparamos valores");  
        //Se activa en tres casos; aprovechamos el calor exterior si no hay mucha hum. ext.; o si hay mucha humedad y la diferencia de temperaturas no es excesiva, para no enfriar demasiado el local. O bien activamos sí o sí cuando hay demasiada hum.
        if ((tempext > tempint and (tempext-tempint) > delta_temp and tempint < tempmax and (humint > humext)) or ((humint - humext) >= delta_hum and (tempint-tempext)<difTempMax) or (humint - humext) >= humMax) { 
          rele_act = true;
          counter = tiempo; //reinicializamos el tiempo mínimo
        }
        else {
          //Desactivamos si no se cumple
          //if (((tempint > tempmax) && (humext - humint) < (delta_hum / 2)) or ((humint - humext) < (delta_hum / 2) and (tempint-tempext)>difTempMax) or (humint - humext) < humMax) { //Desactivamos cuando no nos interesa la temperatura ext. o bien la humedad ext.
            rele_act = false;
          }
      }
  }

  //Actuamos en ventilador y LED indicador según situación
  if (!rele_act) {
    digitalWrite(relePin, LOW);
    digitalWrite(relePin2, LOW);
  }
  else {
    if ((humint - humext) >= humMax) {
      digitalWrite(relePin, LOW);//Nos aseguramos de no encender los dos
      delay(200);
      digitalWrite(relePin2, HIGH);
    }
    else {
    digitalWrite(relePin2,LOW);
    delay(200);
    digitalWrite(relePin, HIGH);
    }
    minutosMotor++;
  }

  if (data_error == false) {
    //Mostramos datos en pantalla
    display.setCursor(0, 0);
    display.clearDisplay();
    display.print(F("INT T: "));
    display.print(tempint);
    display.print(F(" C HUM: "));
    display.println(int(humint));
    display.print(F("EXT T: "));
    display.print(tempext);
    display.print(F(" C HUM: "));
    display.println(int(humext));
    display.print(F("HRS MTR/T: "));
    display.print(hrsRele);
    display.print(F("/"));
    display.println(hrsTotal);
    
    if (rele_act) {
      display.print(F("  MOTOR EN VEL. "));  
      if (digitalRead(relePin)==HIGH) {
        display.println(F("1"));  
      }
      else {
        display.println(F("2"));  
      }
    }
    else {
      display.println(F("    MOTOR OFF"));  
    }
    display.display();
    for (int i = 0; i <= 20; i++) { 
      enterSleep(); //Se muestra 10 segundos.
    }
    display.clearDisplay();
    display.setCursor(6, 10);
    display.println(F("   SAVING OLED"));
    display.display();
    for (int i = 0; i <= 4; i++) { 
      enterSleep(); //Se muestra 5 segundos.
    }
    display.clearDisplay();
    display.display();
  }  
  
  //Envío de datos por serial
//  Serial.println("Ringmaster 2018");
//  Serial.print("Temp int: ");
//  Serial.println(tempint);
//  Serial.print("Temp ext: ");
//  Serial.println(tempext);
//  Serial.print("Humedad int: ");
//  Serial.println(humint);
//  Serial.print("Humedad ext: ");
//  Serial.println(humext);
//  Serial.print("Delta humedad: ");
//  Serial.println(delta_hum);
//  Serial.print("Delta temperatura: ");
//  Serial.println(delta_temp);
//  delay(500);
  
  
// _______________________________ GUARDAMOS TIEMPOS _______________________________________
//*** Si el motor ha estado activado, sumamos el tiempo y lo guardamos *******
//Estuvo encendido y acaba de apagarse; sumamos tiempo al contador de horas del motor y reseteamos
if (rele_act==false and minutosMotor>0) { 
    hrsRele= hrsRele + long(((minutosMotor)/60));
    EEPROM.update(direccionInicial,hrsRele);
    minutosMotor=0;
}

//*** Cada hora salvamos a la EEPROM los datos que han cambiado (no hacerlo mas frecuente para prevenir el envejecimiento prematuro de la FLASH)
if (ctrArduino>=60) { //Si ha pasado 1 hora
      //Salvamos los datos cambiados
      hrsTotal++;
      EEPROM.update(direccionInicial+4,hrsTotal);
      ctrArduino=0;
      if (minutosMotor>60) { //Guardamos 1 hora de activación del relé
        minutosMotor=minutosMotor-60;
        hrsRele++;
        EEPROM.update(direccionInicial,hrsRele);
      }
}
  
for (int i = 0; i <= 96; i++) { 
  enterSleep(); //Se ejecuta cada minuto.
}
ctrArduino++;

}

//RUTINAS
//Code from http://donalmorrissey.blogspot.com.es/2010/04/sleeping-arduino-part-5-wake-up-via.html
/***************************************************
    Name:        ISR(WDT_vect)

    Returns:     Nothing.

    Parameters:  None.

    Description: Watchdog Interrupt Service. This
                 is executed when watchdog timed out.

 ***************************************************/
ISR(WDT_vect)
{
  //Aquí el código que queremos se ejecute cuando el watchdog "despierta" al procesador

}



/***************************************************
    Name:        enterSleep

    Returns:     Nothing.

    Parameters:  None.

    Description: Enters the arduino into sleep mode.

 ***************************************************/
void enterSleep(void)
{
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);   /* EDIT: could also use SLEEP_MODE_PWR_SAVE for less power consumption. */
  sleep_enable();

  /* Now enter sleep mode. */
  sleep_mode();

  /* The program will continue from here after the WDT timeout*/
  sleep_disable(); /* First thing to do is disable sleep. */

  /* Re-enable the peripherals. */
  power_all_enable();
}

// This function runs the splash screen as you turn on the detector
void OpeningScreen(void) {
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(8, 0);
  display.clearDisplay();
  display.print(F("Monitor \n   Humedad"));
  display.display();
  delay(1500);
  display.setTextSize(1);
  display.setCursor(0, 8);
  display.clearDisplay();
  display.print(F("(C) Ringmaster 2018"));
  display.display();
  display.clearDisplay();
}

Como comentaba, este programa está preparado para activar el relé sólo si merece la pena, evitando consumo innecesario de energía y alargando la vida del ventilador; en este caso en las horas del día más calurosas (se supone que también hay menos humedad en la calle), pero menor de XºC,
los valores más razonables de delta_temp y delta_hum es activarlo cuando tenemos entre un 5 y un 10% más de humedad en el interior (ajustar los valores personalizables un poco según la situación), y siempre que la temperatura externa sea mayor (pero menor de XºC) activo el ventilador al menos media hora; además de esta forma, evitamos enfriar el local ya que en las horas más frías de invierno, también suelen coincidir con más humedad. En verano, en caso de subir por encima de 30ºC en el interior (configurable), también lo apagamos.

No hay comentarios:

Publicar un comentario

Puede dejar su comentario. Los comentarios descalificativos o sin relación ninguna con el tema tratado serán eliminados sin previo aviso. Antes de plantear una duda, asegúrate de que la respuesta no está en otra entrada del tema visitando la etiqueta que hay al final del artículo para verlos todos; muchas veces lo que planteas puede haber sido corregido o comentado en otra entrada posterior.