lunes, 9 de noviembre de 2015

DHT11 + EEPROM

El objetivo de este tutorial es almacenar datos del DHT11 en la memoria EEPROM.
Si quieren saber todo sobre la EEPROM, les recomiendo ver el tutorial anterior y sensor humedad y temperatura dht11

Utilizaré lo que use al final del tutorial, que son los struct
Crearemos un struct con 3 campos
  1. temperatura
  2. humedad
  3. fecha/hora

También investigaremos cual es el tipo de variable que nos conviene para cada uno, ya sea, byte, char[10], String, int, long, etc 
  • char: 1
  • byte: 1
  • int: 2
  • unsigned int: 2
  • long: 4
  • unsigned long: 4
  • float: 4
  • double: 4
  • String: 6
Hay que buscar que el tamaño sea el más pequeño posible, porque recordemos que la EEPROM es una memoria pequeña y limitada

Hay analizar si queremos la temperatura y humedad de tipo entero 23 C° y 43 % o con número decimales 23,12 C° y 43,21 %, donde eso conlleva al doble de tamaño. int = 2 y float = 4

Para que nuestro proyecto sea de mayor exactitud voy a usar las variables de tipo float, porque que nuestras muestras sean lo más exactas posibles.
Porque es lo mismo decir 23,01 C° que 23,99 C°, cual si tuviéramos enteros siempre sería 23 C° y no veríamos ningún tipo de variación.

Entonces copio y pego el código de tutorial sensor de humedad y temperatura pero le cambio la variable int por el float, para obtener temperatura y humedad con decimales
#include "DHT.h"
#define DHTPIN 7
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
 
void setup() {
Serial.begin(9600);
dht.begin();
}
void loop() {  
float h = dht.readHumidity();// Lee la humedad
float t= dht.readTemperature();//Lee la temperatura
//////////////////////////////////////////////////Humedad
Serial.print("Humedad Relativa: ");                
Serial.print(h);//Escribe la humedad
Serial.println(" %");                    
///////////////////////////////////////////////////Temperatura              
Serial.print("Temperatura: ");                  
Serial.print(t);//Escribe la temperatura
Serial.println(" C'");                  
//delay (150);
///////////////////////////////////////////////////            
}

Salida
Humedad Relativa: 36.00 %
Temperatura: 18.00 C'
Humedad Relativa: 36.00 %
Temperatura: 18.00 C'
Humedad Relativa: 36.00 %
Temperatura: 18.00 C'



Como veo, es lo mismo que hubiera usado los enteros (int)

Entonces voy intentarlo una vez más, siguiendo el tutorial todo sobre println 
Voy a cambiar
Serial.print(h);//Escribe la humedad
por
Serial.print(h, DEC);//Escribe la humedad
para que salga obligadamente decimales


El resultado es peor aún.
Por lo cual me decido a trabajar con los enteros.

Ahora si somos mas curiosos podemos ver que mirando este cuadro comparativo, que habla la precisión de los sensoreres DHT11 (el que estoy usando) y el DHT22

y después de revisar la librerías del DHT

Me doy cuenta que las librería si te permite tenes resultados de tipo float, pero el problema esta en que el DHT11 no tiene la precisión que estaba buscando en la primera instancia.

Si alguno tiene un DHT22 y quiero comentar como les fue en la implementación, se los agradecería.

Continuamos...
La temperatura y la humedad, van a hacer tipo de enteros (int) y la fecha/hora va a hace de tipo String
Entonces nuestro struct nos quedaría algo así
struct MiObjeto{
  int temp; //tamaño 2 byte
  int hum;    //tamaño 2 byte
  String fecha; //tamaño 6 byte
};

Ahí le llame "MiObjeto" pero ustedes le pueden llamar como quieran. Esto fue lo primero que se me ocurrió.
Si hago un
MiObjeto customVar = {0,0,""};
Serial.println(sizeof(customVar));

Obtengo como resultado = 10
Hasta ahora va todo en orden, int = 2, int = 2, String = 6. Todo suma 10.
Y reviso el tamaño que tiene mi EEPROM de mi Arduino que estoy usando (en mi caso el MEGA)
Serial.println( EEPROM.length());
Salida = 4096


Esto quiere decir que si 4096 / 10 = 409,6 
Puedo almacenar 409 objetos, 409 struct. Es buen número.
Supongamos ahora que tenemos que hacer un lectura 1 vez cada una hora.
409 / 24 veces al día = 17, 03 días

También tenemos que pensar, que pasa si la memoria se llena, cuando llegamos al final, porque ahora estoy usando un Arduino Mega pero si pasamos a un arduino UNO, ya se achica mucho más, y si aumentamos la frecuencia de grabado, aumenta más todavía y así.
En el caso que se llena hay que evaluar si largamos algún cartel que diga, memoria llena, o reiniciarlo  (poniendo la dirección = 0) y que vaya sobrescribiendo desde el principio.

Uno como programador siempre tiene que pensar el mejor caso y el peor caso. Aunque lamentablemente para muchos siempre llega a aparecer el peor caso.



Otra forma de hacerlo: ajpdsoft github
Navegando por Internet, encontré los link que puse arriba, he notado que guardan la temperatura y la humedad en la EEPROM, como yo lo intento hacer, pero hay algo que me genera ruido, es que lo guardan uno debajo del otro, sin generar una estructura. En mi punto de vista, funciona cual me parece bien, pero si la temperatura y la humedad, y en mi caso también la fecha, están asociada a un instante de tiempo tienen que ir juntos de alguna forma. 
Sea un objeto, un arreglo, una estructura, nose, algo, de alguna forma. No puede estar uno debajo de la otra. Por que en algún punto se forma engorroso, no sabes a quien le pertenece a quien.

Por ejemplo
23C°
43%
11C° 
Si leemos esos tres datos, el 43% a quien esta asociado al 23 C° o al 11 C°. 

Link de interés: Timer.h 

Por el momento la fecha/hora es un problema, vas a tener que pasar de usar un String a un char[14] para poder guardar 12/03/15-09:12.
Habrá que investigar si usaremos la función Timer.h o millis() en crudo o incorporar un RTC (Reloj de tiempo real), este último sería el camino más fácil.

Código para escribir
#include <EEPROM.h>
#include "DHT.h"
#define DHTPIN 7
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);

int eeAddress = 0;

struct MiObjeto{
  int temp; //tamaño 2 byte
  int hum;    //tamaño 2 byte
};

MiObjeto customVar = {0,0};

void setup() {
  Serial.begin(9600);
  dht.begin(); //inicia el dht11

  int t= dht.readTemperature();//Lee la temperatura
  int h = dht.readHumidity();// Lee la humedad
  
  grabarMiObjeto(t, h);
}
void loop() {  
  
}

void grabarMiObjeto(int x, int y){
  customVar.temp = x;
  customVar.hum = y;
  EEPROM.put(eeAddress, customVar); 
  eeAddress += sizeof(customVar);
}

Lo que hice fue agregar la lectura del dht11 y guardarla en la primera dirección de la EEPROM. Esto es un gran avance aun que sigue quedando pendiente la fecha y la hora.

Seguramente usaremos el código del tutorial de millis()

Código para leer
#include <EEPROM.h>
int eeAddress = 0;
struct MiObjeto{
  int temp; //tamaño 2 byte
  int hum;    //tamaño 2 byte
};

void setup() {
  Serial.begin(9600);

  MiObjeto customVar;

  for (int i=0; i < 10; i++){
    Serial.print("Struct numero: ");Serial.println(i+1);
    EEPROM.get(eeAddress, customVar);
    Serial.println(customVar.temp);
    Serial.println(customVar.hum);
    eeAddress += sizeof(customVar);
  }

}

void loop() {
  // put your main code here, to run repeatedly:

}

El problema que se nos plantea esto, es cuando realizamos varias veces esta escrituras y lecturas se sobreescribe la información, porque siempre empieza grabando desde la posición cero.
Entonces lo que vamos a hacer agregar a nuestro programa que lea la última dirección almacenada.
Así vamos acumulando información sin tenes que perder lo que ya guardamos anteriormente

Además
 if(eeAddress >= EEPROM.length()) //para salvar el desbordameinto
    eeAddress = 0;
  voltageValue[4]= String(eeAddress);
Vamos a preguntar si llego al final de su capacidad de memoría para no tengamos un desbordamiento.
Si llegamos al final, o dejamos de guardar o empezamos desde 0.
En mi caso, se reiniciará y empezará desde la primera posición disponible

Como voy relatando este tutorial, es como realidad yo programo. Espero que le sea de agrado.
Como pueden ver voy a poquito.
Hago lecturas, hago escrituras, después le pongo loop, después me pongo a pensar lo que puede llegar a pasar y así sucesivamente. Siempre probando, guardando, probando y guardando.
En mi caso particular he tenido muy buenos resultados.

Vamos a tener que hacer 2 programas para poder guardar el direccionamiento
El Primero donde decimos que en la dirección0 = posición0
Código
#include <EEPROM.h>
#include "DHT.h"
#define DHTPIN 7
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);

int eeAddress; // empezamos en la dirección = 1 almacenar  
                   
struct MiObjeto{
  int temp; //tamaño 2 byte
  int hum;    //tamaño 2 byte
};

MiObjeto customVar = {0,0};

void setup() {
  Serial.begin(9600);
  dht.begin(); //inicia el dht11

  int t= dht.readTemperature();//Lee la temperatura
  int h = dht.readHumidity();// Lee la humedad

  
  //PRIMER VEZ - COMENTAMOS ESTA LINEA 
  //eeAddress = EEPROM.read(0); //dirección=0
  //SEGUNDA VEZ - DESCOMENTAMOS LA DE ARRIBA Y COMENTAMOS LA DE ABAJO
  eeAddress = 1;

  Serial.println(eeAddress);
  grabarMiObjeto(t, h);
  Serial.println(eeAddress);
  grabarMiObjeto(t, h);
  Serial.println(eeAddress);
  grabarMiObjeto(t, h);
  Serial.println(eeAddress);
  grabarMiObjeto(t, h);
  Serial.println(eeAddress);

  EEPROM.write(0, eeAddress);
  
}
void loop() {  
  
}


void grabarMiObjeto(int x, int y){
  customVar.temp = x;
  customVar.hum = y;
  EEPROM.put(eeAddress, customVar); 
  eeAddress += sizeof(customVar);
}



Y si lo volvemos a ejecutar, vuelvo a salir lo mismo valores
Y si lo volvemos a ejecutar, de nuevo vuelven a salir lo mismos valores, porque siempre desde la dirección 1
La dirección 0, contiene en este caso el 17, la última posición disponible para poder grabar

Ahora..
eeAddress = EEPROM.read(0); //DESCOMENTAMOS ESTE LINEA!

// eeAddress = 1;   //COMENTAMOS ESTA


En el Segundo código es donde decimos que la dirección0 = tiene el último valor guardado
(Si empezamos con este directamente, a lo mejor ya tenia un 42, y empieza desde acá, por eso importante ejecutar el código anterior para poder serializarlo)
#include <EEPROM.h>
#include "DHT.h"
#define DHTPIN 7
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);

int eeAddress; // empezamos en la dirección = 1 almacenar  
                   
struct MiObjeto{
  int temp; //tamaño 2 byte
  int hum;    //tamaño 2 byte
};

MiObjeto customVar = {0,0};

void setup() {
  Serial.begin(9600);
  dht.begin(); //inicia el dht11

  int t= dht.readTemperature();//Lee la temperatura
  int h = dht.readHumidity();// Lee la humedad

  
  //PRIMER VEZ - COMENTAMOS ESTA LINEA 
  eeAddress = EEPROM.read(0); //dirección=0
  //SEGUNDA VEZ - DESCOMENTAMOS LA DE ARRIBA Y COMENTAMOS LA DE ABAJO
  //eeAddress = 1;

  Serial.println(eeAddress);
  grabarMiObjeto(t, h);
  Serial.println(eeAddress);
  grabarMiObjeto(t, h);
  Serial.println(eeAddress);
  grabarMiObjeto(t, h);
  Serial.println(eeAddress);
  grabarMiObjeto(t, h);
  Serial.println(eeAddress);

  EEPROM.write(0, eeAddress);
  
}
void loop() {  
  
}


void grabarMiObjeto(int x, int y){
  customVar.temp = x;
  customVar.hum = y;
  EEPROM.put(eeAddress, customVar); 
  eeAddress += sizeof(customVar);
}

Dirección0 = última dirección disponible para almacenar




Podemos ver que se ahora se pueden guardar progresivamente la temperatura y la humedad, sin necesidad de sobre-escribirla cada vez que iniciamos nuestro Arduino.

Código de Lectura
#include <EEPROM.h>
int eeAddress;
struct MiObjeto{
  int temp; //tamaño 2 byte
  int hum;    //tamaño 2 byte
};

void setup() {
  Serial.begin(9600);

  MiObjeto customVar;
  eeAddress = 1;
  
  Serial.print("direccion: "); Serial.println(EEPROM.read(0));
  for (int i=1; i < 10; i++){  // i = 0 está la última direccio´almacenada
    Serial.print("Struct numero: ");Serial.println(i);
    EEPROM.get(eeAddress, customVar);
    Serial.println(customVar.temp);
    Serial.println(customVar.hum);
    eeAddress += sizeof(customVar);
  }

}

void loop() {
  // put your main code here, to run repeatedly:

}


Acá lo podemos ver un ejemplo total, donde cambie las lecturas del dht11 por números genericos 111, 222, 333, 444 para que se pueda ver mejor












1 comentario:

  1. Curso de Android presencial en D.F. aprende Programación de Dispositivos Móviles desde la instalación y configuración hasta poder realizar aplicaciones utilizando base de datos SQL Lite.

    ResponderEliminar