[gelöst]Daten in struct eintragen/auslesen

Hi

Bin schon einige Zeit dabei, mir mittels struct einen Datentyp zu bauen, Der meine CAN-Nachrichten/Teilnehmer aufnimmt.
Global erstelle ich, so mein Verständnis, den Datentyp CAN_NACHRICHT und deklariere auch gleiche eine Variable CAN_MSG dieses Typs.

struct CAN_NACHRICHT{
  union id {
    uint32_t _dword;
    uint16_t _word[2];
    uint8_t  _byte[4];
  };
  union value {
    uint64_t _ddword;
    uint32_t _dword[2];
    uint16_t _word[4];
    uint8_t _byte[8];
  };
  uint32_t _timestamp;
} CAN_MSG;

Global kann ich hier keine Daten eintragen, dort bekomme ich die Fehlermelung

'CAN_MSG' does not name a type

Nach einigen Runden Try&Error fand ich heraus, daß ich z.B. in setup(); der Variable durchaus Werte zuordnen kann.
Die gleiche Zeile in setup(); wird klaglos geschluckt :).
Aber ich bekomme keine Daten in die per union aufgesplitteten 'Bereiche' rein.
Bei

CAN_MSG.id._byte[0]=0;

bekomme ich

invalid use of 'union CAN_NACHRICHT::id'

.

Ich verstehe nicht, was ich hier falsch mache.

Weiß hier Jemand mehr oder kann mein Code-Stück soweit umstricken, daß ich hier was eingetragen bekomme?

MfG

Einfach eine union zu definieren reicht nicht.
Du musst im struct auch schon eine Instanz der union anlegen/vorbereiten, sonst wird das nix.

Beispiel:

struct 
{
  union  
  {
    uint32_t _dword;
    uint16_t _word[2];
    uint8_t  _byte[4];
  }id;
  union  
  {
    uint64_t _ddword;
    uint32_t _dword[2];
    uint16_t _word[4];
    uint8_t _byte[8];
  }value;
  uint32_t _timestamp;
} CAN_MSG;


void setup() 
{
  CAN_MSG.id._byte[0]=0;
  Serial.begin(9600);
  Serial.println("Start");
}

void loop() 
{

}

Bedenke:
Diese Art der Type Konvertierung, kann böse ins Auge gehen.

Hi

Da habe ich gerade die Lösung gefunden, Da schreibst Du quasi genau das Gleiche :o

Viele Try&Error später kam ich auch auf diese Idee:

typedef struct {
  union {
    uint32_t _dword;
    uint16_t _word[2];
    uint8_t  _byte[4];
  }id;
  union  {
    uint64_t _ddword;
    uint32_t _dword[2];
    uint16_t _word[4];
    uint8_t _byte[8];
  }value;
  uint32_t _timestamp;
}CAN_NACHRICHT;         //Datentyp 'CAN_NACHRICHT' erstellt
CAN_NACHRICHT CAN_MSG;  //Variable dieses Typ erstellt

Sobald ich in einer Funktion bin, kann ich auch Werte zuweisen, z.B. in setup():

 CAN_MSG._timestamp = millis();
  CAN_MSG.id._byte[0]=0;

CAN_NACHRICHT versuchsvariable = {23,55,223}; //ID 23, Nachricht 55, Timestamp 223 geht auch :)

Worin denkst Du, besteht hier die Gefahr?
_ddword werde ich wohl nicht benötigen, aber warum nicht Nehmen, was man eh bekommt und nicht extra bezahlen muß?
Die ID liegt mir als uint32_t vor und wird wohl auch nur noch als Byte benutzt (zum Speichern im FRam).
Gleiches mit der eigentlichen Nachricht, Die bei CAN 8 Byte betragen kann.

Danke Dir für Deine Korrektur - schön, daß sich Das mit meinem Erguss deckt :slight_smile:

MfG

Edit
Abgekupfert von
Strukturen, sommergut.de

postmaster-ino:
Bin schon einige Zeit dabei, ...

Dein Problem ist mir bestens bekannt. Wobei mich die Fehlermeldung reichlich in die Irre geführt hat. Anfängerglück :slight_smile:

„Funktions-/Methodenaufrufe“ niemals außerhalb setup() oder loop() hat sich hoffentlich als „Standardverhalten“ beim Programmieren in mein Hirn gebrannt. War das eine Flucherei ...

Gruß

Gregor

C.183: Don’t use a union for type punning

Ich durfte mal in einem Team mitwirken, da wurde sowas nicht diskutiert.
Jede Art von Beharren da drauf, wäre ein Grund für eine Abmahnung gewesen.

Hi

Schwere Kost, der Link. :o
Eigentlich brauche ich nur die ID zerstückelt in Bytes, die Nachricht sind eh Bytes.
Da nur ich die Daten speicher bzw. lese, sehe ich noch kein Problem darin, wenn die 32-bit-ID statt wie vermutet in Little-Endian in Big-Endian im FRam landet.
Beim Auslesen stückel ich mir die Einzelbytes ja wieder 'in der richtigen Reihenfolge' zusammen, wodurch sich die 32-Bit-ID wieder ergeben sollte.
Hier dürfte es egal sein, ob _byte[0] MSB oder LSB darstellt, da der Inhalt beim Rücklesen ja wieder in _byte[0] landet.

Habe ich die Befürchtung zu dem Leitfaden hier richtig interpretiert?

MfG

postmaster-ino:
Schwere Kost, der Link. :o

Allerdings. Man achte auf die Feinheiten :slight_smile:

Ich habe bislang alles mit Klassen oder Strukturen (structs) hinbekommen. Hat vielleicht damit zu tun, dass ich meinen Einstieg direkt in C++ gemacht habe.

Gruß

Gregor

Habe ich die Befürchtung zu dem Leitfaden hier richtig interpretiert?

Du möchtest Bestätigung, für dein Beharrungsvermögen, obwohl ich mein Urteil schon in mächtige Worte verpackt hatte.

Beharrungsvermögen ist an sich ok.....
Das das Ziel ist in dieser konkreten Situation .... irgendwie.... zumindest seltsam.

Warum verwendest du nicht den Vorschlag aus dem Link?
Der ist unkompliziert und tuts mit allen Kompiler gleichemaßen.
Von heute, bis in die nächsten 100 Jahre. zumindest solange, wie es C++ gibt.
Und ja, auch dieser Weg löst nicht das Endian Problem.... da ist Handarbeit angesagt.

void setup() 
{
  uint32_t id = 0x11223344; 
  
  Serial.begin(9600);
  Serial.println("Start");
  
  for(int i = 0;i < sizeof(id); i++)
  {
    Serial.println(*(reinterpret_cast<byte*>(&id)+i),HEX);
  }
}

void loop() 
{
}

Der Cast kostet nichts! Es wird kein Laufzeitcode generiert.
Tut mit allen halbwegs modernen C++ Compilern gleichermaßen.

Aber wie gesagt: Das Endian Problem ist damit nicht aus der Welt.


im FRam

Mein Tipp:
Schaue dir get() und put() in der EEPROM Lib an.
Das kann alle Datentypen speichern und lesen.
Sowas solltest du dir bauen.

Und nicht wild und krank konvertieren, auf eine überflüssige und potentiell gefährliche weise, wobei erfahrene Programmierer ein rotes Tuch leuchten sehen.
(habe extra den Link gepostet, um zu zeigen, dass ich nicht der einzige bin, dem sich die Nackenhäärchen dabei aufstellen)

Meine Meinung:
Keinesfalls darf sowas(union, zu diesem Zweck) als Empfehlung an Anfänger verbreitet werden.
Es ist fatal, sich an solche "Fehler" zu gewöhnen. Bittere Erfahrungen und anschließendes Umlernen ist teuer. Sich sofort an das "Richtige" zu gewöhnen ist auch eine Form des Beharrens.
Ein Beharren auf dem "Wahren Weg", ist ein gutes Beharren.

Hi combie

Ich möchte Bestätigung, daß ich das von Dir angedeutete Problem richtig erkannt habe.
In meinem Fall sehe ich nämlich kein Problem mit irgend welchen Endianen - der µC, Der den Kram in den FRam prügelt, holt Den auch wieder raus.
Bei einer Übertragung 'aus dem Arduino heraus' habe ich auch mit Deinem Code das Endian-Problem, wie Du selber schreibst.
Wenn ich LSB...MSB uint8_t sende und am anderen Ende uint8_t in der Reihenfolge LSB...MSB wieder zusammen baue, sehe ich immer noch kein Problem.
Möglich, daß Du darauf hinaus willst, wenn eine andere Familie mit in die Kommunikation einbezogen wird, weil Diese dann mit einer anderen Bitbreite arbeitet - dann sage Das aber doch bitte so.
Dein Wissen über C++ übersteigt Meines um Welten - was verhindert Dein Code, wo Meiner gnadenlos am Brückenpfeiler kleben bleibt?

MfG

was verhindert Dein Code, wo Meiner gnadenlos am Brückenpfeiler kleben bleibt?

Du baust einen Zufallsgenerator.

Dein Code nutzt ein undefiniertes Verhalten der Programmiersprache C++.
Damit ist der Code falsch.

Selbst wenn er bei AVR-GCC funktioniert.
Dann ist das eher Zufall, als spezifiziert.
Ja, die GCC Spezifikation geht da weiter, als die C++ Spezifikation.

Aber bei einem Wechsel des Kompilers da hilft dir die AVR-GCC Spezifikation keinen Millimeter weiter.

Ganz klar und deutlich:
Du handelst dir damit eine versteckte und unnötige Abhängigkeit ein.
Schwer zu debuggen!
Proprietärer Code
Der dem unbedarften Anwender irgendwann und unerwartet auf die Füße fallen kann.


Möglich, daß Du darauf hinaus willst, wenn eine andere Familie mit in die Kommunikation einbezogen wird, weil Diese dann mit einer anderen Bitbreite arbeitet - dann sage Das aber doch bitte so.

Das hat alles nichts mit dem Endian Problem zu tun!
Solange das Fram nur von AVRs gelesen/geschrieben wird, kannst du die Endian Sache getrost ignorieren.

Hi

Trotzdem ist doch definiert, wie der Kompiler ein uint64_t Wert in den Speicher legt.
Da bei union der Speicher ident ist und nur mit anderen Namen ebenfalls angesprochen werden kann in Datentypen, bei Denen ebenfalls definiert ist, wie Diese im Speicher anzukommen haben, erschließt sich mir das Problem immer noch nicht.

uint8_t eine Speicherzelle
uint16_t zwei Speicherzellen, von 'links' beginnend (wäre Little Endian, nur nebenbei)
uint32_t vier Speicherzellen, dito
...

Sofern mein uint8_t Block genau so lang ist, wie der uint32_t Block, also keine Informationen beim 'Umzug' zwischen den Bitbreiten verloren gehen, bekomme ich, egal wie Das der AVR/Kompiler intern regelt, die gleichen Daten.

Wie geschrieben sehe ich ein mögliches Problem, wenn ein breiterer µC mit ins Boot kommt (oder schlimmer, ein 'Schmälerer' - beim AVR nicht zu befürchten) und ich nicht explizit die Bitbreite angebe - also statt uint8_t nur 'unsigned int' - also wo unklar ist, wie viele Bits diese Variable umfasst, sehe ich Probleme.

Sofern die ganzen Kompiler mit den Bitbreiten zurecht kommen, sollte Das auch in Zukunft funktionieren - wenn's wohl auf breiteren µCs dann unnötig Speicher kostet - wobei beim Arduino ein bool auch ein Byte belegt, das Thema hatten wir aber bereits und konnte, wenn man denn will, ebenfalls mit einem struct erschlagen werden (müsste Suchen).

Vor den Endianen habe ich keine Angst, sollte einer der µC Big-Endian benutzen, wird Das wohl irgendwo stehen und ich traue mir zu, die Byte-Reihenfolge Dem anzupassen.

MfG

Offensichtlich fehlt da noch was, um dir klar zu machen, wo es klemmt....

union Test
{
  uint32_t asUint;
  byte asByteArray[sizeof(uint32_t)];
};

Jetzt kann man eine Instanz/Speicherobjekt erzeugen...

z.B. so:

Test test;

So weit ist noch alles OK.

Und jetzt kommt deine falsche Annahme:
Du gehst davon aus, dass test.asUint und test.asByteArray die gleichen Adressen im Speicher belegen.

Das ist leider ein Irrtum.
Das ist nicht in der Sprache C++ definiert.
Im Gegenteil, das wurde absichtlich offen gelassen. Wie so viele kleine andere Dinge auch. Spielraum für Compilerbauer und Optimierungen.

"Mein" Verfahren GARANTIERT, dass die gleichen Adressen verwendet werden.
Und das unter Wasser, mit jedem Prozessor, mit jedem C++ Kompiler.

Nachtrag, aus recht verlässlicher Quelle:

The details of that allocation are implementation-defined, and it's undefined behavior to read from the member of the union that wasn't most recently written. Many compilers implement, as a non-standard language extension, the ability to read inactive members of a union.

Aus: Union declaration - cppreference.com
Prüfe bitte deine C++ Bücher daraufhin ab.
Die Warnung solltest du in jedem "guten" Buch finden.

combie:
"Mein" Verfahren GARANTIERT, dass die gleichen Adressen verwendet werden.
Und das unter Wasser, mit jedem Prozessor, mit jedem C++ Kompiler.

Funktioniert das auch morgen noch? Auch rück- und seitwärts? Am Wochenende?

SCNR

Gregor

Ja!

Von heute, bis in die nächsten 100 Jahre. zumindest solange, wie es C++ gibt.

postmaster-ino:
Edit
Abgekupfert von
Strukturen, sommergut.de

Habe mir gerade Teile des Buches durchgelesen...
Es scheint mir, als C Buch, recht brauchbar zu sein.

Nur, dummerweise ist Arduino C++ und nicht C.
Das dort zu lesende ist also nicht, oder nur sehr eingeschränkt, in unserer Welt brauchbar.
Die Gefahr ist groß, dass man das falsche lernt.

Ich will damit sagen, dass es sehr lehrreich sein kann, eine zweit und dritt Sprache zu lernen.
Aber die C++ Feinheiten, oder auch die ganzen Verbesserungen welche C++ bietet, wirst du in einem solchen Buch nicht finden.

Ein Vergleich:
Es wäre ein bisschen so, als hättest du bei Ikea ein Möbel gekauft und es liegt die falsche Aufbauanleitung im Karton.

Natürlich kann unsere Arduino IDE auch C Dateien übersetzen.
Es ist also nutzbar. Aber nicht ohne besondere Vorkehrungen/Maßnahmen.
C Code und C++ Code, in einer Datei gemischt, das geht nicht.