Variablen bitweise/direkt beschreiben

Hallo @all,
ich bin nicht der C-Profi, deshalb schon wieder eine Frage: Ich möchte einen Float-Wert in einem Eeprom speichern. Der Eeprom arbeitet ja Byte-Orientiert und der Float-Wert ist (lt. Doku) ja 4 Byte groß.

Wie kann ich unter C eine Float-Variabel direkt (also die 4 Byte) auslesen und schreiben, damit ich die Werte in ein Eeprom schreiben kann oder aus dem Eeprom wieder herstellen kann?

Vielen Dank für jeden Tipp

mcGeorge:
Ich möchte einen Float-Wert in einem Eeprom speichern. Der Eeprom arbeitet ja Byte-Orientiert

Also byteweise und nicht wie im Thema angegeben bitweise.

mcGeorge:
und der Float-Wert ist (lt. Doku) ja 4 Byte groß.

Wenn Du unsicher bist oder Du es dem Compiler überlassen möchtest, die genaue Datengröße festzustellen, kann Du es mit "sizeof" genau feststellen.

mcGeorge:
Wie kann ich unter C eine Float-Variabel direkt (also die 4 Byte) auslesen und schreiben, damit ich die Werte in ein Eeprom schreiben kann oder aus dem Eeprom wieder herstellen kann?

Da gibt es verschiedene Möglichkeiten. Ich deklariere mir für einen solchen Fall einen Byte-Pointer auf die Float-Variable.

Deklaration:
float myFloatVar;
byte* wert = (byte*) &myFloatVar;

Nun kann ich mit wert[0], wert[1], wert[2], wert[3] auf die einzelnen Bytes zugreifen.

Ich habe mal einen Beispiel-Sketch gemacht, in dem diese Technik genutzt wird und die beiden Funktionen EEPromSave und EEPromLoad dabei so allgemein gehalten, dass man diese Funktionen auch in richtigen Sketches zum Speichern in und Laden aus dem EEPROM verwenden kann.

#include <EEPROM.h>


void EEPromSave(byte* wert, int numbytes, int eprom_offset)
{
  // input:
  // wert: pointer to byte array which holds the value to save
  // numbytes: number of bytes to be saved
  // eprom_offset: offset in EEPROM for saving bytes
  // output: void (nothing)
  for (int i=0;i<numbytes;i++)
    EEPROM.write(eprom_offset+i, wert[i]);
}

void EEPromLoad(byte* wert, int numbytes, int eprom_offset)
{
  // input:
  // wert: pointer to byte array which takes the loaded value
  // numbytes: number of bytes to be loaded
  // eprom_offset: offset in EEPROM for loading bytes
  // output: void (nothing)
  for (int i=0;i<numbytes;i++)
    wert[i] = EEPROM.read(eprom_offset+i);
}


#define FLOATSAVEOFFSET 100

void setup()
{
  float myFloatVar;
  byte* myFloatVarBytes = (byte*) &myFloatVar;

  Serial.begin(9600);
  myFloatVar=47,11;
  Serial.print("myFloatVar anfangs: ");Serial.println(myFloatVar);
  EEPromSave(myFloatVarBytes, sizeof(myFloatVar), FLOATSAVEOFFSET);
  myFloatVar=0;
  Serial.print("myFloatVar anderer Wert: ");Serial.println(myFloatVar);
  EEPromLoad(myFloatVarBytes, sizeof(myFloatVar), FLOATSAVEOFFSET);
  Serial.print("myFloatVar aus EEPROM: ");Serial.println(myFloatVar);
}

void loop()
{
}

Achtung: Das EEPROM ist nicht beliebig oft beschreibbar!
Also am besten die Zeile mit dem Aufruf von EEPromSave auskommentieren, wenn sie nicht tatsächlich benötigt wird. Lesen aus dem EEPROM kann und darf man beliebig oft.

Alternativ zu jurs' Pointern kannst du eine union verwenden:

union { float f; byte b[4];} x;

Der gleiche Speicherplatz kann nun als float x.f oder als byte array x.b[] angesprochen werden.

michael_x:
Alternativ zu jurs' Pointern kannst du eine union verwenden:

Aber nicht zusammen mit den oben von mir geposteten Universalfunktionen zum Speichern in und Laden aus dem EEPROM, die man für alles mögliche verwenden kann. Auf meine Weise schreibe ich eine Ladefunktion und eine Speicherfunktion mit Übergabe eines Pointers auf einen Datenbereich und die Größe des Datenbereichs, und egal was in dem Datenbereich drin ist, wird damit alles gespeichert und geladen: floats und ints, eindimensionale float- oder int-arrays, char-arrays, mehrdimensionale float- int oder char-arrays, structs und was es sonst an Datentypen alles gibt. Da brauche ich nicht für jede neue Art von Variable auch neue Lade- und Speicherfunktionen schreiben.

Wenn es mit Pointern gemacht wird statt mit neu erfundenen Datentypen, fördert es die Wiederverwertbarkeit von Code, weil man eben nicht für jeden Spezialfall schon wieder speziellen Code schreiben muss.

Stimmt schon!

Wenn Datentypen egal sind: Was hältst du von einem void* ?
Spart dir in deinem Beispiel eine Variable, zumindest ein cast bei der Verwendung deiner Funktion.

/* Aktualisiert den EPROM mit dem hier definierten Wert, 
    nur als Beispiel, das für sich gesehen natürlich keinen Sinn macht 
;*/
void EEPromSave(void* wert, size_t size, uint16_t eprom_offset); // woanders implementiert, Ersatz für #include "EEProm.h"
void EEPromLoad(void* wert, size_t size, uint16_t eprom_offset);

float myFloatVar = 47.11;
const uint16_t FLOATSAVEOFFSET= 0;

void setup()
{
  float oldFloat;
  EEPromLoad(& oldFloat, sizeof(oldFloat),  FLOATSAVEOFFSET);
  if ( oldFloat != myFloatVar)   // nur bei Änderung EEPROM schreiben 
      EEPromSave(& myFloatVar, sizeof(myFloatVar), FLOATSAVEOFFSET);
}
void loop() { }

michael_x:
Wenn Datentypen egal sind: Was hältst du von einem void* ?

Hm, gerade mal getestet. Die EEPROM.write und EEPROM.read Funktionen mögen auf meinem Arduino keine per void* übergebenen Werte ins EEPROM schreiben, sondern damit liefert der Compiler bei mir:

error: pointer of type 'void *' used in arithmetic

Ich sehe momentan konkret nicht, wie es mit void* funktionieren soll.

error: pointer of type 'void *' used in arithmetic

Ich sehe momentan konkret nicht, wie es mit void* funktionieren soll.

Klar, deine write und read Methode muss intern schon wissen was sie machen soll,
(nämlich den void * als byte * verwenden)
Aber der Benutzer deiner Funktion kriegt mit der Deklaration des Übergabeparameters als void * gesagt, dass er jeden Pointer liefern darf.
Das ist doch das was du willst, oder ?

michael_x:
Klar, deine write und read Methode muss intern schon wissen was sie machen soll,
(nämlich den void * als byte * verwenden)

Na ja, EEPROM.write und EEPROM.read sind nicht "meine", sondern Methoden aus "#include <EEPROM.h>", und die wissen nur, wie sie bytes lesen und schreiben. Wenn ich byte* an meine eigenen Funktionen übergebe, weiß der Compiler, dass er den Funktionen aus EEPROM.h ein Byte übergeben muss. Wenn ich bei mir void* übergebe, dann weiß der Compiler es nicht.

Anyway: Der von mir oben in meiner ersten Antwort gepostete Sketch funktioniert. Falls sich jemand an den zwei Bytes überflüssigem Speicherverbrauch stört durch die Deklaration von "wert":
byte* wert = (byte*) &myFloatVar;
der kann diese Deklaration auch weglassen und stattdessen direkt im Funktionsaufruf auf byte* typecasten:

void setup()
{
  float myFloatVar;
  Serial.begin(9600);
  myFloatVar=90.23;
  Serial.print("myFloatVar anfangs: ");Serial.println(myFloatVar);
  EEPromSave((byte*) &myFloatVar, sizeof(myFloatVar), FLOATSAVEOFFSET);
  myFloatVar=0;
  Serial.print("myFloatVar anderer Wert: ");Serial.println(myFloatVar);
  EEPromLoad((byte*) &myFloatVar, sizeof(myFloatVar), FLOATSAVEOFFSET);
  Serial.print("myFloatVar aus EEPROM: ");Serial.println(myFloatVar);
}

Kann ja mal sein, dass es auf 2 Bytes RAM-Speicherverbrauch ankommt.

Obwohl: Wenn man viel mehr als 2 Bytes RAM-Speicher einsparen wollte, würde man in seinen Programmen ja zunächst mal komplett auf den Datentyp "float" verzichten. So schlimm scheint es also beim OP noch nicht zu sein, dass er in seinem Programm noch mit float und massig Speicherverbrauch herumfuhrwerken kann.

Schau mal hier: Arduino Playground - EEPROMWriteAnything