Dans cet article, nous allons voir comment afficher sur un écran la date et l'heure courantes, avec la possibilité de se synchroniser sur un serveur de temps public (NTP - Network Time Protocol), comme le font tous les PC ou téléphones.
Matériel
Pour cet exemple, il faut vous munir :
- d'un arduino (ici un DUE)
- un shield Ethernet
- un module RTC (Real Time Clock, ici un Gravitech)
- un écran LCD
Voici un schéma du montage :
L'écran est branché, de manière classique, sur des broches numériques. Le module RTC quant à lui est intégré sur le bus I2C par les broches SDA1 et SCL1.
Logiciel
Nous allons utiliser les librairies :
- Ethernet
- Wire1 (pour le bus I2C)
- LiquidCrystal
- Time (pour faciliter la décomposition de la date à partir d'un timestamp)
Données importantes :
Selon la spécification du constructeur, l'adresse de communication du module RTC sur le bus I2C est 0x68. Le serveur NTP choisi est celui de l'Observatoire de Paris (ntp-p1.obspm.fr, IP: 145.238.203.14). Le dialogue avec un serveur NTP se fait par échange de paquets par le protocole UDP.
C'est parti!
1. Configuration d'une date par défaut sur le module RTC
Commençons par un premier échange avec ce module :
const int I2C_address = 0x68;
...
Wire1.begin(); // join i2c bus (address optional for master)
Wire1.beginTransmission(I2C_address);// Début de transaction I2C
Wire1.write(0); // Arrête l'oscillateur
Wire1.write(0); // sec
Wire1.write(0x11); // min
Wire1.write(0x20); // heure
Wire1.write(6); // jour de la semaine
Wire1.write(0x20); // jour
Wire1.write(9); // mois
Wire1.write(0x14); // annee
Wire1.write(0); // Redémarre l'oscillateur
Wire1.endTransmission(); // Fin de transaction I2C
Ici nous définissons la date "20/09/2014 20:11:00". Remarquez que les valeurs transmises sont au format hexadécimal.
2. Lecture de la date sur le module RTC
Interrogeons notre module pour récupérer la date :
void getTime(char* tempData) {
byte i = -1;
byte error;
Wire1.beginTransmission(I2C_address); // Début de transaction I2C
Wire1.write(0); // Demande les info à partir de l'adresse 0 (soit toutes les info)
error = Wire1.endTransmission(); // Fin de transaction I2C
if(error==0) {
Wire1.requestFrom(I2C_address, 7); // Récupère les info (7 octets = 7 valeurs correspondant à l'heure et à la date courante)
while(Wire1.available())
{
tempData[++i] = Wire1.read(); // receive a byte as character
}
} else {
// le composant RTC n'etait pas joignable, on relance le bus I2C pour la prochaine fois
Wire1.begin();
}
}
Cette méthode nous permet de récupérer, dans un tableau de 7 char (tempData), les données du module.
3. Affichage de la date sur l'écran
void displayTime()
{
char tempchar[7] = {'\0'};
// recuperation de la date
getTime(tempchar);
// affichage
if(tempchar[0]!='\0') {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(tempchar[4],HEX);
lcd.write("/");
lcd.print(tempchar[5],HEX);
lcd.write("/20");
lcd.print(tempchar[6],HEX);
lcd.setCursor(0, 1);
lcd.print(tempchar[2],HEX);
lcd.write(":");
lcd.print(tempchar[1],HEX);
lcd.write(":");
lcd.print(tempchar[0],HEX);
}
}
Avec cette méthode, on envoie à l'écran chaque caractère (toujours au format hexadecimal) retourné par l'appel au module RTC.
Il ne vous reste plus qu'à placer l'appel à cette méthode dans une boucle toutes les secondes pour voir défiler le temps. Essayez même de couper l'alimentation du module RTC : l'affichage de l'heure se fige, jusqu'à nouvelle alimentation du module. Elle sera alors mise à jour avec la date qui a continué à défiler grâce à la pile du module.
4. Interrogation du serveur de temps
Le shield Ethernet va nous permettre de contacter le serveur public et de récupérer les informations de date "de référence".
IPAddress timeServer(145, 238, 203, 14); // ntp-p1.obspm.fr (IP: 145.238.203.14)
const int NTP_PACKET_SIZE= 48; // NTP time stamp is in the first 48 bytes of the message
uint8_t packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
/* Set this to the offset (in seconds) to your local time
GMT - 2 */
const long timeZoneOffset = -7200L;
// A UDP instance to let us send and receive packets over UDP
EthernetUDP udpClient;
EthernetClient client;
/**
* Démarrage des librairies
*/
Ethernet.begin(mac);
udpClient.begin(8888); // port UDP local
/**
* Envoi de la requete
*/
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
udpClient.beginPacket(timeServer, 123); //NTP requests are to port 123
udpClient.write(packetBuffer,NTP_PACKET_SIZE);
udpClient.endPacket();
/**
* Parcours de la réponse du serveur
*/
unsigned long epoch = 0;
if ( udpClient.parsePacket() ) {
// We've received a packet, read the data from it
udpClient.read(packetBuffer,NTP_PACKET_SIZE); // read the packet into the buffer
//the timestamp starts at byte 40 of the received packet and is four bytes,
// or two words, long. First, esxtract the two words:
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
// combine the four bytes (two words) into a long integer
// this is NTP time (seconds since Jan 1 1900):
unsigned long secsSince1900 = highWord << 16 | lowWord;
// now convert NTP time into everyday time:
// Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
const unsigned long seventyYears = 2208988800UL + timeZoneOffset;
// subtract seventy years:
epoch = secsSince1900 - seventyYears;
}
/**
* Configuration du module RTC avec la nouvelle date
*/
setTime(epoch); // on affecte le timestamp récupéré à la librairie Time
Wire1.beginTransmission(I2C_address);// Début de transaction I2C
Wire1.write(0); // Arrête l'oscillateur
String strval = String(second(), DEC);
Wire1.write(strtol(strval.c_str(),NULL, HEX)); // sec
strval = String(minute(), DEC);
Wire1.write(strtol(strval.c_str(),NULL, HEX)); // min
strval = String(hour(), DEC);
Wire1.write(strtol(strval.c_str(),NULL, HEX)); // heure
Wire1.write(weekday()-1); // jour de la semaine
strval = String(day(), DEC);
Wire1.write(strtol(strval.c_str(),NULL, HEX)); // jour
strval = String(month(), DEC);
Wire1.write(strtol(strval.c_str(),NULL, HEX)); // mois
strval = String(year(), DEC);
Wire1.write(strtol(strval.c_str(),NULL, HEX)); // annee
Wire1.write(0); // Redémarre l'oscillateur
Wire1.endTransmission(); // Fin de transaction I2C
La librairie Time nous permet de décomposer facilement les données de la date. Mais le serveur NTP nous a envoyé la date au format décimal "20/09/2014 20:11:00". Il faut donc convertir les données au format hexa avant de configurer le module RTC.
Ainsi, après l'appel à ce code (à découper en méthodes pour plus de clarté), l'affichage de la date sera automatiquement actualisé avec la date de référence.
Have fun!Sources :
- http://skyduino.wordpress.com/2012/01/07/tutoriel-arduino-horloge-rtc-ds1307-librairie-liquidcrystal/
- http://arduino.cc/en/Tutorial/UdpNTPClient
0 commentaires:
Enregistrer un commentaire