Speicherverwaltung

II. Serielle I²C-EEPROMs

In diesem Kapitel wird der Zugriff auf I²C-EEPROMs behandelt. Diese Speicherart zeichnet sich dadurch aus, dass sie einfach zu verdrahten (4 Leitungen inklusive Spannungsversorgung) ist und relativ wenig Platz verbraucht (i.d.R. ICs mit lediglich 8 Pins z.B. in TSSOP-Gehäusen mit einer Grundfläche von weniger als 33mm²). Übliche Speichergrößen dieser ICs liegen zwischen 2 und 256kBit.

Voraussetzungen: Für dieses Tutorial wird eine Installation der JControl/IDE sowie ein JControl-basiertes Gerät mit I²C-Interface (z.B. JControl/SmartDisplay, JControl/Sticker oder JControl/Stamp) benötigt. Für das vorgestellte Beispiel wurde ein EEPROM des Herstellers Atmel (AT24Cxx-Serie) http://www.atmel.com/dyn/resources/prod_documents/doc0180.pdf eingesetzt.

Download: http://www.jcontrol.org/examples/I2CExample.zip Dieses Tutorial mit allen Quelltexten und Ressourcen als JControl/IDE-Projekt (ZIP-Archiv).




Die Verbindung zwischen JControl-Gerät und I²C-EEPROM

Um das serielle EEPROM vom Typ 2402 (z.B. Atmel AT24C02) über den I²C-Bus mit einem JControl-Gerät zu verbinden, werden vier Leitungen benötigt. Zwei dieser Leitungen versorgen das EEPROM mit Spannung: VCC (=+5V) und GND (=0V). Die anderen beiden Leitungen sind für die Kommunikation zuständig: SDA, SCL. Bild 1 zeigt die Verbindungen zwischen einem JControl/SmartDisplay und dem EEPROM. Das Datenblatt des JControl/SmartDisplays http://www.domologic.com/download/pdf/jcontrol_smartdisplay.pdf zeigt dessen genaue Pinbelegung.

Bild 1: Genereller Versuchsaufbau

Hinweis: Die Beschaltung der Pins 1, 2 und 3 des 24C02 beeinflussen dessen I²C-Adresse. Mit Pin 7 kann zusätzlich festgelegt werden, ob in den Speicher geschrieben werden darf (dies ist für dieses Beispiel zulässig). Nähere Informationen können dem entsprechenden Datenblatt entnommen werden.

Achtung: Beim Anschluss externer Peripheriekomponenten an den I²C-Bus ist zu beachten, dass die Signale SDA und SCL über jeweils einen Widerstand (i.d.R. 27k Ohm) an das Potential HIGH (hier: VCC = +5V) gebunden werden müssen, um Störungen zu verhindern (siehe Bild 1). Das Evaluationboard des JControl/SmartDisplays, bzw. der JControl/Stamp verfügt bereits über diese Widerstände, sodass die Peripherie (das EEPROM) direkt mit den herausgeführten I²C-Anschlüssen verbunden werden kann. Nähere Informationen zu den Evalutionboards finden sie in den Datenblättern http://www.domologic.com/support/ch1/index_de.html.


I²C (Inter-IC-Communication)


Beispiel: I²C-EEPROMs

Wir wollen nun für die Ansteuerung unseres seriellen EEPROMs eine spezielle Klasse SerialEEPROM entwerfen, die alle für Lese- und Schreibzugriffe notwendigen I²C-Aufrufe in zwei Methoden read und write kapselt.



Die Klasse SerialEEPROM

Bevor wir mit der Implementierung von read und write beginnen, soll zunächst ein Konstruktor für die Klasse SerialEEPROM erstellt werden. Er soll als Parameter die Angabe des angeschlossenen EEPROM-Typs ermöglichen und daraufhin einige Steuerparameter berechnen (Adresse, Größe etc.). Die folgenden Atmel EEPROM-Typen sollen unterstützt werden:

Nachfolgender Quelltext zeigt eine mögliche Implementierung des Konstruktors:

16    public class SerialEEPROM {
17    
18      /** Atmel EEPROM types */
19      public static final int TYPE_C02=2;
20      public static final int TYPE_C04=4;
21      public static final int TYPE_C08=8;
22      public static final int TYPE_C16=16;
23    
24      /** EEPROM chip address */
25      private int address;
26      /** EEPROM size */
27      private int size;
28      /** EEPROM maximum burst transfer */
29      private int maxburst;
30    
31      /** Constructs a new SerEPP access object
32       * @param type the chip type of the serial EEprom,
33       *         possible values are defined as constants
34       * @param address the address of the chip as defined by
35       *         its address input pins, on C04, C08 and C16
36       *         a number of LSBs is ignored.
37       */
38      public SerialEEPROM(int type, int address){
39        this.address = ((16-type) & (address<<1)) + 0xa0;
40        this.size = type << 7;
41        this.maxburst = (type >= TYPE_C04) ? 16 : 8;
42      }
43    }
Listing 1: Auszug aus SerialEEPROM.java

Nachdem nun die I2C-Adresse des EEPROMs (address), der Speicherplatz (size) und die Anzahl maximal in Folge übertragbarer Bytes (maxburst) berechnet wurden, kann die Implementierung der read- und write-Methoden beginnen.



Die Methode read()

Die read-Methode soll vier Parameter erhalten: Ein byte-Array, in das die gelesenen Daten kopiert werden, die Variablen startindex und length zur Angabe der Startposition und Länge für den Kopiervorgang in das Array und wordaddr, die Leseadresse. Damit ergibt sich der folgende Quelltext, wobei sich der eigentliche I2C-Zugriff auf drei Zeilen beschränkt:

44    /** Reads out the serial EEPROM to the destination byte array.
45     * @param data the byte array to fill with data
46     * @param startindex the index in data to start filling
47     * @param length number of bytes to read
48     * @param wordaddr the word address of the EEprom to start reading
49     * @return the number of bytes read (could be less than array
50     *         stop-startindex)
51     * @throws IOException on communication error (maybe data is partially
52     *         filled)
53     */
54    public int read(byte[] data,
55                    int startindex,
56                    int length,
57                    int wordaddr) throws IOException {
58      int read = 0,stopindex=startindex+length;
59      byte[] sendaddr = new byte[1];
60      int i = size-wordaddr+startindex;
61    
62      // check stopindex is inside param boundaries
63      if (stopindex > i) stopindex = i;
64    
65      // read bytes from EEPROM using I2C
66      while (startindex < stopindex) {
67        sendaddr[0] = (byte)(wordaddr&255);
68        int count = stopindex-startindex; // transfer length
69        int devaddr = address+((wordaddr>>7)&14); // read address
70        i = 256-(wordaddr&255);
71        if (count>i) count=i; // page boundry
72    
73        // I2C access
74        I2C i2c = new I2C(devaddr);
75        i2c.write(sendaddr, 0, 1);
76        i2c.read(data, startindex, startindex+count);
77    
78        // update variables
79        startindex += count;
80        wordaddr += count;
81        read += count;
82      }
83      // return number of bytes read
84      return read;
85    }
Listing 2: Auszug aus SerialEEPROM.java

Die Variable sendaddr wird auf die Leseadresse initialisiert und per I2C.write()-Kommando an das EEPROM gesendet. Danach wird die I2C.read()-Methode mit dem byte-Array als Parameter aufgerufen, um eine Anzahl Bytes aus dem EEPROM über I2C einzulesen. Diese Operationen werden in einer while-Schleife so lange durchgeführt, bis die gewünschte Anzahl Bytes übertragen wurde. Dies ist notwendig, wenn die Anzahl der zu lesenden Bytes die Seitengröße des seriellen EEPROMs übersteigt und daher die Leseadresse inkrementiert werden muss.



Die Methode write()

Die Implementierung der write-Methode gestaltet sich ähnlich zu jener von read. Im Wesentlichen muss der I2C-Lese- in einen Schreibzugriff umgeschrieben werden. Außerdem kommt nun der im Konstruktor berechnete Parameter maxburst zum Einsatz, der die Anzahl der pro Seite beschreibbaren Bytes bestimmt.

83    /** Reads out the serial EEPROM to the destination byte array.
84     * @param data the byte array to fill with data
85     * @param startindex the index in data to start filling
86     * @param length number of bytes to read
87     * @param wordaddr the word address of the EEprom to start reading
88     * @return the number of bytes read (could be less than array
89     *         stop-startindex)
90     * @throws IOException on communication error (maybe data is partially
91     *         filled)
92     */
93    public int read(byte[] data,
94                    int startindex,
95                    int length,
96                    int wordaddr) throws IOException {
97      int read = 0,stopindex=startindex+length;
98      byte[] sendaddr = new byte[1];
99      int i = size-wordaddr+startindex;
100    
101      // check stopindex is inside param boundaries
102      if (stopindex > i) stopindex = i;
103    
104      // read bytes from EEPROM using I2C
105      while (startindex < stopindex) {
106        sendaddr[0] = (byte)(wordaddr&255);
107        int count = stopindex-startindex; // transfer length
108        int devaddr = address+((wordaddr>>7)&14); // read address
109        i = 256-(wordaddr&255);
110        if (count>i) count=i; // page boundry
111    
112        // I2C access
113        I2C i2c = new I2C(devaddr);
114        i2c.write(sendaddr, 0, 1);
115        i2c.read(data, startindex, startindex+count);
116    
117        // update variables
118        startindex += count;
119        wordaddr += count;
120        read += count;
121      }
122      // return number of bytes read
123      return read;
124    }
Listing 3: Auszug aus SerialEEPROM.java



Ein Testprogramm

Die Implementierung der SerialEEPROM-Methoden ist nun abgeschlossen. Um sie einem Funktionstest zu unterziehen, wollen wir ein kleines Testprogramm schreiben. Dieses soll eine "I2C EEPROM command shell" über die serielle RS232-Schnittstelle implementieren. Mit einem PC nebst Terminalprogramm können dann simple Kommandos zum Lesen und Schreiben auf das serielle EEPROM abgesetzt werden.

Die "command shell" soll folgende Befehle unterstützen:

KommandoArgumentBeschreibung
aIntegerAdresse für nächste Operation setzen
wStringString auf das serielle EEPROM schreiben
rIntegerString der angegeben Länge vom seriellen EEPROM lesen
Tabelle 1: Befehle der command shell

Die Implementierung des Testprogramms kann komplett in der main-Methode der SerialEEPROM-Klasse vorgenommen werden. Zur Unterscheidung der Benutzerkommandos bedienen wir uns eines einfachen switch-case-Konstrukts. Die SerialEEPROM-Funktionsaufrufe sind im nachfolgend gelisteten Quelltext markiert.

124    /**
125     * Test code for the <code>SerialEEPROM</code> functions.
126     */
127    public static void main(String[] args) {
128      try{
129        RS232 rs=new RS232(19200);
130        rs.println("I2C EEprom command shell");
131        rs.println("a set address (decimal integer+CR)");
132        rs.print("w write a string to set ");
133        rs.println("address (String+CR)");
134        rs.print("r read a string from set ");
135        rs.println("address (decimal integer+CR)");
136        rs.println("turn off your local echo");
137        int addr=0;
138        SerialEEPROM epp=new SerialEEPROM(TYPE_C08,0);
139        for(;;){
140          try{
141            rs.write('>');
142            char c=rs.read();
143            rs.write(c); // echo
144            switch(c){
145              case 'a':
146                rs.println();
147                rs.print("new address: ");
148                String line=rs.readLine();
149                rs.println();
150                addr=Integer.parseInt(line);
151                rs.println("set address to: ".concat(
152                           String.valueOf(addr)));
153                break;
154              case 'w':
155                rs.println();
156                rs.print("data to store: ");
157                line=rs.readLine();
158                rs.println();
159                byte[] buf=line.getBytes();
160                epp.write(buf, 0, buf.length, addr);
161                rs.print("written ".concat(String.valueOf(buf.length)));
162                rs.println("bytes to address: ".concat(
163                           String.valueOf(addr)));
164                break;
165              case 'r':
166                rs.println();
167                rs.print("amount of bytes to read: ");
168                line=rs.readLine();
169                rs.println();
170                buf=new byte[Integer.parseInt(line)];
171                epp.read(buf, 0, buf.length, addr);
172                rs.print("read string \"".concat(new String(buf)));
173                rs.println("\" from address: ".concat(
174                           String.valueOf(addr)));
175                break;
176              }
177          } catch(IOException e){
178            rs.println("IOException");
179          }
180        }
181      } catch(IOException e){}
182    }
Listing 4: Auszug aus SerialEEPROM.java

Zum Testen verbinden Sie ein serielles EEPROM mit Ihrem JControl-Gerät, laden Sie nun das komplette I2CExample http://www.jcontrol.org/examples/I2CExample.zip als ZIP-Datei herunter und packen Sie diese in ein leeres Verzeichnis aus. Öffnen Sie das enthaltene JControl/IDE-Projekt und programmieren Sie Ihr JControl-Gerät damit. ACHTUNG: Achten Sie darauf, dass das SerialEEPROM-Objekt mit den korrekten Daten (Typ, Addresse) initialisiert wird!

Über das intergrierte RS232-Terminalprogamm (ab JControl/IDE Version 3.0.1) können Sie nun mit Ihrem JControl-Gerät kommunizieren und auf das serielle EEPROM zugreifen. Alternativ können sie ein Terminalprogramm starten (unter Windows beispielsweise HyperTerminal) und konfigurieren Sie die serielle Schnittstelle auf 19200 Baud, 8 Datenbit, keine Parity, 1 Stoppbit und keine Flusskontrolle. Eine Sitzung mit der "I2C EEPROM command shell" könnte wie folgt aussehen:

Beispielsitzung des EEPROM-Testprogramms



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