Int() gibt zwei verschiedene Werte zurück !?

Liebes Forum, bei mir spukts. Besser gesagt: in meinem Arduino Nano.

Ich schreibe ein Programm, in dem ein (float-)Wert eingestellt werden kann: "blattdicke". Was und wozu, spielt IMHO keine Rolle. Jedenfalls muss er ins EEPROM geschrieben werden. Grundsätzlich funktiniert das auch, ABER VORHER wird der Wert leicht verfälscht.

Ich habe testhalber die drei Zeilen eingbaut:

  Serial.println("Blattdicke : " + String(blattdicke));
  Serial.println("Blattdicke * 100: " + String(blattdicke * 100));
  Serial.println("Blattdicke (int(blattdicke * 100)) : " + String(int(blattdicke * 100)));

Die Zeilen ergeben im Serial-Monitor:

16:37:49.540 -> Blattdicke : 1.60
16:37:49.577 -> Blattdicke * 100: 160.00
16:37:49.614 -> Blattdicke (int(blattdicke * 100)) : 159

Bitte WAS? 159 - Warum?

Ich habe das gleiche in einen neuen Sketch eingebaut und dort stimmt das Ergebnis:

16:37:05.199 -> Blattdicke : 1.60
16:37:05.199 -> Blattdicke * 100: 160.00
16:37:05.232 -> Blattdicke (int(blattdicke * 100)) : 160

Warum einmal 159 und einmal 160?

Weiß jemand wie ich den Poltergeist aus meinem Arduino vertreibe? Könnte das was mit dem freien RAM zu tun haben (ich nutze allerdings "nur" 80%)?

Danke und Gruß, kuahmelcher

Du bist auf eine typische Float Eigenschaft gestoßen.
Nicht alle Zahlen sind EXAKT in float darstellbar.
Weswegen sich auch Vergleiche mit == verbieten.

Deine 160 scheint dazu zu gehören.

Hier, ist zwar nicht C++, sondern PHP, aber der Warnung Kasten trifft auch auf C++ zu.
https://www.php.net/manual/de/language.types.float.php

OK, das scheint andere auch zu stören. Aber mein Verdacht mit dem Poltergeist ist dadurch eher bestätigt als widerlegt - oder? :laughing:

Lösungsvorschlag für das eigene Problem: "blattdicke" direkt und ausschließlich als INT anlegen und nur bei der Ausgabe durch 100 teilen? Ich probiere das mal aus. Gottseidank sieht man den Code ja bei der Ausführung nicht.
Danke und GRuß, kuahmelcher

Nö, funzt nicht. Vielleicht bin ich auch einfach ein zu schlichter Geist, ich hatte nur "Grundkurs MAthe". Vielleicht ist der wahre Wert von "160" eben doch nur "159". Hat man ja öfter im Leben. :crazy_face:

Nächster Ansatz: Ich runde vorm Speichern. Dummerweise werden immer "0.05" addiert oder subtrahiert, so dass vorher mit 500 multipliziert werden müsste, aber was schlaueres fällt mir nicht ein …

Gruß, kuahmelcher

Das was du suchst/meinst, nennt sich "Festkommaarithmetik".
Und nein, das ist nichts besonderes, sondern ein übliches Verfahren.

Warum speicherst du nicht die float Values?
Einmal float immer float.

Denn:
Vom konvertieren, werden die Zahlen weder schöner, noch irgendwie besser.

Ähem, weil ich nicht weiß dass und wie das geht, echt wahr. :grinning: Aber Danke für den Tipp, vielleicht ist das ja die einfachste Lösung.

Ja, das stimmt natürlich - und der Code wird auch immer unübersichtlicher.

Dann lass uns doch mal darüber reden!

Zeige was du hast, und ich zeige dir wie man das aufpeppen könnte.

Sehr gern. Also - ich habe insgesamt vier Werte, die ich speichern muss: einer ist ein byte, drei sind floats. Ich habe mir zwei Funktionen gebastelt, die eben ins EEPROM schreiben und lesen. Ich wandle momentan die "floats" in "ints" um und runde dabei so genau wie nötig (eine oder zwei Nachkommastellen). Die Schreib-Funktion zerhackt den übergebnen int-Wert erst mal arithmetisch und mit modulo und schreibt in zwei aufeinander folgende bytes. Ich glaube, das ist usus so.
Ich habe mal ein kleines Minimalbeispiel gebastelt - nonsense, aber da kannst du die Strategie leicht nachvollziehen.

Direkt floats zu speichern wäre schon an verschiedenen Stellen eine deutliche Vereinfachung.

Danke schon mal im Voraus und Gruß, kuahmelcher.

#include <EEPROM.h>

float blattdicke;

void setup() {
  blattdicke = eeprom_read(8) / 100.0;
}

void loop() {
  blattdicke = 2.5;
  eeprom_write(8, (blattdicke * 100));
}

void eeprom_write(byte adresse, int wert) {
  byte low  = wert % 256;
  byte high = wert / 256;
  EEPROM.update(adresse, low);
  EEPROM.update(adresse + 1, high);
}

int eeprom_read(byte adresse) {
  byte low  = EEPROM.read(adresse);
  byte high = EEPROM.read(adresse + 1);
  return low + ((high << 8) & 0xFF00);
}

Usus:

Durch häufiges Wiederholen üblich gewordene Verhaltensweise einer kleineren Gruppe von Personen

Mag sein......

Bei mir ist was anderes Usus.
Vielleicht ist die "ich Gruppe" ja noch kleiner


#include <Streaming.h> // die Lib findest du selber ;-)
Print &cout = Serial; // cout Emulation für "Arme"

#include <EEPROM.h>

template<typename T> void eeprom_write(T &eep, T &ram)
{
  EEPROM.put(int(&eep), ram); 
}

template<typename T> void eeprom_read(T &eep, T &ram)
{
  EEPROM.get(int(&eep), ram); 
}

struct Daten // Behaelter bauen
{
 float dicke;
 float a;
 float b;
 byte  c;
};

Daten imEEPROM EEMEM {2.1,4.2,PI,42}; // arduino baut *.eep Datei mit den default werten.



void setup() 
{
  Serial.begin(9600);
  cout << F("Start: ") << F(__FILE__) << endl;
  Daten temp {2.1,4.2,PI,42};
 // eeprom_write(imEEPROM,temp); // schreiben
  
  Daten imRAM;
  
  eeprom_read(imEEPROM,imRAM); // lesen

  cout << F("dicke: ") << imRAM.dicke << endl;

  // nur das byte lesen + schreiben
  int adresse = int(&imEEPROM)+offsetof(Daten,c); // 
  cout << F("das byte: ") << EEPROM[adresse] << endl;
  EEPROM[adresse]++; // byte hochzaehlen
  cout << F("das byte: ") << EEPROM[adresse] << endl;

  // Daten::b lesen
  float b;
  adresse = int(&imEEPROM.b);//alternative Adressberechnung
  EEPROM.get(adresse,b);
  cout << F("b: ") << b << endl;

  // Ohne Adresse
  eeprom_read(imEEPROM.b,b); // lesen
  cout << F("b: ") << b << endl; 
}

void loop() 
{

}

Hi Combie und Danke für das Beispiel. Ich verstehe den Code spontan nicht. Liegt an meinem fehlendem Know-How. :laughing:

Wenn du erlaubst formuliere ich ein paar Aussagen und du klärst mich auf, wo ich falsch liege - OK?

  • die Lib <Streaming.h> wird für COUT benötigt und hat mit dem EEPROM-Nutzung nix zu tun (habe ich noch nie benutzt)
  • die Struktur nutzt du, um immer alles "en bloc" schrieben und lesen zu können, auch die ist nicht zwingend nötig (habe ich auch noch nie benutzt)
  • die Templates nutzt du … ja, warum eigentlich? Wo ist der Vorteil zu einer normalen Funktion? (habe ich … )
  • wozu liest du nacheinander alle Adressen des EEPROMS aus? ("// nur das byte lesen + schreiben")?
  • wie liest du "// Ohne Adresse"?

Combie, ich glaube du überschätzt meine C-Kenntnisse - ich verstehe nur Bahnhof :roll_eyes:

Danke für Hinweise und Gruß, kuahmelcher.

Strukturen benutz man um verschiedenartige Dinge, zusammengehörige Dinge, zu Gruppieren.
Was zusammen gehörig ist, landet so in einem Klumpen.
Ich rate zur Ordnung und zu Systematiken.

Die Template Funktionen habe ich erstellt um (fast) ALLE Datentypen typesicher anfassen zu können. Über Referenzen. Und um von der händischen Adressvergabe weg zu kommen.

Du verwendest die feste Adresse 8. Das ist eine magische Zahl, welche du händisch festlegen musst. Ich lasse den Compiler alle Adressberechnungen machen. Der ist in dem Punkt erheblich zuverlässiger als jeder Mensch. Der ist nicht schlampig, irrt sich nicht.

Tue ich nicht.

Der wichtigste Teil:
Der Code zeigt, wie man Strukturen schreibt und liest.
Wie man eine Struktur, oder beliebige andere Variablen, im EEMEM platziert

Der Rest:
Dann zeigt er noch ein paar Wege, wie man auf die einzelnen Strukturelemente zugreifen kann, wenn man nicht den ganzen Klumpen auf einmal verarbeiten will.

Das ist kein C.
Arduino ist C++.
Und die EEPROM Klasse ist recht ausgefuchstes C++.
Die Klasse bietet u.A. ein Iterator und ein ArrayAccess Interface.

ca. 9 Jahre bist du schon dabei.
Evtl. ist es ja mal Zeit für ein schönes dickes und modernes C++ Buch, damit du die Sprache mal kennenlernst, welche du da verwendest.

Ich habe es noch mal vereinfacht, auf das wesentliche reduziert:

#include <EEPROM.h>


template<typename T> void eeprom_write(T &eep, T &ram)
{
  EEPROM.put(unsigned(&eep), ram); 
}

template<typename T> void eeprom_read(T &eep, T &ram)
{
  EEPROM.get(unsigned(&eep), ram); 
}



float blattdickeE EEMEM; // Blattdicke liegt im EEPROM 

void setup() 
{
  Serial.begin(9600);
  
  float blattdicke = 2.5; // lokale Blattdicke
  eeprom_write(blattdickeE,blattdicke); // lokale Blattdicke ins EEPROM schreiben
} // lokale Blattdicke vergessen

void loop() 
{
  delay(2000);
  
  float blattdicke; // lokale Blattdicke
  eeprom_read(blattdickeE,blattdicke); // lokale Blattdicke aus EEPROM einlesen
  Serial.println(blattdicke); // lokale Variable ausgeben
} // lokale Blattdicke vergessen


Noch Fragen offen?

Ja:

Der Sinn dieses Wrappers ist doch nur, dass er sicherstellt, dass eep und ram vom gleichen Typ sind, oder?
Aber dass eep eine Variable mit EEMEM Attribut ist und ram eine normale im RAM , muss man schon selber richtig machen, damit es funktioniert.
Sonst kriegt man keinen Fehler, nicht mal eine Warnung.
Man muss also schon genau wissen was man da tut, und kann dann auch genausogut direkt
EEPROM.put verwenden?

Insbesondere dass

float blattdicke; // lokale Blattdicke
  eeprom_read(blattdicke,blattdickeE); // lokale Blattdicke aus EEPROM einlesen

völlig falsch ist, sagt einem keiner.

Da ist mir EEPROM.get eigentlich lieber, was auf den Unterschied zwischen einer EEPROM Adresse und einer beliebigen Variablen achtet.

Ja, das ist ein Grund!
Der zweite Grund ist den Cast auf die Adresse zu kapseln.
Typesicher zu kapseln.
Der dritte: Sich nicht um Adressen zu kümmern.
Der vierte: Weils einfach besser ist.
Der fünfte: Du bist dagegen, also hat sich die Provokation gelohnt :japanese_ogre:

Ja!
Denn der Compiler weiß das nicht.
Memory Sections sind nicht Teil der C++ Spezifikation, sondern in diesem Fall eine AVR Spezialität. Schade, aber von mir nicht beeinflussbar.

Es werden durch das Verfahren mindestens drei Fehlerquellen vermieden/gemeldet.
Eine Fehlerquelle erfasst es nicht.
Und zwar: Das menschliche Unvermögen, die Funktionssignatur zu akzeptieren.
Das empfinde ich auch als schade.
Sehe aber keine Chance das ressourcenschonend zu lösen.

Häää... (du machst mich verwirrt)
Das get() interessiert sich für gar nix!
(dachte ich bisher)

Oder, zeige mir bitte was du meinst, wie get() den Unterschied beachtet, oder am besten auch meldet.
Denn das kenne ich noch nicht.
Ich möchte lernen.

Ja, OK. Grundsätzlich sicher richtig.

OK, sicher für größere Speichernutzungen der einzig richtige Weg. Aber ausgerechnet MEIN Compiler scheint eine ziemliche Schlampe zu sein (siehe Originalpost), oder besser gesagt der Prozessor. :sweat_smile: (Ich wäre erstickt, wenn ich das nicht gesagt hätte)

Ich habe mir den Sketch mal unter 2022 gespeichert und werde ihn "zerlegen" und damit 'rumprobieren. Mittlerweile weiß ich, dass solche Dinge oft nur deshalb kompliziert erscheinen, weil ich sie noch nicht richtig verstanden habe.

Ja … allerdings bin ich bisher in diesem Forum hervorragend gefahren: Freundliche Fragen ergeben freundliche Antworten. Könntest du irgend ein Buch empfehlen? Ich bin ja nicht unwillig, nur möchte ich mich nicht mit theorielastigen, trockenen Büchern rumärgern. Das lese ich eh nicht. Wie du sicher gemerkt hast mache ich das "nur" zum Spaß. :grinning:

Na - jedenfalls Danke für die Mühen, ich habe zwei neue Ansätze, die ich übernehmen werde.
Gruß, kuahmelcher.

Dann vergiss bitte alles, was ich je zu dir gesagt habe, und alles was ich zukünftig sage, bitte ignorieren.
Vielleicht sogar wohlwollend ignorieren.

Das klingt jetzt irgendwie enttäuscht. Da gibt‘s keinen Grund zu. Du hast mir jetzt schon geholfen und ich bin sehr dankbar. Ich will keine doku vorgelesen bekommen, die Frage war eher nach einer Empfehlung. Schlechte dokus lese ich nicht (wenn‘s nicht sein muss).

Danke und Gruß, kuahmelcher

Nee, nee...
Ich bin nicht enttäuscht. Da mache dir mal keine übertriebenen Hoffnungen...
Und selbst wenn, wäre eine solche Enttäuschung, doch eher ein Grund zur Freude!

Ein C++ Grundlagen Buch ist immer Theorielastig.
Muss es sein! Es kann nicht anders.
Es ist eine aneinander Reihung von meist glasharten und staubtrocknen Fakten.

Zudem habe ich mich hier im Forum schon längst, klar und deutlich, für ein bestimmtes Buch ausgesprochen. Ok, die veraltete Auflage ist mittlerweile vergriffen (gewesen). In neuerer Version C++20 ist es weiterhin erhältlich.

Bin mir aber längst nicht sicher, dass/ob dir das Buch schmeckt. Befürchte da das Gegenteil. Denn aus deinen Postings leuchtet mir ein bisschen Ablehnung entgegen.

So, das habe ich jetzt mal getestet.
Denn ich konnte nicht glauben, was ich da lesen musste.

Diese Verwirrung musste ich auflösen.

Hier dann das Resultat:

/*
 * Disclaimer:
 * Dieser Code enthält logische Fehler 
 * welche vom Compiler nicht erkannt 
 * werden können
 */

#include <EEPROM.h>

unsigned testR; // im ram
unsigned testE EEMEM; // im eeprom

unsigned adresse {8};

void setup() 
{
  EEPROM.get(unsigned(&testE), testR); 
  EEPROM.get(unsigned(&testR), testE); 

  EEPROM.get(testE, testR); 
  EEPROM.get(testR, testE); 

  EEPROM.get(adresse, testR); 
  EEPROM.get(adresse, testE); 
  
  EEPROM.get(testE, adresse); 
  EEPROM.get(testR, adresse); 
}

void loop() 
{

}