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:
- batteriegepufferter Speicher mit 56 Bytes für Jahr, Monat, Tag, Minute, Sekunde, Wochentag
- Schalttage bis 2100
- I2C-Interface (auch two-wire genannt)
- automatische Erkennung des Spannungsabfalls
- nur 500 nA im Batteir Backup Modus (eine CR2032 mit 220 mAh dürfte den DS1037 also rechnerisch 50 Jahre versorgen)
- Betrieb im Temperaturbereich von -40 ... +85 °C
- programmierbarer Rechteck-Generator
- AM/PM - Bit / 12-Stunden-Modus
und verfügt über folgende Pins:
- 1 - X1 --> Anschluss 1 für Quarz mit 32,768 kHz
- 2 - X2 --> Anschluss 2 für Quarz mit 32,768 kHz
- 3 - VBAT --> +3V Batterie (min/max: 2.0 ... 3.5V)
- 4 - GND
- 5 - SDA (Serial Data) für I2C-Kommunikation
- 6 - SCL (Serial Clock) für I2C-Kommunikation
- 7 - SQW (Square Ware) Ausgang Rechteck-Kurve mit 1Hz, 4kHz, 8kHz, 32kHz
- 8 - VCC --> +5V (min/max: 4.5 ... 5.5V)
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:
- RTC_DS1307 RTC instanziiert ein RTC-Objekt. RTC.begin() initialisiert es
- mit RTC.isrunning() kann man abfragen, ob die RTC-Uhr läuft, also auch, ob sie eine Zeit enthält
- mit RTC.adjust(DateTime(__DATE__, __TIME__) kann man dann eine Zeit in der RTC speichern (hier der Zeitpunkt der Kompilierung des Sketches)
- mit DateTime now = RTC.now() lädt man die Zeit aus der RTC und speichert sie in einem DateTime-Objekt, das dann alle Daten enthält
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:
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()