PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : mal wieder eine EEPROM Frage



roboter
23.07.2006, 12:07
Hallo,
ich habe ein Problem mit dem EEPROM (ATMega32) , und zwar habe möchte ich gerne Daten im EEPROM ablegen, bei jeden neustart des Gerätes sollen diese Daten ausgelesen werden.

Ich habe bis jetzt im Datenblatt gelesen, auf http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial und natürlich hier im Roboternetz gesucht. Vor allem der Thread https://www.roboternetz.de/phpBB2/zeigebeitrag.php?t=18051&highlight=eeprom scheint ganz gut zu sein, doch ich habe mit meinen eher mäßigen C Kenntnissen nicht alles verstanden.

Ich habe bei mir ein "char h_wert[15][4];"
Bei starten des gerätes sollen daten aus dem EEPROM gelesen werden und in das Feld h_wert geschrieben werden. Wenn das Gerät erstmal läuft, sollen abundzu bestimmte werte aus h_feld wieder in den EEPROM gespeichert werden...

Könnte jemand versuchen, mit zu erklären wie genau das funktioniert? Schonmal Danke im Voraus :-)

roboter
23.07.2006, 17:55
So, habe selber nochmal weitergemacht:



void speicherwerte() {
globalInterruptEnable(false); // Interrupts deaktivieren
for (i=0;i<15;i++) {
for (j=0;j<4;j++) {
while(EECR & (1<<EEWE)) // warten bis Schreiben beendet
EEAR=(i*4+j); // adresse setzen
EEDR=wert; // Date
EECR |= (1<<EEMWE); // MWE
EECR |= (1<<EEWE); // Schreiben starten
}
}
globalInterruptEnable(true); // Interrupts aktivieren
}

void ladewerte() {
int i,j;

globalInterruptEnable(false); // Interrupts deaktivieren
for (i=0;i<15;i++) {
for (j=0;j<4;j++) {
while(EECR & (1<<EEWE)) // warten bis Schreiben beendet
eear=(i*4+j);
EECR |= (1<<EERE); // Lesen starten
h_wert[i][j]=EEDR; // byte lesen
}
}
globalInterruptEnable(true); // Interrupts aktivieren

}

Habe im Moment aber keinen ISP da, könnte evt, jemand drüberschauen, ob das so funktionieren könnte?

SprinterSB
24.07.2006, 06:28
Nehmem wir mal an, dein h_feld und dein h_feld_EE haben die gleiche Größe, etwa


#include <avr/io.h>
#include <avr/eeprom.h>

#define EEMEM __attribute__((section(".eeprom")))

char h_feld[15][4];
const char h_feld_EE[15][4] EEMEM =
{
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 16},
...
};

In neueren avr-gcc gibt's schon den Define EEMEM, dann musste das nich mehr selber definen.



void init_h_held()
{
eeprom_read_block (h_feld, h_feld_ee, sizeof (h_feld));
}

oberallgeier
11.01.2013, 09:30
Ich möchte mehrere Vektoren im EEPROM (m)eines Controllers - mega.. - ablegen, Bytes und Words. Ich möchte die auf ganz bestimmten Stellen ablegen, damit ich nach einem Flashen das EEPROM nicht neu beschreiben muss bzw. damit ich nach dem Flashen auf die "alten" EEPROM-Werte auch wieder zugreifen kann, da es sich um Einstell- und Justagedaten dreht.

Anmerkung: habe den alten Thread wegen des genau passenden Titels wieder aufgewärmt.

Variable kann ich im EEPROM an einer bestimmten Stelle ablegen und auch wieder auslesen, auch nach dem neu Programmieren, das läuft problemlos. Ein Array der Art

/* Byte-Array */
uint8_t eeFooByteArray1[] EEMEM = { 18, 3, 70 };
uint8_t eeFooByteArray2[] EEMEM = { 30, 7, 79 };

kommt dann aber möglicherweise an einen Speicherplatz, der variieren kann. So gehts mir jedenfalls bei einem früheren, winzigen Projekt. Von diesem Projekt habe ich etwa diesen Stand:

Textfeld, das ins EEPROM beim Flashvorgang geschrieben wird.
unsigned char TestTimedOut[] EEMEM = "Timeout!";

Definition einer (EINER!) Bytevariablen auf die Adresse 0x004
#define EE_VALUE2 0x004 // Bytevariable

Wert dieser Variablen
[EE_VALUE2] = 0xFF

und so eine Schreibroutine

// ================================================== ============================ =
void DEEsch (int8_t data, uint8_t EEPadd) // Datum wegschreiben auf Adresse
{ //
// Evtl Parameter : (unsigned short address, unsigned char data)
// - - - - - - - - - - - - - - -// cli ();
while (EECR & (1<<EEPE)); // Schreiben möglich ? vorheriger Zugriff zu Ende?
EEAR = EEPadd; // Setze Adresse
EEDR = data; // Schreib Datum ins EEPROM-Data register
EECR |= (1<<EEMPE); //
EECR |= (1<<EEPE); // Starte Schreibzugriff
sei (); //
// - - - - - - - - - - - - - - -//
} //
// === Ende DEEsch (uint8_t dat, uint8_t EEPadd)
// ================================================== ============================ =


Bitte jetzt um Hilfe/Erläuterung zu diesen Fragen, der Vollständigkeit halber auch ein paar Anfängerfragen. Danke schon jetzt für die Antworten.

Wie definiere ich den GENAUEN Ort eines Datenfeldes im EEPROM?

Was bedeutet dieses ..foo.., ..Foo.. das ich bei EEPROM-Themen immer wieder lese?

Was bedeutet in den Zeilen
// schreiben
eeprom_write_word (&eeFooWord, 2222);
dieses &eeFooWord? Word verstehe ich (vermute ich), aber & und Foo ? ?

sast
11.01.2013, 10:38
Zu Foo siehe http://de.wikipedia.org/wiki/Metasyntaktische_Variable
Variablennamen sind der künstlerischen Freiheit und dem Einfal(l/t)sreichtum der Programmierer ausgeliefert. Darüber sollte man nicht allzusehr grübeln.

Wenn du EEMEM verwendest, dann übergibst du dem Compiler die Obhut über deine EEPROM Variablen. Es geht auch ohne, dann musst du dich nur selbst um die Ablage im Speicher kümmern und, dass du nicht unbeabsichtigte Überschneidungen hast, weil du dann doch mal einen Datentyp änderst.

Könnte mir allerdings vorstellen, dass bei gleicher Reihenfolge im Code auch das EEPROM immer genau so belegt wird. Aber das hab ich jetzt nur geraten und ich würde meine Hand dafür nicht ins Feuer legen.

sast

markusj
11.01.2013, 13:40
Es gibt für diese "Wo liegt das Zeug im EEPROM"-Problematik auch eine schönere Lösung. Bestehend aus einem Headerfile in das du deine Konfigurationsdatenstruktur reinschreibst, ein Sourcefile und einem Anwendungsbeispiel.

config.h


typedef struct {
int foo; // Eigene Konfigurationsvariablen hier unterbringen
} Config_Data_t;

extern Config_Data_t * const config;

config.c


#include <avr/eeprom.h>

#define EEPROM_SIZE (E2END + 1)

typedef struct {
uint8_t firstByteDummy;

Config_Data_t config;

uint8_t aligmentDummy[EEPROM_SIZE - sizeof(Config_Data_t) - 1];
} Config_Data_Container_t;

EEMEM Config_Data_Container_t container = {
.config = {
.foo = 42 // Beispielinitialisierung
}, };

Config_Data_t * const config = &container.config;

Anwendungsbeispiel


#include "config.h"
#include <avr/eeprom.h>

eeprom_read_word(&config->foo)


Erklärung: Config_Data_Container_t belegt den ganzen EEPROM. Vollständig. Das erste Byte wird ausgespart (angeblich kann es ungewollt bei Ausfällen der Stromversorgung o.ä. überschrieben werden), den Rest hinter der eigentlichen Nutzdatenstruktur belegt ein Dummy-Array. Dadurch bleibt die Adresse der Daten immer gleich da der Compiler gar keinen Spielraum zum umorganisieren hat, außer man nimmt selbst Änderungen vor. Auf die Daten zugreifen kann man über einen Pointer der direkt auf den Anfang der Nutzdaten zeigt. Müssen die Daten nicht vorinitialisiert werden, kann auf den entsprechenden Abschnitt in config.c verzichtet werden.

@sast: Die Reihenfolge in der die Variablen im Speicher liegen kannst du nicht beeinflussen, das gilt für den RAM genauso wie für den EEPROM. Wenn du ein sicheres Speicherlayout haben möchtest, musst du das ganze als struct ablegen.

mfG
Markus

sast
11.01.2013, 14:00
Deshalb hatte ich ja auch geschrieben, dass ich meine Hand dafür nicht ins Feuer legen würde.

Am Ende machst du hier das, was ich vorgeschlagen hatte. Du bestimmst selbst die Ablage der Daten im Speicher.
Sehr schönes ausführliches Beispiel. Besonders, da du die Möglichkeit von Überschneidungen schon mit abgefangen hast.

sast

oberallgeier
12.01.2013, 14:35
Es gibt ... eine schönere Lösung ... ein Sourcefile das du nicht anfassen musst und ...@sast - danke für die Hilfe, Markus - danke für Deine schöne Aufstellung und Deine Erläuterungen dazu. Dein Vertrauen in meine Kenntnisse ist aber zu groß. Ich habe erstmal gestern das Thema struct im Kernighan&R durchgeackert, stundenlang nach dem Sinn von * und & geinternettet, habe halbwegs begriffen dass "container" nicht nur in C++ vorkommen und laaaangsam erhellt sich der Horizont (aber z.B. "extern Config_Data_t * const config;" kapier ich immer noch nicht). Egal. (Erkenntnis: Interessant, mit wie wenig C-Kenntnissen man funktionierende Programme schreiben kann.)

Das bisschen, das ich kapiert hatte, hab ich trotzdem mal ausprobiert (experimentelle Softwareentwicklung :-/ ).

Zum Beispiel:

uint8_t firstByteDummy EEMEM;
int16_t eeSeroff[12] EEMEM; // Offset Servovorgabe
dazu noch der Dummy - und das EEPROM ist voll *ggg*.

Nur wenn ich den Offset für Servo 10 abspeichern will :

...
eeprom_busy_wait();
eeprom_write_word ((int16_t)eeSeroff[10], 8 );
...

führt das zu der Warnung "passing argument 1 of '__eewr_word_m328p' makes pointer from integer without a cast" - und egal wo ich ein * oder ein & hinpappe (auch deren Sinn und Zweck war mir bis gestern nicht bekannt) - es klappt nicht.

Daher schon wieder die Bitte/Frage - wo gehts lang ?

markusj
12.01.2013, 16:27
Ich zweifle nicht an deinen Fähigkeiten, aber auch Dritte mit weniger Erfahrung sollten den Code verwenden können, daher das ganze. Besagten Passus zum Sourcefile habe ich inzwischen gelöscht, weil er nicht korrekt ist wenn man den EEPROM vorinitialisieren möchte.

Zu deinem Problem: eeSeroff[10] greift auf ein Element im Speicher zu, eeprom_write_word erwartet aber eine Adresse. Ergo musst du die Adresse Organisieren, das macht &
Korrekt wäre also:

eeprom_write_word (&eeSeroff[10], 8 );

mfG
Markus

oberallgeier
12.01.2013, 17:16
Danke für den Zuspruch - ich zweifle bei neuen Abschnitten von C schon öfters an meinen Fähigkeiten. Und danke für Deine Hilfe.


int16_t muell;
muell = 7;
eeprom_busy_wait();
eeprom_write_word ( &eeSeroff[10], muell );
Sorry, das mit dem Adressoperator hatte ich vermutet, nein, das hatte ich in Beispielen schon gesehen. Da gibt er mir nur leider auch ne Warnung ab:
pointer targets in passing argument 1 of '__eewr_word_m328p' differ in signedness

In der Zwischenzeit habe ich entgegen meinen Gepflogenheiten das Ganze geflasht - trotz Warnungen sowohl beim EEPROM-Schreiben als auch beim EEPROM-Lesen. In der Ausführung bringt der Lesezugriff die Bestätigung, dass das Schreiben erfolgreich und korrekt war. Anm.: Den Schreibwert setze ich nach dem Schreibbefehl zu 0 um Selbstbetrug zu vermeiden - und die Gleichheit der Typdeklaration(en) der Beteiligten ist auch geprüft.

Bleibt also (nur) die Frage - was bewegt den Compiler zu dieser Warnung. Klappen tuts ja :(

schorsch_76
12.01.2013, 21:34
pointer targets in passing argument 1 of '__eewr_word_m328p' differ in signedness

Der Compiler hast (fast) immer Recht.

Definition von eeprom_write_word:
void eeprom_write_word (uint16_t *__p, uint16_t __value)

Was gibst du rein? Einen pointer auf ein int16_t. Die Funktion erwartet ein uint16_t. Andere "signedness" ;) In dem Fall kann das ignoriert werden.

Gruß
Georg

oberallgeier
12.01.2013, 22:06
... Was gibst du rein? ... int16_t. Die Funktion erwartet ein uint16_t ...Danke Schorsch. Grad hatte ich (nochmal) nachgegrübelt, nachgesehen und war draufgekommen, dass es auch ein avr-libc-user-manual gibt mit ner site über EEPROM Handling. Und schon hatte es geklappt. Aber da hattest Du schon Deinen Hinweis gepostet. Jedenfalls - vielen Dank, die Warnungen sind abgeschaltet. Dumm, eigentlich hätte ich das Vorzeichen benötigt.

Der Compiler hat (fast) immer Recht.

markusj
12.01.2013, 23:21
argument 1 of '__eewr_word_m328p' differ in signedness

Sooo schlecht sind die Meldungen vom GCC auch nicht, im Grunde ist die Lösung ganz einfach:

uint16_t eeprom_read_word (const uint16_t *__p)
Der Pointer ist vom Typ const unsigned int16. Dein Array besteht aus signed int16. Der Compiler bemängelt, dass der Zeiger der eigentlich auf einen vorzeichenlosen 16-Bit-Integer zeigen sollte in Wirklichkeit auf einen vorzeichenbehafteten Integer zeigt. Die Warnung ist in dem Kontext harmlos, du kannst sie durch einen Cast ruhigstellen: (uint16_t *)

mfG
Markus

Nachtrag: Verdammt, zu langsam und die zweite Seite nicht gesehen ...

oberallgeier
13.01.2013, 08:31
... schlecht ... vom GCC auch nicht ... zu langsam ... zweite Seite nicht gesehen ...Die Meldungen und eben auch die Warnungen nehme ich seit den ersten paar C-Monaten schon ernst. Ach - und zu langsam bist Du nicht. Lieber langsam und deutlich als eine schnelle, nichtssagende Bemerkung. Und ich finde es prima, dass ich mit Deiner/eurer Hilfe in diese eher tieferen Details reinkomme.

Danke für alle klaren Hinweise und Erklärungen, ich hab wieder etwas dazu gelernt (und arbeite an dem Thema noch ein bisschen weiter). Schönen Sonntag allen, ich habe Neuschnee, s..kalt, Tourenschi stehen an der Türe - gleich gehts rauf in die Berge...

schorsch_76
13.01.2013, 09:22
Dumm, eigentlich hätte ich das Vorzeichen benötigt.

Der Compiler hat (fast) immer Recht.

In deinem Fall sollte das auch mit dieser Funktion klappen. In dieser Funktion wird dann dein int16_t so behandelt als ob es ein uint16_t sei. ABER: Es wird ja nicht gerechnet sondern nur gespeichert. Es geht, in diesem Fall, nichts verloren. Auch beim lesen, kommt dann genau die Zeichenfolge wieder raus.

Diese Warnung ist dann wichtig, wenn du damit rechnest. Hier wäre bsp. -7 als uint16_t 32768+7 (Zweierkomplement (http://de.wikipedia.org/wiki/Zweierkomplement)). Aber die Bitfolge ist exakt dieselbe. Das 2^15 Bit ist das Vorzeichenbit. Bei uint16_t ist das ein einfacher Summand in der Zahl wie jede andere Zahl auch. Bei int16_t ist das 2^15 Bit das Vorzeichen.

Warum machen die Prozessoren das so? Weil mit dem Zweierkomplement eine Subtraktion fast wie eine Addition ausgeführt werden kann ;)

Gruß
Georg