enc28J60 e Arduino (13)

luca 04/01/2013 27

L’articolo di oggi vi mostrerà come ottenere via Internet l’ora esatta, utilizzando il servizio NTP (Network Time Protocol).

NTP

NTP è un protocollo client-server a livello applicativo; utilizza come protocollo di trasporto UDP sulla porta 123.

Interrogando un server NTP, si ottiene come risposta un valore (timestamp) di 64bit così suddiviso:

  • i primi 32bit indicano il numero di secondi trascorsi dal giorno 01/01/1900
  • i rimanenti 32bit sono la frazione del secondo attuale

Esistono diversi server NTP: negli Stati Uniti ad esempio il NIST (National Institute of Standards and Technology) mantiene una rete di server. Vivendo in Italia, ho scelto di utilizzare come time server per questo articolo quello offerto dall’INRiM (Istituto Nazionale di Ricerca Metereologica):

static byte ntpServer[] = {193,204,114,232};

Arduino

La libreria EtherCard mette a disposizione due metodi per interagire con un server NTP:

  • ntpRequest(ntpServer, srcPort), invia la richiesta al server specificato;
  • ntpProcessAnswer(&timeStamp, srcPort), riceve la risposta e ne estrae il timestamp (solo i primi 32bit).

Il parametro srcPort serve per identificare, tra i vari pacchetti che lo shield Ethernet può ricevere, quello che contiene la risposta del server: il suo valore è arbitrario ma deve essere uguale sia nella Request che nella ProcessAnswer.

Una volta ottenuto il valore di timestamp, è necessario convertirlo in data-ora. Il procedimento è il seguente:

  • si conta il numero di anni completi contenuti nel valore di timestamp;
  • si passa poi a verificare quanti mesi completi sono contenuti nel resto;
  • si prosegue con giorni, ore, minuti;
  • il resto finale indica il numero di secondi attuali.

Alcune piccole complicazioni:

Gli anni possono essere bisestili: per tali anni il numero di secondi da considerare non è 365*86400 ma 366*86500. Per calcolare se un anno è bisestile si può utilizzare la formula:

boolean isLeapYear(unsigned int year) {
 
  return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
}

Il numero di giorni in un mese è variabile: il valore è memorizzato in un apposito array:

static int days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

Se l’anno è bisestile, Febbraio ha 29 giorni:

if(isLeapYear(year) && month == 1) seconds = SECONDS_IN_DAY * 29;

Il timestamp infine è riferito a GMT, se siamo in un differente fuso orario, dobbiamo correggerne il valore:

#define TIME_ZONE               +1
[...]
printDate(timeStamp + 3600 * TIME_ZONE);

Lo sketch completo è disponibile su Github… ecco uno screenshot del suo funzionamento:

27 Comments »

  1. MARCO 06/01/2013 at 11:47 - Reply

    Grazie,cercavo proprio un’esempio del genere!

  2. Sol 09/02/2013 at 21:10 - Reply

    I love your tutorials! You are the only person who understands AND shares his wisdom about the ENC28j60 – thank you so much!

    Do you know how to put the value of a variable on the webserver so the user who is on the website can read it?

    • luca 12/02/2013 at 09:25 - Reply

      Hi Sol

      could you please better explain what do you need?

  3. claudio 12/02/2013 at 15:20 - Reply

    grazie per questo esempio, ho tuttavia un problema per un mio progettino su un antifurto casalingo.
    Come posso ottenere il tempo da server senza inviare un comando da seriale?
    Ho stupidamente ponticellato rx e tx ed inviato con il comando Serial.print la lettera ‘n’ il sisytema si ferma al messaggio ‘ request send ‘
    grazie Claudio

  4. hernan 23/03/2013 at 12:38 - Reply

    Ciao Luca scusami per il disturbo, ma mi sapresti spiegare come inviare e ricevere dati fra arduino ed una pagina web senza che quest’ultima si trovi nella memoria di arduino ? io vorrei inviare qualche byte a una pagina web, questa la elabori tramite qualche script e mostri i risultati, ad esempio arduino acquisisce la temperatura di un posto, elabora e spedisce i dati a questa pagina web che visualizzerà la temperatura, come lo faresti?? grazie ancora per i tuoi tutorial.

    • luca 25/03/2013 at 09:53 - Reply

      Ciao Hernan,

      la cosa è molto semplice: puoi realizzare una pagina web dinamica (in php, asp…) e inviare ad essa i dati con Arduino, tramite i metodi GET o POST.

  5. hernan 26/03/2013 at 12:00 - Reply

    potresti farci un breve tutorial su questo argomento? in internet non si trova proprio esempi del genere tranne due o tre esempi fatti anche male, comunque complimenti, girando in rete i tuoi tutorial sono molto apprezzati dalla comunità arduino, ho trovato tanti riferimenti al tuo sito in siti portoghesi, inglesi, spagnoli, ecc..

    • luca 27/03/2013 at 08:53 - Reply

      ok! ci lavoro…

  6. igor 14/04/2013 at 14:03 - Reply

    Grande tutorial.
    Ho però un problema. Usando il suo esempio (senza seriale) il server INRIM mi risponde sempre con timestamp=0
    Ho dhcp disabilitato su router e perciò ho modificato lo sketch dando
    ether.staticSetup(myip,mygt,mydns); con i miei valori. Per provare ho anche modificato nella libreria ethercard di default la mask a 255.255.255.0.

    Ho anche provato ad aprire sul router la porta udp 123 verso l’ip dell’arduino, ma ricevo sempre 0.

    • luca 16/04/2013 at 20:28 - Reply

      Igor,

      sicuramente non serve il NAT perché la comunicazione è da Arduino verso Internet. Arduino ti stampa via seriale anche “NTP answer received”?

      • igor 18/04/2013 at 10:25 - Reply

        Grazie per la risposta. Si, la risposta arriva ma sempre con tutti i byte a zero (ho stampato il buffer di risposta e dal byte 30° mi sembra in poi è tutto a 0). Con il server INRIM non mi funziona.
        Ho cambiato server a cui fare la richiesta e ora funziona (non ricordo però quale ho messo, rispondo da ufficio e non da casa).

        • luca 26/04/2013 at 14:08 - Reply

          ottimo Igor, strano che non ti vada INRIM: ho appena provato lo sketch e mi risponde…

          • igor 03/05/2013 at 15:09 -

            Infatti trovo strano anche io. Non ho più riprovato. Magari bisogna essere registrati?

  7. tom 01/05/2013 at 17:36 - Reply

    Hi, your tutorials are the only decent tutorial about ENC28j60 I can find. Can you give me an example how to apply the data/time result on webserver?

    • luca 01/05/2013 at 19:21 - Reply

      Hi Tom and thanks! Could you please explain better what you need? Display the time got from NTP on a webpage?

  8. tom 12/05/2013 at 19:10 - Reply

    Hi, me again. Please ignore my last question. Here is the new one – no matter I change whatever other NTP IP, they still show me the same timestamp, do you know why?

    • luca 13/05/2013 at 09:42 - Reply

      Hi Tom, could you please explain better? Do you get always the same timestamp? Doesn’t it change?

  9. francesco 21/06/2013 at 14:12 - Reply

    Ho scoperto perche a me non va …da bravo cieco che sono io monto un chip Wiznet W5100 e per questomotivo (credo on vada)..comeposso adattare questo (stupendo e ricercatissimo )codice?

    • luca 21/06/2013 at 18:41 - Reply

      ciao, è impossibile adattarlo: il W5100 è completamente diverso. Per tale chip esistono le librerie ufficiali Arduino.
      bye

  10. Marco 10/07/2013 at 09:31 - Reply

    fatal error: avr/pgmspace.h: No such file or directory
    compilation terminated.

    Eppure il file c’è e come… Come mai?

    • luca 10/07/2013 at 15:38 - Reply

      Ciao Marco

      stai provando a compilare l’esempio di questo post?

  11. Ivan 15/08/2013 at 11:19 - Reply

    Hi, Luca,

    I find your tutorials extremely well-written and useful. One question regarding this particular example – ntpProcessAnswer indeed processes only the first timestamp and drops the fraction altogether.
    Could you provide any tips on how we could process the fraction timestamp manually?
    I was considering setting the software RTC via NTP with a bit more accuracy, and accounting for the milliseconds would increase the accuracy to +/- network latency.

    • luca 10/09/2013 at 08:25 - Reply

      Hi Ivan

      take a look at the file “tcpip.cpp” in the EtherCard library folder: the ntpProcessAnswer() method gets the first 4 bytes of the NTP packet: if you need also to get the milliseconds, retrieve also the next 4 bytes: http://www.meinbergglobal.com/english/info/ntp-packet.htm

  12. Piero 05/01/2014 at 02:26 - Reply

    Ciao Luca,
    anzitutto grazie davvero per tutti i tuoi post, in particolare proprio questi sulla ENC28J60, sei utilissimo e preziosissimo :-)

    Ti disturbo per un consiglio. Vorrei implementare il controllo dell’orario NTP in uno sketch che usa la libreria RTClib (https://github.com/adafruit/RTClib) , che di certo conosci. Per ora vorrei usare soltanto il softrtc, e poi magari ” infierire” configurando un DS1307 che interroga un server NTP per settare data e ora esatte. Visto che l’rtc va configurato nel setup(), avevo pensato di fare una cosa del genere, per ottenere il TIMESTAMP dal server NTP e poi, dopo averlo “lavorato”, configurarci appunto l’rtc

    #include “EtherCard.h”

    static byte mymac[] = {0x00,0x1A,0x4B,0x38,0x0C,0x5C};
    byte Ethernet::buffer[700];

    static byte ntpServer[] = {65,55,56,206}; // è time.windows.com, ma non funzionano anche altri
    static byte srcPort = 0;

    uint32_t timeStamp;
    boolean requestSent;

    void setup(){

    Serial.begin(9600);

    … istruzioni per la rete…

    ether.packetLoop(ether.packetReceive());
    ether.ntpRequest(ntpServer, srcPort);
    Serial.println(“NTP request sent”);
    ether.ntpProcessAnswer(&timeStamp, srcPort);
    Serial.println(“NTP answer received”);
    Serial.print(“Timestamp: “);
    Serial.println(timeStamp);

    … istruzioni per trasformare il timestamp e settare l’rtc …

    } // fine setup

    void loop(){

    } // fine loop

    Ma restituisce soltanto 0.
    Uhm… cosa mi sfugge? non è che ntpRequest & C. possono funzionare soltanto in un loop?
    Riesco a farlo in un loop, ma non vorrei inviare una richiesta al server NTP ogni secondo (ehm… per visualizzare data e ora su un LCD :-))
    A me servirebbe il timestamp soltanto una volta alla partenza dello sketch o in seguito a un eventuale reset… Cosa mi consigli?

    Grazie in anticipo per la risposta, un saluto e… continua così!
    ;-)

    • luca 14/01/2014 at 18:57 - Reply

      Ciao Piero

      grazie per i complimenti ;) è come giustamente hai intuito: per come funziona la libreria, è necessario il loop perché con le istruzioni ether.packetLoop(ether.packetReceive()) la libreria processa un pacchetto alla volta, quindi hai bisogno che tali istruzioni vengano eseguite più volte per stabilire la connessione, inviare la richiesta, gestire la risposta…
      Puoi comunque usare una variabile di servizio booleana (es. “timeSet”) da configurare a false dopo che hai ricevuto almeno una risposta dal timeserver e quindi nel loop controllarne il valore: se è a false non mandi più nuove richieste.

  13. Piero 19/01/2014 at 14:59 - Reply

    Ciao Luca,
    grazier per il consiglio… mi sembra di esserci finalmente riuscito :-)

    Non ti posto qui il codice per non incasinarti oltre il blog. Se ti va di darci un occhio, lo trovi qui:

    http://pastebin.com/Rz2RGTFq

    ora bisogna “soltanto” trovare il modo di passare il TIMESTAMP all’istruzione rtc.begin(), che però è nel setup() e se lo metto nel loop() sembra non funzionare… mumble mumble….
    grazie ancora,
    ciao

    • luca 27/01/2014 at 22:08 - Reply

      Ottimo lavoro Piero! Invece che rtc.begin() puoi usare il metodo adjust() a cui passi un oggetto di tipo DateTime…

Leave A Response »

Questo sito usa i cookie per poterti offrire una migliore esperienza di navigazione maggiori informazioni

Questo sito utilizza i cookie per fonire la migliore esperienza di navigazione possibile. Continuando a utilizzare questo sito senza modificare le impostazioni dei cookie o clicchi su "Accetta" permetti al loro utilizzo.

Chiudi