miércoles, 9 de noviembre de 2016

Termostato de dos cortes con Arduino

Mientras que los termómetros solo nos dicen cuál es la temperatura del ambiente, los termostatos también son capaces de regularla, entonces, voy a tratar de desarrollar un termostato.

La idea es sencilla, se trata de desarrollar un dispositivo capaz de medir la temperatura e informarla en una pantalla LCD, así cómo setear dos temperaturas desde donde encender/apagar un sistema calefactor o un sistema refrigerador.

Supongamos que necesitamos de un artefacto donde conservar una sustancia dentro de un determinado rango de temperaturas, podría tratarse de un medicamento, el que se debe mantener entre 25 °C y 45 °C. 

O quisiéramos controlar un Aire acondicionado y una estufa eléctrica de modo de encender el aire al alcanzar determinada temperatura y apagarlo cuando la temperatura descienda cierta cantidad de grados, así cómo encender la estufa cuando la temperatura desciende por debajo de un límite inferior y apagarla al alcanzar una temperatura más confortable. 

En ambos casos podemos usar un Arduino para activar/desactivar dos relés de comando de los aparatos refrigerador/calefactor.

En la práctica, no voy a construir ningún aparato ni dispositivo conservador, no lo necesito y dudo de que en algún momento lo necesite, pero nada me impide teorizar al respecto, e implementarlo en Proteus, donde simular el comportamiento de la electrónica y del programa es muy fácil, bondades del CAD (Desarrollo Asistido por Computadora) que permite "armar todo en la compu" sin soldar un solo cable.

Entonces... manos a la obra (o dedos al teclado).

EL HARDWARE:

No hay mucho que decir de la electrónica necesaria, ya que existen shields (módulos comerciales) que cubren todas las necesidades de este proyecto. La construcción simplemente se limitaría a adquirirlos e interconectarlos adecuadamente, entonces, necesitaríamos:

  • Arduino uno R3 (o cualquier otro)
  • Módulo control de dos relés de 5V de alimentación.
  • Módulo keypad con con display.
  • Sensor de temperatura LM35.

Esta lista es solo orientadora, se puede usar electrónica discreta, de echo en Proteus no existen dichos módulos y al momento de simularlos hay que "construirlos".

Algunas páginas para conocer un poco de estos shield´s, así cómo la hoja de datos del LM35:
http://saber.patagoniatec.com/control-modulo-de-reles-arduino-argentina-ptec/
http://www.dx.com/es/p/diy-lm35-linear-temperature-sensor-module-black-166653#.WB8xIdXhCM8
http://www.ti.com/lit/ds/symlink/lm35.pdf

Sin más preámbulos, el circuito desarrollado:


Por supuesto que se puede mejorar esta electrónica, si estuviera usando un módulo control de dos relés tendría un acople óptico (optoacoplador) entre el Arduino y los transistores de conmutación de los relés, lo que aportaría una aislarción de por lo menos 2KV, protegiendo así al componente principal, o sea al Arduino.

En fin, queda a criterio del lector, a efectos de experimentar y verificar el programa, es suficiente este circuito.

En cuanto a Proteus y su simulación de Arduino, hay varios titulares disponibles en la red, solo diré que hay que tener abierto el entorno de programación de Arduino y configurado de modo tal que nos informe donde almacena el archivo .hex; que será en un directorio temporal, al cerrar el entorno, se borra el temporal y deja de funcionar la simulación. claro que siempre se puede copiar al .hex a un directorio seguro, pero como por lo general estamos modificando el programa y verificando en la simulación, no sería práctico recuperar el archivo temporal .hex hasta no acabar definitivamente con la programación (cosa que raras veces terminamos).

Mis preferencias de configuración del entorno (Versión 1.6.9):

Esta configuración me permite ver en el área de notificaciones del entorno el path donde se almacena el archivo .hex:

 La que hay que copiar al porta papeles seleccionándolo con el mouse y luego usando la combinación de teclas <Control + C> (no el botón derecho del mouse), para pegarlo en el editor del componente "SIMULINO" en proteus, opción "Program File":


Un proceso un tanto engorroso, por lo menos la primera vez.

Como dije, hay muchísimos tutoriales en Internet que explican muy bien cómo hacer esto, a título de ejemplo, dejo el siguiente link:
https://www.youtube.com/watch?v=5FDFVUKLVX4

Y otro más, que no conocía y me parece excelente:
http://huborarduino.com/simulacion/curso-simulacion.html

Pasemos al desarrollo de la lógica de control.

EL SOFTWARE:

El programa completo es el siguiente:

#include <EEPROM.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

#define btnRIGHT  0
#define btnUP     1
#define btnDOWN   2
#define btnLEFT   3
#define btnSELECT 4
#define btnNONE   5

const byte PinRefr = 2;
const byte PinCalf = 3;
const byte PinIlum = 10;

float Centigr;
float Kelvin;
float Fahrenh;
float Rankine;
float Reaumur;

int ValorAnalog = 0;
int SelecTemp   = 0;
int TMin        = 20;
int TMax        = 40;
byte RangoTem   = 5;
int SelecMenu   = 0;
int EstBtnAnt   = btnNONE; 
int EstBtnAct   = btnNONE; 
unsigned long tiempo;
char temp[16] = " ";
boolean AuxTMin = false;
boolean AuxTMax = false;

void setup() {
  lcd.begin(16,2);
  pinMode(PinRefr, OUTPUT);
  pinMode(PinCalf, OUTPUT);
  pinMode(PinIlum, OUTPUT);
  digitalWrite(PinRefr, LOW);
  digitalWrite(PinCalf, LOW);
  digitalWrite(PinIlum, HIGH);
  lcd.setCursor(0,0);
  lcd.print(" E. E. S. T. #6 ");
  lcd.setCursor(0,1);
  lcd.print("Prof. VIEGAS B. ");
  delay(2500);
  if (!cargarConfiguracion()) {
    lcd.setCursor(0,0);
    lcd.print("ERROR LECURA MEM");
    lcd.setCursor(0,1);
    lcd.print("Carga conf. def.");
    delay(1500);
  }
  digitalWrite(PinIlum, LOW);
  lcd.clear();
  tiempo = millis();
}

void loop() {
  Centigr = centigr ();
  Kelvin  = kelvin  (Centigr);
  Fahrenh = fahrenh (Centigr);
  Rankine = rankine (Centigr);
  Reaumur = reaumur (Centigr);

  if (Centigr <= TMin & !AuxTMin) {
    digitalWrite(PinCalf, HIGH);
    AuxTMin = true;
  }
  if (Centigr >= TMin + RangoTem & AuxTMin) {
    digitalWrite(PinCalf, LOW);
    AuxTMin = false;
  }

  if (Centigr >= TMax & !AuxTMax) {
    digitalWrite(PinRefr, HIGH);
    AuxTMax = true;
  }
  if (Centigr <= TMax - RangoTem & AuxTMax) {
    digitalWrite(PinRefr, LOW);
    AuxTMax = false;
  }
  
  EstBtnAct = Leer_Botones(); 
  switch(EstBtnAct) {
    case btnRIGHT : if(SelecMenu==0) SelecTemp++; break;
    case btnLEFT  : if(SelecMenu==0) SelecTemp--; break;
    case btnUP    : if(SelecMenu==1) TMin++; else if(SelecMenu==2) TMax++; break;
    case btnDOWN  : if(SelecMenu==1) TMin--; else if(SelecMenu==2) TMax--; break;
    case btnSELECT: SelecMenu++; break;
  }

  if (EstBtnAnt != EstBtnAct) {
    digitalWrite(PinIlum, HIGH);
    delay(100);
    tiempo = millis();
  }
  
  if (SelecMenu > 2) SelecMenu = 0; 
  if (SelecTemp > 3) SelecTemp = 0; 
  if (SelecTemp < 0) SelecTemp = 3;
  
  if (TMin > 135) TMin = 135; 
  if (TMax > 150) TMax = 150; 
  if (TMin > TMax - (RangoTem*2) & SelecMenu == 1) TMin = TMax - (RangoTem*2);   
  if (TMax < TMin + (RangoTem*2) & SelecMenu == 2) TMax = TMin + (RangoTem*2);
  
  lcd.setCursor(0,0);
  switch(SelecMenu) {
    case 0: 
      lcd.print("Temperatura:    ");
      Escribir(Centigr, "C  ", 0);
      switch (SelecTemp) {
        case 0: Escribir(Kelvin , "K", 9); break
        case 1: Escribir(Fahrenh, "F", 9); break;  
        case 2: Escribir(Rankine, "R", 9); break;
        case 3: Escribir(Reaumur, "Re",8); break;
      } 
      break
    case 1: SetearLimites("Setear Lim. Inf."); break;
    case 2: SetearLimites("Setear Lim. Sup."); break
   }
  
  if (millis() - tiempo > 5000) {
    digitalWrite(PinIlum, LOW);
    SelecMenu = 0;
    guardarConfiguracion();
    tiempo    = millis();
  }
  EstBtnAnt = EstBtnAct;
}

int Leer_Botones() {
  ValorAnalog = analogRead(A0);
  if (ValorAnalog > 900)  return btnNONE;
  if (ValorAnalog < 50 )  return btnRIGHT; 
  if (ValorAnalog < 250)  return btnUP;
  if (ValorAnalog < 450)  return btnDOWN;
  if (ValorAnalog < 650)  return btnLEFT;
  if (ValorAnalog < 850)  return btnSELECT; 
  return btnNONE;                      
}

float centigr() { return (500.0 * analogRead(A1))/1023; }
float kelvin (float& cent){ return (cent + 273.15); }
float fahrenh(float& cent) { return (cent * 1.8 + 32); }
float rankine(float& cent) { return ((cent + 273.15)*1.8); }
float reaumur(float& cent) { return (cent / 1.25); }

void Escribir(float& r, String t, byte c) {
  char buffn[8] = " ";   
  dtostrf(r,6,2,buffn);  
  String cadena = buffn;
  cadena.concat(t);
  lcd.setCursor(c,1);
  lcd.print(cadena);
}

void SetearLimites(char s[16]) {
  lcd.print(s); 
  lcd.setCursor(0,1);
  switch(SelecMenu) {
    case 1: sprintf(temp, "%3d Ini. %3d Fin", TMin, TMin + RangoTem); break;
    case 2: sprintf(temp, "%3d Ini. %3d Fin", TMax, TMax - RangoTem); break;
  }  
  lcd.print(temp);
}

boolean cargarConfiguracion(){
  if ((EEPROM.read(0) == 'V') && 
      (EEPROM.read(1) == 'B') &&  
      (EEPROM.read(2) == 'V')) {
    SelecTemp = EEPROM.read(3); 
    TMin      = EEPROM.read(4);  
    TMax      = EEPROM.read(5);
    RangoTem  = EEPROM.read(6);
    return true;
  }
  return false;
}

void guardarConfiguracion(){
  EEPROM.write(0, 'V');
  EEPROM.write(1, 'B');
  EEPROM.write(2, 'V');
  EEPROM.write(3, SelecTemp);
  EEPROM.write(4, TMin);
  EEPROM.write(5, TMax);
  EEPROM.write(6, RangoTem);
}

Voy a tratar de explicarlo.

#include <EEPROM.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

Estas líneas se encargan de incorporar dos librerías externas, la librería EPROM (lo que quiere decir que usaré la memoria eprom del arduino, para almacenar/recuperar la configuración o seteo principalmente de las temperaturas mínima y máxima de corte) y la librería LiquidCrystal, que me soluciona la vida a la hora de usar el display. A continuación la declaración del objeto lcd con sus respectivos pines de conexión.

#define btnRIGHT 0
#define btnUP 1
#define btnDOWN 2
#define btnLEFT 3
#define btnSELECT 4
#define btnNONE 5

Definición de los literales y sus respectivos valores que devolverá la función encargada de leer los botones del Módulo keypad.

const byte PinRefr = 2;
const byte PinCalf = 3;
const byte PinIlum = 10;

Constantes referentes a otros pines usados del arduino, siendo PinRefr el pin encargado de controlar al relé de accionamiento del sistema Refrigerador, PinCalf el encargado del sistema Calefactor y PinIlum el de encender/apagar el led de iluminación del display.

float Centigr;
float Kelvin;
float Fahrenh;
float Rankine;
float Reaumur;

Estas variables de coma flotante almacenan los valores de temperatura leída del LM35, se lee el valor analógico, se convierte a grados centígrados, y luego de centígrados a las demás escalas, a saber:
  • Kelvin (K) 
  • Fahrenheit (F) 
  • Rankine (R) 
  • Réaumur (Re) 
La última de estas escalas no está vigente hoy día. Recomiendo visitar la página de wikipedia:
https://es.wikipedia.org/wiki/Temperatura

int ValorAnalog = 0;
int SelecTemp = 0;
int TMin = 20;
int TMax = 40;
byte RangoTem = 5;
int SelecMenu = 0;
int EstBtnAnt = btnNONE; 
int EstBtnAct = btnNONE; 
unsigned long tiempo;
char temp[16] = " ";
boolean AuxTMin = false;
boolean AuxTMax = false;

Y las variables globales de trabajo necesarias (según mi criterio) en el programa:
  • ValorAnalog: Se usa para determinar el botón pulsado 
  • SelecTemp : Selector de escalas de temperatura a mostrar 
  • TMin : Temperatura mínima o de accionamiento del sistema calefactor 
  • TMax : Temperatura máxima o de accionamiento del sistema refrigerador 
  • RangoTem : Rango entre accionamiento/detención de cualquiera de los sistemas 
  • SelecMenu : Selector del menú a presentar en display 
  • EstBtnAnt : Estado del botón anterior 
  • EstBtnAct : Estado del botón actual (para determinar cambios) 
  • tiempo : Medir tiempos, visualización de un menú, apagado de iluminación, etc 
  • temp[16] : Cadena auxiliar para formateo de datos a presentar en display 
  • AuxTMin : Auxiliar booleana para mantener activo el relé de calefacción 
  • AuxTMax : Auxiliar booleana para mantener activo el relé de refrigeración 

Función de inicialización del dispositivo, necesaria en la estructura de un programa arduino:

void setup() {
  lcd.begin(16,2); //Columnas y filas del objeto lcd

//Configurar pines del arduino
  pinMode(PinRefr, OUTPUT);
  pinMode(PinCalf, OUTPUT);
  pinMode(PinIlum, OUTPUT);

// Setear estado inicial de los pines
  digitalWrite(PinRefr, LOW);
  digitalWrite(PinCalf, LOW);
  digitalWrite(PinIlum, HIGH); // led display encendido

//presentar propaganda por dos segundos y medio 
  lcd.setCursor(0,0);
  lcd.print(" E. E. S. T. #6 ");
  lcd.setCursor(0,1);
  lcd.print("Prof. VIEGAS B. ");
  delay(2500);

//Cargar configuración, de fallar la carga, se informa durante
//un segundo y medio de la situación 
  if (!cargarConfiguracion()) {
    lcd.setCursor(0,0);
    lcd.print("ERROR LECURA MEM");
    lcd.setCursor(0,1);
    lcd.print("Carga conf. def.");
    delay(1500);
  }

//Apagar led, limpiar display y almacenar el tiempo de inicialización 
  digitalWrite(PinIlum, LOW);
  lcd.clear();
  tiempo = millis();
}

A continuación, la función loop o bucle de programa principal:

void loop() {
  Centigr = centigr ();
  Kelvin = kelvin (Centigr);
  Fahrenh = fahrenh (Centigr);
  Rankine = rankine (Centigr);
  Reaumur = reaumur (Centigr);

Lo primero, leer la temperatura desde el sensor LM35 en grados centígrados y aplicar las funciones de conversión a las demás escalas.

  if (Centigr <= TMin & !AuxTMin) {
    digitalWrite(PinCalf, HIGH);
    AuxTMin = true;
  }
  if (Centigr >= TMin + RangoTem & AuxTMin) {
    digitalWrite(PinCalf, LOW);
    AuxTMin = false;
  }

Estos dos condicionales se encargan de encender o apagar la salida digital conectada al relé de calefacción.

SI la temperatura medida es menor o igual a la temperatura mínima Y el relé se encuentra desactivado ENTONCES se enciende el relé y se actualiza el estado de la variable auxiliar que representa su estado.

SI la temperatura medida es mayor o igual a la temperatura mínima más un rango de actuación Y el relé se encuentra activado ENTONCES se apaga el relé y se actualiza el estado de la variable auxiliar que representa su estado.

Dicho de otra manera, lo que hace es encender el relé al alcanzar la mínima, y lo apaga al superar la mínima teniendo en cuenta un rango de 5 grados entre encendido y apagado.

  if (Centigr >= TMax & !AuxTMax) {
    digitalWrite(PinRefr, HIGH);
    AuxTMax = true;
  }
  if (Centigr <= TMax - RangoTem & AuxTMax) {
    digitalWrite(PinRefr, LOW);
    AuxTMax = false;
  }

Los siguientes dos condicionales se comportan de la misma manera, pero actuando en función de la temperatura máxima y de su correspondiente relé. 

Establecido entonces el funcionamiento principal del programa, o sea leer la temperatura y activar el relé que corresponda según se encuentre la temperatura en determinado rango o valor, pasamos a leer el estado de los botones y actuar en consecuencia.

  EstBtnAct = Leer_Botones(); 
  switch(EstBtnAct) {
    case btnRIGHT : if (SelecMenu == 0) SelecTemp++; break;
    case btnLEFT  : if (SelecMenu == 0) SelecTemp--; break;
    case btnUP    : if (SelecMenu == 1) TMin++; else if (SelecMenu == 2) TMax++; break;
    case btnDOWN  : if (SelecMenu == 1) TMin--; else if (SelecMenu == 2) TMax--; break;
    case btnSELECT: SelecMenu++; break;
  }

Lo que hago, básicamente, es: según el botón pulsado y la pantalla visualizada, actuar incrementando o decrementando diversas variables.

 Hay tres posibles pantallas:

La primera es de información de la temperatura medida, la segunda permite setear la mínima temperatura de encendido del sistema calefactor y su desactivación, y la tercer pantalla permite setear la temperatura de encendido del sistema refrigerador y su desactivación.

Entonces, la variable SelecMenu solo podrá tomar los valores 0, 1 y 2 según sea cualquiera de estas tres pantallas de visualización.

Por otro lado, estando en la primer pantalla, se puede informar la temperatura en cualquiera de las posibles escalas previstas:


En las cuatro pantallas, se informa la temperatura en C y en otra escala (K, F, R y Re). Según el valor de la variable SelecTemp se visualiza cualquiera de las cuatro pantallas, o sea dicha variable podrá tomar los valores 0, 1, 2 y 3 correspondiéndose cada valor con una pantalla de información.

Volviendo al código y según el botón pulsado:
  • Sea btnRIGHT y estando en la pantalla de visualización de escalas (SelecMenu == 0), entonces incrementamos SelecTemp para pasar a la siguiente pantalla de visualización.
  •  Sea btnLEFT y estamos en la pantalla de visualización de escalas (SelecMenu == 0), entonces decrementamos SelecTemp para volver a la pantalla de visualización anterior. 
  • Sea btnUP  y estando en la pantalla de seteo de la mínima (SelecMenu == 1) entonces incrementamos la temperatura mínima TMin, si no, estando en la pantalla de seteo de la máxima (SelecMenu == 2) entonces incrementamos la temperatura máxima TMax.
  • Sea  btnDOWN  y estando en la pantalla de seteo de mínima (SelecMenu == 1) entonces decrementamos la temperatura mínima TMin, si no, estando en la pantalla de seteo de la máxima (SelecMenu == 2) entonces decrementamos la temperatura máxima TMax.
  • Sea btnSELECT entonces cambiamos de pantalla de menú incrementando SelecMenu.
Espero se entienda, ya que ese es el "corazón" del sistema de visualización y seteo.

El siguiente condicional se encarga de encender el led de iluminación del display, al pulsar cualquier botón:

  if (EstBtnAnt != EstBtnAct) {
    digitalWrite(PinIlum, HIGH);
    delay(100);
    tiempo = millis();
  }

Si detectamos que se pulsó un botón (EstBtnAnt != EstBtnAct) entonces encendemos el pin de iluminación, damos un mínimo retardo de 100 ms para evitar rebotes falsos y actualizamos la variable tiempo para iniciar un nuevo conteo, el display permanecerá encendido durante cinco segundos a partir de este momento.

  if (SelecMenu > 2) SelecMenu = 0; 
  if (SelecTemp > 3) SelecTemp = 0; 
  if (SelecTemp < 0) SelecTemp = 3;

Estas líneas aseguran los rangos posibles de las variables SelecMenu y SelecTemp.

Si SelecMenu supera el valor 2, entonces se la pone a cero, o lo que es lo mismo, al llegar a la última pantalla, se vuelve a la primera.

Si SelecTemp es mayor a 3, se vuelve a la primer pantalla, si estamos pasando las pantallas de visualización de escalas en forma ascendente, al llegar a la última se vuelve a la primera.

Si SelecTemp es menor a 0, se pasa a la última pantalla, si estamos pasando las pantallas de visualización de escalas en forma descendente, al llegar a la primera se vuelve a la última.

Los siguientes condicionales hacen lo propio con los valores máximo y mínimo de temperatura:

  if (TMin > 135) TMin = 135; 
  if (TMax > 150) TMax = 150; 
  if (TMin > TMax - (RangoTem * 2) & SelecMenu == 1) TMin = TMax - (RangoTem * 2);    
  if (TMax < TMin + (RangoTem * 2) & SelecMenu == 2) TMax = TMin + (RangoTem * 2);

La temperatura minina no puede ser mayor de 135 °C. La máxima está limitada al máximo que permite el LM35.

Por otro lado, se impide que temperatura mínima sea mayor que la temperatura máxima - 10 grados,  así como la máxima no sea menor que la mínima + 10 grados.

Al momento de setear las temperaturas mínima y máxima, teniendo en cuenta un ciclo de histéresis de 5 grados en el funcionamiento de los diversos actuadores, se impide que se pudieran solapar  los límites de apagado de cualquier actuador (no sea cosa que encendamos la estufa y el aire acondicionado al mismo tiempo...)

A continuación, la visualización en el display:

  lcd.setCursor(0,0);
  switch(SelecMenu) {
    case 0:
      lcd.print("Temperatura:    ");
      Escribir(Centigr, "C  ", 0);
      switch (SelecTemp) {
        case 0: Escribir(Kelvin , "K", 9); break;
        case 1: Escribir(Fahrenh, "F", 9); break;
        case 2: Escribir(Rankine, "R", 9); break;
        case 3: Escribir(Reaumur, "Re",8); break;
      }
      break;
    case 1: SetearLimites("Setear Lim. Inf."); break;
    case 2: SetearLimites("Setear Lim. Sup."); break;
   }

No hay mucho que decir de este código ya que solo se trata de presentar el correspondiente menú según sea el estado de las variables SelecMenu y SelecTemp, y la escritura en el display se soluciona a través de una función de formateo: Escribir() que se encuentra un poco más abajo en el programa, o la función -también de formateo- SetearLimites(), por supuesto desarrollada más abajo.

Según sea SelecMenu, se presentan las posibles escalas de visualización de temperatura, o se pasa a las correspondientes opciones de seteo de mínima y máxima.

  if (millis() - tiempo > 5000) {
    digitalWrite(PinIlum, LOW);
    SelecMenu = 0;
    guardarConfiguracion();
    tiempo    = millis();
  }

Este código se encarga de apagar la iluminación del display si pasan 5 segundos sin tocar ningún botón, fija la presentación en la pantalla informativa de temperatura y guarda la configuración.

  EstBtnAnt = EstBtnAct;
}

Por último, se iguala la variable estado de botón anterior (EstBtnAnt) al estado de la variable estado del botón actual (EstBtnAct) quedando a la espera de una nueva pulsación. Y termina aquí el bucle principal del programa.

int Leer_Botones() {
  ValorAnalog = analogRead(A0);
  if (ValorAnalog > 900)  return btnNONE;
  if (ValorAnalog < 50 )  return btnRIGHT; 
  if (ValorAnalog < 250)  return btnUP;
  if (ValorAnalog < 450)  return btnDOWN;
  if (ValorAnalog < 650)  return btnLEFT;
  if (ValorAnalog < 850)  return btnSELECT; 
  return btnNONE;                      

Esta función se encarga de leer el botón pulsado (entrada analógica A0). Como en el modulo display ingeniosamente los botones están en serie para solo usar una entrada analógica del arduino, cada botón dará un valor de tensión distinto y exclusivo a cada botón. Entonces según sea el botón pulsado, la función devuelve un literal acorde.

Las siguientes funciones se encargan de leer la temperatura desde la entrada analógica A1, y convertirla a las otras escalas.

float centigr() { return (500.0 * analogRead(A1))/1023; }

float kelvin (float& cent){ return (cent + 273.15); }

float fahrenh(float& cent) { return (cent * 1.8 + 32); }

float rankine(float& cent) { return ((cent + 273.15)*1.8); }

float reaumur(float& cent) { return (cent / 1.25); }

Siendo la función centigr() la que se encarga de leer la temperatura en °C. Esta fórmula sale de la relación del sensor con los grados. Es fácilmente rastreable por la web pero vamos a intentar explicarla un poco: El sensor de temperatura LM35 responde a variaciones de 10 mV por cada grado centígrado. Si el sensor detecta 1 grado centígrado a la salida del sensor obtenemos 10 mV. Ejemplo: 26,4ºC = 264 mV = 0.264 V.

Tenemos que el convertidor analógico digital del arduino que es de 10 bits de resolución, los valores variarán entre 0 y 1023, entonces Vout = (5V * Dato) / 1023 siendo  (0<Dato<1023) y para ajustar la escala a grados centígrados: Vout = ((5V * Dato) * 100) / 1023, o lo que es lo mismo:
(500.0 * analogRead (A1)) / 1023.

Las otras funciones solo hacen la conversión de centígrado a las otras escalas.

void Escribir(float& r, String t, byte c) {
  char buffn[8] = " ";   //Cadena donde almacenaremos el número convertido
  dtostrf(r,6,2,buffn);  //Llamada a la función
  String cadena = buffn;
  cadena.concat(t);
  lcd.setCursor(c,1);
  lcd.print(cadena);
}

En el caso de presentar con formato un valor de coma flotante, se me presentó el problema que la función sprintf() en arduino no funciona, esto se debe a que el entorno trata de obtener el .hex más chico posible, y esta función no trabaja de la misma manera que en C convencional, ya que es una versión resumida del original. Pero por fortuna existe dtostrf() que si soluciona este problema. Convierte el número real (r) a una cadena de 6 caracteres total con 2 decimales (6,2) devolviendo la cadena formateada en un buffer de tipo char (buffn).

A partir de tener el número ya convertido en cadena, simplemente se lo copia a la variable auxiliar Cadena de tipo String para concatenarla con la letra que representa la escala (parámetro t), por último se sitúa el cursor ya sea en la primer mitad de la segunda fila o la segunda mitad, según corresponda.

void SetearLimites(char s[16]) {
  lcd.print(s); 
  lcd.setCursor(0,1);
  switch(SelecMenu) {
    case 1: sprintf(temp, "%3d Ini. %3d Fin", TMin, TMin + RangoTem); break;
    case 2: sprintf(temp, "%3d Ini. %3d Fin", TMax, TMax - RangoTem); break;
  }  
  lcd.print(temp);
}

La función SetearLimites() hace algo similar, solo que en este caso al tratarse de números enteros si se puede usar la función sprintf() que facilita el formateo, presentando según sea el caso de SelecMenu las temperaturas mínima o máxima.

boolean cargarConfiguracion(){
  if ((EEPROM.read(0) == 'V') && (EEPROM.read(1) == 'B') &&  (EEPROM.read(2) == 'V')) {
    SelecTemp = EEPROM.read(3); 
    TMin      = EEPROM.read(4);  
    TMax      = EEPROM.read(5);
    RangoTem  = EEPROM.read(6);
    return true;
  }
  return false;
}

La función que se encarga de cargar la configuración primero verifica de que en la EPROM se encuentre grababa la "contraseña" VBV (nada muy original), a efectos de no cargar valores aleatorios, de no encontrar dicha cadena devuelve false, y se dejan los valores por defecto para las variables SelecTemp, TMin, TMax y RangoTem.

La primera vez que se energice el dispositivo no encontrará la susodicha contraseña, por ende informará del error de lectura y mantendrá los valores por defecto para esas variables, pero en cuanto el dispositivo funcione el tiempo necesario para guardar la configuración, ya cargará los valores seteados para estas variables.

por último, la función que se encarga de guardar la configuración:

void guardarConfiguracion(){
  EEPROM.write(0, 'V');
  EEPROM.write(1, 'B');
  EEPROM.write(2, 'V');
  EEPROM.write(3, SelecTemp);
  EEPROM.write(4, TMin);
  EEPROM.write(5, TMax);
  EEPROM.write(6, RangoTem);
}

Simplemente escribe en cada posición de la memoria EPROM la contraseña y los valores de las variables. Demás está que diga que la única condición a tener en cuenta es la de leer de las mismas posiciones donde se escribió.

Y hasta aquí, este proyecto "teórico", notese que la variable RangoTem se almacena, pero no es un valor que cambie, esto es porque bien podría ser este un valor modificable dándole un poco más de versatilidad al termostato, dejo como tarea entonces a quien pudiera estar interesado en esto la modificación de dicha variable.


2 comentarios: