G043 Los relojes RTC y Arduino

Descripción
Los relojes de tiempo real o RTCs (Real Time Clock). Hemos visto la librería TimeLib para Arduino, pero usando el reloj interno del propio Arduino, opción siempre disponible y de precio imbatible. 


Pero tiene varios problemas, al ser interno, se reinicia cada 50 días aproximadamente, y además si cortamos la corriente se, pone el reloj a 00 y vuelve a empezar desde el 1 de enero de 1970.

Necesitamos una solución, que nos permita usar un reloj fiable que se mantenga incluso cuando apagamos Arduino. 

La solución es el módulo para reloj digital es el DS1307 de Maxim. Por muy poco dinero realiza todas las funciones para llevar un reloj con calendario y solo hay que añadirle una batería externa para que no pierda la hora al perder la corriente. 


Sus características son:
RTC: Real Time Clock, o reloj de tiempo real. Que lleva la cuenta de segundos minutos y horas además de día, mes y año automáticamente, válido hasta el año 2100.

56 byte de memoria RAM respaldada por una batería exterior, que mantiene la fecha y hora cuando no hay corriente.

Detección automática de corte de corriente y cambio a modo batería.

Muy bajo consumo, lo que ayuda a que la batería dure entre 5 y 10 años.

I2C integrado en el mismo chip.
 
 Además, es bastante fácil conseguir un reloj con batería montado en una placa y listo para usarse en nuestros proyectos, por unos pocos euros.

En esta sesión vamos a usar uno de estas placas, la Tiny RTC, con el DS1307, montado en un soporte de conexión, con su cristal, una batería de respaldo, y bus I2C, con lo que solo tendremos que conectar unos pocos cables y podremos empezar a jugar con el tema.
 
 
Esquema

De nuevo la conexión es trivial. Simplemente conectar SDA a Arduino A4 y SCL a A5.
 
Montaje

 
nota.-
El pin SQW, es una salida digital del chip DS1307, que nos permite generar una señal cuadrada de frecuencia programable( De1Hz, 4kHz, 8kHz,32kHz) que podemos emplear como base de tiempos para el reloj de cualquier circuito sin necesidad de un cristal adicional de cuarzo y circuito oscilador.

Por eso, en este caso, como no vamos a usarlo, lo dejamos sin conectar tranquilamente, después de comprobar en la hoja de normas que podemos hacerlo sin problemas.

El pin DS → conexión para un sensor de temperatura tipo DS18B20 que algunos HW-111 incorporan o permiten conectar externamente. No interviene en el funcionamiento del RTC
 
Código
En la sesión anterior descargamos e instalamos la nueva librería Time en nuestro IDE.  La ventaja de utilizar una librería coherente es que todas las funciones que vimos en la sesión previa siguen siendo válidas y se usan igual aunque tengamos ahora un reloj diferente.
Basta con que instalemos el modulo correspondiente al nuevo reloj DS1307. Y para ello descargamos e instalamos una nueva librería aquí Libreria DS1307RTC,
Como lo normal cuando instalas un RTC, es que no tenga la hora ajustada, vamos a empezar por ahí. 
Lo primero es comprobar que el RTC está conectado y detectado por Arduino para ello usamos el siguiente programa
El siguiente código permite saber si el RTC está conectado mediante un escaneo
#include <Wire.h>
void setup() {
  Wire.begin();
  Serial.begin(115200);
  Serial.println("Escaneando I2C...");
}
void loop() {
  byte error, address;
  int devices = 0;
  for(address = 1; address < 127; address++) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    if (error == 0) {
      Serial.print("Dispositivo encontrado en 0x");
      if (address < 16)
        Serial.print("0");
      Serial.println(address, HEX);
      devices++;
    }
  }
  if (devices == 0)
    Serial.println("No se encontraron dispositivos I2C");
  else
    Serial.println("Escaneo terminado");
  delay(5000);
}

El resultado debe ser 0X50 que es la memoria EPROM del RTC y 0X68 que es el I2C del RTC 

Lo segundo y más importante es inicializar el RTC y lo hacemos con el siguiente programa
#include <Wire.h>
#include <DS1307RTC.h>
#include <TimeLib.h>

void setup() {
  Serial.begin(115200);
  delay(1000);

  setTime(10, 30, 0, 31, 5, 2026);

  if (RTC.set(now())) {
    Serial.println("RTC inicializado correctamente");
  } else {
    Serial.println("Error al inicializar RTC");
  }
}

void loop() {}
Si hemos obtenido el mensaje RTC inicializado correctamente ya podemos seguir.

Los siguiente es una prueba de lectura, para saber si llega la hora desde el RTC
Prueba de lectura del RTC

#include <Wire.h>
#include <DS1307RTC.h>
#include <TimeLib.h>

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

  tmElements_t tm;

  if (RTC.read(tm)) {
    Serial.print(tm.Hour);
    Serial.print(":");
    Serial.print(tm.Minute);
    Serial.print(":");
    Serial.println(tm.Second);
  }
  else {
    Serial.println("No se puede leer el RTC");
  }
}

void loop() {
}

El siguiente código es un reloj, que al iniciar Arduino te pregunta si quieres poner el reloj en hora y fecha, en caso negativo seguirá con la hora y fecha que tenga el RTC en su interior.
#include <Wire.h>
#include <TimeLib.h>
#include <DS1307RTC.h>

bool rtcOK = false;
String buffer;

void setup() {
  Serial.begin(115200);
  Wire.begin();

  delay(1000);

  Serial.println("================================");
  Serial.println("      RELOJ DS1307 RTC");
  Serial.println("================================");

  syncRTC();

  Serial.println("Quieres ajustar hora? (y/n)");

  while (!Serial.available());
  if (Serial.read() == 'y') {
    setTimeFlow();
  }
}

void loop() {
  syncRTC();
  printClock();
  delay(1000);
}

// ---------------- SINCRONIZAR RTC ----------------
void syncRTC() {
  tmElements_t tm;

  if (RTC.read(tm)) {
    setTime(tm.Hour, tm.Minute, tm.Second,
            tm.Day, tm.Month,
            tmYearToCalendar(tm.Year));

    rtcOK = true;
  } else {
    rtcOK = false;
  }
}

// ---------------- AJUSTE HORA + FECHA ----------------
void setTimeFlow() {

  int h, m, s, d, mo, y;

  Serial.println("Introduce hora (HH MM SS):");
  readLine();
  sscanf(buffer.c_str(), "%d %d %d", &h, &m, &s);

  Serial.println("Introduce fecha (DD MM YYYY):");
  readLine();
  sscanf(buffer.c_str(), "%d %d %d", &d, &mo, &y);

  // ---- AJUSTE INTERNO ----
  setTime(h, m, s, d, mo, y);

  // ---- PREPARAR ESTRUCTURA RTC ----
  tmElements_t tm;

  tm.Hour = h;
  tm.Minute = m;
  tm.Second = s;

  tm.Day = d;
  tm.Month = mo;

  // MUY IMPORTANTE: conversión correcta del año
  tm.Year = CalendarYrToTm(y);

  // ---- ESCRIBIR EN RTC ----
  if (RTC.write(tm)) {
    Serial.println("RTC actualizado correctamente.");
  } else {
    Serial.println("ERROR al escribir en el RTC.");
  }
}

// ---------------- LECTURA SEGURA SERIAL ----------------
void readLine() {
  buffer = "";

  while (buffer.length() == 0) {
    if (Serial.available()) {
      buffer = Serial.readStringUntil('\n');
      buffer.trim();
    }
  }
}

// ---------------- MOSTRAR RELOJ ----------------
void printClock() {

  print2(hour()); Serial.print(":");
  print2(minute()); Serial.print(":");
  print2(second());

  Serial.print("  ");

  print2(day()); Serial.print("/");
  print2(month()); Serial.print("/");
  Serial.print(year());

  Serial.print("  ");

  if (rtcOK) Serial.print("[RTC]");
  else Serial.print("[INT]");

  Serial.println();
}

// ---------------- FORMATO 2 DIGITOS ----------------
void print2(int v) {
  if (v < 10) Serial.print("0");
  Serial.print(v);
}


Usando la fecha y hora del compilador.
Si estás haciendo pruebas con la librería a deshoras, es frecuente que tengas que recompilar una y otra vez tu código, y si cada vez tienes que poner en hora a mano el reloj.

El compilador de Arduino, dispone de un par de argumentos que nos informan de la fecha y hora de la última compilación: __DATE__ y __TIME__.

Podemos aprovechar esto para poner en hora el reloj interno corriendo un programita que convierta estos valores a formato fecha time_t y asignarlo al valor del reloj interno.

De este modo, aunque arrastraremos un par de segundos de error, asignaremos una hora razonablemente buena al reloj, sin necesidad de estar recompilando continuamente para ponerlo en hora.

Basta con que corráis este programa, aquí tenéis el programa de Ajuste automatico de fecha y hora

#include <Wire.h>
#include <TimeLib.h>
#include <DS1307RTC.h>

// ---------------- PARSE DE __DATE__ ----------------
void setFromCompileTime() {

  const char *date = __DATE__;
  const char *time = __TIME__;

  char monthStr[4];
  int day, year;
  int hour, minute, second;

  // DATE: "May 31 2026"
  sscanf(date, "%s %d %d", monthStr, &day, &year);

  // convertir mes
  int month = monthStringToInt(monthStr);

  // TIME: "12:34:56"
  sscanf(time, "%d:%d:%d", &hour, &minute, &second);

  // ajustar reloj interno
  setTime(hour, minute, second, day, month, year);

  // escribir en RTC
  tmElements_t tm;

  tm.Hour = hour;
  tm.Minute = minute;
  tm.Second = second;

  tm.Day = day;
  tm.Month = month;
  tm.Year = CalendarYrToTm(year);

  if (RTC.write(tm)) {
    Serial.println("RTC actualizado con hora de compilación");
  } else {
    Serial.println("ERROR escribiendo RTC");
  }
}

// ---------------- CONVERTIR MES ----------------
int monthStringToInt(const char *m) {

  if (strcmp(m, "Jan") == 0) return 1;
  if (strcmp(m, "Feb") == 0) return 2;
  if (strcmp(m, "Mar") == 0) return 3;
  if (strcmp(m, "Apr") == 0) return 4;
  if (strcmp(m, "May") == 0) return 5;
  if (strcmp(m, "Jun") == 0) return 6;
  if (strcmp(m, "Jul") == 0) return 7;
  if (strcmp(m, "Aug") == 0) return 8;
  if (strcmp(m, "Sep") == 0) return 9;
  if (strcmp(m, "Oct") == 0) return 10;
  if (strcmp(m, "Nov") == 0) return 11;
  if (strcmp(m, "Dec") == 0) return 12;

  return 1;
}

// ---------------- SETUP ----------------
void setup() {

  Serial.begin(115200);
  Wire.begin();

  delay(1000);

  Serial.println("=== AJUSTE AUTOMATICO RTC ===");

  setFromCompileTime();

  Serial.println("Listo. Hora sincronizada.");
}

// ---------------- LOOP ----------------
void loop() {

  tmElements_t tm;

  if (RTC.read(tm)) {
    setTime(tm.Hour, tm.Minute, tm.Second,
            tm.Day, tm.Month,
            tmYearToCalendar(tm.Year));
  }

  printClock();
  delay(1000);
}

// ---------------- MOSTRAR RELOJ ----------------
void printClock() {

  print2(hour()); Serial.print(":");
  print2(minute()); Serial.print(":");
  print2(second());

  Serial.print("  ");

  print2(day()); Serial.print("/");
  print2(month()); Serial.print("/");
  Serial.print(year());

  Serial.println();
}

// ---------------- FORMATO ----------------
void print2(int v) {
  if (v < 10) Serial.print("0");
  Serial.print(v);
} 

Librería TimeAlarms
Existe una librería más, llamada TimeAlarms que nos permite definir alarmas en nuestro programa, muy al estilo de la que podemos fijar en un reloj despertador.

Cuando una alarma dispara, llama a una función específica que se ejecuta en el momento preestablecido.

Descargar e instalar la librería TimeAlarms.

Dispone de varias funciones que vamos a enumerar aquí:
Alarm.alarmRepeat(hours, minutes, seconds, function);
Crea una alarma que se repite cada día a la hora especificada y ejecuta la función

Alarm.alarmRepeat(dayofweek, hours, minutes, seconds, function);
Crea una alarma que dispara solo el día de la semana especificada a la hora prevista

Alarm.alarmOnce(hours, minutes, seconds, function);
Dispara mañana una única vez a la hora fijada y ejecuta la función

Alarm.alarmOnce(dayofweek, hours, minutes, seconds, function);
Dispara una única alarma el próximo día de la semana que le digamos y ejecuta la función

Alarm.timerRepeat(seconds, function);
Llamará a la función especificada cada tantos segundos

.timerOnce(seconds, function);
Llamará una única vez a la función al cabo de los segundos especificados

Es una librería interesante si tenéis que repetir tareas periódicamente (Como enviar datos a algún sitio) y además nos ofrece uno timers cómodos que se ejecutan de forma programada, sin necesidad de definir interrupciones que podrían interferir con otros funciones.

La librería TimeAlarm no emplea interrupciones, sino que se apoya en la gestión interna de TimeLib

TimeAlarm no necesita ningún hardware o RTC especifico, solo requiere que la librería TimeLib esté cargada.

Se pueden definir hasta 6 alarmas, cambiando el header de TiemeAlarm.h

No se pueden definir intervalos menores de 1 segundo. Para periodos inferiores se requiere usar una interrupción programada o Timer que se verá en otra ocasión

Aquí tienes un ejemplo de uso 

#include <Wire.h>
#include <TimeLib.h>
#include <TimeAlarms.h>
#include <DS1307RTC.h>

// ---------------- ACCIONES ----------------
void alarma1() {
  Serial.println("🔔 ALARMA 1: Despertar / tarea 1");
}

void alarma2() {
  Serial.println("🔔 ALARMA 2: Recordatorio importante");
}

void alarma3() {
  Serial.println("🔔 ALARMA 3: Evento diario");
}

void tareaCada10s() {
  Serial.println("⏱ Tarea periódica cada 10 segundos");
}

// ---------------- SETUP ----------------
void setup() {
  Serial.begin(115200);
  Wire.begin();

  delay(1000);

  Serial.println("=== SISTEMA DE ALARMAS ===");

  // ---- sincronizar con RTC ----
  setSyncProvider(RTC.get);

  if (timeStatus() != timeSet) {
    Serial.println("RTC no disponible -> usando tiempo interno");
    setTime(12, 0, 0, 1, 1, 2026);
  } else {
    Serial.println("RTC sincronizado");
  }

  // ---------------- ALARMAS ----------------

  // diaria
  Alarm.alarmRepeat(7, 30, 0, alarma1);
  Alarm.alarmRepeat(13, 07, 0, alarma2);
  Alarm.alarmRepeat(20, 0, 0, alarma3);

  // cada 10 segundos
  Alarm.timerRepeat(10, tareaCada10s);

  Serial.println("Alarmas activadas");
}

// ---------------- LOOP ----------------
void loop() {
  digitalClockDisplay();
  Alarm.delay(1000);   // IMPORTANTE: reemplaza delay()
}

// ---------------- RELOJ ----------------
void digitalClockDisplay() {

  print2(hour()); Serial.print(":");
  print2(minute()); Serial.print(":");
  print2(second());

  Serial.print("  ");

  print2(day()); Serial.print("/");
  print2(month()); Serial.print("/");
  Serial.print(year());

  Serial.println();
}

// ---------------- FORMATO ----------------
void print2(int v) {
  if (v < 10) Serial.print("0");
  Serial.print(v);
}
 


No hay comentarios:

Publicar un comentario