Arduino Lektion 93: setzen der Uhrzeit an einer RTC DS3231 RealTimeClock

In diesem Beitrag möchte ich gesondert auf das setzen der Uhrzeit an einer RTC DS3231 eingehen. 

Ich habe bereits ein ausführliches Tutorial zur RTC DS3231 unter Arduino Lektion 17: RealTimeClock RTC DS3231 erstellt, jedoch sind noch einpaar Fragen offen geblieben welche ich nun hier ergänzend liefern möchte.

Es gibt die RTC in zwei Varianten wobei beide Ihren Vor und Nachteil haben, die kleinere hat die gleiche Funktion jedoch ist die Batterie verlötet d.h. wenn diese einmal verschlissen ist muss eine neue Eingelötet werden wobei beim größeren Model die Batterie einfach ausgetauscht werden kann.

Für die große RTC mit Batteriefach für eine LIR-2032 wird eine wiederaufladbare Knopfzelle CR2032 (Bezeichnung LIR-2032) benötigt. Diese sind deutlich teurer als die „normalen“ einmal Batterien. Diese wiederaufladbaren Akkus sind auf ebay.de für ca. 4€ inkl. Versandkosten erhältlich.

Teileliste

Microcontroller

Für die nachfolgenden Beschreibungen verwende ich den kompatiblen Arduino UNO Microcontroller von Keyestudio.

Arduino UNO kompatibles Board von Keyestudio
Arduino UNO kompatibles Board von Keyestudio

Diesen Microcontroller habe ich bereits im Tutorial Arduino UNO kompatibles Board von Keyestudio ausführlich beschrieben. Jedoch ist dieser wie ein „normaler“ Arduino UNO nur das dieser einpaar Pins mehr hat.

Module

Und man benötigt den Hauptakteur die RTC, wie bereits erwähnt verwende ich hier 2 verschiedene Module welche jedoch mit der selben Bibliothek betrieben werden kann und somit sich eigentlich nicht unterscheiden. Einmal habe ich die RTC DS3231 in Groß und einmal in Klein. Was beide Varianten eint ist, das diese über I2C angeschlossen werden (SDA & SCL).

Zubehör

Des Weiteren werden einige Breadboardkabel und ggf. ein Breadboard (min. 170 Pins) benötigt.

Breadboards
Breadboards

Aufbau & Schaltung

Die beiden RTC Module verfügen jeweils über die I2C Pins somit werden diese an die analogen Pins A4 (SDA) & analogen Pin A5 (SCL) angeschlossen.

RTC DS3231 am Arduino UNO
RTC DS3231 am Arduino UNO

Anschluß

RTC DS3231 groß  RTC DS3231 klein  Arduino UNO  
SCLDanaloger Pin A5
SDACanaloger Pin A4
5V+5V
GNDGND

Erstmal verwende ich die kleine Variante der RTC DS3231 und möchte aufzeigen wie diese programmiert wird.

RTC DS3231 am Arduino UNO von Keyestudio
RTC DS3231 am Arduino UNO von Keyestudio

Programmieren

Nun, nachdem die RTC erfolgreich mit dem Arduino verbunden wurde, können wir diese programmieren. Zunächst einmal müssen wir die Zeit setzen, dieses können wir mit zwei Methoden tun, einmal manuell über die Eingabe des seriellen Monitors oder über einen Zeitstempel eines Sketches. Beides hat seine Vor & Nachteile aber sie funktionieren.

manuelle Eingabe über den seriellen Monitor der Arduino IDE

Als erstes möchte ich erläutern wie man das Datum und die Uhrzeit über den seriellen Monitor der Arduino IDE einstellen kann. 

Es gibt auch andere Programme über welche man eine serielle Verbindung zu einem Microcontroller wie dem Arduino UNO aufbauen kann, zbsp. Platform I/O  oder aber Atmels eigene Entwicklung Atmel Studio. Beide Tools sind sehr gut aber auch sehr Umfangreich und für kleinere Projekte viel zu umständlich (nach meiner Meinung).

Den seriellen Monitor der Arduino IDE erreicht man entweder über den Shortcut Strg + Umschalt + M , der Schaltfläche oben rechts oder aber über das Hauptmenü Werkzeuge > Serieller Monitor.

serieller Monitor der Arduino IDE
serieller Monitor der Arduino IDE

Man kann in diesem Fenster nicht nur Daten betrachten welche vom Microcontroller zbsp. per Serial.print(), Serial.println() gesendet werden sondern man kann auch Daten absenden. Die Daten werden in dem Eingabefeld eingegeben und mit Enter bestätigt. 

empfangen von Daten über einer seriellen Schnittstelle

Um Daten in einem Sketch zu verarbeiten, müssen wir zunächst auf das empfangen von Daten reagieren. Dazu prüft man zunächst ob Daten anliegen und kann diese danach auswerten.

void setup() {
  //beginn der seriellen Kommunikation mit
  //9600 baud
  Serial.begin(9600);
}

void loop() {
   //Wenn Daten verfügbar sind dann...
   if (Serial.available() > 0) {
        //lesen der Daten von der seriellen Schnittstelle
        int data = Serial.read();
        //Ausgeben und Formatieren der Daten
        Serial.println(data, DEC);
   } 
}

Dabei wird jedes Zeichen als ASCII Nummer zurück geliefert.

Ausgabe der ASCII Nummern auf dem seriellen Monitor
Ausgabe der ASCII Nummern auf dem seriellen Monitor

Wir wollen jedoch keine ASCII Zeichen speichern sondern Text, daher benötigen wir ein Char Array. Das Char Array bietet uns vereinfacht gesagt „out of the Box“ die Möglichkeit unsere ASCII Nummern Werte in lesbare Zeichen umzuwandeln. Des Weiten benötigen wir noch eine Funktion um zu prüfen ob nun in diesem Char Array die Zeichenkette „set“ vorkommt, denn nur dann soll die Auswertung der Eingabe erfolgen.

void setup() {
  //beginn der seriellen Kommunikation mit
  //9600 baud
  Serial.begin(9600);
}

void loop() {
   //Char Array für das Speichern der empfangenen Daten.
   char linebuf[30] = {};
   //Zähler für die Zeichen
   byte counter = 0;
   //Wenn Daten verfügbar sind dann...
   if (Serial.available() > 0) {
        delay(250);
        //solange Daten von der seriellen Schnittstelle
        //empfangen werden...
        while(Serial.available()){
          //speichern der Zeichen in dem Char Array 
          linebuf[counter] = Serial.read();          
          if(counter < sizeof(linebuf)-1) {
            counter++;
          }
        }

        Serial.print("Zeichenkette set in [");
        Serial.print(linebuf);
        Serial.print("] ");
        
        //Wenn in dem Char Array das Wort "set" vorkommt dann...
        //Problem ist dass, das Wort "set" nicht nur am Anfang
        //stehen muss sondern auch in der Mitte oder am Ende der Zeichenkette.
        if (!strstr(linebuf,"set")){
             Serial.print("nicht");
        }

        Serial.println(" gefunden!");
   } 
}

Ein Problem ist hier jedoch dass, nicht geprüft wird ob die Zeichenkette „set“ am Anfang oder Ende steht, es wird lediglich geprüft das diese Vorkommt.

Ausgabe der Prüfung auf das Vorkommen der Zeichenkette "set"
Ausgabe der Prüfung auf das Vorkommen der Zeichenkette „set“

Nun kann man mit der Pointer Substraction aus dem Char Array die Position herausrechnen.

 if(strstr(linebuf,"set")){
    Serial.print("Position ");
    Serial.println(s-linebuf);
 }

Die Programmiersprache für den Arduino ist C bzw. C++. Diese Programmiersprache arbeitet mit Pointern diese Zeigen auf Adressen im Speicher. Wie das genau funktioniert werden ich in einem späteren Tutorial erläutern, für dieses Tutorial sollte es reichen zu wissen das man in diese Speicherbereiche lesen und schreiben kann.

Nachdem wir nun die Position der Zeichenkette „set“ ermitteln können, wollen wir festlegen das die Eingabe nur ausgewertet wird denn die Zeichenkette bei Position 0 steht (quasi am Anfang der Eingabe).

void setup() {
  //beginn der seriellen Kommunikation mit
  //9600 baud
  Serial.begin(9600);
}

void loop() {
   //Char Array für das Speichern der empfangenen Daten.
   char linebuf[30] = {};
   //Zähler für die Zeichen
   byte counter = 0;

   boolean readData = false;
   boolean foundKeyWord = false;
   boolean keyWordIsOnPosZero = false;
   
   //Wenn Daten verfügbar sind dann...
   if (Serial.available() > 0) {
        delay(250);
        readData = true;
        //solange Daten von der seriellen Schnittstelle
        //empfangen werden...
        while(Serial.available()){
          //speichern der Zeichen in dem Char Array 
          linebuf[counter] = Serial.read();          
          if(counter < sizeof(linebuf)-1) {
            counter++;
          }
        }

        //Variable mit Pointer zu einem Speicherbereich
        char *s;
        //zuweisen eines Wertes (es wird kein neuer Speicher belegt)
        s = strstr(linebuf,"set");
        //Wenn ein Wert hinterlegt wurde dann...
        if(s){
          foundKeyWord = true;
          //Ermitteln der Position durch Pointer Substraction
          int pos = s-linebuf;
          //Wenn die Zeichenkette "set" am Anfang steht dann...
          if(pos == 0){
            keyWordIsOnPosZero = true;
          }
        }
   }

   if(readData && foundKeyWord && keyWordIsOnPosZero){
      //Auswerten der Daten   
   } else if(readData){
      Serial.println("!! Fehler !!");
      //Wenn das Schlüsselwort "set" nicht gefunden wurde...
      if(!foundKeyWord){
        Serial.println("Das Schlüsselwort \"set\" wurde nicht gefunden!");   
      }

      //Wenn das Schlüsselwort "set" nicht an Position 0 steht, dann...
      if(!keyWordIsOnPosZero){
        Serial.print("Das Schlüsselwort \"set\" steht nicht an Position 0");   
        Serial.print("der Zeichenkette [");   
        Serial.print(linebuf);   
        Serial.println("]");           
      }
   }
}

Wenn nun die Zeichenkette nicht gefunden wird und / oder nicht an Position 1 steht dann soll eine entsprechende Meldung ausgegeben werden.

Fehlermeldung nach dem validieren der empfangenen Daten
Fehlermeldung nach dem validieren der empfangenen Daten

Wir haben nun die empfangenen Daten erfolgreich validiert somit können wir nun im nächsten Gang die Zeichenkette parsen und das Datum und die Uhrzeit extrahieren.

Um den Sketch jedoch übersichtlich zu halten wird das ganze zunächst in eine eigene Funktion ausgelagert welche als Rückgabewert ein Boolean hat. Ein Boolean kann zwei Status annehmen True & False. Die neue Funktion liefert also Boolean.True zurück wenn das lesen der Werte okay ist und Boolean.False wenn ein Fehler aufgetreten ist. (Die Fehlermeldungen werden weiterhin ausgegeben.)

char linebuf[30] = {};
boolean readData;

void setup() {
  //beginn der seriellen Kommunikation mit
  //9600 baud
  Serial.begin(9600);
}

void loop() {
  readDataFromSerial();
  if(readData){
    boolean isParameterSet = parameterIsSet();  
    if(isParameterSet == true){
      //Hier wird nun das Datum und die Uhrzeit geparst.
      Serial.println("parsen des Datums und der Uhrzeit");
    }
  }
}

void readDataFromSerial(){
   //Char Array für das Speichern der empfangenen Daten.
   linebuf[30] = {};
   //Zähler für die Zeichen
   byte counter = 0;

   readData = false;
 
   //Wenn Daten verfügbar sind dann...
   if (Serial.available() > 0) {
        delay(250);
        readData = true;
        //solange Daten von der seriellen Schnittstelle
        //empfangen werden...
        while(Serial.available()){
          //speichern der Zeichen in dem Char Array 
          linebuf[counter] = Serial.read();          
          if(counter < sizeof(linebuf)-1) {
            counter++;
          }
        }
   } else {
     readData = false;
   }
   return linebuf;
}

boolean parameterIsSet(){
  boolean result = true;
  boolean foundKeyWord = false;
  boolean keyWordIsOnPosZero = false;
  
  //Variable mit Pointer zu einem Speicherbereich
  char *s;
  //zuweisen eines Wertes (es wird kein neuer Speicher belegt)
  s = strstr(linebuf,"set");
  //Wenn ein Wert hinterlegt wurde dann...
  if(s){
    foundKeyWord = true;
    //Ermitteln der Position durch Pointer Substraction
    int pos = s-linebuf;
    //Wenn die Zeichenkette "set" am Anfang steht dann...
    if(pos == 0){
       keyWordIsOnPosZero = true;
    }
  }
  
  if(!foundKeyWord || !keyWordIsOnPosZero){
    result = false;
    Serial.println("!! Fehler !!");
    //Wenn das Schlüsselwort "set" nicht gefunden wurde...
    if(!foundKeyWord){  
      Serial.println("Das Schlüsselwort \"set\" wurde nicht gefunden!");   
    }

    //Wenn das Schlüsselwort "set" nicht an Position 0 steht, dann...
    if(!keyWordIsOnPosZero){
      Serial.print("Das Schlüsselwort \"set\" steht nicht an Position 0");   
      Serial.print(" der Zeichenkette [");   
      Serial.print(linebuf);   
      Serial.println("]");           
    }
  }
  return result;
}

parsen von Datum & Uhrzeit

definieren des Datumsformats

Bevor wir mit dem parsen des Datums und der Uhrzeit beginnen, müssen wir zunächst definieren in welchem Format dieses geliefert werden muss. Da ich aus Deutschland komme und die meisten Besucher / Betrachter meines Blogs aus eben Deutschland sind, möchte ich erläutern wie man das Format TT.MM.JJJJ HH:mm:SS parst. 

Wobei

  • TT – für den Tag mit ggf. führender 0 steht, (min. 00, max. 31)
  • MM – für den Monat mit ggf. führender 0 steht, (min. 01, max. 12)
  • JJJJ – für das Jahr in 4 stellig steht, (min. 1900, max. 9999)
  • HH – für die Stunde mit ggf. führender 0 steht, (min. 00, max. 23)
  • mm – für die Minute mit ggf. führender 0 steht, (min. 00, max. 59)
  • SS – für die Sekunde mit ggf. führende 0 steht, (min. 00, max. 59)

Das Trennzeichen für Tag,Monat und Jahr ist ein Punkt und für die Uhrzeit der Doppelpunkt.

#include <Wire.h> //Bibliothek für die kommunikation mit der RTC
#define RTC_I2C_ADDRESS 0x68 // I2C Adresse des RTC  DS3231

char linebuf[30] = {};
boolean readData;

int t,mo, j, st, mi, s;

unsigned long lastReadRTC = -1;

void setup() {
  //Kommunikation über die Wire.h bibliothek beginnen.   
  Wire.begin(); 
  //beginn der seriellen Kommunikation mit 9600 baud
  Serial.begin(9600);
}

void loop() {
  readDataFromSerial();
  //Wenn Daten gelesen wurden dann...
  if(readData){
    //prüfen ob in den gelesenen Daten das Schlüsselwort "set" vorkommt,
    //und das Schlüsselwort an erster stelle steht, dann...
    boolean isParameterSet = parameterIsSet();  
    if(isParameterSet == true){
      //Hier wird nun das Datum und die Uhrzeit geparst.
      String line = linebuf;
      //den Zeitstempel aus dem String extrahieren, 
      //dieser beginnt bei der Position 4
      String timestamp = line.substring(4);
      timestamp.replace("\r","");
      timestamp.replace("\n","");
      //prüfen ob der Zeitstempel im richtigen Format ist
      boolean isTimestampCorrect = timestampIsCorrect(timestamp);
      //wenn der Zeitstempel korrekt ist dann soll dieser auf die RTC geschrieben werden.
      if(isTimestampCorrect){
        rtcWriteTime();
      }
    }
  }

  //alle 5 Sekunden den Wert der RTC ausgeben.
  unsigned long currentMilliseconds = millis(); 
  if((lastReadRTC + 5000) < currentMilliseconds){
    lastReadRTC = currentMilliseconds;
    Serial.println(rtcReadTime());
  }
}

//Funktion zum schreiben / setzen der Uhrzeit.
void rtcWriteTime(){
  Wire.beginTransmission(RTC_I2C_ADDRESS);
  Wire.write(0); // Der Wert 0 aktiviert das RTC Modul.
  Wire.write(decToBcd(s));    
  Wire.write(decToBcd(mi));
  Wire.write(decToBcd(st));                                  
  Wire.write(decToBcd(0)); // Wochentag unberücksichtigt
  Wire.write(decToBcd(t));
  Wire.write(decToBcd(mo));
  Wire.write(decToBcd(j-2000));  
  Wire.endTransmission();  
}

//Ließt den aktuellen Zeitstempel aus dem RTC Modul.
String rtcReadTime(){
  Wire.beginTransmission(RTC_I2C_ADDRESS); //Aufbau der Verbindung zur Adresse 0x68
  Wire.write(0);
  Wire.endTransmission();
  Wire.requestFrom(RTC_I2C_ADDRESS, 7);
 int sekunde    = bcdToDec(Wire.read() & 0x7f);
 int minute     = bcdToDec(Wire.read()); 
 int stunde     = bcdToDec(Wire.read() & 0x3f); 
  //Der Wochentag wird hier nicht ausgelesen da dieses mit 
  //dem Modul RTC DS3231 nicht über die Wire.h zuverlässig funktioniert.
  /* wochentag  =*/ bcdToDec(Wire.read());
 int tag        = bcdToDec(Wire.read());
 int monat      = bcdToDec(Wire.read());
 int jahr       = bcdToDec(Wire.read())+2000;  

 
 
 char timestamp[30];
 sprintf(timestamp,"%02d.%02d.%4d %02d:%02d:%02d",tag,monat,jahr,stunde,minute,sekunde);
 return timestamp;
}

boolean timestampIsCorrect(String timestamp){
  boolean result = true;
  //Der Zeitstempel muss 19 Zeichen lang sein.
  if(timestamp.length() == 19){
    //Das Datum muss inkl. den Punkten 10 Zeichen lang sein
    String datum = timestamp.substring(0,10);
    String tag = datum.substring(0,2);
    String monat = datum.substring(3,5);
    String jahr = datum.substring(6,10);

    //Die Uhrzeit beginnt ab dem 11 Zeichen aus dem Zeitstempel
    String uhrzeit = timestamp.substring(11);
    String stunde = uhrzeit.substring(0,2);
    String minute = uhrzeit.substring(3,5);
    String sekunde = uhrzeit.substring(6);
    
    //prüfen ob in den Variablen tag, monat, jahr, stunde, minute, sekunde nur Zahlen sind
    if(!isNummeric(tag) || !isNummeric(monat) || !isNummeric(jahr)
       || !isNummeric(stunde) || !isNummeric(minute) || !isNummeric(sekunde)){
      result = false;
    }

    //Wenn das Format korrekt ist, d.h. zbsp. 
    //der Tag darf nicht weniger als 0 Stunden und nicht mehr als 23 Stunden haben,
    //die Minute darf nicht weniger als 0 Minuten haben und nicht mehr als 59, usw.
    if(!isFormatCorrect(tag.toInt(), monat.toInt(), jahr.toInt(), stunde.toInt(), minute.toInt(), sekunde.toInt() )){
      //Wenn das Format nicht korrekt ist dann wird "false" zurück geliefert.
      result = false;
    } else {
      //Wenn das Format korrekt ist dann sollen die Werte aus den Strings in die Felder geschrieben werden.
      t = tag.toInt();
      mo = monat.toInt();
      j = jahr.toInt();

      st = stunde.toInt();
      mi = minute.toInt();
      s = sekunde.toInt();
    }
    
  } else {
    Serial.print("Der Zeitstempel muss 20 Zeichenlang sein. [");
    Serial.print(timestamp);
    Serial.println("]");
    Serial.print("Länge des Zeitstempels ");
    Serial.print(timestamp.length());
    Serial.println(" Zeichen");
    result = false;
  }

  if(result == false){
    Serial.print("Der übergebene Zeitstempelt ist nicht korrekt. [");
    Serial.print(timestamp);
    Serial.println("]");
    Serial.println("Beispiel 24.05.2019 19:35:34");
  }
  
  return result;
}

boolean isFormatCorrect(int tag, int monat, int jahr, int stunde, int minute, int sekunde ){
  boolean result = true;
  if(tag <0 || tag > 31){
    result = false;
  }

  if(monat <1 || monat > 12){
    result = false;
  }

  if(jahr <1900 || jahr > 9999){
    result = false;
  }

  if(stunde <0 || stunde > 23){
    result = false;
  }

  if(minute <0 || minute > 59){
    result = false;
  }

  if(sekunde <0 || sekunde > 59){
    result = false;
  }

  return result;
}

//prüfen ob in dem Parameter chars nur Zahlen enthalten sind
boolean isNummeric(String chars){
  boolean result = true;
  for(int i=0;i<chars.length();i++){
    int asciiNumber = chars.charAt(i);
    //Die Zahlen von 0 bis 9 liegen im ASCII Raum 48 bis 57, d.h.
    //wenn die ASCII Zahl kleiner oder größer ist, dann ist es keine Zahl.
    if(asciiNumber <48 || asciiNumber>57){
      result = false;
      break;
    }
  }
  return result;
}

void readDataFromSerial(){
   //Char Array für das Speichern der empfangenen Daten.
   linebuf[30] = {};
   //Zähler für die Zeichen
   byte counter = 0;

   readData = false;
 
   //Wenn Daten verfügbar sind dann...
   if (Serial.available() > 0) {
        delay(250);
        readData = true;
        //solange Daten von der seriellen Schnittstelle
        //empfangen werden...
        while(Serial.available()){
          //speichern der Zeichen in dem Char Array 
          linebuf[counter] = Serial.read();          
          if(counter < sizeof(linebuf)-1) {
            counter++;
          }
        }
   } else {
     readData = false;
   }
   return linebuf;
}

boolean parameterIsSet(){
  boolean result = true;
  boolean foundKeyWord = false;
  boolean keyWordIsOnPosZero = false;
  
  //Variable mit Pointer zu einem Speicherbereich
  char *s;
  //zuweisen eines Wertes (es wird kein neuer Speicher belegt)
  s = strstr(linebuf,"set");
  //Wenn ein Wert hinterlegt wurde dann...
  if(s){
    foundKeyWord = true;
    //Ermitteln der Position durch Pointer Substraction
    int pos = s-linebuf;
    //Wenn die Zeichenkette "set" am Anfang steht dann...
    if(pos == 0){
       keyWordIsOnPosZero = true;
    }
  }
  
  if(!foundKeyWord || !keyWordIsOnPosZero){
    result = false;
    Serial.println("!! Fehler !!");
    //Wenn das Schlüsselwort "set" nicht gefunden wurde...
    if(!foundKeyWord){  
      Serial.println("Das Schlüsselwort \"set\" wurde nicht gefunden!");   
    }

    //Wenn das Schlüsselwort "set" nicht an Position 0 steht, dann...
    if(!keyWordIsOnPosZero){
      Serial.print("Das Schlüsselwort \"set\" steht nicht an Position 0");   
      Serial.print(" der Zeichenkette [");   
      Serial.print(linebuf);   
      Serial.println("]");           
    }
  }
  return result;
}

//Convertiert Dezimalzeichen in binäre Zeichen.
byte decToBcd(byte val){
  return ( (val/10*16) + (val%10) );
}

//Convertiert binäre Zeichen in Dezimal Zeichen.
byte bcdToDec(byte val){
  return ( (val/16*10) + (val%16) );
}

Wir haben nun die Werte für Tag, Monat, Jahr, Stunde, Minute und Sekunde in den Felder t, mo, j, st, mi, s.
Im nächsten Schritt müssen wir diese Werte auf die RealTimeClock schreiben. Da diese Funktion auch für die 2. Lösung benötigt wird möchte ich zunächst erläutern wie man einen Zeitstempel aus einem deployten Sketch auslesen kann. Hier gehts zur Funktion zum schreiben eines Zeitstempels auf die RTC.

lesen eines Zeitstempels von einem deployten Sketch

Wenn ein Sketch auf den Arduino hochgeladen wird (umgangssprachlich als deployment bezeichnet),  dann wird zusätzlich ein Timestamp abgelegt, dieser entspricht bis auf wenige Sekunden dem aktuellen Zeitstempel. Das Problem ist nur dass, der Sketch einmal ausgeführt und die Zeit sofort auf die RTC geschrieben werden muss, danach darf dieses nicht erneut gestartet werden.

Daher wird das ganze nicht in der loop ausgeführt sondern im Setup somit wird sichergestellt dass, diese Funktion nur einmal beim starten ausgeführt wird.

Für den nachfolgenden Sketch bedienen wir uns zweier Konstanten „__DATE__“ & „__TIME__“ diese werden mit dem Zeitstempel des Uploads setzt. 

Das Datum ist im Format „Jun 30 2019“ somit müssen wir die engl. Abkürzungen für die Monate gegen den Monatsnamen prüfen. Diese Prüfung mache ich mit einer einfachen Schleife über die Monate und wenn diese Werte gleich sind breche ich die Schleife ab und merke mir den Index. Zu diesem Index muss jedoch noch eine Zahl drauf addiert werden, denn Arrays beginnen immer bei 0 jedoch die Monate bei 1.

#include <Wire.h> //Bibliothek für die kommunikation mit der RTC
#define RTC_I2C_ADDRESS 0x68 // I2C Adresse des RTC  DS3231

//Variablen für die Werte aus Datum und Uhrzeit
int tag, monat, jahr;
int stunde, minute, sekunde;

//Array mit den abkürzungen für die Monate
const String months[12] = {"Jan", "Feb", "Mar", "Apr","May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

void setup() {
  //Kommunikation über die Wire.h bibliothek beginnen.   
  Wire.begin(); 

  //beginn der seriellen Kommunikation mit 9600baud
  Serial.begin(9600);

  //parsen des Datums
  parseDate(__DATE__);
  //parsen der Uhrzeit
  parseTime(__TIME__);

  //Ausgeben des Zeitstempels auf dem seriellen Monitor.
  char timestamp[30];
  sprintf(timestamp,"%02d.%02d.%4d %02d:%02d:%02d",tag,monat,jahr,stunde,minute,sekunde);
  Serial.println(timestamp);

  rtcWriteTime();
}

void parseDate(String date){
  String mo = date.substring(0,3);
  String t = date.substring(4,6);
  String j = date.substring(7);

  tag = t.toInt();
  
  for(int i=0;i<=11;i++){
    String month = months[i];
    if(mo == month){
      monat = i;
      //Da Arrays bei 0 beginnen müssen wir dem Zähler noch einen Wert dazu addieren.
      ++monat;
      break;
    }
  }

  jahr = j.toInt();
}

void parseTime(String time){
  String st = time.substring(0,2);
  String mi = time.substring(3,5);
  String se = time.substring(6);

  stunde = st.toInt();
  minute = mi.toInt();
  sekunde = se.toInt();  
}

//Funktion zum schreiben / setzen der Uhrzeit.
void rtcWriteTime(){
  Wire.beginTransmission(RTC_I2C_ADDRESS);
  Wire.write(0); // Der Wert 0 aktiviert das RTC Modul.
  Wire.write(decToBcd(sekunde));    
  Wire.write(decToBcd(minute));
  Wire.write(decToBcd(stunde));                                  
  Wire.write(decToBcd(0)); // Wochentag unberücksichtigt
  Wire.write(decToBcd(tag));
  Wire.write(decToBcd(monat));
  Wire.write(decToBcd(jahr-2000));  
  Wire.endTransmission();  
}

//Convertiert Dezimalzeichen in binäre Zeichen.
byte decToBcd(byte val){
  return ( (val/10*16) + (val%10) );
}

void loop() {
  // Bleibt in diesem Beispiel leer.
}

Die Funktion „loop“ bleibt in diesem Beispiel leer da der Code wie bereits beschrieben nur einmal ausgeführt werden soll.

Was nun noch fehlt ist ein Sketch um sich dann die Zeit anzuzeigen bzw. etwas damit zu tun.

#include <Wire.h> //Bibliothek für die kommunikation mit der RTC
#define RTC_I2C_ADDRESS 0x68 // I2C Adresse des RTC  DS3231

unsigned long lastReadRTC = -1;

void setup() {
  //Kommunikation über die Wire.h bibliothek beginnen.   
  Wire.begin(); 

  //beginn der seriellen Kommunikation mit 9600baud
  Serial.begin(9600);
}

//Ließt den aktuellen Zeitstempel aus dem RTC Modul.
String rtcReadTime(){
  Wire.beginTransmission(RTC_I2C_ADDRESS); //Aufbau der Verbindung zur Adresse 0x68
  Wire.write(0);
  Wire.endTransmission();
  Wire.requestFrom(RTC_I2C_ADDRESS, 7);
 int sekunde    = bcdToDec(Wire.read() & 0x7f);
 int minute     = bcdToDec(Wire.read()); 
 int stunde     = bcdToDec(Wire.read() & 0x3f); 
  //Der Wochentag wird hier nicht ausgelesen da dieses mit 
  //dem Modul RTC DS3231 nicht über die Wire.h zuverlässig funktioniert.
  /* wochentag  =*/ bcdToDec(Wire.read());
 int tag        = bcdToDec(Wire.read());
 int monat      = bcdToDec(Wire.read());
 int jahr       = bcdToDec(Wire.read())+2000;  
 
 char timestamp[30];
 sprintf(timestamp,"%02d.%02d.%4d %02d:%02d:%02d",tag,monat,jahr,stunde,minute,sekunde);
 return timestamp;
}

//Convertiert binäre Zeichen in Dezimal Zeichen.
byte bcdToDec(byte val){
  return ( (val/16*10) + (val%16) );
}

void loop() {
  //alle 5 Sekunden den Wert der RTC ausgeben.
  unsigned long currentMilliseconds = millis(); 
  if((lastReadRTC + 5000) < currentMilliseconds){
    lastReadRTC = currentMilliseconds;
    Serial.println(rtcReadTime());
  }
}

Video

Hier nun ein kurzes Video.

 

schreiben eines Zeitstempels auf die RTC

Für das schreiben des Zeitstempels benötigen wir zusätzlich eine Bibliothek damit wir auf die RTC zugreifen können. (Es ist die Wire Bibliothek.)

Diese Bibliothek ist der Arduino IDE bereits beigefügt somit muss in der Regel nichts installiert werden.

Zusätzlich müssen wir die Adresse der RTC definieren, diese können wir entweder dem Datenblatt entnehmen oder aber mit einem I2C Scanner herausfinden.

#include <Wire.h> //Bibliothek für die kommunikation mit der RTC
#define RTC_I2C_ADDRESS 0x68 // I2C Adresse des RTC  DS3231

Des Weiteren benötigen wir 2 zusätzliche Hilfsfunktionen welche uns die Integerwerte für Tag, Monat, Jahr usw. in Binäre Werte umwandelt (und wieder zurück).

//Convertiert Dezimalzeichen in binäre Zeichen.
byte decToBcd(byte val){
  return ( (val/10*16) + (val%10) );
}

//Convertiert binäre Zeichen in Dezimal Zeichen.
byte bcdToDec(byte val){
  return ( (val/16*10) + (val%16) );
}

Diese beiden Funktionen setze ich im Sketch ganz nach unten da diese nur zur Hilfe dienen.

Wie bereits erwähnt dient dieses Tutorial zur Ergänzung daher nutze ich im groben die Funktionen aus dem bereits bekannten Tutorial wieder. Daher, hier nun die Methode um die Zeit zu setzen:

//Funktion zum schreiben / setzen der Uhrzeit.
void rtcWriteTime(){
  Wire.beginTransmission(RTC_I2C_ADDRESS);
  Wire.write(0); // Der Wert 0 aktiviert das RTC Modul.
  Wire.write(decToBcd(s));    
  Wire.write(decToBcd(mi));
  Wire.write(decToBcd(st));                                  
  Wire.write(decToBcd(0)); // Wochentag unberücksichtigt
  Wire.write(decToBcd(t));
  Wire.write(decToBcd(mo));
  Wire.write(decToBcd(j-2000));  
  Wire.endTransmission();  
}

Diese Funktion rufen wir bei erfolgreichen lesen und parsen des Zeitstempels auf.

boolean isParameterSet = parameterIsSet();  
if(isParameterSet == true){
   //Hier wird nun das Datum und die Uhrzeit geparst.
   String line = linebuf;
   //den Zeitstempel aus dem String extrahieren, 
   //dieser beginnt bei der Position 4
   String timestamp = line.substring(4);
   timestamp.replace("\r","");
   timestamp.replace("\n","");
   //prüfen ob der Zeitstempel im richtigen Format ist
   boolean isTimestampCorrect = timestampIsCorrect(timestamp);
   //wenn der Zeitstempel korrekt ist dann soll dieser auf die RTC geschrieben werden.
   if(isTimestampCorrect){
     rtcWriteTime();
   }
}

Ausgabe der aktuellen Zeit von der RTC

Damit wir die aktuelle Zeit lesen können benötigen wir wiederum eine Funktion welche die Daten von der RTC ließt und für uns umwandelt.

//Ließt den aktuellen Zeitstempel aus dem RTC Modul.
String rtcReadTime(){
  Wire.beginTransmission(RTC_I2C_ADDRESS); //Aufbau der Verbindung zur Adresse 0x68
  Wire.write(0);
  Wire.endTransmission();
  Wire.requestFrom(RTC_I2C_ADDRESS, 7);
 int sekunde    = bcdToDec(Wire.read() & 0x7f);
 int minute     = bcdToDec(Wire.read()); 
 int stunde     = bcdToDec(Wire.read() & 0x3f); 
  //Der Wochentag wird hier nicht ausgelesen da dieses mit 
  //dem Modul RTC DS3231 nicht über die Wire.h zuverlässig funktioniert.
  /* wochentag  =*/ bcdToDec(Wire.read());
 int tag        = bcdToDec(Wire.read());
 int monat      = bcdToDec(Wire.read());
 int jahr       = bcdToDec(Wire.read())+2000;  

 
 
 char timestamp[30];
 sprintf(timestamp,"%02d.%02d.%4d %02d:%02d:%02d",tag,monat,jahr,stunde,minute,sekunde);
 return timestamp;
}

In der vorletzen Zeile der Funktion, formatieren wir uns unseren Zeitstempel wieder in das deutsche Format.

Damit wir in einem Sketch einen Zeitstempel lesen und setzen können habe ich dieses so gelöst das alle 5 Sekunden ein Zeitstempel ausgegeben wird. Somit kann man eine ggf. auftretende Fehlermeldung lesen.

unsigned long lastReadRTC = -1;

void loop() {
   ...
   //alle 5 Sekunden den Wert der RTC ausgeben.
   unsigned long currentMilliseconds = millis(); 
   if((lastReadRTC + 5000) < currentMilliseconds){
     lastReadRTC = currentMilliseconds;
     Serial.println(rtcReadTime());
   }
}

 

Downloads

Hier möchte ich nun die Sketche für den Arduino zum Download anbieten.

 

 

4 Gedanken zu „Arduino Lektion 93: setzen der Uhrzeit an einer RTC DS3231 RealTimeClock

  • 2. September 2019 um 16:11
    Permalink

    Die obige Tabelle „Anschluss“…
    SDA und SCL vertauscht ?

    Antwort
    • 3. September 2019 um 16:33
      Permalink

      Hi,

      ja sorry. Habe ich nun korrigiert.
      Danke für den Hinweis.

      Gruß,

      Stefan

      Antwort
  • 15. Mai 2020 um 10:40
    Permalink

    Hallo, der Code klappt sehr gut.
    Hast du zufällig auch noch ein Code wo die Uhrzeit dann auf ein LCD Display angezeigt wird?

    Grüße,
    Nane

    Antwort

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.