RTC DS3231 impostare allarmi

ziopippo:
Inoltre mi assale anche un altro dubbio. La libreria da me usata gestisce automaticamente il passaggio all'ora solare/legale? Considera automaticamente anche gli anni bisestili?

L'RTC gestisce i bisestili, che sono comuni a tutti ma non i passaggi ora estiva ora invernale che invece dipendono da cosa si decide a livello di stato, poiche molti stati non seguono il fuso che gli competerebbe ma non è il caso dell'Italia.
Per quello c'è la libreria TimeZone, se la usi ti dico subito che devi cambiare l'include time.h in TimeLib.h a causa di un conflitto di nomi.
Se la usi, va configurata usando le seguenti istruzioni

//Central European Time (Frankfurt, Paris, Rome)
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};     //Central European Summer Time
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};       //Central European Standard Time
Timezone myTZ(CEST, CET);
TimeChangeRule *tcr;        //pointer to the time change rule, use to get TZ abbrev
time_t utc, local;

zoomx:
L'RTC gestisce i bisestili, che sono comuni a tutti ma non i passaggi ora estiva ora invernale che invece dipendono da cosa si decide a livello di stato, poiche molti stati non seguono il fuso che gli competerebbe ma non è il caso dell'Italia.
Per quello c'è la libreria TimeZone, se la usi ti dico subito che devi cambiare l'include time.h in TimeLib.h a causa di un conflitto di nomi.
Se la usi, va configurata usando le seguenti istruzioni

//Central European Time (Frankfurt, Paris, Rome)

TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};    //Central European Summer Time
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};      //Central European Standard Time
Timezone myTZ(CEST, CET);
TimeChangeRule *tcr;        //pointer to the time change rule, use to get TZ abbrev
time_t utc, local;

Bene, allora vorrei chiederti un consiglio.
Come già accennato, il progetto prevede oltre che ad un ovvio LCD anche un encoder rotativo (per risparmiare spazio) per gestire gli allarmi ed altro. Non avendo volutamente previsto una connessione ad internet per l'autoaggiornamento della data via NTP, secondo te, ha senso cambiare libreria in modo che gestica il passaggio dell'ora legale o conviene farlo fare manualmete dall'utente? Considera che vorrei prevedere anche una funzione per correggere eventuali errori nell'orario; A proposito di ciò devo ancora riuscire a capire bene come posso sfruttare la libreria che sto usando (LCDMenuLib.h di Nils Feldkämper) in modo da poter usare l'encoder per settare orario ed allarmi. Tale libreria l'ho usata già per un altro progetto ma utilizzando dei pulsanti ed è stato piuttosto smplice aggiustare il codice secondo le mie esigenze ma, con l'encoder, devo ancora trovare la soluzione giusta.

Io l'ho fatto perché ad ogni passaggio tra orari devo mettere a punto un po' di orologi in giro per cui non ne volevo aggiungere altri.
Aggiungere la connessione consuma sicuramente piedini, se usi la scheda Ethernet sono quelli della SPI.

Io invece ho voluto scartare l'ipotesi di internet perchè non essendo per me ma per un amico, se disgraziatamente dovesse cambiare router (come ha già più volte fatto) sarei costretto a tornare da lui ogni volta a riprogrmmare le credenziali di accesso al WiFi; So benissimo che ci sono librerie che prevedono la selezione del SSID ma poi resterebe il problema dell'inserimento della password.
Domanda: Se decidessi di mettere la libreria che mi hai segnalato, è sufficiente inserire/modificare qulle quattro righe di codice che mi hai postato?

Si, se non ricordo male quelle sono le uniche righe che ti personalizzano la libreria.

Dopo varie capocciate sul muro per cercare di capire come dichiarare le varie variabili finalmente sono riuscito ad ottenere ciò che volevo.
Ora mi chiedo se esiste un metodo più snello ed elegante per avere lo stesso risultato.

/*
   Esempio per accensione ed spegnimento relè temporizzati
*/
#include <TimeLib.h>
#include <TimeAlarms.h>
#include <LiquidCrystal_I2C.h>
#include <DS3232RTC.h>
#include <Wire.h>

AlarmId id;

// lcd object
LiquidCrystal_I2C lcd(0x27, 16, 2); // set the LCD address to 0x27 for a 16 chars and 2 line display

//================================= DEFINISCO LE VARIABILI DEI RELE' ====================================
const int releCO2 = 8;       //relè CO2             R1 NC
const int relePlafo = 9;    //relè Plafoniera       R4 NA
const int releTermo = 10;   //relè Temoriscaldatore R3 NA
const int releAereatore = 12; //relè Aereatore      R2 NA


#define RelayOn  HIGH
#define RelayOff LOW

#define RelayAirOn  LOW
#define RelayAirOff HIGH

#define RelayTermoOn  LOW
#define RelayTermoOff HIGH

#define RelayPlafoOn  LOW
#define RelayPlafoOff HIGH

//unsigned long CO2Timer[4]; // array per ore, minuti, secondi, durata
unsigned long AirTimer[4]; // array per ore, minuti, secondi, durata
//unsigned longPlafoTimer[4]; // array per ore, minuti, secondi, durata

unsigned long CO2Timer [4] = {10, 3, 0, 10}; // imposto timer alle 08:14:00 per la durata di 4 ore
unsigned long PlafoTimer [4] = {10, 11, 40, 10}; // imposto timer Plafo alle 11:14:30 per la durata di 30 ore


void setup() {
  Serial.begin(57600);
  while (!Serial) ; // wait for Arduino Serial Monitor


  setSyncProvider(RTC.get);  // the function to get the time from the RTC
  if (timeStatus() != timeSet)
    Serial.println("Unable to sync with the RTC");
  else
    Serial.println("RTC has set the system time");

  //setTime(01, 59, 30, 3, 24, 18); // set time to Saturday 8:29:00am Jan 1 2011

  lcd.init();
  lcd.clear();
  lcd.backlight();

  digitalWrite(releCO2 , RelayOn);
  pinMode(releCO2 , OUTPUT);
  digitalWrite(releAereatore , RelayAirOff);
  pinMode(releAereatore , OUTPUT);
  digitalWrite(releTermo , RelayTermoOn);
  pinMode(releTermo , OUTPUT);
  digitalWrite(relePlafo , RelayPlafoOff);
  pinMode(relePlafo , OUTPUT);


  // create the alarms, to trigger at specific times
  Alarm.alarmRepeat(CO2Timer[0], CO2Timer[1], CO2Timer[2], CO2); // 18:430 every day
  Alarm.alarmRepeat(PlafoTimer[0], PlafoTimer[1], PlafoTimer[2], Plafo); // 21:43:00 every day

  digitalClockDisplay();
  unsigned long ora[3] = {hour(), minute(), second()};
}

void loop() {
  printTime();
  Alarm.delay(0); // wait one second between clock display
  checkTimers();
}

// functions to be called when an alarm triggers:
void CO2() {
  Serial.println("Alarm: accendo CO2");
  lcd.setCursor(0, 0);
  lcd.print("CO2:ON AIR:OFF");
  digitalWrite(releCO2 , RelayOn); // Accendo CO2
  digitalWrite(releAereatore , RelayAirOff); // Spengo Aereatore
  Alarm.timerOnce(0, 0, CO2Timer[3], EndCO2); // tra quanto tempo in ore, minuti, secondi intervenire
}

void EndCO2() {
  Serial.println("Spengo CO2 - accendo AIR");
  lcd.setCursor(0, 0);
  lcd.print("CO2:OFF AIR:ON ");
  digitalWrite(releAereatore , RelayAirOn); //Accendo Aereatore
}


void Plafo() {
  lcd.setCursor(0, 0);
  lcd.print("PLAFO:ON                 ");
  Serial.println("Alarm: Accendo LUCI");
  digitalWrite(relePlafo , RelayOn); // Accendo luci
  Alarm.timerOnce(0, 0, PlafoTimer[3], EndPlafo); // tra quanto tempo in ore, minuti, secondi intervenire
}

void EndPlafo() {
  Serial.println("Spengo LUCI");
  lcd.setCursor(0, 0);
  lcd.print("PLAFO:OFF                 ");
  digitalWrite(relePlafo , RelayOff); //Spengo luci
  // esegui le varie istruzioni
}

void p2dig(uint8_t v)
// Print 2 digits with leading zero
{
  if (v < 10) lcd.print("0");
  lcd.print(v);
}

void printDigits(int digits) {
  Serial.print(":");
  if (digits < 10)
    Serial.print('0');
  Serial.print(digits);
}

const char *dow2String(uint8_t code)
// Day of week to string. DOW 1=Sunday, 0 is undefined
{
  static const char *str[] = {"---", "Dom", "Lun", "Mar", "Mer", "Gio", "Ven", "Sab"};

  if (code > 7) code = 0;
  return (str[code]);
}

void printTime(void)
// Display the current time on the display
{
  // display the current time
  lcd.setCursor(0, 1);
  p2dig (hour());
  lcd.print(":");
  p2dig(minute());
  lcd.print(":");
  p2dig(second());
}

void digitalClockDisplay() {
  // digital clock display of the time
  Serial.print(hour());
  printDigits(minute());
  printDigits(second());
  Serial.print(' ');
  Serial.print(day());
  Serial.print('/');
  Serial.print(month());
  Serial.print('/');
  Serial.print(year());

  //Serial.println();


  Alarm.delay(1000); // wait one second between clock display
}

void checkTimers() {
  // verifico l'ora attuale per rispettare i timers in caso di mancanza di corrente
  unsigned long ora[3] = {hour(), minute(), second()};
  unsigned  long nows = ( (ora[0] * 3600) + (ora[1] * 60) + ora[2] ); // trasformo l'ora attuale in secondi
  unsigned  long CO2Start = ( (CO2Timer[0] * 3600) + (CO2Timer[1] * 60) + CO2Timer[2] );
  unsigned  long PlafoStart = ( (PlafoTimer[0]  * 3600) + (PlafoTimer[1] * 60) + PlafoTimer[2] );
  unsigned  long CO2End = (CO2Timer[3] * 3600);
  unsigned  long PlafoEnd = (PlafoTimer[3] * 3600);
  Serial.print("CO2Start: ");
  Serial.print (CO2Start);
  Serial.print(" Durata: ");
  Serial.print (CO2End);

  Serial.print(" Totale Secondi: ");
  Serial.print (CO2Start + CO2End);
  /* Serial.print(" - PlafoStart: ");
    Serial.print (PlafoStart);
    Serial.print(" PlafoEnd: ");
    Serial.print (PlafoEnd);
  */

  Serial.print(" - now: ");
  Serial.print(nows);
  Serial.print (" ");

  if  ( (nows >= CO2Start) && (nows <= (CO2Start + CO2End))) {
    Serial.print (" CO2 ACCESA - AIR SPENTA ");
    lcd.setCursor(0, 0);
    lcd.print("CO2:ON-AIR:OFF");
    digitalWrite(releCO2 , RelayOn); // Accendo CO2
    digitalWrite(releAereatore , RelayAirOff); // Spengo  Aereatore
  }
  else {
    Serial.print (" CO2 SPENTA - AIR ACCESA ");
    lcd.setCursor(0, 0);
    lcd.print("CO2:OFF AIR:ON ");
    digitalWrite(releCO2 , RelayOff); // Spengo CO2
    digitalWrite(releAereatore , RelayAirOn); // Accendo Aereatore
  }
  if  ((nows >= PlafoStart) && (nows <= PlafoStart + PlafoEnd)) {
    Serial.print (" PLAFO ACCESA ");
    lcd.setCursor(9, 1);
    lcd.print("LED:ON");
    digitalWrite(relePlafo , RelayPlafoOn); // Accendo Plafo
  }
  else {
    Serial.print (" PLAFO SPENTA ");
    lcd.setCursor(9, 1);
    lcd.print("LED:OFF");
    digitalWrite(relePlafo , RelayPlafoOff); // Spengo Plafo
  }
  Serial.println ();
}

usare un altro font? :smiley: :smiley:

Aggiungo che se tu stai realizzando un impianto che , ad esempio, ha a che fare con un acquario oppure regola anche l'illuminazione artificiale di una serra ma non ha a che fare con umani (ad esempio l'illuminazione serve agli animali e non alle persone che vogliono osservarli) il passaggio tra ora estiva ed invernale può essere tralasciato.

zoomx:
Aggiungo che se tu stai realizzando un impianto che , ad esempio, ha a che fare con un acquario oppure regola anche l'illuminazione artificiale di una serra ma non ha a che fare con umani (ad esempio l'illuminazione serve agli animali e non alle persone che vogliono osservarli) il passaggio tra ora estiva ed invernale può essere tralasciato.

Si ci ha azzeccato! Il progetto serve ad automatizzare l'acquario di un amico. Convengo con te che non ha vitale importanza la gestione dell'ora legale/solare però, vuoi mettere a non vedere l'ora sbagliata sul display per 5 o 7 mesi? :smiling_imp: :grin:

Ed aggiungo... se posso limitare al minimo gli smanettamenti, sono più tranquillo che non trova bug nel codice. :grinning: :stuck_out_tongue_closed_eyes: :smiling_imp: :smiling_imp: :smiling_imp:

Patrick_M:
ho dato un'occhiata al readme.txt della libreria degli allarmi e ho trovato questo:

Alarm.timerOnce(Hour, Minute, Second, TimerFunction);
Description: As timerOnce above, but period is the number of seconds in the given Hour, Minute and Second parameters

quindi puoi impostare l'allarme all'orario che vuoi come gia facevi e impostare la durata per l'intervento con la timerOnce che accetta ore, minuti e secondi
quindi seguendo l'esempio

#include <Time.h>

#include <TimeAlarms.h>

void setup()
{
  Serial.begin(9600);
  setTime(8,29,0,1,1,11); // set time to Saturday 8:29:00am Jan 1 2011
  // create the alarms
  Alarm.alarmRepeat(8,30,0, MorningAlarm);  // 8:30am every day
  Alarm.alarmRepeat(17,45,0,EveningAlarm);  // 5:45pm every day
 
//
// nel caso di mancanza corrente
// qui ci va il controllo se siamo in un orario successivo al previsto per l'allarme
//per l'attivazione/disattivazione dei relè
// qualche cosa tipo
// if (oraAdesso > oraPrimoAllarme && oraAdesso < oraFineAllarme)
//    attiva/disattiva relè;

}

....

Stavo dando una riorganizzata al codice per aggiungere altre funzioni e, mi sono accorto che prevedendo i controlli sull'orario che ho implementato, posso a questo punto eliminare totalmente la libreria TimeAlarms in quanto la bypassa totalmente!
Che ne pensi?

...
 if  ( (nows >= CO2Start) && (nows <= (CO2Start + CO2End))) {
    Serial.print (" CO2 ACCESA - AIR SPENTA ");
    lcd.setCursor(0, 0);
    lcd.print("CO2:ON");
    digitalWrite(releCO2 , RelayOn); // Accendo CO2
    digitalWrite(releAereatore , RelayAirOff); // Spengo  Aereatore
  }
  else {
    Serial.print (" CO2 SPENTA - AIR ACCESA ");
    lcd.setCursor(0, 0);
    lcd.print("CO2:OFF");
    digitalWrite(releCO2 , RelayOff); // Spengo CO2
    digitalWrite(releAereatore , RelayAirOn); // Accendo Aereatore
  }
...

Alarm.alarmRepeat(8,30,0, MorningAlarm); // 8:30am every day
Alarm.alarmRepeat(17,45,0,EveningAlarm); // 5:45pm every day

queste non le usi più?

edit....

si certo, facendo i controlli come nel tuo secondo pezzo è come se impostassi degli allarmi. A questo punto prevedendo un aggiornamento periodico dell'ora in modo da essere abbastanza precisi puoi levare la timerAllerm

Patrick_M:
Alarm.alarmRepeat(8,30,0, MorningAlarm); // 8:30am every day
Alarm.alarmRepeat(17,45,0,EveningAlarm); // 5:45pm every day

queste non le usi più?

Ho provato a commentarle insieme all'include della libreria TimeAlarms.h e mi sono accorto che non ne aveva bisogno.

Ora mi capita una cosa stranissima. Se recupero l'ora corrente con

void SyncRTC() {
  setSyncProvider(RTC.get);  // the function to get the time from the RTC
  if (timeStatus() != timeSet)
    Serial.println("Errore sincronizzazione con RTC");
  else
    Serial.println("Orario sincronizzato con RTC");
  digitalClockDisplay(); // Stampo l'ora corrente sul monitor seriale
  
  }

e poi dopo aver effettuato le dovute modifiche con

void ore()
{
  if (LCDML_BUTTON_checkUp()) // incremento l'orario
  {
    tmpHours = tmpHours < 23 ? tmpHours + 1 : tmpHours;
    lcd.setCursor(4, 0);
    if ( tmpHours < 10 ) lcd.print("0");
    lcd.print(tmpHours);
    LCDML_BUTTON_resetUp();
  }
  else if (LCDML_BUTTON_checkDown()) // decremento l'orario
  {
    tmpHours = tmpHours > 0 ? tmpHours - 1 : tmpHours;
    lcd.setCursor(4, 0);
    if ( tmpHours < 10 ) lcd.print("0");
    lcd.print(tmpHours);
    LCDML_BUTTON_resetDown();
  }
  else if (LCDML_BUTTON_checkEnter()) // passo ai minuti
  { LCDML_BUTTON_resetEnter();
    stato = 1;
  }
}

void minuti()
{

  if (LCDML_BUTTON_checkUp()) // incremento i minuti
  {
    tmpMinutes = tmpMinutes < 59 ? tmpMinutes + 1 : tmpMinutes;
    lcd.setCursor(7, 0);
    if ( tmpMinutes < 10 ) lcd.print("0");
    lcd.print(tmpMinutes);
    LCDML_BUTTON_resetUp();
  }
  else if (LCDML_BUTTON_checkDown()) // decremento i minuti
  {
    tmpMinutes = tmpMinutes > 0 ? tmpMinutes - 1 : tmpMinutes;
    lcd.setCursor(7, 0);
    if ( tmpMinutes < 10 ) lcd.print("0");
    lcd.print(tmpMinutes);
    LCDML_BUTTON_resetDown();
  }
  else if (LCDML_BUTTON_checkEnter()) // termino
    stato = 2;
  LCDML_BUTTON_resetEnter();
}

e vado a riscrivere l'orario aggiornato con RTC.write(tm) come nel codice sotto,

void LCDML_DISP_loop(LCDML_FUNC_set_time)
{
  // loop function, can be run in a loop when LCDML_DISP_triggerMenu(xx) is set
  // the quit button works in every DISP function without any checks; it starts the loop_end function
  g_lcdml_initscreen = millis(); // reset initscreen timer

  switch (stato) {
    case 0:
      ore();
      break;
    case 1:
      minuti();
      break;
    case 2:
      tm.Hour = tmpHours;             //set the tm structure to 23h31m30s on 13Feb2009
      tm.Minute = tmpMinutes;
      tm.Second = 0;
      /*
           tm.Day = 18;
         tm.Month = 5;
         tm.Year = 2018 - 1970;
      */
      SyncRTC(); // Sincronizzo il RTC per recuperare la data corretta
      RTC.write(tm);            //set the RTC from the tm structure
      Serial.println("MEMORIZZO ED ESCO");
      SyncRTC(); // Sincronizzo il RTC per aggiornare ora e data sul display
      // end function for callback
      LCDML_DISP_funcend();
      stato = 0;
      break;
  }

non capisco perchè l'orario lo setta correttamente mentre, a volte, mi memorizza date assurde sull'RTC sia che io preveda il recupero e la riscrittura della data corrente sia che decida di non aggiornare affatto la data.
Tipo, per esempio, da un aggiornamento di orario fatto poco fa, mi ha aggiornato la data con questa:

Orario sincronizzato con RTC
11:10:25 Gio, 31/12/2065

:o :confused:

Consigli?

serve il listato completo... per ammattire un po :smiley:

però vedo che tu non tocchi la data quindi c'è qualche errore da qualche parte

qui nel caso di aumento orario

tmpHours = tmpHours < 23 ? tmpHours + 1 : tmpHours;

se tmpHours è minore di 23 (esempio 22) incrementa di 1 (esempio 23)
se tmpHours è uguale a 23 non incrementa, resta 23

stessa cosa nel decremento

tmpHours = tmpHours > 0 ? tmpHours - 1 : tmpHours;

se tmpHours è maggiore di 0 (esempio 1) decrementa di 1 (esempio 0)
se tmpHours è uguale a 0 non decrementa, resta 0

è corretto ciò che succede?

la funzione write dell'rtc è questa

void DS3231RTC::write(tmElements_t &tm)
{
  Wire.beginTransmission(DS3231_CTRL_ID);
  Wire.write((uint8_t)0); // reset register pointer
  
  Wire.write(dec2bcd(tm.Second)) ;   
  Wire.write(dec2bcd(tm.Minute));
  Wire.write(dec2bcd(tm.Hour));      // sets 24 hour format
  Wire.write(dec2bcd(tm.Wday));   
  Wire.write(dec2bcd(tm.Day));
  Wire.write(dec2bcd(tm.Month));
  Wire.write(dec2bcd(tmYearToY2k(tm.Year)));   

  Wire.endTransmission();  
}

però tu non imposti la data, perlomeno nel pezzo che hai allegato...

Grazie Patrick_M per la risposta.
Sciolgo subito i tuoi dubbi; Mi fermo a 23 perchè poi non avrebbe senso scrivere 24 visto che è possibile tornare a zero per settare la mezzanotte.

Come hai ben intuito la data non la tocco affatto ed è proprio per questo che non capisco da dove prenda quei valori assurdi e, soprattutto, per quale stranissimo motivo me l'aggiorna visto che io dichiaro solo l'orario! :confused:

se non gli passi i dati che a quanto pare sono richiesti e non opzionali .... presumo che prenda dei valori (che però dovrebbero essere quelli della data iniziale) predefiniti

la cosa migliore sarebbe leggere i valori correnti (tutti), modificare quelli che serve e poi riscriverli (tutti)

Patrick_M:
se non gli passi i dati che a quanto pare sono richiesti e non opzionali .... presumo che prenda dei valori (che però dovrebbero essere quelli della data iniziale) predefiniti

la cosa migliore sarebbe leggere i valori correnti (tutti), modificare quelli che serve e poi riscriverli (tutti)

Concordo su entrambe le cose tant'è che la prima volta ho provato a non passarli poi, per sicurezza, ho impostato in modo che venga richiamata due volte la funzione SyncRTC() proprio per far leggere la data certa prima di scrivere con rtc(write) e dopo aver scritto con rtc(write) per verificare cosa ha combinato.
In entrambi i casi scrive idiozie! :confused:

si ma non vedo il pezzo che dopo aver letto con SyncRTC() copia i valori nelle varie tm.day, tm.year... e così via

Patrick_M:
si ma non vedo il pezzo che dopo aver letto con SyncRTC() copia i valori nelle varie tm.day, tm.year... e così via

E' tutto qui. Leggo, modifico e scrivo.

 case 2:
      tm.Hour = tmpHours;             //set the tm structure to 23h31m30s on 13Feb2009
      tm.Minute = tmpMinutes;
      tm.Second = 0;
      /*
           tm.Day = 18;
         tm.Month = 5;
         tm.Year = 2018 - 1970;
      */
      SyncRTC(); // Sincronizzo il RTC per recuperare la data corretta
      RTC.write(tm);            //set the RTC from the tm structure
      Serial.println("MEMORIZZO ED ESCO");
      SyncRTC(); // Sincronizzo il RTC per aggiornare ora e data sul display
      // end function for callback
      LCDML_DISP_funcend();
      stato = 0;
      break;

quello che non facicio però è:

  Wire.beginTransmission(DS3231_CTRL_ID);
  Wire.write((uint8_t)0); // reset register pointer

e neanche

  Wire.endTransmission();

Sarà qui il problema?

Altra domanda:
Se volessi utilizzare la memoria SRAM dell'RTC per memorizzare alcuni dati tipo: temperatura, orario e durata dei vari allarmi, ecc. pensi sia fattibile? Quanti dati posso eventualmente salvare? Qual'è la procedura per scriverli e leggerli? :slight_smile: