Echtzeituhr mit RTC auf 8fach-7Segment-Display anzeigen

Als Grundlage für unsere heutige Schaltung dient das Prototype Shield mit 8fach-7Segment-Anzeige mit MAX7219, wie bereits im vorletzten Projekt vorgestellt.

Dort hatten wir in der Demo ja schon die Anzeige von Datum und Uhrzeit (Tag, Monat, Stunde, Minute) auf den 8 Stellen des Displays. Das Problem dabei ist allerdings, dass jedesmal, wenn das Gerät vom Strom getrennt wird, die Uhrzeit verloren geht.

Man bräuchte also eine Uhr, die auch ohne Strom weiterläuft. Genau dafür sind sogenannte Real Time Clocks (kurz RTC) entwickelt worden. Sobald die Stromversorgung unterbrochen wird, übernimmt eine kleine Bufferbatterie und lässt die Zeit weiterlaufen.

Schalten wir unser Gerät dann wieder an, können wir die laufende Zeit aus der RTC wieder auslesen und anzeigen.

RTC mit DS1307


Real Time Clocks gibt es als fertige Module, auf denen z. B. wie hier der eigentliche Uhrenchip, ein Dallas DS1307, ein Atmel 24C32 mit 32 kB EEPROM-Speicher für die Speicherung der Uhrzeit Ansprache ebenfalls über I2C, ein Quartz mit 32,768 kHz als Taktgeber für die Uhr und ein paar weitere Komponenten verbaut sind.

In das TinyRTC getaufte Modul gehört auf die Rückseite (normalerweise) eine CR2032 Knopfzelle, damit sich das Modul auch die Zeit weiterlaufen lassen kann, wenn der Arduino ausgeschaltet ist.

"Normalerweise" deshalb, weil bei meinem, dem oben abgebildeten Modul, ein Beschaltungsfehler vorhanden ist: die Knopfzelle versorgt im ausgeschalteten Zustand den gesamten Arduino mit Spannung, auch alle LEDs, die an diesem hängen. Damit ist die Knopfzelle in kürzester Zeit leer gelutscht. Richtig angeschlossen wird dieses fehlerhafte Modul über BAT und GND rechts unten. Ich habe einfach zwei AAA-Batterien in Reihe geschaltet und hier angelötet. Will man eine Knopfzelle benutzen, muss man den Pluspol (das kleinere Blech) auftrennen und mit dem BAT-Lötpad verbinden.

Richtig verdrahtet wird auch nur noch das RTC-Modul von der Batterie gespeist und hält entprechend jahrelang.

Der DS1307 bietet folgende Features:
und verfügt über folgende Pins: Ist die Spannung an VCC (normalerweise 5V) größer als die an VBAT (normalerweise 3V), wird auf Batterie umgeschaltet. Fällt VCC unter das 1.25-fache von VBAT (ergibt normalerweise 3.75V), dann werden Schreib- und Lesezugriffe gesperrt, aber die Stromversorgung geschieht immer noch über VCC. VBAT sollte zwischen 2.0V und 3.5V sein, damit alles normal funktioniert.

Die Genauigkeit der Uhr hängt natürlich von der Qualität des angeschlossenen Quartzes ab, hier ist ggf. auch die Umgebungstemperatur, die ggf. die Schwingfrequenz des Quartzes beeinflusst, zu berücksichtigen.

Die Adressen des DS1307 sehen wie folgt aus: 00h Sekunde Bit 7 = Clock Halt (CH) 01h Minute 02h Stunde Bit 6 = 12 Stunden Modus 03h Tag 04h Wochentag 05h Monat 06h Jahr 07h Control Byte (zur Steuerung des Rechteckkurvenausgangs, Näheres siehe Datenblatt) Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0 OUT 0 0 SQWE 0 0 RS1 RS0 08h ... 3Fh: RAM (56 Bytes) Der DS1307 wird über das I2C-Protokoll im regulären Modus (100 kHz) angesprochen, die Details sind dem Datenblatt zu entnehmen.

Biblibotheken für RTC-Zugriff


Für den RTC-Zugriff gibt es eine Reihe von Bibliotheken. Manche unterstützen nur den DS1307, andere auch noch andere Chips. Da der Speicher im Arduino immer knapp ist, empfiehlt es sich meiner Meinung nach, die Bibliothek zu nehmen, die genau das kann, was man braucht, aber auch nicht mehr, denn das wäre überflüssiger Balast.

Die Bibliothek MD_DS1307 von majicDesigns z. B. bietet für eine DS1307-RTC alles, was man braucht und bringt ein schönes Beispielprogramm mit, mit denen man alle Features des DS1307 ausprobieren kann und dies komfortabel mit Befehlen, die man über das serielle Interface in der Arduino IDE eingibt.

Letztendlich habe ich mich aber für eine noch schlankere Version einer Lib von JeeLabs entschieden, die hier heruntergeladen werden kann. Diese benutze ich auch im folgenden Beispielprogramm für die Echtzeituhr.

Diese Lib kennt eigentlich nur eine Instanziierung und 3 Befehle: Das reicht aber vollkommen aus, wenn man keinen 12 Stunden-Modus und anderen Schnick-Schnack braucht.


Unser Programm zeigt die Zeit auf 3 Modi an, zwischen denen man mit dem oberen Taster wechseln kann: Datum/Zeit, Datum mit Jahr, Zeit mit Sekunden. Zu Anfang wird abgefragt, ob die RTC auch läuft, wenn nicht, wird der Zeitpunkt, an dem der Sketch kompiliert wurde genommen, in die RTC geladen und die RTC gestartet. Solange die Batterie in der RTC jetzt nicht leer wird (und das sind Jahrzehnte), behält das Modul die Zeit bei und deren interne Uhr läuft weiter, auch wenn der Arduino von der Stromversorgung getrennt wird

Wird der Arduino wieder angeschlossen, wird erkannt, dass die RTC läuft, deren Zeit geladen und angezeigt.

Wem die Kompilierungszeit nicht genau genug ist (diese aktualisiert sich natürlich bei jedem Kompilieren/Hochladen), der kann das Beispielprgroamm der MD-Library hernehmen, um die Zeit sekundengenau über die serielle Schnittstelle einzustellen.


Und so sieht das Programm in Aktion aus:



Hier der dazu gehörigen Quellcode:

Source-Code

//////////////////////////////////////////////////////// // (C) 2019 by Oliver Kuhlemann // // Bei Verwendung freue ich mich über Namensnennung, // // Quellenangabe und Verlinkung // // Quelle: http://cool-web.de/arduino/ // //////////////////////////////////////////////////////// #include "LedControl.h" // Lib für 8fach-7Segment-Anzeige mit MAX7219 #include <Wire.h> #include "RTClib.h" // RTC-Lib by JeeLabs http://news.jeelabs.org/code/ #define PinTaster 11 RTC_DS1307 RTC; LedControl lc=LedControl(7,5,6,1); // Anzeige initialisieren // 7 = DIN // 5 = CLK // 6 = CS / LOAD // 1 = eine Anzeige / 1 Max7291 int mode=1; // Anzeigemodus 1-3 void setup() { pinMode(PinTaster, INPUT_PULLUP); lc.shutdown(0,false); // MAX7219 aufwecken (schläft zu Beginn) lc.setIntensity(0,8); // Helligkeit setzen (0 bis 15) lc.clearDisplay(0); // Display leeren Wire.begin(); RTC.begin(); // RTC-Objekt initialisieren //Serial.begin(115200); //Serial.println (__DATE__); //Serial.println (__TIME__); if (! RTC.isrunning()) { // RT-Clock läuft nicht, initialisieren mit Kompilierungsdatum / Zeit RTC.adjust(DateTime(__DATE__, __TIME__)); } } void segText(String strg) { // schreibt String bis 8 Zeichen auf Segmentanzeige, Punkte werden eingeschoben unsigned int siz=strg.length(); char c; int p; int i; p=7; i=0; while (p>=0) { // nächster Buchstabe ein Punkt, Komma, Doppelpunkt? if (i < siz-1) { c=strg[i+1]; if (c=='.' || c==',' || c=='.' || c==':' || c==';') { lc.setChar(0,p,strg[i],true); siz++; i+=2; p--; continue; } } if (i < siz) { lc.setChar(0,p,strg[i],false); } else { lc.setChar(0,p,' ',false); } i++; p--; } } long waitTaster(long maxWait) { // gibt zurück, wieviele msecs der Taster gedrückt gehalten war long msecs=0; while (digitalRead(PinTaster) == HIGH) { delay (1); msecs++; if (maxWait != 0 && msecs > maxWait) return 0; } // wielange gehalten ? msecs=0; while (digitalRead(PinTaster) == LOW) { delay (1); msecs++; } return msecs; } void incMode() { mode++; if (mode > 3) mode =1; } void loop() { DateTime now; char msg[16]; char c; while (1) { DateTime now = RTC.now(); if (mode == 1) { // Datum/Zeit 30.01.16.52 sprintf (msg, "%02d.%02d.%2d.%02d", now.day(), now.month(), now.hour(), now.minute()); c=now.minute()%10+48; } else if (mode == 2) { // Datum voll 30.01.2019 sprintf (msg, "%02d.%02d.%04d", now.day(), now.month(), now.year()); c=now.year()%10+48; } else if (mode == 3) { // Zeit voll 16.52.34 sprintf (msg, "%02d.%02d.%02d ", now.hour(), now.minute(), now.second()); c=' '; } segText (msg); if (waitTaster(500) > 0) { incMode(); continue; } lc.setChar(0,0,c,true); if (waitTaster(500) > 0) { incMode(); continue; } } //end while 1 } //end loop()