Datalogger de nivel agua autonomo.

Muy buenas, estoy haciendo un datalogger para medir el nivel de agua en un estanque en el que varia habitualmente el nivel. Mi intención es tomar un dato de nivel cada hora y almacenarlo en una tarjeta SD junto con la hora de recogida del dato. El aparato ha de funcionar con batería o pila porque está en un lugar un poco inaccesible. Esto me permitirá ir cada varios meses a recoger los datos almacenados y cambiar las baterias.

Por el momento esta es la lista de materiales que tengo pensado utilizar:

  • Arduino nano
  • Adaptador de tarjeta SD
  • RTC. Tengo este: ZS-042 que incluye un DS3231
  • Sensor de presión diferencial. He pedido este: MPX5010DP
  • Baterías. Había pensado en unas de litio de este estilo: Baterias Li-ion

Creo que por el momento eso es todo en cuanto a materiales. Creo que con un par de baterías de esas me dan 7,4v que ya podría alimentar todo. En cuanto a cuanto me duraran no lo tengo muy claro, pone que son unos 3200-3400 mAh. Es decir que si pongo 2 serían 6400-6800 mAh. Cuando tenga el proyecto más avanzado tendre que mirar consumos y hacer una estimación.

Tengo bastante claro que si quiero alargar la autonomía tendré que apagar arduino. Mientras me llegan los materiales que he pedido, me he puesto con el reloj RTC que es lo que tengo a mano.

Lo que quiero hacer es que me de la hora en el momento de la medición y utilizar la alarma para despertar arduino por medio de una interrupción externa. Por lo que he leído parece ser que es posible hacer esto.

He decidido utilizar las librerías LowPower y RTClib.

Este es el código como lo tengo ahora, de momento he puesto la alarma cada minuto en vez de cada hora para hacer las pruebas:

#include <Wire.h>
#include "RTClib.h"
#include "LowPower.h"

RTC_DS3231 rtc;

DateTime alarma;
const int PinAlarma = 2;

void setup() {
  pinMode(PinAlarma,INPUT);
  Serial.begin(9600);
  comprobar_RTC();
  alarma = DateTime(20, 1, 1, 0, 0, 0); // Configura la fecha de alarma: año(0-99), mes(1-12), día(1-31),hora(0-23), minuto (0-59), segundo(0-59)
  rtc.setAlarm1(alarma,DS3231_A1_Second);  //Configuro la alarma1 para que salte cada minuto. Ver otras opciones en enum Ds3231Alarm1Mode
}

void loop() {
  attachInterrupt(digitalPinToInterrupt(PinAlarma), Despierta, LOW);  //Activa las interrupciones externas
  LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);  //Duerme arduino

  //Cuando despierte arduino, lo hará en este punto.
  
  DateTime ahora = rtc.now(); // Obtener fecha actual 

  Serial.println("_____________________________");
  Serial.print("Hora actual: ");
  Serial.print(ahora.hour());
  Serial.print(":");
  Serial.print(ahora.minute());
  Serial.print(":");
  Serial.println(ahora.second());
  Serial.print("Hora alarma: ");
  Serial.print(alarma.hour());
  Serial.print(":");
  Serial.print(alarma.minute());
  Serial.print(":");
  Serial.println(alarma.second());
  Serial.print(" Estado Alarma: ");
  Serial.println(rtc.alarmFired(1));  //imprime el estado de la alarma. 1 activo, 0 desactivada.

}

void Despierta(){
  detachInterrupt(digitalPinToInterrupt(PinAlarma));  //Desactiva las interupciones
  if (rtc.alarmFired(1)==1){   //En caso de que haya saltado la alarma la vuelve a resetear
    rtc.clearAlarm(1);
  }
}

void comprobar_RTC(){
   if (!rtc.begin()) {
      Serial.println(F("No se encuentra RTC"));
      while (1);
   }
   
   // Poner en hora y fecha actual el reloj, solo la primera vez, luego comentar y volver a cargar.
   // Ponemos en hora con los valores de la fecha y la hora en que el sketch ha sido compilado.
   //rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}

En cuanto a las conexiones, los pines del ZS042 los tengo conectados así: GND->GND de arduino, VCC->5v, SDA->A4 de arduino, SCL->A5 de arduino, SQW->pin2 de arduino

Por lo que he leído el pin 2 es el que tengo que usar en el arduino nano y el uno para las interrupciones.
He probado el código sin la interrupción y el powerdown y parece que va todo correcto. La cosa es que me da un poco de miedo hacer la prueba de dormir arduino porque he leído que si no lo haces bien lo puedes dejar dormido. Me gustaría que alguien le echara un ojo a lo que he hecho hasta ahora antes de probar.

Por otra parte, entre las opciones de apagado he puesto BOD_OFF, que según he leído, apaga un circuito que sirve para detectar niveles bajos de tensión. Tengo entendido que ahorra consumo, pero me entran dudas de los riesgos que corro teniendo en cuenta que va a funcionar con baterías. Por el momento no he conseguido encontrar mucha información sobre esto.

Seguiré con mis progresos y espero vuestras aportaciones.

Un saludo y gracias. :slight_smile:

**Moderador:**Hola @Daniel-G.
En la sección proyectos tienes estos dos hilos que debiste haber leído antes de postear

Como tu consulta es para otra sección lo muevo a Software.
Agradezco que hayas posteado debidamente el código y los enlaces.
Solo ubicaste mal tu tópico.

Hola Surbyte, si lo leí lo que pasa que como voy a seguir poniendo todos mis progresos y dudas que vaya teniendo durante el desarrollo entendí que le correspondía en proyectos. De todos modos gracias.

Respecto a mis dudas sobre apagar el BOD, he entendido que lo que hace el BOD es monitorizar el nivel de tensión mientras funciona arduino. Si esta desciende por debajo de cierto valor automáticamente se activa el Brown-out reset. Así que en teoría entiendo que simplemente es que seguiría funcionando con una tensión demasiado baja, al final se apagara entiendo.

Además he encontrado este post en el que me solucionan la duda

  1. ¿Corro riesgo de estropear mi arduino en modo sleep si la batería se acaba y por ello debo preever el uso de alguna función o algún tipo de hardware para evitarlo, o no es necesario?

El arduino no se estropeara, simplemente dejara de funcionar o lo hará mal. Pero la batería no debería descargarse por debajo de un determinado voltaje, se puede dañar...

Creo que si solo deja de funcionar o me empieza a dar lecturas erróneas puedo filtrarlas posteriormente. Creo que el problema es que se descargue demasiado la batería y se estropee por ello. De momento eso no me preocupa tanto.

Bueno, al final me he decido a probar. El código que había puesto más arriba no me funcionaba. Asi que lo que he hecho es resetear la alarma en el setup por si se queda alguna vez activa al tener pila propia el RTC. También saqué el reseteo de alarma de la función despierta y lo puse en el loop. Por último agregué un pequeño delay porque arduino se apagaba antes de sacarme toda la información por el monitor serie. Al final el código está así:

#include <Wire.h>
#include "RTClib.h"
#include "LowPower.h"

RTC_DS3231 rtc;

DateTime alarma;
const int PinAlarma = 2;

void setup() {
  pinMode(PinAlarma,INPUT);
  Serial.begin(9600);
  comprobar_RTC();
  alarma = DateTime(20, 1, 1, 0, 0, 0); // Configura la fecha de alarma: año(0-99), mes(1-12), día(1-31),hora(0-23), minuto (0-59), segundo(0-59)
  rtc.setAlarm1(alarma,DS3231_A1_Second);  //Configuro la alarma1 para que salte cada minuto. Ver otras opciones en enum Ds3231Alarm1Mode
  rtc.clearAlarm(1); 
}

void loop() {
  attachInterrupt(digitalPinToInterrupt(PinAlarma), Despierta, LOW);  //Activa las interrupciones externas
  LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);  //Duerme arduino
  //Cuando despierte arduino, lo hará en este punto.
  
  DateTime ahora = rtc.now(); // Obtener fecha actual 

  Serial.println("_____________________________");
  Serial.print("Hora actual: ");
  Serial.print(ahora.hour());
  Serial.print(":");
  Serial.print(ahora.minute());
  Serial.print(":");
  Serial.println(ahora.second());
  Serial.print("Hora alarma: ");
  Serial.print(alarma.hour());
  Serial.print(":");
  Serial.print(alarma.minute());
  Serial.print(":");
  Serial.println(alarma.second());
  Serial.print("Estado Alarma: ");
  Serial.println(rtc.alarmFired(1));  //imprime el estado de la alarma. 1 activo, 0 desactivada.

  if (rtc.alarmFired(1)==1){   //En caso de que haya saltado la alarma la vuelve a resetear
    rtc.clearAlarm(1);
  }

  delay(200); //Sin este delay no me llega a imprimir por el monitor serie todo.
  
}

void Despierta(){
  detachInterrupt(digitalPinToInterrupt(PinAlarma));  //Desactiva las interupciones
}

void comprobar_RTC(){
   if (!rtc.begin()) {
      Serial.println(F("No se encuentra RTC"));
      while (1);
   }
   
   // Poner en hora y fecha actual el reloj, solo la primera vez, luego comentar y volver a cargar.
   // Ponemos en hora con los valores de la fecha y la hora en que el sketch ha sido compilado.
   //rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}

A ver si me van llegando el resto de componentes y puedo seguir :slight_smile:

Poco a poco sigo avanzando. Ahora me he puesto con la escritura de los datos en la tarjeta SD y el sensor de nivel.

Esto es la parte del código que estoy cacharreando ahora.

#include <SD.h>
File logFile;

#include <Wire.h>
#include "RTClib.h"
RTC_DS3231 rtc;
 
void setup() {
  Serial.begin(9600);
  comprobar_RTC();
}

void loop() {
  DateTime ahora = rtc.now(); // Obtener fecha actual 
  if(ahora.second()==0){
      float valor2 = leer_nivel2();
      guardar_datos(ahora, valor2);
  }
}

float leer_nivel2(){ /********Leer en sensor de nivel y pasar a m***********/
  int aux = 0;
  for(int i=0;i<10;i++){
  aux = aux + analogRead(A0);   //sera un valor comprendido entre 0 y 1023. Tomamos 10 valores para promediar.
  delay (100);
  }
  float ValorSensor = aux / 10.00; //sera un valor comprendido entre 0 y 1023.

  float NivelAgua2 = (((ValorSensor/5)-0.04)/0.09)*10.1974; //Sacar la formula de la hoja de características

  Serial.print("ValorSensor: ");
  Serial.print(ValorSensor);
  Serial.print(", Nivel del Agua: ");
  Serial.println(NivelAgua2);
  
  return NivelAgua2;
}

void guardar_datos(DateTime fecha, float valor){ /********Guartar datos en la tarjeta SD***********/
  Serial.print(F("Iniciando SD ..."));
  if (!SD.begin(9))
  {
    Serial.println(F("Error al iniciar"));
    return;
  }
  else {
    Serial.println(F("Iniciado correctamente"));
  }
  
  logFile = SD.open("datalog.txt", FILE_WRITE); // Abrir archivo y escribir valor
  
  if (logFile) {
    logFile.print(fecha.year(), DEC);
    logFile.print('/');
    logFile.print(fecha.month(), DEC);
    logFile.print('/');
    logFile.print(fecha.day(), DEC);
    logFile.print(" ");
    logFile.print(fecha.hour(), DEC);
    logFile.print(':');
    logFile.print(fecha.minute(), DEC);
    logFile.print(':');
    logFile.print(fecha.second(), DEC);
    logFile.print(", ");
    logFile.print(valor);
    logFile.println(" cm");
    logFile.close();
  } 
  else {
    Serial.println("Error al abrir el archivo");
  }
  delay(500);
}

void comprobar_RTC(){
   if (!rtc.begin()) {
      Serial.println(F("No se encuentra RTC"));
      while (1);
   }
}

La parte de la tarjeta SD creo que ya está acabada, por lo menos hasta ahora me funciona bien.

En cuanto al sensor de nivel, para pasar los datos de la entrada analógica a cm he usado la fórmula que viene en la hoja de características.

Vout = Vs x (0.09 x P + 0.04) ± error
Que despejado me queda:
P = ((Vout/Vs)-0.04) / 0.09

Los resultados que me da son lógicos pero no se ajustan exactamente a la realidad. Me voy a poner a afinar la fórmula porque el voltaje suministrado Vs he puesto 5 v y midiendo con el multimetro veo que en realidad son 4,91.

A esto le tengo que añadir un valor de error. Porque en el caso de que no haya diferencia de presión (P=0) la fórmula me queda Vout = Vs x (0.04) ± error
En mi caso en la entrada me arroja el valor de 0.147 cuando según la fórmula sería 4.91*0.04 = 0.196, así que tendré que quitar 0.049.

De todos modos antes de seguir intentando calibrar el sensor quiero hacerme un tubo con una escala graduada donde hacer las pruebas en condiciones.

Además ahora mismo tengo conectada la salida del MPX5010 directamente a A0 y en la hoja de características veo esto.

Voy a ponerlo así en cuanto consiga la resistencia y el condensador, creo que eso estabilizará la señal y me evitará ruidos. Así que mientras me llegan y me preparo el tubo graduado me toda esperarme. Cualquier corrección o sujerencia son bienvenidos :slight_smile: