Auf Basis des Chips DS3231 werden günstige und sehr genaue RTC-Module (real time clock) zum Einsatz mit Microcontrollern angeboten. Sie versorgen z.B. Arduinos, die keinen WLAN-Chip und somit keinen Zugang zu einem Internet-Zeitserver haben, mit der aktuellen Zeit. Die Uhr läuft sehr präzise, weil die Schwingungen des Quarzes im DS3231 an die Umgebungstemperatur angepasst werden. Der dazu verwendete Sensor kann auch abgefragt werden, hat aber nur eine Genauigkeit von ±3 Grad – deutlich ungenauer als die speziellen Temperatursensoren DS18B20 oder DHT22.
Lt. Datenblatt beträgt die Abweichung bei „normaler” Umgebungstemperatur von 0-40 °C maximal ±1 Minute pro Jahr bzw. rund eine Sekunde alle sechs Tage – zum Datenloggen o.ä. sicher genau genug. Die Kommunikation erfolgt über das I2C-Protokoll, nutzt am ESP32 also die beiden GPIO-Pins 21 (SDA – Daten) und 22 (SCL – Takt; alternativ GPIO 17 für SDA und 16 für SCL am zweiten I2C-Bus). Die Uhr wird über eine Knopfzelle versorgt, die je nach Modell mehrere Jahre halten soll.
Verschiedene DS3231 Modultypen

Es gibt mehrere Modultypen: das hier gezeigte ist sehr kompakt mit Abmessungen von ca. 1,3 cm * 1,3 cm * 1,3 cm (die Höhe wird maßgeblich von den ca. 8 mm hohen Anschlussbuchsen bestimmt). Bei Nutzung am Raspberry Pi kann es direkt auf dessen GPIO-Leiste gesteckt werden, andernfalls benötigt man vier Kabel (für VCC, GND und die beiden erwähnten I2C-Anschlüsse). Das Modul hat die I2C-Adresse 0x68 und wird von einer angelöteten 3 Volt-Knopfzelle versorgt – Typ CR927. [1]Laut Kundenantwort bei Amazon. Ich habe noch nicht nachgeguckt und werde das erst tun, wenn die Batterie den Geist aufgibt – was hoffentlich erst in ein bis zwei Jahren der Fall ist.
Ein anderes häufig angebotenes Modul besteht aus einer etwas größeren Platine (ca. 4 cm * 2 cm) mit einem Batteriefach für Standard-Knopfzellen vom Typ CR2032 [2]Bei einer Versorgungsspannung von 5 Volt (wie am Arduino) versucht das Modul die Batterie zu laden (siehe z.B. diese Diskussion im Arduino-Forum). Standardknopfzellen vertragen das nicht (Zerstörung … Continue reading und Anschlusspins statt Buchsen (Bild bei AZ-Delivery ). Die I2C-Adresse ist 0x57 und kann durch ein Adressfeld mit drei Kontakten angepasst werden. Außerdem ist ein EEPROM AT24C32 zum Speichern kleiner Datenmengen vorhanden (32 Kbit bzw. 4 KByte Speicher), um das Modul z.B. als Datenlogger einzusetzen. Eine ausführliche Beschreibung des Adressfelds und des AT24C32-Speichers gibt es auf lastminuteengineers.com. Neben diesen beiden sehr preiswerten RTC-Modulen (ca. 1 bis 3 €) gibt es weitere, die z.B. an bestimmte Microcontroller-Boards angepasst sind.
Wegen des größeren Funktionsumfangs und der leicht austauschbaren Batterie würde ich bei einem Neukauf das zweite Modul wählen, es sei denn minimaler Platzbedarf ist von Bedeutung. Ich habe nun aber das kleine Modul und mache, was damit geht, nämlich die Zeit zu stellen und sie auszulesen, um die Systemzeit im ESP32 zu setzen.
Bibliothek RTClib
Als Bibliothek nutze ich die RTClib von Adafruit, die man über die Bibliotheksverwaltung der Arduino-IDE (Suche nach „rtclib”) oder nach dem Download von der GitHub-Seite installieren kann. Es gibt aber viele Alternativen, wenn man in der Bibliotheksverwaltung nach „rtc” oder „ds3231” sucht, z.B. die Bibliothek Rtc by Makuna von Michael Miller (Github). Sie unterstützt neben dem DS3231 auch das oben erwähnte AT24C32-EEPROM und andere RTC-Chips und hat eine ordentlich aussehende Dokumentation und Beispielprogramme.
In einem mit der Bibliothek installierten Beispielprogramm (Menü Datei → Beispiele → RTClib → ds3231
) wird gezeigt, wie man die Zeit einstellen kann, wenn man (z.B. am Arduino) keinen Zugriff auf einen NTP-Server hat – man nutzt den Zeitstempel im Programm, der den Zeitpunkt des Kompilierens in der Arduino-IDE angibt. Lädt man das Programm sofort hoch, hat man nur eine Differenz von wenigen Sekunden zur tatsächlichen Zeit auf dem Rechner, an den der Microcontroller angeschlossen ist.
// following line sets the RTC to the date & time this sketch was compiled rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
Genauer ist es, die Zeit von einem NTP-Server zu holen – Details dazu sind im Beitrag zum TM1637-Modul beschrieben. Eine weitere Möglichkeit zeigt das zweite Programm – das manuelle Setzen der Zeit per Eingabe im seriellen Monitor der Arduino-IDE.
Einstellen der Zeit per NTP-Server
Im Programm müssen SSID (Name) und Passswort für das WLAN gesetzt werden. Im Setup wird geprüft, ob die RTC bereits gestellt ist. Falls nicht, z.B. bei der ersten Inbetriebnahme oder nach einem Batteriewechsel, werden die Systemzeit des ESP32 und die RTC per NTP-Server-Abfrage gestellt. Andernfalls wird nur die Systemzeit nach der RTC gesetzt. Will man die Echtzeituhr auf jeden Fall stellen, lässt man einfach die entsprechende If-Abfrage if (rtc.lostPower())
im Programm weg bzw. ersetzt sie durch if (true)
.
Die Programmschleife zeigt alle 10 Sekunden die Zeit der Echtzeituhr (UTC) und die lokale Uhrzeit (Systemzeit anhand der eingestellten Zeitzone) sowie die vom internen Sensor gemessene Temperatur an.
/* DS3231 RTC - Zeit per NTP setzen und aus der RTC auslesen * * 2019-07-21 Heiko (unsinnsbasis.de) */ #include <Wire.h> #include "RTClib.h" #include <WiFi.h> #include "time.h" #include <sys/time.h> RTC_DS3231 rtc; struct tm tdata; // lokale Zeit const char* ssid = "Deine_WLAN_SSID"; const char* password = "Dein_WLAN_Passwort"; const char* ntp_server = "fritz.box"; // oder (de/at/ch).pool.ntp.org // lokale Zeitzone definieren // https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv #define TIMEZONE "CET-1CEST,M3.5.0/02,M10.5.0/03" char days_of_week[7][11] = {"Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"}; void setup () { DateTime now; timeval tv; int rc; Serial.begin(115200); if (! rtc.begin()) { Serial.println("Keine Echtzeituhr gefunden!"); while (1); } if (rtc.lostPower()) { Serial.println("RTC wird neu gestellt"); // mit dem WLAN verbinden; nach Ermitteln der Zeit kann die // Verbindung wieder getrennt werden Serial.printf("Verbindung herstellen mit %s ", ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(" Verbunden!"); // Zeit vom NTP-Server holen configTime(0, 0, ntp_server); // vor Trennung des WLANs einmal getLocalTime() aufrufen getLocalTime(&tdata); setenv("TZ", TIMEZONE, 1); // Zeitzone einstellen // WLAN trennen WiFi.disconnect(true); WiFi.mode(WIFI_OFF); // RTC anhand der Systemzeit stellen rtc.adjust(time(NULL)); } else { // (System-)Zeit aus der RTC übernehmen now = rtc.now(); tv.tv_sec = now.unixtime(); tv.tv_usec = 0; // keine Mikrosekunden setzen rc = settimeofday(&tv, NULL); // NULL = keine Zeitzoneninfo an dieser Stelle ... setenv("TZ", TIMEZONE, 1); // ... sondern hier: Zeitzone einstellen Serial.printf("Returncode beim Setzen der Systemzeit nach der RTC: %d\n\n", rc); } } void loop () { DateTime now = rtc.now(); Serial.printf("RTC Unix-Zeit - Datum TT.MM.JJJJ und Zeit: %s %02d.%02d.%d %02d:%02d:%02d\n", days_of_week[now.dayOfTheWeek()], now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second()); Serial.printf("RTC Unix-Zeit seit 1.1.1970 - %d Sekunden bzw. %d Tage\n", now.unixtime(), now.unixtime() / 86400L); getLocalTime(&tdata); Serial.printf("Systemzeit: time() = %d\n", time(NULL)); Serial.printf("Lokale Uhrzeit (aus Systemzeit): %02d:%02d:%02d\n", tdata.tm_hour, tdata.tm_min, tdata.tm_sec); Serial.printf("RTC-Temperatur: %2.1f °C\n\n", rtc.getTemperature()); delay(10000); }
Wurde die Uhr vorher nicht noch nicht gestellt, verbindet sich der ESP32 mit dem WLAN . Das Programm macht folgende Ausgabe (Screenshot):
RTC wird neu gestellt Verbindung herstellen mit HS Gastzugang ..... Verbunden! RTC Unix-Zeit - Datum TT.MM.JJJJ und Zeit: Samstag 03.08.2019 16:24:29 RTC Unix Zeit seit 1.1.1970 - 1564849469 Sekunden bzw. 18111 Tage Systemzeit: time() = 1564849469 Lokale Uhrzeit (aus Systemzeit): 18:24:29 RTC-Temperatur: 26.8 °C RTC Unix-Zeit - Datum TT.MM.JJJJ und Zeit: Samstag 03.08.2019 16:24:39 [...]
Bei einem späteren Durchlauf mit bereits gestellter Uhr sieht die Ausgabe so aus:
Returncode beim Setzen der Systemzeit nach der RTC: 0 RTC Unix-Zeit - Datum TT.MM.JJJJ und Zeit: Samstag 03.08.2019 16:43:47 RTC Unix Zeit seit 1.1.1970 - 1564850627 Sekunden bzw. 18111 Tage Systemzeit: time() = 1564850627 Lokale Uhrzeit (aus Systemzeit): 18:43:47 RTC-Temperatur: 26.2 °C
Manuelle Eingabe der Zeit im seriellen Monitor

Außerdem kann man auch Daten über die serielle Schnittstelle (den COM-Port, der auf dem PC dem USB-Anschluss zugewiesen wird) zum ESP32 übertragen – in der Arduino-IDE nutzt man für Tastatureingaben den seriellen Monitor.
Das folgende Programm zeigt die in der RTC gespeicherte Zeit an und wartet auf eine Eingabe. Zwei Eingabeformate sind möglich: JJJJ-MM-TT HH:MM:SS
, z.B. „2019-07-26 18:00:00” für den 26. Juli 2019 18 Uhr, und HH:MM:SS
. Im zweiten Fall wird nur die Uhrzeit neu gestellt, das Datum bleibt unverändert. Die Sekunden können jeweils weggelassen werden (dann werden sie auf Null gesetzt), und die Trennzeichen (Minuszeichen, Doppelpunkte, Leerzeichen …) sind frei wählbar. Wichtig ist nur, dass das Jahr vierstellig und alle anderen Zahlen zweistellig eingegeben werden mit jeweils genau einem Zeichen dazwischen. Im Programm findet keine Prüfung der Eingabe statt – wer Buchstaben, ungültige Datumswerte oder zu viele Trennzeichen eingibt, hat selber Schuld. 😉 Die Eingabe kann man so oft wiederholen, bis die gewünschte Zeit eingestellt ist. Die Programmschleife zählt die Sekunden hoch und zeigt alle 10 Sekunden die Zeit als UTC und Ortszeit an.
Die Eingabezeile befindet sich am oberen Rand des seriellen Monitors. Damit die Enter-Taste (Zeilenvorschub) als Ende der Eingabe erkannt wird, muss unten in der Auswahlbox „Neue Zeile” eingestellt sein. Außerdem muss – wie immer bei Nutzung des seriellen Monitors – die dort eingestellte Übertragungsrate mit dem im Programm per Serial.begin()
eingestellten Wert übereinstimmen; in diesem Fall 115200.

/* DS3231 RTC - Eingabe der Zeit im seriellen Monitor * * 2019-07-26 Heiko (unsinnsbasis.de) */ #include <sys/time.h> #include "RTClib.h" // lokale Zeitzone definieren // https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv #define TIMEZONE "CET-1CEST,M3.5.0/02,M10.5.0/03" RTC_DS3231 rtc; time_t tm_before = 0; const char * init_msg = "Datum/Zeit im Format YYYY-MM-DD HH:MM:SS eingeben\n" "oder Zeit HH:MM:SS (lässt das Datum unverändert).\n" "(Trennzeichen beliebig; Sekunden optional)"; void setup() { DateTime now; timeval tv; int rc; Serial.begin(115200); if (! rtc.begin()) { Serial.println("Keine Echtzeituhr gefunden!"); while (1); } if (rtc.lostPower()) { Serial.println("RTC muss gestellt werden."); } else { // Systemzeit aus RTC übernehmen now = rtc.now(); tv.tv_sec = now.unixtime(); Serial.printf("Unix-Zeit aus RTC: %d\n", tv.tv_sec); tv.tv_usec = 0; // keine Mikrosekunden setzen rc = settimeofday(&tv, NULL); // NULL = keine Zeitzoneninfo an dieser Stelle ... Serial.printf("Zeit wurde aus RTC übernommen; Returncode: %d\n\n", rc); } setenv("TZ", TIMEZONE, 1); // Zeitzone einstellen Serial.println(init_msg); } void loop() { String str; int year, mon, day, hour, min, sec, rc; time_t tm_now; struct tm *tms; timeval tv; DateTime now; delay(200); // 5 mal pro Sekunde auf Eingabe warten reicht tm_now = time(NULL); if (tm_now > tm_before) { // ist eine neue Sekunde angebrochen? -> Zeit anzeigen tm_before = tm_now; if (tm_now % 10 == 0) { // alle 10 s die aktuelle Zeit ausgeben Serial.print("\nGMT/UTC: "); Serial.print(asctime(gmtime(&tm_now))); Serial.print("Ortszeit: "); Serial.print(asctime(localtime(&tm_now))); } else { // ansonsten nur die Sekunden ausgeben Serial.printf("...%1d ", tm_now % 10); } } if (Serial.available() > 0) { str = Serial.readStringUntil('\n'); if (str.length() < 5) { Serial.printf("\nFalsche Eingabe.\n"); } else { if (str.length() < 9) { // zwischen 5 und 8 Zeichen, also kein Datum, nur Zeit hour = str.substring(0,2).toInt(); min = str.substring(3,5).toInt(); sec = 0; if (str.length() > 6) { sec = str.substring(6,8).toInt(); } // Datum aus Systemzeit übernehmen tms = gmtime(&tm_now); year = tms->tm_year+1900; mon = tms->tm_mon+1; day = tms->tm_mday; } else { // komplettes Datum mit Zeit eingegeben year = str.substring(0,4).toInt(); mon = str.substring(5,7).toInt(); day = str.substring(8,10).toInt(); hour = str.substring(11,13).toInt(); min = str.substring(14,16).toInt(); sec = 0; if (str.length() >= 19) { sec = str.substring(17,19).toInt(); } } Serial.printf("\nJahr = %04d, Monat = %02d, Tag = %02d\n", year, mon, day); Serial.printf("Stunde = %02d, Minute = %02d, Sekunde = %02d\n", hour, min, sec); // RTC und Systemzeit setzen rtc.adjust(DateTime(year, mon,day, hour, min, sec)); now = rtc.now(); Serial.printf("RTC Unix-Zeit: %d\n", now.unixtime()); tv.tv_sec = now.unixtime(); tv.tv_usec = 0; // keine Mikrosekunden setzen rc = settimeofday(&tv, NULL); // NULL = keine Zeitzoneninfo an dieser Stelle Serial.printf("Returncode beim Setzen der Systemzeit: %d\n", rc); tm_before = now.unixtime(); Serial.println("Für eine erneute Korrektur der Zeit:"); Serial.println(init_msg); } } }
Datenblätter
- DS3231: Produktseite des Herstellers Maxim Integrated mit Link zum Datenblatt (PDF)
- AT24C32: PDF zum in einigen Modulen verbauten EEPROM
Themenverwandte Beiträge
Typische 7-Segment-Anzeigen bestehen aus sieben LEDs (meist plus einer weiteren für den Dezimalpunkt...
Mehrstellige 7-Segment-Anzeigen direkt per Schieberegister zum Anzeigen von Daten zu nutzen, bedeute...
LittleFS ist ein Dateisystem für den Flash-Speicher verschiedener Mikrocontroller. Auf den ESP-Contr...
Für viele Einsatzzwecke von ESP32, Arduino und Konsorten muss man die Basisfunktionalitäten, z.B. zu...
Fußnoten
1↑ | Laut Kundenantwort bei Amazon. Ich habe noch nicht nachgeguckt und werde das erst tun, wenn die Batterie den Geist aufgibt – was hoffentlich erst in ein bis zwei Jahren der Fall ist. |
---|---|
2↑ | Bei einer Versorgungsspannung von 5 Volt (wie am Arduino) versucht das Modul die Batterie zu laden (siehe z.B. diese Diskussion im Arduino-Forum). Standardknopfzellen vertragen das nicht (Zerstörung droht)! In der Diskussion ist ein Bild verlinkt, auf dem man erkennen kann, welche Leitung auf der Platine durchtrennt werden muss, damit die Batterie nicht geladen wird. Alternativ kann man einen wiederaufladbaren LIR2032-Akku verwenden oder das Modul mit 3,3 Volt betreiben – am ESP32 sollten also keine Probleme auftreten. |
Hallo
erstmal vielen Dank!!!
deine 2 sketch funktionieren wunderbar am ESP32
– jedoch nur mit den standart GPIO-Pins 21 (SDA – Daten) und 22 (SCL – Takt)
– alternativ GPIO 17 für SDA und 16 für SCL am zweiten I2C-Bus – funktioniert hingegen leider nicht.
muss ich irgendwo was ändern um den 2ten I2C Bus SDA=17 und SCL=16 vom ESP32 nutzen zu können.
mfg Rico
Hi
hat sich erledigt.
Habe es hinbekommen den alternativen i2c Bus zum laufen zu bekommen.
Bitte oben einfügen
#define SDA 17
#define SCL 16
und
Serial.begin(115200);
if (! rtc.begin()) {
in
Serial.begin(115200);
Wire.begin(SDA, SCL);
if (! rtc.begin()) {
ändern
Hallo
habe es hinbekommen läuft jetzt auch mit alternativeb I2C Bus.
#define SDA 17
#define SCL 16
am anfang einfügen (lief auch mit z.b. GPIO 32 und 33)
und am anfang vom void setup bereich noch Wire.begin(SDA, SCL); einfügen.
also etwa so
void setup () {
Wire.begin(SDA, SCL);
DateTime now;
usw…………
das war es auch schon
mfg Rico
Moin Rico,
danke für die Frage und vor allem, dass du dann auch die erfolgreiche Lösung nachgereicht hast. Der Beitrag ist schon etwas älter, deswegen fehlt der Hinweis auf den Text zum I2C-Interface (https://unsinnsbasis.de/i2c-schnittstelle-esp32-arduino/). Ich nehme das zum Anlass, das demnächst zu ergänzen (und noch ein paar andere Dinge zu ändern).
Das betrifft vermutlich auch noch andere Beiträge, die untereinander besser verlinkt sein könnten und um die mich dann in der nächsten Zeit auch kümmern werde.
Viele Grüße,
Heiko