swRTC

Negli ultimi tempi ho notato che in diversi hanno cercato un modo per implementare in software un RTC, ossia un orologio in tempo reale, con il proprio Arduino. La libreria che ho sviluppato si chiama swRTC e, come dice il nome, serve per creare un RTC software con ore:minuti:secondi e giorno:mese:anno.
La libreria tiene conto degli anni bisestili ma non dell'ora legale.

In allegato trovate un pacchetto ZIP (per portabilità su più SO) da scompattare nella propria cartella delle librerie. Per usare la libreria basta un

#include <swRTC.h>

Poi si crea un'istanza della libreria:

swRTC rtc;

Prima di poter usare la libreria è necessario impostare la data e l'ora:

rtc.stopRTC();
rtc.setTime(ore, minuti, secondi);
rtc.setDate(giorno, mese, anno);
rtc.startRTC();

L'impostazione può essere fatta nel setup con dei valori predefiniti oppure anche durante l'esecuzione dello sketch, se ad esempio si ha un display LCD con il quale interagire con l'utente.

Per recuperare le informazioni sull'ora e sulla data basta usare i seguenti metodi:

rtc.getSeconds();
rtc.getMinutes();
rtc.getHours();
rtc.getDay();
rtc.getMonth();
rtc.getYear();

La libreria usa un interrupt basato sull'overflow del contatore incrementato dal timer 2: per questo motivo non interferisce con le funzioni temporali di Arduino quali delay e millis, che sono invece basate sul timer 0. Per contro, si perdono la funzionalità PWM sui pin 10/11 dato che su quei pin essa è gestita proprio dal timer 2.
La libreria è usabile al momento su micro sui micro Atmega8/8A, Atmega48/88/168/328/328 (con e senza core "P" PicoPower), compreso ovviamente le Arduino 2009/UNO, Atmega644/1284, Attiny25/45/85, Attiny24/44/84, Atmega640/1280/1281/2560/2561 (comprese ovviamente le Arduino Mega1280 e Mega2560) e Attiny2313, con clock di 1, 8 e 16 MHz. Altri micro e frequenze saranno aggiunti in seguito.

Alla libreria è allegato anche uno sketch di esempio ed un file README con altre informazioni.

Ringrazio pubblicamente lesto che mi ha dato una mano stanotte a sistemare alcuni problemi di codice.
Piccola precisazione: non sono un programmatore di C navigato (lo uso solo da 1 annetto) per cui se ci sono errori grossolani segnalatemeli pure.

La libreria è in una versione iniziale per cui è da provare sulle "lunghe distanze". Per quel poco che ho potuto verificarla, pare funzionare. Se, dopo le vostre verifiche, la libreria è valida, la includo nel playground così che sia accessibile a tutti.

Introdotto dalla versione 0.8 il deltaT, una correzione per riallineare l'RTC all'ora reale. La funzione accetta un numero che rappresenta il numero di secondi al giorno da sommare o sottrare per correggere gli scostamenti tra il tempo misurato internamente e quelle effettivo.

Dalla versione 0.8.4 la libreria supporta i timestamp, sia con epoca 1900 che 1970 (UNIX) tramite le funzioni getTimestamp e setClockWithTimestamp, rispettivamente per avere un timestamp basato sull'orologio interno e per impostare l'orologio con un timestamp.

La versione 1.0 è la prima versione stabile e completa della libreria. Rispetto alla 0.8.4 è stata aggiunta una funzione per avere il giorno della settimana di uno specifico timestamp.

Dalla versione 1.2.0 è possibile usare il modulo Real-Time Counter integrato in alcune MCU. Basta aggiungere la seguente #define prima dell'inclusione della libreria:

#define USE_INTERNAL_RTC
#include "swRTC.h"

AGGIORNAMENTO:
la libreria è stata resa pubblica ed è stato aperto un thread sul forum internazionale:
http://arduino.cc/forum/index.php/topic,80489.0.html

EDIT:
documentazione completa in italiano può essere trovata qui:
http://www.leonardomiliani.com/?p=411

AGGIORNAMENTI:
la libreria funziona sia con l'IDE 1.0 che con le precedenti versioni 0022/0023;

  • 1.0.2: contiene un primo fix per il problema dei timestamp calcolati su date successive al 29/02/2012;

  • 1.0.3: supporto (tranne che per i micro Attiny) del clock a 4 MHz;

  • 1.0.5: nuova funzione getWeekDay e supporto per gli Attiny4313; nuovo sketch di esempio;

  • 1.0.6: sistemato un bug nel calcolo del timestamp;

  • 1.1.0: setDeltaT ora lavora con gli interi; nuovo metodo getDeltaT;

  • 1.1.1: sistemato un bug in setDeltaT;

  • 1.1.2: getDelta restituisce il valore di deltaT con il giusto segno

  • 1.2.0: ora la libreria supporta il modulo Real-Time Counter presente in alcune MCU

DOWNLOAD:
l'ultima versione è disponibile a questa pagina

Grazie Leo72 (e Lesto) per il contributo.
Ho proprio l'occasione di provarlo!

mi associo con i ringraziamenti, ora da bravo impedito provo e vedo che riesco a fare :sweat_smile:

ho ordinato dei ds1307 ma per molte delle applicazioni mi basterebbe avere le 24h, quindi sembra perfetta!!!!

if (((year % 4) == 0) && ((year % 100) != 0) or ((year % 400) == 0))

funziona lo stesso, quindi figo...ma era voluto? ho visto che nel resto del codice avete usato le classiche || come operando or

Complimenti per l'uso dei timer (per me sono ancora un semi arabo) e anche per il supporto a diverse frequenze di clock :slight_smile:

l'unico peccato è che resettando Arduino si perde l'ora, altrimenti sarebbe un perfetto sostituto degli RTC!

leo!!! dovevi farla 1 mese fa cosi' risparmiavo i soldi dei ds1307!!! XD

e' perfetta per quello che mi serve, ovvero una mangiatoia per i pesci, a me basta che segni ore e minuti, ora devo solo integrare le accensioni alle ore che voglio e che capisca come inserire piu' allarmi a vari tempi :wink:

complimenti! molto utile!!

Altri micro e frequenze saranno aggiunti in seguito.

per caso avete intenzione di fare anche quella per gli attiny85? :smiley: :smiley:

@Federico:
no, effettivamente no.... e la cosa buffa è che non mi ha neanche segnalato l'errore il compilatore... boh..
Ora lo correggo.
PS sul reset:
purtroppo è una implementazione SW per cui ha i suoi limiti.

@reizel:
metti una serie di if e fai un controllo sulle ore e sui minuti.

if ((orarioHH1==rtc.getHours()) && (orario1MM==rtc.getSeconds()) { sfamaipesci(); }
if ((orarioHH2==rtc.getHours()) && (orario2MM==rtc.getSeconds()) { sfamaipesci(); }

ecc...

@alexdb:
vorrei per i tiny84/85, e magari mettere anche altre frequenze (4 e 20 MHz)

[EDIT]
ho rimesso online la versione con l'or "giusto (||) :wink:

@Federico:
no, effettivamente no.... e la cosa buffa è che non mi ha neanche segnalato l'errore il compilatore... boh..

si, sono rimasto stupito anch'io...i tanti misteri del wiring :stuck_out_tongue_closed_eyes:

PS sul reset:
purtroppo è una implementazione SW per cui ha i suoi limiti.

Si si, ne sono consapevole, però sono rimasto affascinato dall'uso dei timer e quindi da come risulta leggera la libreria :smiley:

secondo me sono stai DEFINEiti and or e not, per rendere il linguaggio più user-friendly (mai capitato di mettere una & o | sola?)

comunque io ho solo risolto un problemino di come far parlare la funzione di interrupt con la libreria, il resto è tutta farina del sacco di leo :slight_smile:

anche per me :grin:

se si stratta solo di una mancanza di corrente/reset limitato, si potrebbe salvare l'ora in eeprom, ma con una scrittura al secondo non credo che abbia una lunga vita.

Grande Leo XD XD XD XD
Una sola curiosità: su qualche Topic lamentavi l'imprecisione dei timer, mi pare, a causa della nota imprecisione del quarzo esterno; questa libreria potrebbe essere associata all'uso del quarzo "orologi" da 32,768KHz?

una prova terra terra per implementare un allarme
lcd 16x2
l'rtc parte alle 12:00:00
alle 12:01:00 accende un led
leo, sai che ti amerei se fossi una donna, o se tu fossi una donna, senti, segnatelo, nella prossima vita me la chiedi e io te la do, vorrei rinascere donna (anche se... lesbica...)... per dirti quanto apprezzo questo tuo lavoro!!!!!!!!!! :stuck_out_tongue_closed_eyes:

#include <swRTC.h>
swRTC rtc;
#include <LiquidCrystal.h>
 
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

const int motordx = 9;
const int motorsx = 8;

void setup() {
    delay(2000);
    rtc.stopRTC();
    rtc.setTime(12,00,00);
    rtc.startRTC();

    lcd.begin(16, 2);
    
    pinMode(motordx, OUTPUT);      
    pinMode(motorsx, OUTPUT);
}

void loop() {
  lcd.setCursor(5, 0);
  lcd.print(rtc.getHours(), DEC);
  lcd.print(":");
  lcd.print(rtc.getMinutes(), DEC);
  lcd.print(":");
  lcd.print(rtc.getSeconds(), DEC);
  lcd.print("  ");
  if ((12==rtc.getHours()) && (1==rtc.getMinutes()) && (0==rtc.getSeconds())) { 
  digitalWrite(motordx, HIGH);
  delay(5000);
  digitalWrite(motordx, LOW);
  delay(100);
  digitalWrite(motorsx, HIGH);
  delay(5000);
  digitalWrite(motorsx, LOW);
  }
  }

@lesto:
sei troppo buono. Tu sei una CIMA relativamente al C, mi sono accorto quanto io poco conosca questo linguaggio.
Per contro riesco a cavarmela perché con tanti anni di programmazione alle spalle, la logica per risolvere certi problemi ormai ce l'ho nel DNA :wink:

@Mike:
mi lamentavo dell'imprecisione dei contatori relativamente agli oscillatori interni. Ovviamente l'RTC software ha una certa precisione SOLO se si usa un quarzo esterno, anche per clock inferiori ai 16 MHz dell'Arduino. Parlando dell'Arduino, esso ha un oscillatore esterno al posto del quarzo che era presente sulla 2009. Forse quest'ultima scheda potrebbe avere una precisione maggiore rispetto alla UNO. Da verificare.

@reizel:
se io fossi nato donna, non sarei qui con voi ma a farla fruttare come si deve.... $) $)

Leo....
Complimenti :slight_smile:
la proverò non appena torno a casa :slight_smile:

leo72:
@Mike:
mi lamentavo dell'imprecisione dei contatori relativamente agli oscillatori interni. Ovviamente l'RTC software ha una certa precisione SOLO se si usa un quarzo esterno, anche per clock inferiori ai 16 MHz dell'Arduino. Parlando dell'Arduino, esso ha un oscillatore esterno al posto del quarzo che era presente sulla 2009. Forse quest'ultima scheda potrebbe avere una precisione maggiore rispetto alla UNO. Da verificare.

Quindi il timer che stai usando è già predisposto per il quarzo dedicato? Cioè carico la tua libreria e collego il quarzo orologio ai pin predisposti e funziona?

@reizel:
se io fossi nato donna, non sarei qui con voi ma a farla fruttare come si deve.... $) $)

finitela entrambi di fare le zoccole :grin:
@ reizel: finalmente è chiaro che quello sballonzolamento continuo è un tuo desiderio di trasformazione fisica, ho sentito che a Casablanca fanno miracoli, in ogni caso alla larga da me ]:smiley: o almeno dalle mie spalle :smiley:

La libreria così com'è funziona con clock a 1/8/16 MHz. Come detto, è precisa se si usa un quarzo esterno. Quindi se la carichi su una Arduino, 2009 o UNO che sia (coi dubbi espressi per l'oscillatore esterno di quest'ultima) va senza problemi. Su un micro in standalone è meglio usare un quarzo esterno, anche se si sta usando il micro a bassa frequenza.

leo72:
@lesto:
sei troppo buono. Tu sei una CIMA relativamente al C, mi sono accorto quanto io poco conosca questo linguaggio.
Per contro riesco a cavarmela perché con tanti anni di programmazione alle spalle, la logica per risolvere certi problemi ormai ce l'ho nel DNA :wink:

il trucco è che non è c, ma c++, insomma la tua non è una libreria ma una classe :slight_smile:

leo72:
La libreria così com'è funziona con clock a 1/8/16 MHz. Come detto, è precisa se si usa un quarzo esterno. Quindi se la carichi su una Arduino, 2009 o UNO che sia (coi dubbi espressi per l'oscillatore esterno di quest'ultima) va senza problemi. Su un micro in standalone è meglio usare un quarzo esterno, anche se si sta usando il micro a bassa frequenza.

la cosa più fica da fare, secondo me, sarebbe l'opzione per usare 2 pin a scelta come input per un quarzo esterno da 32,768KHz, però non so come funzionino...

Beh, allora si torna a quello che avevo detto in un altro thread, ossia che usando il modulo RTC puoi collegare un quarzo da 32768 Hz esterno.
Ci studierò sopra.

Fantastica soluzione, a quel paese il mio rtc hw!!!! :smiley:

azz, in 2 ore il topic e' andato a in seconda pagina! scrivo per riportarlo in alto visto che e' utilissimo.

allora ho fatto il codice della mia mangiatoia per pesci usando l' swrtc di leo, ora son segnati 9 allarmi che corrispondono a 9 pasti giornalieri

problema: vorrei poter decidere quanti pasti distribuire, ho un rotary switch binario da 0 a 9 collegato su A1-2-3-4 come posso integrare questo tipo di selezione nel codice? ovvero 1 pasto/giorno 2pasti/giorno ... 9 pasti/giorno mi serve uno spunto, non so come fare...

#include <swRTC.h>
swRTC rtc;
#include <LiquidCrystal.h>
 
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

const int motordx = 9;
const int motorsx = 8;
const int finecorsadx = 7; 
const int finecorsasx = 6; 
const int bit1 = A1;
const int bit2 = A2;
const int bit3 = A3; 
const int bit4 = A4; 
  int avvio = 0; 
  int valavvio = 0;
  int valfinecorsadx = 0;
  int valfinecorsasx = 0;

void setup() {
    delay(2000);
    rtc.stopRTC();
    rtc.setTime(20,00,30);
    rtc.startRTC();

    lcd.begin(16, 2);
    
    pinMode(motordx, OUTPUT);      
    pinMode(motorsx, OUTPUT);
    
    pinMode(finecorsadx, INPUT); 
    pinMode(finecorsasx, INPUT); 
}

void loop() {
  lcd.setCursor(5, 0);
  lcd.print(rtc.getHours(), DEC);
  lcd.print(":");
  lcd.print(rtc.getMinutes(), DEC);
  lcd.print(":");
  lcd.print(rtc.getSeconds(), DEC);
  lcd.print("  ");
  
  if ((6==rtc.getHours()) && (30==rtc.getMinutes()) && (0==rtc.getSeconds())) { 
    (avvio = 1);
    }  
  
  if ((8==rtc.getHours()) && (0==rtc.getMinutes()) && (0==rtc.getSeconds())) {
    (avvio = 1);
    }
  
  if ((9==rtc.getHours()) && (30==rtc.getMinutes()) && (0==rtc.getSeconds())) {
    (avvio = 1);
    }
  
  if ((11==rtc.getHours()) && (0==rtc.getMinutes()) && (0==rtc.getSeconds())) {
    (avvio = 1);
    }  
  
  if ((12==rtc.getHours()) && (30==rtc.getMinutes()) && (0==rtc.getSeconds())) {
    (avvio = 1);
    }
  
  if ((14==rtc.getHours()) && (0==rtc.getMinutes()) && (0==rtc.getSeconds())) {
    (avvio = 1);
    }
    
  if ((15==rtc.getHours()) && (30==rtc.getMinutes()) && (0==rtc.getSeconds())) {
    (avvio = 1);
    }  
  
  if ((17==rtc.getHours()) && (0==rtc.getMinutes()) && (0==rtc.getSeconds())) {
    (avvio = 1);
    }
  
  if ((18==rtc.getHours()) && (30==rtc.getMinutes()) && (0==rtc.getSeconds())) {
    (avvio = 1);
    }  
    
    valavvio = avvio;
    
    if (valavvio == 1){ 
    avvio = 0;
    digitalWrite(motordx, HIGH);
    }

    valfinecorsadx = digitalRead(finecorsadx);
    
    if (valfinecorsadx == HIGH){
    digitalWrite(motordx, LOW);
    delay(50);
    digitalWrite(motorsx, HIGH);
    }
    
    valfinecorsasx = digitalRead(finecorsasx);
    
    if (valfinecorsasx == HIGH){
    digitalWrite(motorsx, LOW);
    }
}

Spiega per bene.
Vuoi scegliere quanti pasti distribuire durante il giorno?

Beh, non è difficile. Prepari un array di 9 celle in cui metti l'orario della distribuzione dei 9 pasti e poi scegli la distribuzione dei pasti con l'encoder a rotazione.

Magari avrai:
orariPasti = {6, 8, 10, 12, 14, 16, 18, 20, 22} (è un esempio)
1 solo pasto: orariPasti[3] (le 12)
2 pasti: orariPasti[2] e orariPasti[5] (10 e 16)
3 pasti: (10, 16, 20 )
ecc...