Forum: Compiler & IDEs struct aus eeprom an funktion übergeben


von technikus (Gast)


Lesenswert?

Hi Forum,

ich tue mich bei oben genanntem Thema schwer und bitte um eure Hilfe!

Ich habe eine struktur definiert
1
typedef struct
2
{
3
  uint8_t protocol;
4
  uint16_t address;
5
  uint16_t command;
6
}structIR;

Ein Array dieser Struktur wird im EEPROM abgelegt
1
structIR eIRFunctions[]  EEMEM = {
2
  { 0x00,0x00,0x00},
3
  { 0x00,0x00,0x00},
4
  { 0x00,0x00,0x00},
5
  { 0x00,0x00,0x00},
6
  { 0x00,0x00,0x00},
7
  { 0x00,0x00,0x00},
8
  { 0x00,0x00,0x00},
9
  { 0x00,0x00,0x00},
10
  { 0x00,0x00,0x00},
11
  { 0x00,0x00,0x00},
12
  { 0x00,0x00,0x00}};

Jetzt möchte ich einer Funktion ein Array Element dieser Struktur 
übergeben und in der Funktion die einzelnen Werte der Strukturfelder im 
EEPROM Speichern.
1
Pseudocode:
2
3
setIrDataToEeprom (&eIRFunctions[0]);
4
5
void setIrDataToEeprom( blablub )
6
{
7
    eeprom_write_byte(blablub.protocoll, 11);  //11 in Strukturelement protocol schreiben
8
    eeprom_write_word(blablub.address, 22);  //22 in Strukturelement address schreiben
9
    eeprom_write_word(blablub.command, 33);  //33 in Strukturelement command schreiben
10
}

Trotz Suche stehe ich total auf dem Schlauch!
Kann mir jemand näher bringen, zu verstehen wie ich die Aufgabenstellung 
löse?

Dank im Vorraus!

Gruß
Stefan

von Mark B. (markbrandis)


Lesenswert?

Das Zauberwort heißt "Zeiger auf Strukturen". Naja, die Zauberwörter.

Deklaration:
1
void setIrDataToEeprom(structIR* structIR_ptr);

Implementierung:
1
void setIrDataToEeprom(structIR* structIR_ptr)
2
{
3
    eeprom_write_byte(structIR_ptr->protocol, 11);  //11 in Strukturelement protocol schreiben
4
    eeprom_write_word(structIR_ptr->address,  22);  //22 in Strukturelement address schreiben
5
    eeprom_write_word(structIR_ptr->command,  33);  //33 in Strukturelement command schreiben
6
7
}

Aufruf (wie gehabt):
1
setIrDataToEeprom (&eIRFunctions[0]);

von Klaus (Gast)


Lesenswert?

Nun, das Schlüsselwort EEMEM ist eine GCC-Erweiterung gegenüber dem 
Standard, der es Dir erlaubt, Variablen im EEPROM zu deklarieren und zu 
initialisieren. An sich kennt C keinen Unterschied zwischen 
Speicherarten.

Entsprechend gehen alle weiteren Operatoren immer davon aus, dass 
Variablen im RAM liegen und der Compiler generiert entsprechende 
Operationen des uC - einen AVR vorausgesetzt -, (in Assembler) daraus.
Wie Du sicher schon festgestellt hast, kennt der AVR keine Befehle um 
Daten aus dem EEPROM zu lesen oder darein zu schreiben - jedenfalls 
nicht in der gleichen Granularität wie solche Daten im RAM. D.h. es sind 
etwa Daten als 8 oder 16-Bit Wort zu lesen und zu schreiben. Das ist 
die, man könnte sagen: primäre Funktion eines uC. Es ist sogar so, dass 
beim AVR eine gewisse Abfolge von Befehlen eingehalten werden muss und 
nur Blöcke von Daten geschrieben werden können. Ausserdem sind solche 
Operationen wesentlich langsamer als solche auf das RAM.

Im Detail kann demnach der '.'-Operator nicht auf Operanden im EEPROM 
angewendet werden. Du musst ihn also für das EEPROM irgendwie mit einer 
Funktion emulieren. Letzlich läuft das daraus hinaus, die Adresse eines 
Strukturmitglieds zu ermitteln. Das wiederrum ist deswegen ein wenig 
komplex, weil Du dazu einen Zeiger auf solch eine Struktur benötigst und 
ausserdem voraussetzen (bzw. prüfen) musst, dass das Layout der Struktur 
im RAM und EEPROM gleich ist. Das ist aber - meiner Kenntnis nach - der 
Fall. Ein letzter Faktor ist die Anordnung der Strukturmitglieder, die 
nicht zwingend ohne Lücken erfolgt. Dazu gibt es das "pragma packed". 
Ich weiss gerade nicht ob der GCC etwa EEMEM structs per default packt.

Ich hoffe die Hinweise helfen Dir ein wenig.

von technikus (Gast)


Lesenswert?

Vielen Dank für eure Ausführungen!
Ich werde das heute testen.

Gruß
Stefan

von Karl H. (kbuchegg)


Lesenswert?

Mark B. schrieb:

> Deklaration:
>
1
> void setIrDataToEeprom(structIR* structIR_ptr);
2
>
>
> Implementierung:
>
1
> void setIrDataToEeprom(structIR* structIR_ptr)
2
> {
3
>     eeprom_write_byte(structIR_ptr->protocol, 11);  //11 in 
4
> Strukturelement protocol schreiben
5
>     eeprom_write_word(structIR_ptr->address,  22);  //22 in 
6
> Strukturelement address schreiben
7
>     eeprom_write_word(structIR_ptr->command,  33);  //33 in 
8
> Strukturelement command schreiben
9
> 
10
> }
11
>
>
> Aufruf (wie gehabt):
>
1
> setIrDataToEeprom (&eIRFunctions[0]);
2
>

Aber eigentlich macht man auch das nicht so.

Ausgangspunkt ist erst mal, dass man ein derartiges Objekt grundsätzlich 
im Speicher braucht, damit man damit in weiterer Folge damit arbeiten 
kann.
Man wird also ein
1
structIR funcData;
haben (oder ein entsprechendes Array, wenn man mehrere derartige Daten 
gleichzeitig im Speicher braucht).

Gilt es da andere Werte reinzuschreiben, dann wird man das direkt in 
diesem Objekt machen. Zum Beispiel durch ....
1
    funcData.protocoll = 11;
2
    funcData.address = 22;
3
    funcData.command = 33;
... oder wie auch immer dann die Werte zustande kommen.

Ist es Zeit die entsprechenden Objektinhalte ins EEPROM zu schreiben 
bzw. vom EEPROM zu lesen, dann gibt es dafür die Funktion 
eeprom_write_block, die einen kompletten zusammenhängenden 
Speicherbereich ohne Ansehen der Innereien aus dem SRAM ins EEPROM 
verfrachtet bzw. umgekehrt vom EEPROM ins SRAM holt. D.h. an dieser 
Stelle muss ich dann gar nicht mehr wissen, wie die Struktur aufgebaut 
ist. Eine derartige Spezialfunktion für die Memberweise Überführung ist 
nur dann notwendig, wenn nicht alle Member der Struktur im EEPROM 
gespeichert werden müssen (Padding ist auf einem AVR ja kein Thema). Im 
Falle eines Arrays erledigt dann zb beim Programmstart ein einziger 
eeprom_read_block die komplette Initialisierung des kompletten Arrays 
aus dem EEPROM.
1
#define NR_FUNCTIONS 11
2
3
structIR IRFunctions[NR_FUNCTIONS];
4
structIR eIRFunctions[NR_FUNCTIONS] EEMEM;
5
6
....
7
int main()
8
{
9
  eeprom_read_block( IRFunctions, eIRFunctions, sizeof(IRFunctions) );
10
11
....

bzw. beim Update zb durch Benutzerinterkation wird 1 Eintrag neu 
geschrieben, nachdem der Benutzer die Werte entsprechend verändert hat:
1
  eeprom_write_block( &IRFunctions[i], &eIRFunctions[i], sizeof(structIR) );

von Mark B. (markbrandis)


Lesenswert?

Karl H. schrieb:
> Ausgangspunkt ist erst mal, dass man ein derartiges Objekt grundsätzlich
> im Speicher braucht, damit man damit in weiterer Folge damit arbeiten
> kann.

Hast Recht. War noch zu früh heute morgen. Hab wohl das EEMEM 
übersehen...

: Bearbeitet durch User
von technikus (Gast)


Lesenswert?

Hallo Karl Heinz,

vielen Dank für deine gut verständlichen Ausführungen!
Wie man heruasliest, bin ich nur Hobby AVR Programmierer. Sobald ein 
Bedürfnis entsteht, suche ich nach einer Lösung.

Heißt das, dass man darauf achten sollte zu allen EEPROM Variablen ein 
Gegenstück im SRAM halten sollte ?
Also auch bei weniger komplexen Datentypen wie uint8_t etc. ?

Wenn ja, müsste ich meine Architektur überdenken.

Jetzt wollte ich die Funktion zum Schreiben des Arrayfeldes in das 
EEPROM ja universell gestaltet. Wie es aussieht benötige ich hierzu drei 
Übergabeparameter.

1. structIR IRFunctions[NR_FUNCTIONS];
2. structIR eIRFunctions[NR_FUNCTIONS] EEMEM;
3. sizeof(structIR)

Und genau hier stehe ich auf dem Schlauch.
- Werden EEPROM und SRAM Variable als Pointer übergeben?
- Wie geschrieben?
- und an eeprom_write_block() witergegeben

Deklaration
1
void setIrDataToEeprom(structIR* structIR_ptr_src,structIR* structIR_ptr_src, size_t size);

Implementierung
1
void setIrDataToEeprom(structIR* structIR_ptr_src,structIR* structIR_ptr_dest, size_t size)
2
{
3
  structIR_ptr_src->protocoll = 11;  //hier ist mir nicht klar ob ich mit dem -> Operator richtig liege
4
  eeprom_write_block(&structIR_ptr_src, &structIR_ptr_dest, size);
5
}

Aufruf
1
   setIrDataToEeprom(&IRFunctions[0],eIRFunctions[0], sizeof(structIR))

Es wäre super, wenn mir hier jemand erklärend unter die Arme greifen 
kann.
Ich welze jetzt mal weiter mein C Buch ;)


Gruß
Stefan

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

technikus schrieb:

> Heißt das, dass man darauf achten sollte zu allen EEPROM Variablen ein
> Gegenstück im SRAM halten sollte ?

Nein. Das wäre Speicherwahnwitz. Aus den gewählten Variablen-Namen kann 
man schlussfolgern, dass es sich um eine IRMP-Anwendung handelt.

Du solltest Dir daher mal den Source von

      DIY Lernfähige Fernbedienung mit IRMP

anschauen, wie das dort gelöst ist.

Der Trick ist ganz einfach: Du brauchst nur eine IRMP-Struct und 
liest/schreibst auch nur ad hoc die Daten eines IRMP-Datensatzes - 
nämlich dann, wenn Du ihn brauchst.

> Jetzt wollte ich die Funktion zum Schreiben des Arrayfeldes in das
> EEPROM ja universell gestaltet. Wie es aussieht benötige ich hierzu drei
> Übergabeparameter.

Benutze das EEPROM einfach als RAM, indem Du auf die Daten mit den 
eeprom-Funktionen (read/write) zugreifst.

> Und genau hier stehe ich auf dem Schlauch.
> - Werden EEPROM und SRAM Variable als Pointer übergeben?

Schau einfach in den Source der lernfähigen Fernbedienung. Das einzige, 
was dort komplizierter ist: Die Fernbedienung dort hat mehrere virtuelle 
Ebenen, mit denen die Tasten belegt werden können, d.h. dort hat man 
noch eine Dimension mehr als Du hast. Ausserdem können die Tasten mit 
Makros belegt werden, d.h. es wird beim Drücken einer Taste eine ganze 
Serie von IRMP-Frames gesendet. Macht eine weitere Dimension mehr ;-)

Aber lass Dich nicht verwirren, sondern schreib Dir am besten 2 
Funktionen:

  read_irmp_data_from_eeprom (&irmp_data, n);
  write_irmp_data_to_eeprom (&irmp_data, n);

mit
  IRMP_DATA irmp_data;    // IRMP-Daten im RAM
  uint8_t n;              // Index in Deinem EEPROM-Array

Das Array selbst brauchst Du nur im EEPROM (also mit EEMEM definiert).

P.S.
Wenn Du Deine abgespeckte struct "structIR" nutzen willst, geht das 
natürlich genauso. Dort hast Du die flags weggelassen. Spart natürlich 
EEPROM-Platz. Dann musst Du halt nochmal zwischen structIR und IRMP_DATA 
kopieren. Aber eine kleine Warnung: In (flags & 0xF0) stehen beim 
KASEIKYO-Protokoll zusätzliche Daten, die nicht mehr in uint16_t command 
passen. Wenn Du kein KASEIKYO-Protokoll benutzt, kannst Du das natürlich 
ignorieren und weglassen. Wenn Du aber den Platz im EEPROM hast, 
solltest Du ruhig die komplette IRMP-Struct speichern. Die Flags 
solltest Du dann aber vor dem Speichern bereinigen mit:

    irmp_data.flags &= 0xF0;

bzw.

    irmp_data.flags &= ~IRSND_REPETITION_MASK;

, um etwaige empfangenen Repetition-flags oder andere zukünftige 
IRMP-Flags auszumaskieren, d.h. zu löschen.

: Bearbeitet durch Moderator
von Karl H. (kbuchegg)


Lesenswert?

technikus schrieb:

> Heißt das, dass man darauf achten sollte zu allen EEPROM Variablen ein
> Gegenstück im SRAM halten sollte ?
> Also auch bei weniger komplexen Datentypen wie uint8_t etc. ?

Du holst dir das in den SRAM Speicher, was du zum arbeiten brauchst. 
Wenn du 25 Konfigurationen im EEPROM hast, von denen aber immer nur eine 
die aktive Konfiguration ist, dann holst du dir auch nur diese eine.

Das willst du sowieso machen, denn der Zugriff aufs EEPROM ist langsam! 
Daher willst du das EEPROM nicht einfach nur wie einen Speicher ansehen, 
so wie jeden anderen auch. Ausser natürlich du hast keine andere Wahl.
Du kannst den EEPROM wie das Hochregallager ansehen, in dem du Werkzeuge 
auf Dauer verstaust. Die Werkzeuge, die du zum Arbeiten brauchst, die 
hast du aber vor dir auf der Werkbank. Mit denen arbeitest du laufend.

> Und genau hier stehe ich auf dem Schlauch.
> - Werden EEPROM und SRAM Variable als Pointer übergeben?

Ja natürlich. Arrays werden an Funktonen in C immer so 'übergeben', dass 
die Funktion die Startadresse des Arrays bekommt.

von Adib (Gast)


Lesenswert?

Hallo Technikus,

wenn ich Konfigurationswerte im EEPROM ablege, sorge ich dafür, dass die 
Strukturen gepackt sind.

Es könnte sonst passieren, dass bei unterschiedlichen 
Compiler-Einstellungen, die Werte auf unterschiedlichen Bytepositionen 
liegen.
Und dann liest du mit dem neuen Compilat nicht mehr das ein, was beim 
alten Compilat noch ging.

Stichworte sind hier:
 __attribute__((_packed_)) und als Compileroption -fpack-struct

Grüße, Adib.

von Karl H. (kbuchegg)


Lesenswert?

Adib schrieb:
> Hallo Technikus,
>
> wenn ich Konfigurationswerte im EEPROM ablege, sorge ich dafür, dass die
> Strukturen gepackt sind.

Die sind auf einem AVR sowieso immer gepackt, weil es keinen Grund gibt 
warum der Compiler auf einem AVR padding Bytes einfügen sollte.

Viel schwerwiegender ist, dass du mittels EEMEM keine Kontrolle darüber 
hast, wo der Linker die EEMEM Variablen im EEPROM anordnet.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.