II. Serielle I²C-EEPROMsIn 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).
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.
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.
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.
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 | } |
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 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 | } |
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 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 | } |
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:
Kommando | Argument | Beschreibung |
---|---|---|
a | Integer | Adresse für nächste Operation setzen |
w | String | String auf das serielle EEPROM schreiben |
r | Integer | String der angegeben Länge vom seriellen EEPROM lesen |
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 | } |
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: