I²C (Inter-IC-Communication)

Der I²C-Bus ist ein bidirektionaler, serieller Zweidrahtbus, über den viele unterschiedliche Peripheriekomponenten an einen Mikroprozessor angeschlossen werden können. I²C ist die Abkürzung für Inter IC Communication und wurde in den frühen 80er Jahren von der Firma Philips entwickelt und zum Patent angemeldet. Es gibt mittlerweile über 50 Lizenznehmer und mehr als 1000 I²C-kompatible Komponenten am Markt. Dieses Tutorial zeigt, wie die Kommunikation über den I²C-Bus in Java programmiert wird. Dazu soll als Beispielanwendung ein handelsüblicher I²C-kompatibler Temperatursensor vom Typ LM75 zur Temperaturmessung eingesetzt werden.

Voraussetzungen: Damit Sie dieses Tutorial nachvollziehen können, benötigen Sie die JControl/IDE sowie ein JControl-basiertes Gerät mit I²C-Schnittstelle. Außerdem benötigen Sie einen Temperatursensor mit I²C-Bus-Anschluß vom Typ LM75 http://www.national.com/pf/LM/LM75.html (Hersteller: National Semiconductor).

Bild 1: JControl in einer I²C-Anwendung

Der I²C-Bus ist mittlerweile zum de-facto Standard für die einfache Kommunikation zwischen integrierten Schaltkreisen geworden. Er wird bei den meisten JControl-kompatiblen Produkten durch eine enstprechende Schnittstelle unterstützt (Anschlüsse I2C_SDA und I2C_SCL). So lassen sich externe Schaltkreise direkt über I²C an ein JControl-Gerät ankoppeln. Bild 1 zeigt allgemein, wie Komponenten mittels I²C-Bus miteinander verbunden werden.

I²C ist ein Bussystem. Das bedeutet, daß mehrere Bus-Teilnehmer an ein- und dieselben Leitungen angeschlossen und nur über unterschiedliche Adressen angesprochen werden. JControl-Geräte arbeiten dabei als "Steuerzentrale" (Bus-Master) und die einzelnen Peripheriekomponenten als gewöhnliche Busteilnehmer (sog. Bus-Slaves). Die individuelle Adresse eines Bus-Slaves (Slave-Adresse) ist bei vielen Peripheriekomponenten werkseitig fest vorgegeben. Über die Beschaltung spezieller Adress-Pins kann die Slave-Adresse in den meisten Fällen aber auch verändert werden, so daß sich mehrere gleichartige I²C-Bus-Komponenten an einen Bus-Master anschließen lassen.

Falls Sie mehr über die Funktionsweise und die umfangreichen Anwendungsmöglichkeiten des I²C-Busses erfahren wollen, so können Sie viele Informationen auf den Web-Seiten von Philips Semiconductors http://www.semiconductors.philips.com/markets/mms/protocols/i2c/ finden.

Ein Abkömmling des I²C-Bus ist der SMBus. Dieser wurde im Jahre 1995 von einigen Unternehmen um den Halbleiter-Hersteller Intel spezifiziert. Das Haupt-Einsatzgebiet für SMBus-fähige Komponenten findet sich bei der PC-Hardware (Motherboards, Notebooks, Grafikkarten etc.); wo über den SMBus nicht nur die CPU-Temperatur, sondern auch Lüfterdrehzahlen, Betriebsspannungen usw. übertragen werden. In eine der nächsten PCI-Spezifikationen soll der SMBus sogar als "Sub-Bus" mit aufgenommen werden. Die Unterschiede zwischen I²C und SMBus betreffen vor allem die Spannungen auf den Bus-Leitungen sowie einige Timing-Parameter. JControl-Geräte sind so ausgelegt, daß sowohl I²C-Bus als auch SMBus-Komponenten angeschlossen werden können. Nähere Informationen zum Thema SMBus finden Sie auf der Homepage des SMBus-Konsortiums http://www.smbus.org.



I²C Komponenten

Anfänglich wurden vor allem kleine Speicherbausteine (EEPROMs) mit I²C-Bus-Schnittstelle eingesetzt. Mittlerweile finden sich I²C-Bus-Bausteine in fast jeder modernen Schaltung und erfüllen dabei sehr vielfältige Funktionen, angefangen von einfachen Analog-Digital-Wandlern, I/O-Port-Erweiterungen, Echtzeituhren (RTCs), Schrittmotor-Steuerungen, Drehzahlmesser für Lüfter bis hin zu Ultraschall-Abstandssensoren.

Temperatursensoren mit I²C-Anschluß sind gleich von mehreren Halbleiter-Herstellern erhältlich. Tabelle 1 listet einige vergleichbare Typen auf sowie deren Parameter (einstellbare Slave-Adressen, Auflösung in °C). Wie eingangs bereits beschrieben wird die angegebene Slave-Adresse dazu verwendet, einen Busteilnehmer eindeutig zu identifizieren. Bei allen hier aufgelisteten I²C-Temperatursensoren kann die Adresse über speziell dafür vorgesehene Pins eingestellt werden.

BezeichnungHerstellerSlave-
Adresse(n)
Auflösung
(in Bits)
1 LSB-Einheit
(in °C)
AD7416Analog Devices0x90, 0x92, 0x94, 0x96, 0x98, 0x9A, 0x9C, 0x9E101/4
LM75National Semiconductor0x90, 0x92, 0x94, 0x96, 0x98, 0x9A, 0x9C, 0x9E91/2
LM80National Semiconductor0x50, 0x52, 0x54, 0x56, 0x58, 0x5A, 0x5C, 0x5E9, 121/2, 1/16
MAX6633Maxim0x80, 0x82, 0x84, 0x86, 0x88, 0x8A, 0x8C, 0x8E, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9A, 0x9C, 0x9E121/16
TMP75Texas Instruments0x90, 0x92, 0x94, 0x96, 0x98, 0x9A, 0x9C, 0x9E9, 10, 11, 121/2, 1/4, 1/8, 1/16
Tabelle 1: Adressen und Auflösungen verschiedener getesteter I²C-Temperatursensoren

Hinweis: Bitte beachten Sie, daß die oben aufgeführten Temperatursensoren zwar alle sehr ähnlich, jedoch nicht zwangsläufig kompatibel sind. Das bedeutet, daß auch die Beispielprogramme ggf. modifiziert werden müssen. Die Unterschiede betreffen nicht nur die Slave-Adresse, sondern auch die Umrechnung der Temperatur in den ausgelesenen Digitalwert. Nähere Informationen hierzu finden Sie in den (verlinkten) Datenblättern.


Der Versuchsaufbau

Um die Funktionalität des I²C-Busses zu demonstrieren, wird in diesem Tutorial ein digitaler Temperatursensor vom Typ LM75 an das JControl/SmartDisplay angeschlossen. Dazu müssen lediglich die I²C-Signale SDA und SCL verbunden werden sowie VCC und GND für die Stromversorgung des Temperatursensors. Die genaue Pinbelegung des JControl/SmartDisplays kann dem Datenblatt http://www.domologic.com/download/pdf/jcontrol_smartdisplay.pdf entnommen werden.

Bild 2: Genereller Versuchsaufbau

Hinweis: Die Beschaltung der Pins 5, 6 und 7 des LM75 beeinflussen dessen I²C-Bus Slave-Adresse. Nähere Informationen hierzu finden Sie im Datenblatt des LM75 http://www.national.com/pf/LM/LM75.html In dem hier behandelten Beispiel werden diese Pins auf GND gelegt (Adresse 0x90). Außerdem wird bei größerer Entfernung zwischen Spannungsversorgung und I²C-Komponente ein Stützkondensator (i.d.R. 100nF) in der Nähe dieser Komponente empfohlen.

Achtung: Beim Anschluss externer Peripheriekomponenten an den I²C-Bus ist zu beachten, dass die Signale SDA und SCL über jeweils einen Pull-Up-Widerstand (i.d.R. 27k Ohm) an das Stromversorgungs-Potential (VCC) angebunden werden müssen. Auf diese Weise wird sichergestellt, daß ein definierter Spannungspegel auf den Leitungen liegt selbst wenn keiner der Bus-Teilnehmer die Leitungen "treibt" (siehe Bild 2).
Die Evaluationboards (z.B. das JControl/SmartDisplay Evaluationboard) verfügen bereits über diese Pull-Up-Widerstände, so daß die Peripherie (hier: der LM75) direkt an die herausgeführten I²C-Signale angeschlossen werden kann. Nähere Informationen zu den Evalutionboards finden sie in den Datenblättern http://www.domologic.com/support/ch1/index_de.html.


Die Klasse I2C

Für den Zugriff auf den I²C-Bus ist bei JControl-Geräten die Klasse jcontrol.comm.I2C vorgesehen, die read- und write-Methoden zum Lesen und Schreiben einzelner Zeichen oder ganzer byte-Arrays implementiert. Bei der Instantiierung der Klasse wird über den Konstrukter-Parameter die Slave-Adresse des angeschlossenen I2C-Bus Bausteins vorgegeben. Eine genaue Beschreibung der I2C-Methoden kann der API Dokumentation http://www.jcontrol.org/current/docs/api/jcontrol/comm/I2C.html entnommen werden.

Die folgende Tabelle gibt einen Überblick über die Methoden der Klasse jcontrol.comm.I2C.

MethodeBeschreibung
I2C(int address)Wird benötigt, um eine Instanz der Klasse I2C zu erzeugen. Diese steht dann über den Parameter address in einer direkten Beziehung zu der angeschlossenen Hardware.
read(byte[] data, int index, int length)Liest (Zustands-)Daten der I²C-Peripherie und schreibt sie in das übergebene Byte-Array data. Das Array wird ab Position index mit der Anzahl length an Bytes gefüllt.
write(byte[] data)Schreibt Zeichen(ketten) in oder übermittelt Kommandos an die I²C-Peripherie.
Tabelle 2: Allgemeine Methoden für die Kommunikation mit I²C-Bus Komponenten

Um das Beispiel so einfach wie möglich zu gestalten, wird im nächsten Abschnitt die Klasse LM75 implementiert. Diese erweitert die Klasse I2C und übernimmt die Kommunikation mit der Hardware.


Die eigene Klasse LM75

Für den komfortablen Zugriff auf den Temperatursensor vom Typ LM75 wird als nächstes eine eigene Klasse mit dem Namen LM75 implementiert. Diese stellt einer Anwendung, die sie verwendet, alle Funktionen des realen Bausteins in Form von Java-Methoden zur Verfügung. Konkret bedeutet das, daß die gemessene Temperatur mit der Methode getTemp() ausgelesen werden kann. Die Klasse LM75 übernimmt damit auf sehr elegante Art und Weise die Funktion eines speziellen "Gerätetreibers" für den Baustein LM75.

Der nachfolgende Quelltext zeigt die Implementierung der Klasse LM75. Sie besteht aus zwei Teilen: Aus dem Konstruktor, der eine Instanz der Klasse anlegt und aus der Methode getTemp(), die den ausgelesenen Temperaturwert zurückliefert.

1    import java.io.IOException;
2    import jcontrol.comm.I2C;
3    
4    /**
5     * <p>LM75 is a class to access the I2C-compatible
6     * temperature sensor LM75 from National Semiconductor.</p>
7     *
8     * <p>(C) DOMOLOGIC Home Automation GmbH 2003-2005</p>
9     */
10    public class LM75 extends I2C{
11    
12      /**
13       *  Constructor of this class.
14       *
15       *  @param address slave address of the device
16       */
17        public LM75(int address){
18          super(address);
19        }
20    
21        /**
22         *  Returns an Integer value representing the
23         *  current die temperatur of the LM75.
24         *
25         *  @returns An integer representing the current
26         *           temperature in "Centigrades" (10*degree Celsius).
27         */
28        public int getTemp() throws IOException {
29            // allocate buffer for temperature
30            byte[] buf = new byte[2];
31            // read content of 16-bit register #0x00
32            read(new byte[] {0x00}, buf, 0, 2);
33            // return integer value
34            return (buf[0]*10) + (((buf[1] & 0x80) >> 7)*5);
35        }
36    }
Listing 1: LM75.java

Wie in Listing 1 zu sehen ist, kapselt die Methode getTemp() die Kommunikation mit dem Temperaturfühler über den I²C-Bus. Damit muß der Programmierer, der fortan die Klasse LM75 verwenden soll, lediglich die Slave-Adresse des Sensors wissen und kann schon den aktuellen Temperaturwert über die Methode getTemp() auslesen.

Zeile 32 zeigt den eigentlichen Funktionsaufruf, der den Temperaturwert aus dem Sensor ausliest. Dabei ist zu sehen, dass das Kommando-Byte 0x00 (für Details: s.u.) gesendet wird, um dem LM75 mitzuteilen, dass er nun seinen Temperaturwert über den I²C-Bus senden soll. Der Wert wird dann in das Byte-Array buf (Kurzform von "Buffer") eingetragen.

Der Temperaturwert besteht aus insgesamt 9 Bits und muß daher in zwei einzelnen Bytes ausgelesen werden. Im ersten Byte steht der ganzzahlige Anteil der aktuellen Temperatur (in °C). Dabei fungiert das oberste Bit 7 des ersten Bytes als Vorzeichen-Bit: Eine logische "1" bedeutet, daß es sich um einen negativen Temperaturwert handelt. Für die Umrechnungsformel, die den Temperaturwert zurückgibt, können wir das jedoch vernachlässigen, da das bei JControl genauso ist. Im zweiten Byte steht nun der (restliche) Bruchteil eines Grades. Hiervon wird beim LM75 jedoch nur das oberste Bit verwendet und mit 1/256°C auflöst. Das heißt: Ist das zweite Byte 0x00, so beträgt der Rest 0°C. Ist es 0x80, so beträgt der Rest 0,5°C. Hier noch einmal die Formel, die die beiden Bytes in einen Temperaturwert umwandeln:

    return (buf[0]*10) + (((buf[1] & 0x80) >> 7)*5);

Der Ganzzahlige Anteil wird hierbei mit 10 multipliziert, da die kleinen JControl-Varianten keine Fließkomma-Zahlen unterstützen. Auch der Bruchteil des Grades wird dabei gleich mit 5 multipliziert. Die Methode getTemp() liefert den Meßwert also in "Zentigraden": Bei einer Temperatur von 20,5°C wird ein Wert von 205 zurückgegeben.

Wenn die Anschlüsse des LM75 beschaltet wurden so wie es im Abschnitt "Der Versuchsaufbau" gezeigt ist, gilt für den LM75 die Sensor-Adresse 144 (Hexadezimal: 0x90). Diese Adresse wird daher im folgenden Abschnitt Ein kleines Testprogramm auch für die Ansteuerung des LM75-Temperatursensors verwendet.

Details zum Kommando-Byte: Das Kommando-Byte teilt dem Sensor mit, auf welches interne Register zugegriffen werden soll. Da der Temperaturwert im LM75 in Register 0 steht, ergibt sich das Kommando-Byte auch zu 0, bzw. zu 0x00 in hexadezimaler Darstellung. Alle hier aufgeführten Informationen bezüglich des LM75 sowie weitere Details sind in dessen Datenblatt http://www.national.com/pf/LM/LM75.html aufgeführt.


Ein kleines Testprogramm

Um die Klasse LM75 zu benutzen, wird nun ein kleines Testprogramm gezeigt, das die Temperaturdaten über diese Klasse ausliest und auf dem Display ausgibt. Das Programm ist sehr einfach und läßt sich im Groben in drei Teilen beschreiben:

1    import java.io.IOException;
2    import jcontrol.comm.DisplayConsole;
3    import jcontrol.lang.ThreadExt;
4    
5    /**
6     * <p>LM75Test shows how to read the LM75 temperature
7     * detector class of the jcontrol.bus.i2c library.</p>
8     *
9     * <p>(C) DOMOLOGIC Home Automation GmbH 2003-2005</p>
10     */
11    public class LM75Test {
12    
13      /** DisplayConsole */
14      DisplayConsole console;
15      int deviceAddress = 0x90;
16    
17      /**
18       * Application Constructor.
19       */
20      public LM75Test() {
21    
22        // init DisplayConsole
23        console = new DisplayConsole();
24    
25        // display startup message
26        console.println("LM75 Test Program.");
27        console.println();
28     
29        LM75 lm75 = new LM75(deviceAddress);
30    
31        for (;;) {
32          int temp = 0;
33          for (;;) {
34            try{
35              temp = lm75.getTemp();//get temperature value
36            }catch(IOException e){
37              console.println("Error: Sensor not responding!");
38            }
39            console.print("Read: ".concat(String.valueOf(temp)));
40            //temperature resolution is 1/10 centigrade
41            int whole = temp/10;
42            int parts = temp%10;
43            console.println(" = ".concat(Integer.toString(whole))
44                                 .concat(".")
45                                 .concat(Integer.toString(parts))
46                                 .concat("\u00b0C"));     
47            try{
48              ThreadExt.sleep(500);//do nothing for 500 msecs
49            }catch(InterruptedException e) {}
50          }
51        }
52      }
53    
54    
55      /**
56       * Main method. Program execution starts here.
57       */
58      public static void main(String[] args) {
59        new LM75Test();// start measuring
60      }
61    }
Listing 2: LM75Test.java

Anmerkung: Die Zeichenkette "\u00b0C" in Zeile 46 steht für "°C". Bei der Sequenz "\u00b0" handelt es sich um die hexadezimale UTF-8-Schreibweise des Grad-Zeichens. Wir verwenden diese, da manche Editoren das (Sonder-)Zeichen "°" beim Abspeichern modifizieren und es dann auf dem JControl-Gerät nicht mehr korrekt dargestellt wird.

Bild 3 zeigt obiges Programm während es im Simulator der JControl/IDE ausgeführt wird. Der Temperaturwert liegt hier natürlich bei 0, da in der Simulation die Verbindung zu einem externen Temperatursensor LM75 nicht hergestellt werden kann. Aber wenn alles richtig funktioniert, dann stünde hier in der Realität der gemessene Temperaturwert.

Bild 3: Screenshot des LM75Test-Programms (JControl/Simulator)



© 2000-2006 DOMOLOGIC Home Automation GmbH. All Rights Reserved.