Eine Echtzeituhr mit dem i2c-Bus anprogrammieren

RTC_com

Viele Sensoren und Microntroller unterstützen den i2c-Bus. Dieser Bus hat den Vorteil, dass nur zwei Drähten zur Kommunikation aller Teilnehmer benötigt werden. Dadurch kann ein so genannter Bus-Master über 100 Microcontroller (Slaves) im Zaum halten. Das Protokoll ist so verbreitet, dass zur Programmierung inzwischen zahlreiche Bibliotheken zur Verfügung stehen.

rtcDer Raspberry Pi stellt den i2c-Bus an Pin 3 (SDA) und Pin 5 (SCL) zur Verfügung. Dort angeschlossene Sensoren und Microcontroller können mit den i2c-tools untersucht werden. Diese sind in den Paketen von Debian-wheezy bereits enthalten und müssen nur nachinstalliert werden.

Der i2c-Bus ist relativ einfach konzipiert: Es gibt einen Master, der den Bus-Takt gibt und die Kommunikation organisiert, und es gibt einen oder mehrere Slaves. Der  Bus-Takt wird über den  SCL-Pin gegeben. Die eigentliche Kommunikation findet dann über den SDA-Pin statt. Wenn mehr als ein Slave an den Drähten hängt, sind so genannte pull-up-Widerstände notwendig. Je nach Microcontroller sind das Widerstände mit Werten von 4,7 kOhm oder 10 kOhm. Die Slaves haben (meist) eine feste Adresse. Zum Beispiel ist die Echtzeituht DS1307 über die Adresse 0x68 erreichbar. Sollen mehrere identische Microcontroller angeschlossen werden, müssen entweder unterschiedliche Adressen eingestellt oder ein weiterer Bus aufgemacht werden. Das kann zum Beispiel ein zwischengeschalteter weiterer Microcontroller sein. Bei manchen ICs kann die Adresse auch eingestellt werden.

Erste Gehversuche

Um den i2c-Bus auf dem RasPi in Betrieb zu nehmen, müssen die Kernel-Module zunächst geladen werden. Die wurden in der Grundkonfiguration des Raspi jedoch auf eine Black-List gesetzt. So werden sie nicht automatisch geladen werden, was Ressourcen schont und weil sie nicht jeder braucht. Daher müssen die Module erst einmal aus der Sperrliste entfernt werden. Dazu öffnet man die Datei /etc/modules.d/raspi-blacklist.conf und kommentiert die beiden Einträge mit einem „#“ aus:

pi@raspberry:~$ sudo nano /etc/modules.d/raspi-blacklist.conf 

# blacklist spi and i2c by default (many users don't need them)

# blacklist spi-bcm2708
# blacklist i2c-bcm2708





[ 4 Zeilen gelesen ]

^G Hilfe   [ Zeile 1/5 (20%), Spalte 1/64 (1%), Zeichen 0/113 (0%) ]Cursor
^X Beenden   ^J Ausrichten^W Wo ist    ^V Seite vor ^U Ausschn. r^T Rechtschr.

Anschließend wird das Modul i2c-dev in die Datei /etc/modules eingetragen:

pi@raspberry:~$ sudo nano /etc/modules

# /etc/modules: kernel modules to load at boot time.
# This file contains the names of kernel modules that should be loaded
# at boot time, one per line. Lines beginning with "#" are ignored.
# Parameters can be specified after the module name.

snd-bcm2835
i2c-dev


[ 8 Zeilen gelesen ]

^G Hilfe ^O Speichern ^R Datei öffn^Y Seite zurü^K Ausschneid^C Cursor
^X Beenden ^J Ausrichten^W Wo ist ^V Seite vor ^U Ausschn. r^T Rechtschr.

Danach kann das Modul i2c-dev geladen werden:

sudo modprobe i2c-dev

IMG20141027230430_4Jetzt sollte der Bus als Geräte-Datei zur Verfügung stehen. Für erste Versuche eignet sich die Echtzeituhr DS1307. Die gibt es als Breakout-Board zusammen mit einem EEPROM-Speicherchip für unter 3 Euro. Linux bringt bereits Treiber von Haus aus mit und kann diese Uhr als Systemuhr einbinden. Der RasPi hat ja keine Systemuhr und bezieht seine Zeit erst bei einer Internetverbindung von einem Zeitserver. Da er auch keine Batterie hat, vergisst er die aktuelle Zeit nach dem Ausschalten. Eine Systemuhr dagegen wird durch eine Batterie gepuffert und läuft daher durch. Für die korrekte Zeit ist der RasPi dann nicht mehr auf eine Internetverbindung angewiesen. Der Anschluss erfolgt wie in der Abbildung unten.

RTC_SteckplatineWenn alles verdrahtet ist, kann der Bus auf angeschlossene Slaves untersucht werden. Dabei hilft das Programm i2cdetect. Mit dem Parameter -y verzichtet das Programm auf den interaktiven Modus und der Parameter „1“ verweist auf den Bus 1. Die Ausgabe sollte so aussehen:

pi@pi4:~$ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00: -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- UU -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- -- 
pi@pi4:~$

An der Adresse 0x50 und der Adresse 0x68 melden sich zwei Slaves. Dabei ist 0x50 die Adresse des EEPROM-ICs 24C32 und 0x68 die Adresse des RTC-ICs DS1307. Grundsätzlich scheint somit alles zu funktionieren. Um die Uhr als Systemuhr anzumelden, muss die Adresse dem System bekannt gegeben werden:

echo ds1307 0x68 > /sys/class/i2c-adapter/i2c-1/new_device

Anschließend kann die Uhr auf die Systemzeit des RasPi gesetzt werden:

pi@raspberry:~$ sudo hwclock -w

und die Uhrzeit ausgelesen werden:

pi@raspberry:~$ sudo hwclock -r
Di 28 Okt 2014 21:17:49 UTC  -0.799118 seconds
pi@raspberry:~$

Die Uhr über den i2c-Bus in C-Programmen nutzen

Der Test oben belegt den Speicherbereich auf dem i2c-Bus, was zu Konflikten mit eigenen Programmen führen kann. Das erkennt auch ein erneuter Aufruf von i2cdetect und liefert anstelle der Adresse ein „UU“. Um Zugriffskonflikte mit eigenen Programmen zu vermeiden, sollte der Treiber das Gerät daher wieder freigeben. Nach den obigen Test genügt ein Neustart.

Danach kann es mit der Programmierung losgehen. Der i2c-Bus ist Teil der GPIO-Pins und daher am BCM2835 des RasPi angeschlossen. Mit der Aktivierung des i2c-bcm2708-Modules steht der i2c-Bus auf dem Raspberry grundsätzlich zur Verfügung. Nun gibt es zwei Wege für die Programmierung. Der erste führt über die Linux-Gerätedatei.

Das Kernelmodul i2c-dev sorgt dafür, dass eine Gerätedatei /dev/i2c-1 angelegt wird. Diese Gerätedatei kann wie eine normale Datei geöffnet und lesend bzw. schreibend mit dem i2c-Gerät kommuniziert werden. Das ist dann die Standardvorgehensweise in der Linux-Programmierung. Allerdings muss damit ein Großteil der Grundlagenarbeit für den i2c-Bus selbst programmiert werden.

Ein anderer Weg führt über die Register des BCM2835. Das ist wieder ein gute Gelegenheit, die Hauptarbeit der bcm2835-Bibliothek zu überlassen. Falls nicht schon passiert sollte die Bibliothek heruntergeladen, kompiliert und installiert werden:

wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.37.tar.gz 
tar zxvf bcm2835-1.37.tar.gz cd bcm2835-1.37 
./configure 
make 
sudo make check 
sudo make install

Die Kommunikation über den i2c-Bus mit der bcm2835-Bibliothek ist sehr einfach. Im Falle der RTC sind es insgesamt 7 Schritte:

  1. Initialisierung des BCM2835 für den i2c-Bus mit bcm2835_i2c_begin();
  2. Einstellen der Adresse mit bcm2835_i2c_setSlaveAddress();
  3. Einstellen des Bus-Takts mit bcm2835_i2c_setClockDivider();
  4. Den Register-Pointer auf 0 setzen mit bcm2835_i2c_write(0x00, 1);
  5. Die Register der DS1307 auslesen mit bcm2835_i2c_read(char *, 7);
  6. Die i2c-Kommunikation beenden mit bcm2835_i2c_end();
  7. Und den BCM2835 auf die Standardwerte setzen mit bcm2835_close();

Um aus den gelesenen Daten etwas sinnvolles anzufangen, muss man den Aufbau und die Bedeutung des Registers des DS1307 kennen. Ein Blick ins Datenblatt zeigt die folgende Struktur:

Bildschirmfoto vom 2014-10-30 21:41:25An Registeradresse 0x00 des i2c-Slaves ist zum Beispiel also ein Byte zu finden, in dem die Anzahl der Sekunden enthalten ist. Allerdings ist diese Zahl nicht als Integer abgelegt sondern BCD-codiert. BCD steht für Binary Coded Digit und will einfach nur sagen, dass die Ziffern einer Dezimalzahl einzeln als Binärwerte abgelegt werden. Jede Ziffer wird dann in höchstens vier Bit (= 1 Nibble) geschrieben. Im ersten Nibble (BIT0 bis BIT3 steht also die Ziffer für die Zahlen von 0 bis 9. im oberen Nibble (BIT4 bis BIT7) die Ziffer für die erst Zehnerpotenz. Ein Beispiel: 0101 1001 steht für 59, da 0101 = 5 und 1001 = 9 ist. Für eine Konvertiertung eines BCD-codierten Bytes genügt ein Makro:

#define BCD2DEC(val) ( ((val) & 0x0f) + ((val) >> 4) * 10 )

Das Makro maskiert das obere Nibble weg, was lediglich das untere Nibble ergibt. Dann schiebt es das obere Nibble nach unten, so dass nur das obere Nibble mit 10 multipliziert wird. So hat man den 10-er Wert und addiert dazu den Wert des unteren Nibbles.

Aus dem Datenblatt geht auch hervor, dass in dem Byte mit den Ziffern einige Bits eine spezielle Bedeutung haben. Im Byte für die Anzahl der Sekunden hat das höchste Bit (BIT7) eine Sonderfunktion. Um die Ziffer korrekt zu berechnen, muss es ausgeblendet werden. Das übernimmt in der Zeile 30 die Maske 0x7f. Wenn das Byte 1001 0101 mit der Maske 0x7f = 0111 1111 per & (AND) verknüpft wird bleibt 0001 0101 übrig. Das höchste Bit wurde „ausmaskiert“. Laut Datenblatt steht an dieser Stelle das clock halt (CH) Bit. Wenn dieses Bit gesetzt ist, wird der Oszillator angehalten. Die Uhr steht. Das Ausblenden ist eigentlich nicht nötig, wenn die Uhr läuft und der Sekunden-Werte gelesen werden soll. Dann ist dieses Bit eh 0 und hat keinen Einfluss auf die Berechnung des BCD-Wertes.

Das ganze Programm zur Ausgabe der Uhrzeit ist daher recht kurz:

#include <bcm2835.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define DS1307_I2C_ADDRESS 0x68

#define BCD2DEC(val) ( ((val) & 0x0f) + ((val) >> 4) * 10 )

uint32_t len = 7;

char cmd[] = { 0x00 };
char rs[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
int i;

int main(int argc, char **argv) {

  if (!bcm2835_init()) return 1;

  bcm2835_i2c_begin();
  bcm2835_i2c_setSlaveAddress(DS1307_I2C_ADDRESS);

  bcm2835_i2c_setClockDivider(BCM2835_I2C_CLOCK_DIVIDER_2500);
  bcm2835_i2c_write(cmd, 1);
  bcm2835_i2c_read(rs, len);
  printf("%02i:%02i:%02i Tag %i %02i.%02i.20%02i\n",
    BCD2DEC(rs[2] & 0x3f),
    BCD2DEC(rs[1] & 0x7f),
    BCD2DEC(rs[0] & 0x7f),
    BCD2DEC(rs[3] & 0x07),
    BCD2DEC(rs[4] & 0x3f),
    BCD2DEC(rs[5] & 0x1f),
    BCD2DEC(rs[6]));
  bcm2835_i2c_end();
  bcm2835_close();

  return 0;
}

Um die Uhrzeit einzustellen, werden die Register-Werte entsprechend geschrieben. Da dabei jedoch die Bits mit Sonderfunktion in der Regel erhalten bleiben sollen, ist es am einfachsten in zwei Schritten vorzugehen. Zunächst werden alle Register ausgelesen und in einem Array gespeichert. Dann wird das Byte mit den Sekunden-Werten im höchsten Bit auf 1 (CH) gesetzt, zurückgeschrieben und damit die Uhr angehalten. Dann werden die Zeitwerte so eingestellt, dass die Steuer-Bits nicht verändert werden. Dann wird das gesamte Array zurückgeschrieben. Nun wird das Byte mit dem Sekunden-Wert mit dem höchsten Bit auf 0 in das Register 0x00 geschrieben und damit die Uhr wieder gestartet. Das alles ist Fleißarbeit und ich kann das gerne hier posten, falls es jemanden interessiert. Als Hilfestellung ist vielleicht das Makro zur Umrechnung von Dezimalwerten in die BCD-Darstellung interessant:

#define DEC2BCD(val) ( ((val) & 0x0f) + ((val) / 10) << 4) )

, , , , , ,

  1. #1 von Joschi am 13. März 2015 - 21:21

    Hat die Batteriebufferung von Haus aus funktioniert beim DS1307? Bei mir vergisst die Uhr die Zeit wenn ich sie vom Strom nehme, trotz Batterie. Ich hab in manchen Foren gelesen, dass die Batterie ein Akku sein muss, warum auch immer.

    • #2 von mnasarek am 16. März 2015 - 14:12

      Wie der DS1307 stromtechnisch gehalten wird, hängt von der Schaltung ab. Ich habe das Tiny RTC Modul mit einem Batterieladekreis, da würde ich auf jeden Fall einen Akku (LIR2032) nehmen, da sonst Strom auf eine Batterie gegeben wird. Kann daher sein, dass die Batterie nach dem ersten Anschluss einen Schuss erhalten hat.

  1. Die serielle Schnittstelle (UART) in Skripten programmieren | Raspberry Pi Lab
  2. Analoge Messwerte mit einem ADC über SPI auslesen | Raspberry Pi Lab

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden /  Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden /  Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s

%d Bloggern gefällt das: