Arduino: Real Time Clock DS1307

Prof. Jürgen Plate

Real Time Clock DS1307

Allgemeines

Der Arduino besitzt keine integrierte Real Time Clock (RTC) wie ein PC. Damit er über die aktuelle Uhrzeit verfügt, benötigt er eine lokale Uhr. Dieses Projekt versorgt den Rechner mit einer autonomen Uhr. Nach jedem Systemstart kann die Uhrzeit aus der RTC ausgelesen und als Systemzeit verwendet werden. Die RTC befindet sich auf einer eigenen Platine und wird per I2C integriert.

Real Time Clock DS1307 (RTC)

Dieser Baustein ist eine Echtzeituhr, die man über die I2C-Schnittstelle ansprechen kann und die batteriegepuffert weiterläuft, wenn der RasPi abgeschaltet ist. Die Genauigkeit ist nicht so überragend, Abweichungen von 1-2 Sekunden in 24 Stunden sind möglich (abhängig von Temperatur und Quarz). Bei großen Temperaturschwankungen sind die Abweichungen noch um einiges größer. An die RTC wird vom Arduino eine möglichst exakte Zeit übertragen. Diese Uhrzeit wird nun von der RTC mit Hilfe des des internen Quarzoszillators aktualisiert. Bei Bedarf wird die Zeit abgefragt. Die Batterie soll nach Datenblatt bis zu 10 Jahre lang halten. In den Registern der RTC stehen die Uhrzeit, der Wochentag und das Datum. Die einzelnen BCD-Werte können über die folgende Adressen ausgelesen werden.

AdresseInhaltBeschreibung
0x00Sekunden und CH-BitBits 0 - 6: Sekunden (0..59)
Bit 8: Uhr ein- und ausschalten
0x01MinutenBits 0 - 6: Minuten (0..59)
0x02StundenBits 0 - 6: Stunden; 12/24h umschaltbar
0x03WochentagBits 0 - 2: Wochentag (1..7, So=1)
0x04TagBits 0 - 5: Tag (0..31)
0x05MonatBits 0 - 4: Monat (0..12)
0x06JahrBits 0 - 7: Jahr (0..99)
0x07EinstellungenSQW-Output einstellen
0x08 – 0x3F RAM56 x 8 Bit

Bem Sekundenregister ist darauf zu achten, dass das MSB (CH Bit) zum Ein- und Ausschalten der Uhr dient. Ist CH = 1, Stoppt der DS1307. Dieses Bit sollte beim Schreiben einer neuen Uhrzeit stets auf 1 gesetzt sein. Nachdem alle Werte gespeichert wurden, wird es zusammen mit der Sekundenangabe wieder auf 0 gesetzen und somit die RTC wieder eingeschaltet. Beim Auslesen der Sekunde muss das MSB natürlich ignoriert werden. Die Stunden werden im Register 2 gespeichert. Ist das Bit 6 des Registers gesetzt, werden die Stunden im 12h-Format angegeben, das Bit 5 gibt dabei AM(low)/PM(high) an. Ist das Bit dagegen gelöscht, werden die Stunden im 24h-Format ausgegegeben. Register 3 enthält den aktuellen Wochentag,wobei der Wert 1 dem Sonntag entspricht, 2 dem Montag usw. Register 7 dient zur Konfiguration des Rechtecksignal-Generators. Die restlichen Register können als batteriegepuffertes RAM verwendet und frei beschrieben werden.

Es gibt diverse Break-Out-Boards mit dem DS1307 im Handel, wobei die meisten die I2C-Anschlüsse direkt zum Arduino führen. Zum Testen wurde ein unter dem Namen "Tiny RTC I2C Module" angebotenes Feriges Board für den Arduino verwendet.

Manche der angegebenen Boards versuchen, die Pufferbatterie zu laden, wenn die Stromversorgung vom Rechner aus erfolgt. Bei solchen Modulen darf keine 3,6-V-Lithium-Knopfzelle (z. B. CR2032) verwendet werden, sondern es muss eine entsprechende Akku-Knopfzelle (z. B. LIR2032) eingesetzt werden. Siehe Schaltung im Anhang.

Ansonsten ist das Modul recht interessant, denn es befindet sich noch ein I2C-EEPROM auf dem Board und man kann optional noch einen DS18B20-Onewire-Temperatursensor auflöten (Schaltplan siehe bei den Links unten). Die RTC ist üblicherweise auf Adresse 0x68 sichtbar. Jetzt kann man schon per Programm auf die Uhr zugreifen, wie das folgende Beispiel in C zeigt. Es genügen dabei die normalen Operationen, die der I2C-Treiber bereitstellt. Für den Zugriff auf die Uhr wurden zwei Funktionen definiert:

Um die Funktionen herum wurde ein Testprogramm verfasst, das über das serielle Terminal mit dem Benutzer kommuniziert. Es sind folgende Kommandos möglich: Zu beachten ist, dass die Uhr die Zahlen im BCD-Format speichert, weshalb noch die Konvertierungsfunktionen dec2bcd() und bcd2dec() benötigt werden.
/* DS1307 I2C Zeit seriell lesen und schreiben
 * Befehle:
 * -t --> Ausgabe Uhrzeit, z. b. 12.34.01
 * -d --> Ausgabe Datum, z. B. 06.12.2017
 * -s yymmddhhmmss --> Setzen Datum und Uhrzeit
 */
 
#include <Wire.h>
 
/* Adresse des Uhrenchips DS1307 */
#define DS1307_ADRESSE 0x68

/* globale Variablen */
int sekunde, minute, stunde, tag, wota, monat, jahr;
 
void setup()
  /* I2C und serielle Schnittstelle initialisieren */
  {
  Wire.begin();
  Serial.begin(9600);
  Serial.println("Befehle:\n -t: Uhrzeit\n -d: Datum");
  Serial.println("-s yymmddhhmmssw: Datum/Zeit/Wochentag(So=1) setzen");
  }
 
void loop()
  {
  char c;             /* eingabezeichen */
  String input = "";  /* Eingabestring */

  ds1307read(DS1307_ADRESSE);
  while (Serial.available()) 
    {
    /* input lesen */
    delay(3);
    input += (char)Serial.read();
    }

  /* Eingabe verarbeiten */
  if (input.length() > 0) 
    {
    if (input.substring(0,2) == "-s")
      /* Datum/Uhrzeit setzen */
      {
      Serial.println("Setze Datum und Uhrzeit.\nAktuell: ");
      printdate();
      printtime();
      /* Eingabestring zerlegen */
      jahr = input.substring(3,5).toInt();
      monat = input.substring(5,7).toInt();
      tag = input.substring(7,9).toInt();
      stunde = input.substring(9,11).toInt();
      minute = input.substring(11,13).toInt();
      sekunde = input.substring(13,15).toInt();
      wota = input.substring(15,16).toInt();
      /* DS1307 stellen */
      ds1307write(DS1307_ADRESSE);
      delay(100);
      /* Kontrollausgabe */
      Serial.println("Neue Werte: ");
      printdate();
      printtime();
      }
    else if (input.substring(0,2)=="-t")
      {
      /* Uhrzeit ausgeben */
      Serial.print("Zeit: ");
      printtime();
      }
    else if (input.substring(0,2)=="-d")
      {
      /* Datum ausgeben */
      Serial.print("Datum: ");
      printdate();
      }
    else
      {
      /* Fehlermeldung */
      Serial.print("Oops: ");
      Serial.println(input);
      }
    input = "";
    }
  delay(1000);
  }

void printtime()
  {
  /* Uhrzeit formatiert ausgeben */
  char buf[10];
  sprintf(buf,"%02d:%02d:%02d\n",stunde, minute, sekunde);
  Serial.print(buf);
  }
 
void printdate()
  {
  /* Datum formatiert ausgeben */
  char wday[8][3] = {"00","So","Mo","Di","Mi","Do","Fr","Sa"};
  char buf[30];
  sprintf(buf,"%2s, %02d.%02d.20%02d\n",wday[wota], tag, monat, jahr);
  Serial.print(buf);
  }

void ds1307read(byte address)
  {
  /* Lesen von Uhrzeit und Datum aus dem DS1307 */
  Wire.beginTransmission(address);
  Wire.write(0x00);
  Wire.endTransmission();
  Wire.requestFrom(address, 7);
  sekunde = bcd2dec(Wire.read());
  minute = bcd2dec(Wire.read());
  stunde = bcd2dec(Wire.read() & 0b111111);
  wota = bcd2dec(Wire.read());
  tag = bcd2dec(Wire.read());
  monat = bcd2dec(Wire.read());
  jahr = bcd2dec(Wire.read());
  }
  
void ds1307write(byte address)
  {
  /* Schreiben von Uhrzeit und Datum in den DS1307 */
  Wire.beginTransmission(address);
  Wire.write(0x00);
  Wire.write(dec2bcd(sekunde));
  Wire.write(dec2bcd(minute));
  Wire.write(dec2bcd(stunde));
  Wire.write(dec2bcd(wota));
  Wire.write(dec2bcd(tag));
  Wire.write(dec2bcd(monat));
  Wire.write(dec2bcd(jahr));
  Wire.write(0x00);
  Wire.endTransmission();
  }

/* Konvertieren BCD <-> Dezimal */
byte dec2bcd(byte val) 
  { return ((val/10*16) + (val%10)); }
  
byte bcd2dec(byte val)
  { return ((val/16*10) + (val%16)); }

Das EEPROM 24C32

Wie man auf dem Bild des käuflichen Boards oben erkennen kann, befinden sich dort zwei ICs. Das ist bei vielen der angebotenen Uhrenboard ein nettes Zusatzfeature, nämlich ein EEPROM 24C32, das in der Lage ist, dauerhaft 4096 Bytes zu speichern - es benötigt auch keine Pufferbatterie wie das RAM im RTC-Baustein. Das EEPROM (Electrically Erasable Programmable Read Only Memory) wird ebenfalls über den I2C-Bus angesprochen. Es liegt per Default auf der Deviceadresse 0x50, weshalb i2cdetect dann auch neben der RTC-Adresse 0x68 diese Adresse anzeigt. Zum Schreiben und Lesen genügen wenige Programmzeilen. Im folgenden Listing sind drei Funktionen definiert:

Als Test wird im Hauptprogramm ein String ins EEPROM geschrieben und anschließend wieder gelesen.
#include <Wire.h>

#define ADDR24C32 0x50

String hello = "Hello World, nice to meet you!\nHow are you?";

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

void write24c32(word address, byte data)
  /* Schreiben eines Bytes ins EEPROM */
  {
  Wire.beginTransmission(ADDR24C32);
  /* Adresse aufsplitten, auf 12 Bit begrenzen */
  Wire.write((address >> 8) & 0x0f); 
  Wire.write(address & 0xff);
  /* Daten schreiben */
  Wire.write(data);
  delay(10);
  Wire.endTransmission();
  delay(10);
  }

byte read24c32(word address)
  /* Lesen eines Bytes vom EEPROM */
  {
  Wire.beginTransmission(ADDR24C32);
  /* Adresse aufsplitten, auf 12 Bit begrenzen */
  Wire.write((address >> 8) & 0x0f);
  Wire.write(address & 0xff);
  Wire.endTransmission();
  delay(10);
  /* Daten lesen */
  Wire.requestFrom(ADDR24C32, 1);
  delay(10);
  return (byte)Wire.read();
  delay(10);
  }
  
void loop()
  {
  char data;
  int i;

  /* Strimg im EEPROM ab Adresse 0 speichern */
  Serial.println("Writing");
  for (i = 0; i < hello.length(); i++)
    {
    write24c32(i,hello[i]);
    Serial.write(hello[i]);
    }
  /* Nullbyte als Stringende */
  write24c32(hello.length(),0);
  Serial.println();

  /* String aus dem EEPROM ab Adresse 0 lesen */
  Serial.println("Reading");
  i = 0;
  do
    {
    data = read24c32(i);
    if (data != 0) Serial.write(data);
    i++;
    }
  while (data != 0);
  Serial.println();

  for(;;); // dynamischer Stop
  }

Links


Copyright © Hochschule München, FK 04, Prof. Jürgen Plate und die Autoren
Letzte Aktualisierung: