DS3231 – Echtzeituhr

Über den Beitrag

In diesem Beitrag möchte ich das Echtzeituhrmodul (englisch: RTC = Real Time Clock) DS3231 vorstellen. Dabei verwende ich die Bibliothek RTCLib von Adafruit, da diese aus meiner Sicht den besten Kompromiss aus Bedienbarkeit und Vollständigkeit darstellt.

Anhand von Beispielsketchen werde ich euch schrittweise in die Funktionen des DS3231 und der Bibliothek einführen. Da ich die originalen Beispielsketche etwas verwirrend fand, habe ich sie teilweise leicht verändert und noch eigene hinzugefügt.

Mithilfe der RTCLib könnt ihr auch die alternativen Echtzeituhren DS1307, PCF8523 und PCF8563 steuern. Darauf gehe ich am Ende des Beitrages kurz ein.

Wie gewohnt, werde ich erst einmal ein bisschen weiter ausholen und zunächst die Eigenschaften des DS3231 Moduls vorstellen.

Was ist eine Echtzeituhr?

Anders gefragt: gibt es denn auch eine „Unechtzeituhr“? Nun, auch die Arduino Boards messen die Zeit. Über die millis() Funktion könnt ihr die Zeit in Millisekunden erfragen, die seit dem Programmstart vergangen ist. Der Arduino kennt aber keine Uhrzeit oder Datum. Echtzeituhren hingegen haben Register, in denen Datum und Uhrzeit gespeichert werden. Darüber hinaus sind sie batteriebetrieben und „vergessen“ diese Daten deswegen nicht. Diese Eigenschaften machen sie zu Echtzeituhren.

Das DS3231 Modul

DS3231 Modul, Vorder- und Rückseite
DS3231 Modul, Vorder- und Rückseite

Der DS3231 im engeren Sinne ist der große, auf dem Board gut erkennbare IC mit den 16 Pins. Ein Datenblatt zu ihm gibt es hier. Ich beziehe mich in meinem Beitrag aber grundsätzlich auf das Board, auch wenn ich in der Kurzform vom DS3231 spreche.

Technische Eigenschaften des DS3231

Die aus meiner Sicht wichtigsten Eigenschaften sind:

  • Zählt Sekunden, Minuten, Stunden, Tage, Wochentage, Monate, Jahre.
    • Einschließlich Schaltjahr Funktion.
  • Maximale Abweichung (bei 0 bis 40 °C): +/-2 ppm (entspricht +/-63 Sekunden / Jahr).
  • Spannungsversorgung: 2.3 – 5.5 Volt.
  • Akku: LIR2032, integrierte Ladefunktion
    • bei Verwendung von CR2032 Knopfzellen Hinweis unten beachten!
  • Stromverbrauch im VCC Betrieb (eigene Messung):
    • bei 5 Volt: 3.6 mA (mit LED) / 0.64 mA (LED entfernt).
    • bei 3.3 Volt: 1.8 mA (mit LED) / 0.36 mA (LED entfernt).
  • Stromverbrauch im Batteriebetrieb: 0.84 – 3.0 µA (Timekeeping Modus, also reiner Zeiterhalt).
  • Zwei programmierbare Alarme mit Interrupt Funktion.
  • Programmierbarer Ausgang für Rechtecksignale (darauf gehe ich nicht im Detail ein).
  • Integrierter Temperatursensor, allerdings mit bescheidener Genauigkeit (+/-3 °C).
  • Kommunikation: I2C, Adresse: 0x68, integrierte Pullup-Widerstände
  • Ein-/Ausgänge:
    • VCC / GND: Versorgungsspannung
    • SDA / SCL: I2C
    • SQW: Ausgang für Rechtecksignale (Squarewave) oder low-aktiver Interrupt
    • 32K: Ausgang für Rechtecksignal mit 32 kHz (fest)

Achtung bei Batteriebetrieb

Ladefunktion des DS3231
Ladefunktion des DS3231

Der DS3231 und andere RTC  Module haben eine Ladefunktion für LIR2032, also wiederaufladbare Akkus. Ihr erkennt das an dem Vorhandensein einer Diode (siehe Bild). Leider sparen viele Shops und liefern das Modul mit einer CR2032, also einer nicht aufladbaren Knopfzelle aus. Das kann gefährlich werden, da das Modul trotzdem versucht, die Knopfzelle zu laden. Sie kann sich dann durch Gasentwicklung aufblähen, zerstört werden und dabei sehr ungesunde Flusssäure abgeben. Entfernt die Diode und / oder den 200 Ohm Widerstand neben der Diode, wenn ihr eine Batterie anstelle eines Akkus verwendet.

Batterie vs. Akku
Batterie vs. Akku

Im Zweifelsfall könnt ihr den DS3231 zur Prüfung ohne Batterie oder Akku an VCC anschließen und die Spannung an den Kontakten des Batteriehalters messen.

Integrierter EEPROM

Vielleicht habt ihr euch schon gefragt, was es mit dem kleinen, achtbeinigen IC und den drei Adressjumpern auf sich hat. Der IC ist ein EEPROM mit der Bezeichnung AT24CS32. Ein Datenblatt dazu gibt es hier. Der AT24CS32 hat eine Kapazität von 32 kBit. Ihr könnt also eine ganze Menge Daten auf ihm speichern. Zusammen mit dem DS3231 könntet ihr beispielsweise regelmäßig Messwerte eines Sensors aufnehmen. Oder ihr speichert Ereignisse, wie z.B. die Zeitpunkte, zu denen ein Bewegungsmelder ausgelöst hat.

Im Gegensatz zum DS3231 könnt ihr die I2C Adresse des AT24CS32 modifizieren. Sind alle Jumper offen, dann ist die Adresse is 0x57. Durch das Schließen von Jumpern könnt ihr die Adressen 0x50 bis 0x56 einstellen. Falls ihr die Adressen prüfen wollt, habe ich hier einen I2C Scanner für euch.

Die RTCLib hat keine Funktionen zur Nutzung des EEPROMs implementiert. Ich werde deswegen auch nicht weiter darauf eingehen.

Anschluss des DS3231 an einen Arduino UNO

Der DS3231 am Arduino

Wie ihr seht, ist die Verkabelung simpel. VCC könntet ihr alternativ auch an 3.3 Volt anschließen. Dann wird allerdings das Laden des Akkus nicht funktionieren. Pullups für die I2C Leitungen werden nicht benötigt. SQW verwende ich in einigen Sketchen als Interrupt Ausgang.

Installation der RTCLib für den DS3231

Ihr könnt die RTCLib (Adafruit) über die Bibliotheksverwaltung der Arduino IDE installieren:

Auswahl der RTCLib
Auswahl der RTCLib

Alternativ ladet ihr die Bibliothek hier von Github herunter.

Einführung in die RTCLib

Wie angekündigt, erkläre ich euch die Funktionen der RTCLib anhand einer Reihe von Beispielsketchen.

Die Zeit stellen und auslesen

Wenn ihr den DS3231 das erste Mal nutzt, kennt er die aktuelle Uhrzeit und das Datum natürlich noch nicht. Das ist wie bei jeder anderen Uhr, die keine Funkuhr ist. Es gibt zwei Möglichkeiten zur Einstellung:

  • rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); übernimmt ganz bequem die Systemzeit eures Computers.
  • rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0)); setzt das Datum nach dem Schema Jahr, Monat, Tag, Stunde, Minute, Sekunde.

Für die Definition von Zeitpunkten, also Datum und Uhrzeit, gibt es in der RTCLib eine komfortable Klasse namens DateTime.

  • DateTime now = rtc.now(); erzeugt das Objekt „now“ der Klasse DateTime und ordnet ihm mit rtc.now(); die aktuelle Zeit zu.

Didaktisch ist es vielleicht ein bisschen ungeschickt das Objekt „now“ und die Funktion now();  gleich zu benennen. Das habe ich von den Original-Beispielsketchen übernommen.

Auf die Jahre, Monate, Tage, Stunden, Minuten und Sekunden der DateTime-Variable now greift ihr mit now.year();, now.month();, etc. zu.

#include "RTClib.h"

RTC_DS3231 rtc;

char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

void setup () {
  Serial.begin(9600);

#ifndef ESP8266
  while (!Serial); // wait for serial port to connect. Needed for native USB
#endif

  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    Serial.flush();
    abort();
  }

    Serial.println("Setting the time...");
    // When time needs to be set on a new device, or after a power loss, the
    // following line sets the RTC to the date & time this sketch was compiled
     rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
  }


void loop () {
    DateTime now = rtc.now();

    Serial.print(now.year(), DEC);
    Serial.print('/');
    Serial.print(now.month(), DEC);
    Serial.print('/');
    Serial.print(now.day(), DEC);
    Serial.print(" (");
    Serial.print(daysOfTheWeek[now.dayOfTheWeek()]);
    Serial.print(") ");
    Serial.print(now.hour(), DEC);
    Serial.print(':');
    Serial.print(now.minute(), DEC);
    Serial.print(':');
    Serial.print(now.second(), DEC);
    Serial.println();

    Serial.println();
    delay(3000);
}

 

Ausgabe von SetTime.ino

DS3231 Beispielsketche: Ausgabe SetTime.ino
Ausgabe SetTime.ino

Mehr Funktionen für das Auslesen

Jetzt lernt ihr ein paar weitere Basisfunktionen kennen. Mit rtc.lostPower(); prüft ihr, ob der DS3231 vom Strom getrennt war. War das der Fall, könnt ihr ihn automatisch neu stellen lassen.

Die RTCLib erlaubt es euch, komfortabel mit Zeiten zu rechnen. TimeSpan(days, hours, minutes, seconds); oder TimeSpan(seconds); legt eine Zeitspanne fest, die ihr auf ein Datum addieren oder von ihm abziehen könnt.

Mit now.unixtime(); erhaltet ihr die Sekunden, die zwischen der Zeit „now“ und dem 1.1.1970 (UTC) vergangen sind. Diese sogenannte Unixzeit wird oft als Referenz verwendet. Ihr könnt die aktuelle Unixzeit hier im Netz abrufen. Mehr Informationen zu der Unixzeit findet ihr hier.

Die vom integrierten Sensor gemessene Temperatur in Grad Celsius erhaltet ihr mit rtc.getTemperature();.

// Date and time functions using a DS3231 RTC connected via I2C and Wire lib
#include "RTClib.h"

RTC_DS3231 rtc;

char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

void setup () {
  Serial.begin(9600);

#ifndef ESP8266
  while (!Serial); // wait for serial port to connect. Needed for native USB
#endif

  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    Serial.flush();
    abort();
  }

  if (rtc.lostPower()) {
    Serial.println("RTC lost power, let's set the time!");
    // When time needs to be set on a new device, or after a power loss, the
    // following line sets the RTC to the date & time this sketch was compiled
    // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
  }

  // When time needs to be re-set on a previously configured device, the
  // following line sets the RTC to the date & time this sketch was compiled
  // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  // This line sets the RTC with an explicit date & time, for example to set
  // January 21, 2014 at 3am you would call:
  // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
}

void loop () {
    DateTime now = rtc.now();

    Serial.print(now.year(), DEC);
    Serial.print('/');
    Serial.print(now.month(), DEC);
    Serial.print('/');
    Serial.print(now.day(), DEC);
    Serial.print(" (");
    Serial.print(daysOfTheWeek[now.dayOfTheWeek()]);
    Serial.print(") ");
    Serial.print(now.hour(), DEC);
    Serial.print(':');
    Serial.print(now.minute(), DEC);
    Serial.print(':');
    Serial.print(now.second(), DEC);
    Serial.println();

    Serial.print(" since midnight 1/1/1970 = ");
    Serial.print(now.unixtime());
    Serial.print("s = ");
    Serial.print(now.unixtime() / 86400L);
    Serial.println("d");

    // calculate a date which is 7 days, 12 hours, 30 minutes, 6 seconds into the future
    DateTime future (now + TimeSpan(7,12,30,6));

    Serial.print(" now + 7d + 12h + 30m + 6s: ");
    Serial.print(future.year(), DEC);
    Serial.print('/');
    Serial.print(future.month(), DEC);
    Serial.print('/');
    Serial.print(future.day(), DEC);
    Serial.print(' ');
    Serial.print(future.hour(), DEC);
    Serial.print(':');
    Serial.print(future.minute(), DEC);
    Serial.print(':');
    Serial.print(future.second(), DEC);
    Serial.println();

    Serial.print("Temperature: ");
    Serial.print(rtc.getTemperature());
    Serial.println(" C");

    Serial.println();
    delay(3000);
}

 

Ausgabe von ds3231.ino

Ausgabe von ds3231.ino
Ausgabe von ds3231.ino

Das Datum / die Uhrzeit komfortabel formatieren

Vielleicht hat euch auch gestört, dass die letzten beiden Sketche die Uhrzeiten des DS3231 ohne vorangestellte Nullen ausgegeben haben. Eine Uhrzeit wie 1:2:3 sieht irgendwie blöd aus. 01:02:03 wäre da besser. Diese und andere Formatierungen der Uhrzeit und des Datums könnt ihr mit der RTCLib ganz bequem vornehmen. Ich denke, dass das Prinzip durch den folgenden Beispielsketch klar wird und ich nichts weiter dazu schreiben muss.

#include <Wire.h>
#include <RTClib.h>

RTC_DS1307 rtc;


void setup () {
  Serial.begin(9600);

#ifndef ESP8266
  while (!Serial); // wait for serial port to connect. Needed for native USB
#endif

  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    Serial.flush();
    abort();
  }

  if (! rtc.isrunning()) {
    Serial.println("RTC is NOT running, let's set the time!");
    // When time needs to be set on a new device, or after a power loss, the
    // following line sets the RTC to the date & time this sketch was compiled
    // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
  }

  // When time needs to be re-set on a previously configured device, the
  // following line sets the RTC to the date & time this sketch was compiled
  // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  // This line sets the RTC with an explicit date & time, for example to set
  // January 21, 2014 at 3am you would call:
  // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
}

void loop() {

   DateTime now = rtc.now();

  //buffer can be defined using following combinations:
  //hh - the hour with a leading zero (00 to 23)
  //mm - the minute with a leading zero (00 to 59)
  //ss - the whole second with a leading zero where applicable (00 to 59)
  //YYYY - the year as four digit number
  //YY - the year as two digit number (00-99)
  //MM - the month as number with a leading zero (01-12)
  //MMM - the abbreviated English month name ('Jan' to 'Dec')
  //DD - the day as number with a leading zero (01 to 31)
  //DDD - the abbreviated English day name ('Mon' to 'Sun')

   char buf1[] = "hh:mm";
   Serial.println(now.toString(buf1));

   char buf2[] = "YYMMDD-hh:mm:ss";
   Serial.println(now.toString(buf2));

   char buf3[] = "Today is DDD, MMM DD YYYY";
   Serial.println(now.toString(buf3));

   char buf4[] = "DD.MM.YYYY";
   Serial.println(now.toString(buf4));

   char buf5[] = "MM/DD/YYYY";
   Serial.println(now.toString(buf5));

   Serial.println();

   delay(2000);
}

Ausgabe von toString.ino

DS3231 Beispielsketche: Ausgabe von toString.ino
Ausgabe von toString.ino

Einen Alarm im DS3231 einrichten

Ihr könnt den DS3231 wie einen Wecker oder einen Timer einrichten. Zwei Alarme sind einstellbar, wir fangen aber erst mal mit einem an. Mithilfe eines Interrupts könnt ihr euch über den Alarm informieren lassen oder ihr fragt aktiv regelmäßig ab, ob ein Alarm ausgelöst wurde.

Zuvor lernt ihr die neue Funktion rtc.disable32K(); kennen. Damit schaltet ihr den 32 kHz Oszillator aus, was generell sinnvoll ist, um Strom zu sparen.

Mit  rtc.setAlarm1(); oder rtc.setAlarm2();  stellt ihr den Alarm ein. Die Funktion erwartet zwei Argumente. Das erste Argument ist ein Zeitpunkt in Form eines DateTime Objekts. In diesem Beispielsketch wird rtc.now(); + TimeSpan(); verwendet, d.h.  der DS3231 wird als Timer eingesetzt. Das zweite Argument lässt sich am besten an Beispielen erklären:

  • DS3231_A1_Hour ist die klassische Weckereinstellung. Der Alarm wird ausgelöst, wenn Stunde, Minute und Sekunde mit der festgelegten Alarmzeit übereinstimmen.  Ein Alarm wird pro Tag ausgelöst.
  • DS3231_A1_Second führt jedes Mal zu einem Alarm, wenn die aktuelle Sekunde mit der Sekundeneinstellung in der Alarmzeit übereinstimmt. Der Alarm wird einmal pro Minute ausgelöst.

Es gibt noch mehr Möglichkeiten und verwirrender Weise sind die erlaubten Parameter für Alarm1 und Alarm2 unterschiedlich:

Alarmzeitparameter für den DS3231
Alarmzeitparameter für den DS3231

Weitere Funktionen für die DS3231 Alarme

  • rtc.clearAlarm(1/2); löscht den Alarm. Ein Alarm bleibt so lange bestehen, bis er gelöscht wird.
  • rtc.writeSqwPinMode(DS3231_OFF); deaktiviert das Rechtecksignal an SQW, damit der Pin als Interrupt Pin genutzt werden kann.
  • rtc.disableAlarm(1/2); deaktiviert die Alarmfunktion 1 oder 2.
  • rtc.alarmFired(1/2); prüft, ob Alarm1 bzw. Alarm2 ausgelöst wurde (Registerprüfung).

Im folgenden Sketch wird ein Alarm in 10 Sekunden ausgelöst.

/* Example implementation of an alarm using DS3231
 *
 * VCC and GND of RTC should be connected to some power source
 * SDA, SCL of RTC should be connected to SDA, SCL of arduino
 * SQW should be connected to CLOCK_INTERRUPT_PIN
 * CLOCK_INTERRUPT_PIN needs to work with interrupts
 */

#include <RTClib.h>
// #include <Wire.h>

RTC_DS3231 rtc;

// the pin that is connected to SQW
#define CLOCK_INTERRUPT_PIN 2

void setup() {
    Serial.begin(9600);
    // initializing the rtc
    if(!rtc.begin()) {
        Serial.println("Couldn't find RTC!");
        Serial.flush();
        abort();
    }
    
//    if(rtc.lostPower()) {
//        // this will adjust to the date and time at compilation
//        rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
//    }
    
    //we don't need the 32K Pin, so disable it
    rtc.disable32K();
    
    // Making it so, that the alarm will trigger an interrupt
    pinMode(CLOCK_INTERRUPT_PIN, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(CLOCK_INTERRUPT_PIN), onAlarm, FALLING);
    
    // set alarm 1, 2 flag to false (so alarm 1, 2 didn't happen so far)
    // if not done, this easily leads to problems, as both register aren't reset on reboot/recompile
    rtc.clearAlarm(1);
    rtc.clearAlarm(2);
    
    // stop oscillating signals at SQW Pin
    // otherwise setAlarm1 will fail
    rtc.writeSqwPinMode(DS3231_OFF);
    
    // turn off alarm 2 (in case it isn't off already)
    // again, this isn't done at reboot, so a previously set alarm could easily go overlooked
    rtc.disableAlarm(2);
    
    // schedule an alarm 10 seconds in the future
    if(!rtc.setAlarm1(
            rtc.now() + TimeSpan(10),
            DS3231_A1_Second // this mode triggers the alarm when the seconds match. See Doxygen for other options
    )) {
        Serial.println("Error, alarm wasn't set!");
    }else {
        Serial.println("Alarm will happen in 10 seconds!");  
    }
}

void loop() {
    // print current time
    char date[10] = "hh:mm:ss";
    rtc.now().toString(date);
    Serial.println(date);
    // resetting SQW and alarm 1 flag
    // using setAlarm1, the next alarm could now be configurated
    if(rtc.alarmFired(1)) {
        rtc.clearAlarm(1);
        Serial.println("Alarm cleared");
    }
    
    delay(2000);
}

void onAlarm() {
    Serial.println("Alarm occured!");
}

 

Ausgabe von ds3231_alarm_10s.ino

DS3231 Beispielsketche: Ausgabe von ds3231_alarm_10s.ino
Ausgabe von ds3231_alarm_10s.ino

Der Alarm wird jede Minute erneut ausgelöst, wenn die Sekunden mit der Alarmzeit übereinstimmen.

Zwei Alarme stellen

Wenn ihr verstanden habt, wie ein Alarm eingestellt wird, dann sind zwei Alarme auch kein Problem. Hier stelle ich keine neuen Funktionen vor und ich glaube, der Sketch ist in Verbindung mit der Ausgabe selbsterklärend.

/* Example implementation of an alarm using DS3231
 *
 * VCC and GND of RTC should be connected to some power source
 * SDA, SCL of RTC should be connected to SDA, SCL of arduino
 * SQW should be connected to CLOCK_INTERRUPT_PIN
 * CLOCK_INTERRUPT_PIN needs to work with interrupts
 */

#include <RTClib.h>
// #include <Wire.h>

RTC_DS3231 rtc;

// the pin that is connected to SQW
#define CLOCK_INTERRUPT_PIN 2

void setup() {
    Serial.begin(9600);
    // initializing the rtc
    if(!rtc.begin()) {
        Serial.println("Couldn't find RTC!");
        Serial.flush();
        abort();
    }
    
//    if(rtc.lostPower()) {
//        // this will adjust to the date and time at compilation
//        rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
//    }
    
    //we don't need the 32K Pin, so disable it
    rtc.disable32K();
    
    // Making it so, that the alarm will trigger an interrupt
    pinMode(CLOCK_INTERRUPT_PIN, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(CLOCK_INTERRUPT_PIN), onAlarm, FALLING);
    
    // set alarm 1, 2 flag to false (so alarm 1, 2 didn't happen so far)
    // if not done, this easily leads to problems, as both register aren't reset on reboot/recompile
    rtc.clearAlarm(1);
    rtc.clearAlarm(2);
    
    // stop oscillating signals at SQW Pin
    // otherwise setAlarm1 will fail
    rtc.writeSqwPinMode(DS3231_OFF);
    
    // turn off alarm 2 (in case it isn't off already)
    // again, this isn't done at reboot, so a previously set alarm could easily go overlooked
    rtc.disableAlarm(2);

    Serial.print("Start time: ");
    printTime();
    
    // schedule an alarm 30 seconds in the future
    rtc.setAlarm1(rtc.now() + TimeSpan(10), DS3231_A1_Second); // this mode triggers the alarm when the seconds match. See Doxygen for other options
    Serial.println("Alarm 1 will happen in 10 seconds!");  

    // schedule an alarm 2 minutes in the future   
    rtc.setAlarm2(rtc.now() + TimeSpan(0,0,2,0), DS3231_A2_Minute); // this mode triggers the alarm when the minutes match. See Doxygen for other options
    Serial.println("Alarm 2 will happen in 2 minutes (when full minutes match)!");
    Serial.println();  
   
}

void loop() {
    // resetting SQW and alarm 1 flag
    // using setAlarm1, the next alarm could now be configurated
    if(rtc.alarmFired(1)) {
        printTime();
        rtc.clearAlarm(1);
        Serial.println("Alarm 1 cleared");
        Serial.println();
    }

    // resetting SQW and alarm 1 flag
    // using setAlarm1, the next alarm could now be configurated
    if(rtc.alarmFired(2)) {
        printTime();
        rtc.clearAlarm(2);
        Serial.println("Alarm 2 cleared");
        Serial.println();
    }
}

void printTime(){
     // print current time
    char date[10] = "hh:mm:ss";
    rtc.now().toString(date);
    Serial.println(date);
}

void onAlarm() {
    Serial.println("Alarm occured!");
}

 

Ausgabe von ds3231_2_alarms.ino

DS3231 Beispielsketche: Ausgabe von ds3231_2_alarms.ino
Ausgabe von ds3231_2_alarms.ino

Hier erkennt man schön die Wirkung des Parameters D3231_A2_Minute.

Einen Alarm an einem Datum / Uhrzeit

Auch in diesem Sketch werden keine neuen Funktionen vorgestellt. Er dient nur der Vertiefung.

#include <RTClib.h>
// #include <Wire.h>

RTC_DS3231 rtc;

// the pin that is connected to SQW
#define CLOCK_INTERRUPT_PIN 2
volatile bool alarm = false;

void setup() {
    Serial.begin(9600);
    if(!rtc.begin()) {
        Serial.println("Couldn't find RTC!");
        Serial.flush();
        abort();
    }
    
//    if(rtc.lostPower()) {
//        // this will adjust to the date and time at compilation
//        rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
//    }
    
    //we don't need the 32K Pin, so disable it
    rtc.disable32K();
    
    // Making it so, that the alarm will trigger an interrupt
    pinMode(CLOCK_INTERRUPT_PIN, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(CLOCK_INTERRUPT_PIN), onAlarm, FALLING);
    
    // set alarm 1, 2 flag to false (so alarm 1, 2 didn't happen so far)
    // if not done, this easily leads to problems, as both register aren't reset on reboot/recompile
    rtc.clearAlarm(1);
    rtc.clearAlarm(2);
    
    // stop oscillating signals at SQW Pin
    // otherwise setAlarm1 will fail
    rtc.writeSqwPinMode(DS3231_OFF);
    
    // turn off alarm 2 (in case it isn't off already)
    // again, this isn't done at reboot, so a previously set alarm could easily go overlooked
    rtc.disableAlarm(2);
    
    // schedule an Alarm for a certain date (day of month), hour, minute, and second
    DateTime alarmTime (2021, 1, 24, 21, 33, 0);
    rtc.setAlarm1(alarmTime, DS3231_A1_Date); 
    Serial.print("Current time: ");
    printTime();
    Serial.println();
}

void loop() {
    if(alarm){
        if(rtc.alarmFired(1)) {
            Serial.println("Alarm occured, current time: ");
            printTime();
            rtc.clearAlarm(1);
            Serial.println("Alarm 1 cleared");
        }
    }
}

void printTime(){
    // print current time
    DateTime now = rtc.now();
    char date[] = "DD.MM.YYYY, ";
    Serial.print(now.toString(date));
    char time[] = "hh:mm:ss";
    rtc.now().toString(time);
    Serial.println(time);
}

void onAlarm() {
    alarm = true;
}

 

Ausgabe von DS3231_alarm_fixed_time_and_date.ino

DS3231 Beispielsketche: Ausgabe von DS3231_alarm_fixed_time_and_date.ino
Ausgabe von DS3231_alarm_fixed_time_and_date.ino

PCF8523, PCF8563 und DS1307

Mithilfe der RTCLib könnt ihr auch die alternativen Echtzeituhren PCF8523, PCF8563 und DS1307 steuern. Den PCF8523 und den DS1307 habe ich ausprobiert. Die Beispielsketche der RTCLib für diese RTCs liefen anstandslos. Die Einstellung und das Lesen von Uhrzeit und Datum funktioniert wie beim DS3231. Das Gleiche gilt für die angenehmen Rechenfunktionen mittels TimeSpan und die zahlreichen Formatierungsoptionen für die Ausgabe.

Der PCF8523 (Datenblatt hier) und der PCF8563 (Datenblatt hier) sind dem DS3231 recht ähnlich. Zumindest hatte ich den Eindruck beim Überfliegen der Register im Datenblatt. Leider hat die RTCLib aber anscheinend nicht alle vorhandenen Funktionen implementiert. Es gibt einen Countdown Timer, aber keine klassische Alarmfunktion.

Alternative zum DS3231: der PCF8523
Alternative zum DS3231: der PCF8523

Der DS1307 ist etwas einfacher gestrickt. Er besitzt an sich keine Alarmfunktion. Dafür hat er wie der DS3231 einen EEPROM integriert und eine Akkuladefunktion.

Alternative zum DS3231: der DS1307

Software RTC

Ihr könnt die Uhrzeit- und Datumsfunktionen der RTCLib aber auch ganz ohne RTC Modul nutzen. Dazu wird die aktuelle Zeit beim Kompilieren von der Systemzeit des Computers einmalig übernommen und dann mit der millis() Funktion aktualisiert. Der Beispielsketch softrtc.ino (den ich hier nicht noch einmal abdrucke) zeigt euch, wie es geht. Die Methode hat zwei Nachteile:

  • Die millis() Methode ist wesentlich ungenauer.
  • Bei jedem Reset oder jeder Stromunterbrechung ist die aktuelle Zeit verloren.

Für die Fortgeschrittenen: Die AVR basierten Arduino Boards benutzen für millis() den Timer0 und den zugehörigen Timer0 Overflow Interrupt. Ihr findet die Definitionen unter Arduino\hardware\arduino\avr\cores\arduino\wiring.c.

DS3231 Bibliotheken: Alternativen zu RTCLib

Ich habe mir auch eine Reihe anderer Bibliotheken für den DS3132 auf Github angeschaut. Wenn ihr die RTCLib als zu groß und komplex empfindet, dann könntet ihr euch die DS3231_Simple Bibliothek einmal anschauen. Der Name ist Programm. Sie besitzt alle Basisfunktionen für die Uhrzeit, einschließlich der Alarmprogrammierung. Was ihr fehlt (Stand Februar 2020), sind die Alarminterrupts am SQW Pin. Ihr müsst also regelmäßig abfragen, ob ein Alarm vorliegt. Ein eindeutiger Vorteil hingegen ist, dass die DS3231 auch Funktionen für das Beschreiben des EEPROMs besitzt.

Sehr akkurat und sehr vollständig ist die Bibliothek DS3132RTC. Nur ist sie vielleicht für den nicht so erfahrenen Arduino Fan ein bisschen schwer verdaulich. Schaut euch die Beispielsketche an, dann wisst ihr was ich meine.

Danksagung

Der Firma Adafruit danke ich für die Veröffentlichung ihrer Bibliothek.

Die Grundlage meines Beitragsbildes, in das ich den DS3231 hineinmontiert habe, stammt von andreas N (domeckopol) auf Pixabay.

13 thoughts on “DS3231 – Echtzeituhr

  1. Hallo Wolfgang,
    Sehr gute Beschreibung danke!
    Ich versuche gerade ein Relais zu einer eingestellten Zeit einzuschalten und so auch wieder auszuschalten. (z.B Aquariumlicht.)
    Ich bringe die Bedingung aber nicht hin, dass dies sauber läuft. Hast du vielleicht zu dem ein Beispiel?

    Liebe Grüsse

    Marin

    1. Hi Marin,
      so müsste es gehen

      Bau auf dem Beispiel mit den zwei Alarmen auf und definiere die Einschalt- und Abschaltzeit:

      DateTime alarmTime1(2021, 1, 24, 18, 30, 0); // Kümmere dich nicht um das Jahr, Monat, Tag -> 18:30 Uhr
      rtc.setAlarm1(alarmTime1, DS3231_A1_Hour); // Es sollten eigentlich (musst du mal probieren) nur die Stunden/Minuten/Sekunden verglichen werden.

      DateTime alarmTime2(2021, 1, 24, 23, 0, 0); // Zweiter Alarm um 23:00 Uhr.
      rtc.setAlarm2(alarmTime1, DS3231_A2_Hour);

      Oder du modifizierst / erweiterst das Beispiel mit dem Alarm an einem bestimmten Tag entsprechend.

      Wenn es nicht geht, dann schicke mir mal deinen Sketch an Wolfgang.Ewald@wolles-elektronikkiste.de.

      VG, Wolfgang

  2. Hallo Wolfgang, Danke für diesen Blog und Danke Tante Google das sie mich hergeführt hat 😀 . Ich arbeite noch nicht sehr lange mit Arduino und Co und suchte nach einem Tut über den DS3231. Benötige das für mein Projekt. Nun habe ich was ich wollte 🙂 2 Alarme pro Tag die mir zu festgelegten Uhrzeiten eine Statusmeldung senden…. Thx …
    Gruß Ralf

  3. Hallo,
    sehr gute und klare Beschreibung. Interessanter Blog. Vielen Dank.
    Folgender Hinweis: Bei mir ergab sich beim Laden des Programms „Einen Alarm an einem Datum / Uhrzeit“ die folgende Fehlermeldung:
    exit status 1
    ‚volatile bool alarm‘ redeclared as different kind of symbol
    Durch Umbenennen der Variablen läuft das Programm o.k.
    Gruß
    Klaus

    1. Hallo Klaus,

      mit dem Arduino UNO bekomme ich keine Fehlermeldung. Darf ich fragen, welches Board du verwendest? Wahrscheinlich ist da „alarm“ schon an anderer Stelle definiert.

      Vielen Dank auf jeden Fall für den Hinweis! VG, Wolfgang

      1. Hallo Ewald,
        diese Fehlermeldung ist bei Verwendung eines ESP 32 aufgetreten. Vielleicht liegt es daran?
        Gruß Klaus

  4. Hallo
    Du gibst eine Vcc von 2,5V bis 5,5V an. Nach AZ dem Hersteller sollen es aber max. 3,3V sein.
    Da ein Anschluss von 5V erfolgt bruache ich keinen Pegelwandler, bei Vcc von 3,3V zu 5V aber schon.
    Was ist mit Int (SQW) Kann der bei Vcc 3,3V ohne Probleme auf eine Leitung mit 5V gelegt werden?

    1. Hallo, mit AZ meinst du AZ Delivery? Auch die schreiben in ihrem e-Book zu dem Teil:
      „Dank der 2,3-5,5 V Spannungstoleranz des DS3231 ist die RTC sehr Controller-freundlich und lässt sich mit allen Arduino-Typen Dank der 2,3-5,5 V Spannungstoleranz des DS3231 ist die RTC sehr
      Controller-freundlich und lässt sich mit allen Arduino-Typen
      verwenden.

      Im Datenblatt des DS3231 ICs werden 3.3 Volt oben im Datenblatt angegeben. Weiter unten findet man die Recommended Operating Conditions. Dort werden 2.3 – 5.5 Volt angegeben. Diese Toleranz sollte für alle Ein- und Ausgänge gelten.

      Wo man eigentlich aufpassen müsste, ist bei den I2C – Leitungen. Da wird als Grenze VCC + 0.3 angegeben. D.h. bei Betrieb mit 3.3 Volt und Anschluss an einen Microcontroller der mit 5 Volt läuft, ist man drüber. Allerdings habe ich bei meinen Recherchen niemanden gesehen, der das berücksichtigt. Was der Int/SQW Pin in Bezug auf VCC verträgt, dazu habe ich keine Angabe gefunden. Funktionieren tut es bei 5 Volt, das habe ich probiert. Ob es langfristig gut ist, kann ich nicht sagen.

  5. Ein sehr Interessanter Blog. Dieses Thema Interessiert mich sehr und wollte einfach mal Danke sagen. Echt top zusammen gefasst.

Schreibe einen Kommentar

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