EEPROM Teil 2 – externe I2C EEPROMs

Über den Beitrag

In meinem letzten Beitrag hatte ich über den internen EEPROM der AVR Mikrocontroller beziehungsweise der AVR basierten Arduino Boards berichtet. In diesem Beitrag widme ich mich nun den externen, I2C gesteuerten EEPROMs. Mit geeigneten Bibliotheken, wie der von Sparkfun, könnt ihr die EEPROMs sehr bequem beschreiben und auslesen. Für ein besseres Verständnis lohnt es sich aber auch nachzuvollziehen, wie die Ansteuerung ohne Bibliothek funktioniert.

Der Beitrag ist folgendermaßen gegliedert:

In einem separaten Beitrag behandele ich die externen, SPI basierten EEPROMs. Sie unterscheiden sich von den I2C basierten EEPROMs, dass ich das nicht in einem einzigen Beitrag unterbringen konnte.

Einführung

Was ist ein EEPROM?

Die Abkürzung EEPROM steht für Electrically Erasable Programmable Read Only Memory, also einen elektrisch löschbaren, programmierbaren Speicher, der nur lesbar ist. Diese in sich etwas widersprüchliche Bezeichnung ist historisch gewachsen. Darauf bin ich in meinem letzten Beitrag kurz eingegangen. Kurz gesagt dient ein EEPROM als Speicher für Daten, die auch nach dem Ausschalten der Versorgungsspannung nicht verloren gehen sollen.

Welche Eigenschaften haben EEPROMs?

Zu den Vorzügen von EEPROMs gehört ihre kompakte Bauweise. Außerdem sind die Daten auf einem EEPROM vergleichsweise lange sicher aufgehoben. Teilweise geben die Hersteller eine Datenhaltbarkeit (Data Retention) von über 200 Jahren an. Ich werde das mal in 200 Jahren prüfen und gegebenenfalls reklamieren ;-).

Nachteilig ist hingegen die vergleichsweise langsame Schreibgeschwindigkeit der EEPROMs. Sie liegt im Bereich von Millisekunden für ein einzelnes Byte. Durch „Page Writing“ steigern die hier besprochenen Modelle ihre effektive Schreibgeschwindigkeit deutlich gegenüber den internen EEPROMs. Jedoch sind sie immer noch unvergleichlich viel langsamer als Flash-Speicher.

Ein EEPROM ist nicht unendlich oft wiederbeschreibbar. Allerdings ist dieser Nachteil oft von nur begrenzter Relevanz, denn die garantierten Schreibzyklen liegen für gewöhnlich bei über einer Million. Wenn ihr dieselbe Speicherzelle jede Sekunde neu beschreibt, hättet ihr die Lebensdauer entsprechend nach ~11.5 Tagen erreicht. Zu hoch sollte die Schreibfrequenz also nicht sein.

Welche EEPROMs ich bespreche

Ich beziehe mich in diesem Beitrag auf die EEPROMs der 24er-Baureihe. Diese EEPROMs werden nach dem Schema „24xxyy“ bezeichnet.

Das „xx“ im Namen codiert unterschiedliche Spannungsbereiche und Übertragungsgeschwindigkeiten. Oft trifft man „LC“-, „C“- und „A“-Typen an. Die abschließende Zahl gibt die Speicherkapazität in Kilobit (kurz: kb o. kbit) an. Unten links seht ihr beispielsweise einen „24LC64„, der also eine Kapazität 64 kbit = 8 Kilobyte (kurz: kB oder kbyte) hat.

Ihr steuert die 24er-Reihe über I2C. Daneben gibt es weitere Reihen. Die 25er-Reihe beispielsweise „hört“ auf SPI, die 11er-, 21er- und 28er-Reihen kommunizieren per One-Wire Techniken.

Weitere Kürzel im Anschluss definieren Bauformen, Temperaturbereiche und anderes.

EEPROM 24LC64
24LC64 EEPROM
EEPROM 24C256
24C256 EEPROM Modul

Strombedarf

Die EEPROMs sind hinsichtlich ihres Strombedarfes recht genügsam. Während des Schreibens brauchen sie in der Regel 0.1 -1.0 Milliampere. Beim Lesen ist der Wert noch geringer. Im Standby liegt der Verbrauch maximal bei wenigen Mikroampere.

Pinout und Anschluss an den Mikrocontroller

Pinout der 24er EEPROM Reihe

Das Pinout Schema der EEPROMs der 24er-Reihe sieht in der Regel wie folgt aus:

EEPROM Pinout
EEPROM Pinout (24er Reihe)
  • A0 / A1 / (A2): Adresspins, 4 bis 8 Adressen sind üblicherweise einstellbar.
  • VCC / GND: Spannungsversorgung, z.B. 2.5 – 5.5 Volt für die 24LCxx-Reihe (Datenblatt prüfen!).
  • WP: Write Protection (Schreibschutz);
    • Inaktiv, wenn mit GND verbunden.
    • Aktiv, wenn mit VCC verbunden.
  • SDA / SCL: I2C-Anschlüsse, max. 400 kHz für 24LCxx-Reihe (Datenblatt prüfen!).
    • Adressschema: 1 0 1 0 A2 A1 A0 mit GND = 0 und VCC = 1.
    • Beispiel A1/A2/A3 an GND → Adresse = 0b1010000 = 80 = 0x50.

Anschluss an einen Arduino Nano

Beispielschaltung für einen 24LC256 an einem Arduino Nano:

EEPROM Schaltung am Arduino Nano
EEPROM Schaltung am Arduino Nano

Oft kann man auf die Pull-Up Widerstände verzichten. Probiert es aus.

Den EEPROM beschreiben und lesen

Im einem ersten, einfachen Beispiel schreiben wir drei Byte-Werte auf den EEPROM und lesen sie dann wieder aus. Ihr leitet den Schreibvorgang ein, indem ihr mittels Wire.beginTransmission() die I2C-Adresse des EEPROMs übergebt.

Im nächsten Schritt übermittelt ihr mit Wire.write() die Speicheradresse, an der ihr euren Wert speichern wollt. Bei Speicherkapazitäten bis 2kbit (=256 byte) könnt ihr die Adresse als ein einzelnes Byte übergeben. Einen so kleinen EEPROM werdet ihr aber nur in Ausnahmefällen einsetzen. Darüber hinaus, bis 512 kbit (= 64 kByte), passt die Adresse in eine Unsigned Integer Variable. Die Adressen teilt ihr in ihr MSB (Most Significant Byte) und LSB (Least Significant Byte) übergebt diese hintereinander.

Ihr schließt den Schreibvorgang mit Wire.endTransmission() ab. Danach müsst ihr dem EEPROM genügend Zeit (die „Write Cycle Time“) geben, bevor ihr einen weiteren Wert speichern könnt. Die „Write Cycle Time“ findet ihr im Datenblatt eures EEPROMs. Typischerweise sind das 5 Millisekunden.

Ihr müsst beim Schreiben über die Adresse Buch führen. Es gibt keine Funktion, die euch warnen würde, dass ein Speicherplatz schon beschrieben ist. Genau genommen gibt es keine Unterscheidung zwischen beschriebenen und unbeschriebenen Speicherzellen. Irgendein Wert steht da auf jeden Fall. Daten können also nur überschrieben werden, um sie zu löschen.

Das Lesen funktioniert wie das Schreiben, nur mit der Wire.requestFrom() Funktion. Zwischen den Leseoperationen müsst ihr keine Wartezeit einfügen.

#include <Wire.h>
#define I2C_ADDRESS 0x50

void setup(){
  Wire.begin();
  Serial.begin(9600);
  
  unsigned int address = 0;
  byte byteVal_1 = 42;
  byte byteVal_2 = 123;
  byte byteVal_3 = 255;
  
  eepromByteWrite(address,byteVal_1);
  address++;
  eepromByteWrite(address,byteVal_2);
  address++;
  eepromByteWrite(address,byteVal_3);
  
  for(address=0; address<3; address++){
    Serial.print("Byte at address ");
    Serial.print(address);
    Serial.print(": ");
    Serial.println(eepromByteRead(address));
  }
}
void loop(){}

void eepromByteWrite(unsigned int addr, byte byteToWrite){
  Wire.beginTransmission(I2C_ADDRESS);
  Wire.write((byte)(addr>>8));
  Wire.write((byte)(addr&0xFF));
  Wire.write(byteToWrite);
  Wire.endTransmission();
  delay(5); // important!
}

int eepromByteRead(unsigned int addr){
  int byteToRead;
  Wire.beginTransmission(I2C_ADDRESS);
  Wire.write((byte)(addr>>8));
  Wire.write((byte)(addr&0xFF));
  Wire.endTransmission();
  Wire.requestFrom(I2C_ADDRESS, 1);
  byteToRead = Wire.read();
  return byteToRead;
}

 

Und so sieht dann die Ausgabe aus:

Ausgabe von ext_eeprom_byte_write_read.ino
Ausgabe von ext_eeprom_byte_write_read.ino

Größere Datenmengen auf den EEPROM schreiben

Im nächsten Beispiel erzeugen wir zunächst ein Array aus einhundert Integer-Werten. Der Wert des i-ten Elements des Arrays ist 10 · i (willkürlich gewählt). Das Array schreiben wir auf den EEPROM, lesen es von EEPROM und geben es auf dem seriellen Monitor aus.

Hier zunächst der Sketch:

#include <Wire.h>
#define I2C_ADDRESS 0x50

void setup(){
  Wire.begin();
  //Wire.setClock(400000);
  Serial.begin(9600);
  unsigned int address = 0;
  unsigned long writeStart = 0;
  unsigned long writeDuration = 0;
  
  unsigned int arraySize = 100;
  int intArray[arraySize];
  
  for(unsigned int i=0; i<arraySize; i++){
    intArray[i] = i*10;
  }
  writeStart = millis();
  writeIntArrayToEEPROM(address, intArray, arraySize);
  writeDuration = millis() - writeStart; 

  address = 0;
  for(unsigned int i=0; i<arraySize; i++){
    Serial.print("intArray[");
    Serial.print(i);
    Serial.print("]: ");
    Serial.println(readIntFromEEPROM(address + 2*i)); 
  }
  Serial.print("Time needed for writing [ms]: ");
  Serial.println(writeDuration);
}

void loop(){}

void writeIntArrayToEEPROM(unsigned int addr, int *iArr, unsigned int arrSize){  
  for(unsigned int i = 0; i<arrSize; i++){
    Wire.beginTransmission(I2C_ADDRESS);
    Wire.write((byte)((2*i+addr)>>8));
    Wire.write((byte)((2*i+addr)&0xFF));
    Wire.write((byte)(iArr[i]>>8));
    Wire.write((byte)(iArr[i])&0xFF);
    Wire.endTransmission();
    delay(5);
//    while(isBusy()){   // alternativ to delay(5).
//      delayMicroseconds(50);
//    }
  }
}
  
unsigned int readIntFromEEPROM(unsigned int addr){
  int intToRead;
  byte msByte;
  byte lsByte;
  Wire.beginTransmission(I2C_ADDRESS);
  Wire.write((byte)(addr>>8));
  Wire.write((byte)(addr&0xFF));
  Wire.endTransmission();
  Wire.requestFrom(I2C_ADDRESS, 2);
  msByte = Wire.read();
  lsByte = Wire.read();
  intToRead = msByte<<8 | lsByte;
  return intToRead;
}

bool isBusy(){
  Wire.beginTransmission(I2C_ADDRESS);
  return Wire.endTransmission();
}

Ein paar Erläuterungen:

  • Das Array wird der Schreibfunktion writeIntArrayToEEPROM() als Zeiger übergeben. Darauf bin ich in meinem letzten Beitrag eingegangen.
  • Ein Integer belegt zwei Bytes. Deshalb muss nicht nur die Adresse, sondern auch der zu schreibende Wert in MSB und LSB zerlegt werden.
Ausgabe von ext_eeprom_int_array_write_read.ino
Ausgabe von ext_eeprom_int_array_write_read.ino

Das Schreiben des Arrays nahm 552 Millisekunden in Anspruch. Davon gehen 500 Millisekunden auf das Konto der „Write Cycle Time“. Sofern euer EEPROM Fast I2C beherrscht, könnt ihr die anderen 52 Millisekunden verringern, indem ihr die Zeile 6, Wire.setClock(400000), entkommentiert. Dadurch erhöht ihr die I2C Frequenz von 100 auf 400 kHz. Damit habe ich den Schreibvorgang auf 519 Millisekunden reduzieren können.

Die fünf Millisekunden Wartezeit für die „Write Cycle Time“ enthalten einen gewissen Sicherheitspuffer. Solange der EEPROM mit dem Schreiben beschäftigt ist, lässt er sich per I2C nicht ansprechen. Das bedeutet, dass er kein Acknowledge sendet, wenn ihr ihn ansprecht. Diese Eigenschaft macht sich die Funktion isBusy() zunutze.

Kommentiert mal die Zeile 43, delay(5), aus und entkommentiert die folgenden drei Zeilen. Mit dieser Maßnahme und dem Wechsel auf 400 kHz sank die benötigte Zeit zum Schreiben des Arrays auf 380 Millisekunden. Die wirkliche „Write Cycle Time“ liegt also bei ca. 3.5 Millisekunden.

EEPROM Page Write

Wie ihr eben gesehen habt, ist es möglich, mehrere Bytes „in einem Rutsch“ (Page Write), also ohne zwischenzeitliches Wire.endTransmission() und delay() auf den EEPROM zu schreiben. Ansonsten hätte der Schreibvorgang für das Integer-Array mindestens 1000 Millisekunden in Anspruch genommen.

Das funktioniert, weil der EEPROM in Speicherabschnitte (Pages) segmentiert ist und für die Pages einen Puffer besitzt. Die übertragenen Werte werden also schnell in den Puffer geschrieben und dann in aller Ruhe von da in den Speicher geschrieben. Die Größe der Pages (Page Size) könnt ihr dem Datenblatt entnehmen. Für einen 24LC64 beträgt die Page Size 32 Bytes, für einen 24LC256 beträgt sie 64 Bytes.

Die Pages beginnen und enden an festen Speicheradressen. Page Writing über das Ende einer Page hinaus funktioniert nicht.

Limitierung durch den Wire.write() Puffer

Für das Page Writing gibt es noch einen limitierenden Faktor auf der Arduino Seite. Und zwar ist das der Puffer für Wire.write(). Das könnt ihr mit dem folgenden Sketch nachvollziehen:

#include <Wire.h>
#define I2C_ADDRESS 0x50

void setup(){
  Wire.begin();
  Serial.begin(9600);
  unsigned int address = 0;
   
  for(unsigned int i=address; i<64; i++){
    eepromByteWrite(i,(byte)i);
  }

  byte byteArray[64];
  for(byte i=0; i<64; i++){
    byteArray[i] = i*2;
  }
  eepromBytePageWrite(address, byteArray, sizeof(byteArray));
  
  address = 0;
  for(unsigned int i=address; i<sizeof(byteArray); i++){
    Serial.print("byteArray[");
    Serial.print(i);
    Serial.print("]: ");
    Serial.println(readEEPROM(i)); 
  }
}
void loop(){}

void eepromByteWrite(unsigned int addr, byte byteToWrite){
  Wire.beginTransmission(I2C_ADDRESS);
  Wire.write((byte)(addr>>8));
  Wire.write((byte)(addr&0xFF));
  Wire.write(byteToWrite);
  Wire.endTransmission();
  delay(5);
}

void eepromBytePageWrite(unsigned int addr, byte *byteArrayToWrite, unsigned int sizeOfArray){
  Wire.beginTransmission(I2C_ADDRESS);
  Wire.write((byte)(addr>>8));
  Wire.write((byte)(addr&0xFF));
  for(unsigned int i=addr; i<sizeOfArray; i++){
    Wire.write(byteArrayToWrite[i]);
  }
  Wire.endTransmission();
  delay(5);
}

byte readEEPROM(unsigned int addr){
  byte byteToRead;
  Wire.beginTransmission(I2C_ADDRESS);
  Wire.write((byte)(addr>>8));
  Wire.write((byte)(addr&0xFF));
  Wire.endTransmission();
  Wire.requestFrom(I2C_ADDRESS, 1);
  byteToRead = Wire.read();
  return byteToRead;
}

 

Der Sketch beschreibt zunächst die ersten 64 Speicherplätze mit einem Wert, und zwar der Adresse selbst. Die Werte werden einzeln und mit 5 Millisekunden Pause geschrieben. Diese Aktion sorgt lediglich für einen definierten Ausgangszustand.

Danach schreibt der Sketch 64 neue Werte (Adresse multipliziert mit 2)  auf den EEPROM. Dabei verwendet er die Page Write Methode. Und so sieht dann unerwarteterweise die Ausgabe aus:

Ausgabe von ext_eeprom_wire_limit_test.ino
Ausgabe von ext_eeprom_wire_limit_test.ino

Die ersten 30 Werte (0-29) sind durch das Page Write Verfahren ersetzt worden, danach treffen wir auf die alten Werte. Der Grund ist folgender: Bei aufeinanderfolgenden Wire.write() Befehlen (also ohne zwischenzeitliches Wire.endTransmission()) werden die zu schreibenden Werte Arduino-seitig in einen Puffer geschrieben, der auf 32 Byte beschränkt ist. Die Übermittlung der Adresse benötigt 2 Bytes, bleiben also noch 30 Bytes für die Daten. Alle zusätzlichen Wire.write() Aufrufe laufen ins Leere.

Korrektes Page Writing

Beim Page Write Verfahren sind also drei Dinge zu berücksichtigen:

  1. Die Page Size,
  2. der Wire.write() Puffer, und
  3. es darf nicht über eine Page Grenze hinaus geschrieben werden.

Im nächsten Beispiel schreiben wir ein Array von 100 Integer-Werten (= 200 Byte) im Page Write Verfahren auf einen EEPROM mit einer Page Size von 64 Byte. Wenn wir bei der Adresse 0 starten, dann haben wir 64 Byte Platz. Der limitierende Faktor ist der Wire.write() Puffer. Also beschreiben wir die Speicherplätze 0-29, dann 30-59. Danach müssen wir das Page Ende nach Adresse 63 beachten und können nur 4 Bytes am Stück schreiben. Dasselbe passiert in den folgenden zwei Pages. Die dann noch verbleibenden 8 Byte schreiben wir in die letzte Page. Schematisch sieht das so aus:

100 Integers auf EEPROM mit 64 Bytes pro Page
100 Integers auf EEPROM mit 64 Bytes pro Page

Als Sketch könnte das zum Beispiel so aussehen:

#include <Wire.h>
#define I2C_ADDRESS 0x50
#define PAGE_SIZE 64
#define WRITE_LIMIT 30 

void setup(){
  Wire.begin();
  Serial.begin(9600);
  unsigned int address = 0;
  unsigned long writeStart = 0;
  unsigned long writeDuration = 0;
  
  unsigned int arraySize = 100;
  int intArray[arraySize];
  
  for(unsigned int i=0; i<arraySize; i++){
    intArray[i] = i*1;
  }
  writeStart = millis();
  writeIntArrayToEEPROM(address, intArray, arraySize);
  writeDuration = millis() - writeStart; 
  
  address = 0;
  for(unsigned int i=0; i<arraySize; i++){
    Serial.print("intArray[");
    Serial.print(i);
    Serial.print("]: ");
    Serial.println(readIntFromEEPROM(address + 2*i)); 
  }
  Serial.print("Time needed for writing [ms]: ");
  Serial.println(writeDuration);
}

void loop(){}

void writeIntArrayToEEPROM(unsigned int addr, int *iArr, unsigned int arrSize){  
  unsigned int noOfIntsStillToWrite = arrSize;
  unsigned int arrayIndex = 0;
  
  while((noOfIntsStillToWrite != 0)){
    unsigned int chunk = (WRITE_LIMIT / sizeof(int));  // max chunk in number of ints
    unsigned int positionInPage = (addr % PAGE_SIZE);  // current position in page
    unsigned int spaceLeftInPage = (PAGE_SIZE - positionInPage) / sizeof(int); // available storage space 
    if(spaceLeftInPage < chunk){
      chunk = spaceLeftInPage;
    }
    if(noOfIntsStillToWrite < chunk){
      chunk = noOfIntsStillToWrite;
    }
    writeEEPROM(addr, iArr, chunk, arrayIndex);
    noOfIntsStillToWrite -= chunk;
    addr += (chunk * 2);
    arrayIndex += chunk;
  } 
}

void writeEEPROM(unsigned int addr, int *iArr, unsigned int chunkSize, unsigned int arrIdx){  
  Wire.beginTransmission(I2C_ADDRESS);
  Wire.write((byte)(addr>>8));
  Wire.write((byte)(addr&0xFF));

  for(unsigned int i=0; i<chunkSize; i++){
    Wire.write((byte)(iArr[i+arrIdx]>>8));
    Wire.write((byte)(iArr[i+arrIdx])&0xFF);
  }
  Wire.endTransmission();
  delay(5); 
}

unsigned int readIntFromEEPROM(unsigned int addr){
  int intToRead;
  byte msByte;
  byte lsByte;
  Wire.beginTransmission(I2C_ADDRESS);
  Wire.write((byte)(addr>>8));
  Wire.write((byte)(addr&0xFF));
  Wire.endTransmission();
  Wire.requestFrom(I2C_ADDRESS, 2);
  msByte = Wire.read();
  lsByte = Wire.read();
  intToRead = msByte<<8 | lsByte;
  return intToRead;
}

 

Ihr – beziehungsweise euer Sketch – müsst also immer wieder berechnen, wie viele Bytes ihr in einem Schritt auf den EEPROM schreiben könnt. Der Geschwindigkeitsgewinn durch die Page Write Methode ist erwartungsgemäß erheblich:

Ausgabe von ext_eeprom_int_array_write_read.ino
Ausgabe von ext_eeprom_int_array_write_read.ino

Auch hier könnt ihr die Geschwindigkeit durch Verwendung der isBusy() Funktion und durch Wechsel auf 400 kHz weiter steigern. Letzteres natürlich nur, wenn der EEPROM das beherrscht.

Sparkfun Bibliothek

Ihr könnt euch das Leben erheblich erleichtern, indem ihr eine Bibliothek verwendet. Ich habe die SparkFun_External_EEPROM_Arduino_Library getestet. Sie ist leicht zu bedienen und recht komfortabel. Ihr erhaltet die Bibliothek über die Bibliotheksverwaltung der Arduino IDE oder ihr ladet sie direkt von GitHub herunter (Link). Die get() und put() Funktionen erinnern an die entsprechenden Funktionen aus EEPROM.h für die internen EEPROMs der AVR Boards. Die Verwendung ist praktisch gleich.

Wenn ihr Strings auf den EEPROM schreiben oder von ihm lesen möchtet, dann verwendet ihr dafür die Funktionen putString() bzw. getString(). Der Rest sollte eigentlich selbsterklärend sein. Hier ein Beispielsketch:

#include <Wire.h>

#include "SparkFun_External_EEPROM.h" // Click here to get the library: http://librarymanager/All#SparkFun_External_EEPROM
ExternalEEPROM myMem;

void setup()
{
  Serial.begin(115200);
  Serial.println("Qwiic EEPROM example");

  Wire.begin();

  if (myMem.begin() == false)
  {
    Serial.println("No memory detected. Freezing.");
    while (1)
      ;
  }
  Serial.println("Memory detected!");

  Serial.print("Mem size in bytes: ");
  Serial.println(myMem.length());

  //Yes you can read and write bytes, but you shouldn't!
  byte myValue1 = 200;
  myMem.write(0, myValue1); //(location, data)

  byte myRead1 = myMem.read(0);
  Serial.print("I read: ");
  Serial.println(myRead1);

  //You should use gets and puts. This will automatically and correctly arrange
  //the bytes for larger variable types.
  int myValue2 = -366;
  myMem.put(10, myValue2); //(location, data)
  int myRead2;
  myMem.get(10, myRead2); //location to read, thing to put data into
  Serial.print("I read: ");
  Serial.println(myRead2);

  float myValue3 = -7.35;
  myMem.put(20, myValue3); //(location, data)
  float myRead3;
  myMem.get(20, myRead3); //location to read, thing to put data into
  Serial.print("I read: ");
  Serial.println(myRead3);

  String myString = "Hi, I am just a simple test string";
  unsigned long nextEEPROMLocation = myMem.putString(30, myString);
  String myRead4 = "";
  myMem.getString(30, myRead4);
  Serial.print("I read: ");
  Serial.println(myRead4);
  Serial.print("Next available EEPROM location: ");
  Serial.println(nextEEPROMLocation);  
}

void loop()
{
}

 

Und so sieht dann die Ausgabe aus:

Ausgabe von Example1_BasicReadWrite.ino
Ausgabe von Example1_BasicReadWrite.ino

Probiert am besten auch die anderen Beispielsketche der Bibliothek aus, insbesondere Example2_Settings.ino.

11 thoughts on “EEPROM Teil 2 – externe I2C EEPROMs

  1. Hallo,
    was müsste ich den tuen wenn meine Zahl deutlich über den bisherigen Werten liegt.
    Konkret müsste ich Werte bis 35.000 speichern?

    1. Hallo, die Größe der zu speichernden Zahlen spielt nur insofern eine Rolle, als die Zahl zum Variablentyp passen muss. Auf einem AVR-basierten Arduino ist 35000 ein unsigned integer. Der signed integer Bereich endet bei 32767. Also usigned int als Typ wählen und ganz“normal“ auf den EEPROM speichern. Oder habe ich die Frage missverstanden?

  2. Achtung, mit Elementen der Klasse „String“ geht das Beispiel NICHT! Bei „String“ wird (vermutlich) die Adresse der String-Instanz im EEPROM gespeichert. Scheinbar funktioniert das Beispiel dann, da die Instanz, die zurückgelesen wird, auf die gleiche Adresse gesetzt wird wie die speichernde Instanz. Und damit den gleichen Inhalt hat. Der Inalt des Strings wird somit mitnichten im EEPROM gespeichert sondern steht halt noch im Speicher des Arduino.
    „String“ wird nicht von der Sparkfun-Bibliothek unterstütz, was auch schon der allererste Issue bemerkt: https://github.com/sparkfun/SparkFun_External_EEPROM_Arduino_Library/issues/1
    Die Lösung ist relativ einfach: char[] statt String benutzen.

    1. Hallo Achim,

      ich muss widersprechen bzw. präzisieren. Denn natürlich sind meine Beispielsketche grundsätzlich geprüft und funktionieren. Die Sparkfun Bibliothek nimmt auch Strings, nur nicht bei Verwendung von ESP32 und ESP8266 basierten Boards. Das liegt an der unterschiedlichen Implementierung von Strings auf AVR- und ESP-Boards. Wer sich für Details zu diesem Thema interessiert, mag hier schauen:

      https://wolles-elektronikkiste.de/sram-management#string_handling_in_sram

      VG, Wolfgang

      1. Hallo Wolfgang,
        ich habe in der Sparkfun-Bibliothek nachgeschaut, sie verwendet ein Template, um die übergebene Variable ins EEPROM zu schreiben. Wird hier nicht ein einfacher Datentyp wie int, float, … verwendet sondern eine Klasse, was String ist, werden die internen Klassen-Variablen gespeichert. Mit anderen Worten, das, was auf dem Stack ist. Dass die Klasse noch was auf dem Heap liegen hat, nämlich die eigentlichen Inhalt des Strings, kann die put()-Funktion nicht wissen. Der Inhalt des Strings wird somit nicht im EEPROM gespeichert.
        Testen lässt sich das ganz leicht, indem du die put() in deinem Beispiel auskommentierst und nur die get() stehen lässt. Und – das ist entscheidend – vor dem Ausführen des modifizierten Programms den Strom zum Mikrocontroller kurz unterbrichst. Dann ist nämlich auch der alte String auf dem Heap weg. Und dann kommt kein vernünftiger String mehr zurück. Nur eine mit Quatsch-Inhalt, aber der gleichen Länge wie der gespeicherte.

        Ausgabe beim ersten Ausführen, mit Abspeichern und Zurücklesen:
        I read:
        -7.35
        and:
        This is no poetry, I am just a simple test String

        Strom kurz abgeschaltet, nur Zurücklesen:
        I read:
        -7.35
        and:
        ▒▒▒▒*}▒j!▒߻▒▒▒▒$▒b▒▒▒m▒▒97▒▒7h
        ▒:

        Viele Grüße und auch noch vielen Dank für deine tolle Seite!

        Achim

      2. Hallo Wolfgang,
        ich habe in der Sparkfun-Bibliothek nachgeschaut, sie verwendet ein Template, um die übergebene Variable ins EEPROM zu schreiben. Wird hier nicht ein einfacher Datentyp wie int, float, … verwendet sondern eine Klasse, was String ist, werden die internen Klassen-Variablen gespeichert. Mit anderen Worten, das, was auf dem Stack ist. Dass die Klasse noch was auf dem Heap liegen hat, nämlich den eigentlichen Inhalt des Strings, kann die put()-Funktion nicht wissen. Der Inhalt des Strings wird somit nicht im EEPROM gespeichert.
        Testen lässt sich das, indem du die put() in deinem Beispiel auskommentierst und nur die get() stehen lässt. Und – das ist entscheidend – vor dem Ausführen des modifizierten Programms den Strom zum Mikrocontroller kurz unterbrichst. Dann ist nämlich auch der alte String auf dem Heap weg. Und dann kommt kein vernünftiger String mehr zurück. Nur einer mit Quatsch-Inhalt, was halt zufällig so auf den Heap liegt.

        Ausgabe beim ersten Ausführen, mit Abspeichern und Zurücklesen:
        I read:
        -7.35
        and:
        This is no poetry, I am just a simple test String

        Strom kurz abgeschaltet, nur Zurücklesen:
        I read:
        -7.35
        and:
        ▒▒▒▒*}▒j!▒߻▒▒▒▒$▒b▒▒▒m▒▒97▒▒7h
        ▒:

        Viele Grüße und auch noch vielen Dank für deine tolle Seite!

        Achim

        1. Hi, vielen Dank. Verstehe, und das mit der Stromunterbrechung macht Sinn! Ich muss mir das nochmal anschauen und das Beispiel modifizieren.

          VG, Wolfgang

        2. Und noch eine Ergänzung: Ich habe der Sparkfun Bibliothek eine putString() und eine getString() Funktion spendiert. Sparkfun hat die Ergänzung in die Bibliothek aufgenommen und veröffentlicht, sodass sie jetzt in der aktuellen Version verfügbar ist.

  3. Bisher kannte ich EEPROMS nur in den AVR-Controllern, die ich vor längerer Zeit auch programmiert habe.
    Auch externe EEPROMS hatte ich über I2C-Bus angeschlossen, musste aber sämtliche Kommandos in Assembler programmieren.
    Die Info, dass es zu den EEPROMS mit I2C Interface auch Bibliotheken gibt ist mir neu.
    Danke und viele Grüße

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert