[Tutorial] Umgang mit dem AVR EEPROM

Umgang mit dem AVR EEPROM in der Arduino Umgebung.
(meine Art damit umzugehen)

Gerne wird das EEPROM für irgendwelche Konfigurationswerte verwendet, welche man nicht im Flash unterbringen möchte. Und genau darum soll es hier gehen.

Doku zur Library: EEPROM
Bitte jedes einzelne Kapitel sorgfältig lesen, sowohl die Beispiele, als auch die Funktionsbeschreibungen.
Ich werde es nicht hier ins deutsche übersetzen.
Auch nicht weiter erläutern.
Aber darauf aufbauen.

Den Fokus möchte ich hier auf die “richtige” Adressierung legen.
Auf dem typischen AVR haben wir drei verschiedene Speicherbereiche.
Flash, der Programmspeicher (10.000 mal beschreibbar)
Ram, der Bereich für veränderliche Daten (ewig oft beschreibbar)
EEPROM, für persistente Daten (100.000 mal beschreibbar)

Auf Addressen in der Ram Section können wir direkt zu greifen.
Für die Flash und auch für die EEPROM Section müssen wir spezielle Zugriffsverfahren verwenden.

In den Library Beispielen wird die Adresse per Hand bestimmt. Das ist recht unglücklich, weil es besondere Aufmerksamkeit erfordert. Einerseits muss man das Verfahren genau verstanden haben, und andererseits ist es selbst dann unübersichtlich und fehlerträchtig.

Dabei kann man den Kompiler recht leicht dazu überreden, die Adressen selbstständig zu berechnen. Dazu muss man dem Kompiler sagen, dass er eine Variable in der EEPROM Section anlegen soll.
Das Vorgehen hat 2 Vorteile:

  1. Der Kompiler legt eine *.epp Datei an, welche man per ISP auf den AVR übertragen kann.
  2. Wir können die Variablen Bezeichner für die Adressierung nutzen.

Alternativ, kann man den Offset in einer Struktur berechnen lassen.

Im folgenden werde ich beide Varianten zeigen.

// -------

Als Beispiel verwende ich hier die interne Referenzspannung. Denn diese hat eine recht große Toleranz, somit macht es Sinn diesen Wert im EEPROM abzulegen. Auch OSCAL Tabellen und Kalibrierwerte für den internen Temperatursensor sind solche Kandidaten. Sowie IP Nummern usw.

Das Beispiel hat ein rudimentäres Menu.
Ein großes P in der seriellen Konsole eingeben, und es zeigt die aktuellen Werte im EEPROM. Beim ersten Lauf können/werden dort noch komische Werte angezeigt, falls man die *.eep Datei nicht aufgespielt hat.

Die Kommandos A und B schreiben dann die jeweiligen Defaultwerte in das EEPROM.

#include <EEPROM.h>


float referenzspannungImEeprom EEMEM = 1.1; // Arduino legt *.eep Datei an
float defaultRef = 1.1;

byte oscalImEeprom EEMEM = 128; // wird von Arduino in der *.eep angelegt
byte defaultOscal = 128;

void printEepromData()
{
  float ref = 0; 
  EEPROM.get((int)&referenzspannungImEeprom,ref); // wert aus dem EEPROM lesen
  Serial.print("Spannung: ");Serial.println(ref);
  
  byte oscal = 0; 
  EEPROM.get((int)&oscalImEeprom,oscal); // wert aus dem EEPROM lesen
  Serial.print("OSCAL: ");Serial.println(oscal);
}

void serialEvent() 
{
  while (Serial.available()) 
  {
    char zeichen = Serial.read();
    switch(zeichen)
    {
      case 'A':   EEPROM.put((int)&referenzspannungImEeprom,defaultRef); // defaultwert schreiben
                  Serial.println("Default Ref geschrieben");
                  break;
                  
      case 'B':   EEPROM.put((int)&oscalImEeprom,defaultOscal); // defaultwert schreiben
                  Serial.println("Default Oscal geschrieben");
                  break;
                  
      case 'P':   printEepromData();
                  break;
    }
  }
}  

void setup() 
{
  Serial.begin(9600);
  Serial.println();
  
  Serial.print("Adresse Ref: ");Serial.println((int)&referenzspannungImEeprom);
  Serial.print("Adresse Oscal: ");Serial.println((int)&oscalImEeprom);
  Serial.println();
    
  Serial.println("Menue:");
  Serial.println("A Default Referenz ins EEPROM schreiben");
  Serial.println("B Default OSCAL Wert ins EEPROM schreiben");
  Serial.println("P Print, zeige EEPROM Daten auf der Konsole");
  Serial.println();
}

void loop() 
{
}

Wie man sieht, verwende ich hier drei Repräsentationen der EEPROM Daten:

  1. Im Flash, die Default Repräsentation
  2. Im EEPROM die persistente Repräsentation
  3. Im RAM die volatile Repräsentation “Die Arbeitskopie”

Ein verwenden der *.eep Datei würde einem die Repräsentation der Default Daten im Flash ersparen.

// -------

Aber es geht auch, ohne Variablen im EEPROM anzulegen. Dazu müssen alle Varablen in einer Struktur abgelegt werden.

Hier noch mal das gleiche Beispiel, nach diesem Prinzp:

#include <EEPROM.h>


struct ConfData
{
  float ref;
  byte oscal;
};


ConfData defaultData = {1.1,128};

void printEepromData()
{
  float ref = 0; 
  EEPROM.get(offsetof(ConfData,ref),ref); // wert aus dem EEPROM lesen
  Serial.print("Spannung: ");Serial.println(ref);
  
  byte oscal = 0; 
  EEPROM.get(offsetof(ConfData,oscal),oscal); // wert aus dem EEPROM lesen
  Serial.print("OSCAL: ");Serial.println(oscal);
}

void serialEvent() 
{
  while (Serial.available()) 
  {
    char zeichen = Serial.read();
    switch(zeichen)
    {
      case 'A':   EEPROM.put(offsetof(ConfData,ref),defaultData.ref); // defaultwert schreiben
                  Serial.println("Default Ref geschrieben");
                  break;
                  
      case 'B':   EEPROM.put(offsetof(ConfData,oscal),defaultData.oscal); // defaultwert schreiben
                  Serial.println("Default Oscal geschrieben");
                  break;
                  
      case 'P':   printEepromData();
                  break;
    }
  }
}  

void setup() 
{
  Serial.begin(9600);
  Serial.println();
  
  Serial.print("Offset Ref: ");Serial.println(offsetof(ConfData,ref));
  Serial.print("Offset Oscal: ");Serial.println(offsetof(ConfData,oscal));
  Serial.println();
    
  Serial.println("Menue:");
  Serial.println("A Default Referenz ins EEPROM schreiben");
  Serial.println("B Default OSCAL Wert ins EEPROM schreiben");
  Serial.println("P Print, zeige EEPROM Daten auf der Konsole");
  Serial.println();
}

void loop() 
{
}

Hier wird jetzt nur eine leere *.eep Datei erzeugt.

// -------

Fallstricke:
Spannung und Takt müssen erhalten bleiben, sonst kann es beim Schreiben versagen.
BOD ist empfehlenswert. (Ist auch Arduino default)
EESAVE Fuse zeigt Wirkung

// -------

War das hilfreich?
Vorschläge für Erweiterungen?
Kritik?

Fortsetzung:

Wie bekommen wir die gewünschten *.eep Daten ins EEPROM?

So sehr uns die Arduino IDE dabei hilft, die *.eep Datei zu erzeugen, sowenig ist sie dabei behilflich sie auf den µC zu schreiben. Über den Bootloader geht es nicht.

Arduino trägt avrdude in sich. Damit, und einem ISP Programmer, geht das.

Die Kommandozeile wie man Avrdude aufruft, sieht man, wenn man die ausführlichen Meldungen in der IDE aktiviert und dann "Upload mit Programmer" drückt. Das ist eine gute Vorlage für das eigene EEPROM Schreib Kommando. Einfach das flash schreiben, durch das eeprom schreiben ersetzen.

Aus meiner persönlichen Sicht, ist die die IDE Funktion "Upload mit Programmer" sowieso problematisch, wenn nicht sogar defekt. Sie überschreibt den Bootloader. Und das, ohne dessen Ressourcen frei zu geben. Die Fuses bleiben falsch stehen, der reservierte Flash Bereich kann nicht von der Anwendung genutzt werden. Bei anderen Boarddefinitionen sieht das manchmal anders aus. Aber die Originalen Arduino Boarddefinitionen tragen das Problem in sich.

Mein Vorschlag ist: Reparieren des Menupunktes "Upload mit Programmer"!

Hier gehe ich davon aus: 1. Dass man einen der üblichen AVR Arduinos, oder einen der vielen kompatiblen verwendet. 2. Dass man den Bootloader behalten möchte. 3. Auch die *.eep Datei übertragen möchte

Die Anweisung, welche ausgeführt wird, wenn man auf "Upload mit Programmer" drückt findet sich in der jeweiligen platform.txt. Den Arduino Entwicklern sei Dank, werden die nötigen Dateien "Anwendung mit Bootloader" und *.eep schon für uns erzeugt. Sie finden sich im jeweiligen Build Ordner.

Es reicht eine platform.local.txt mit einer Zeile anzulegen, damit wir das Ziel erreichen.

tools.avrdude.program.pattern="{cmd.path}" "-C{config.path}" {program.verbose} {program.verify} -p{build.mcu} -c{protocol} {program.extra_params} "-Uflash:w:{build.path}/{build.project_name}.with_bootloader.hex:i" "-Ueeprom:w:{build.path}/{build.project_name}.eep:i"

Der original Eintrag in der platform.txt kommt nicht mehr zur Geltung, wenn ein gleich benannter Eintrag in der platform.local.txt existiert.

Wenn man die ausführlichen Meldungen beim kompilieren aktiviert, zeigt die IDE, welche Hardwaredefinition sie verwendet, und dort muss diese platform.local.txt angelegt werden.

Zur Klärung: Hier wird nur die Funktion "Upload mit Programmer" geändert. Der normale Upload ändert sich nicht. Auch das "Bootloader brennen" bleibt unberührt erhalten.

combie: Vorschläge für Erweiterungen?

An anderer Stelle hattest Du schon mal beschrieben, wie es mit eep-Datei geht. Das könntest Du doch hierher "kopieren", dann wäre das Thema vollständig.

Ich habe meinen kleinen Beitrag gelöscht, danke bitte gerne, dann kannst Du #1 für die Fortsetzung nutzen :)