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:
- Der Kompiler legt eine *.epp Datei an, welche man per ISP auf den AVR übertragen kann.
- 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:
- Im Flash, die Default Repräsentation
- Im EEPROM die persistente Repräsentation
- 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?