[Problem gelöst] long long int in float32 umwandeln?



  • Hi,

    ich habe hier einen Bytestream, in dem signed- und unsigned int-Werte unterschiedlicher Länge (8 bis 64bit/1 bis 8 Byte, big-endian/MSB) vorkommen, die ich auf einer 32bit-MCU auslesen will. Mein bisheriger Ansatz funktioniert leider nur mit positiven Werten, für negative fehlt mir die Idee.

    Im Code vor dem Ausschnitt unten wird bool signauf true gesetzt, wenn der Bytestream eine signed int ankündigt. uint8_t *cq ist ein Zeiger auf die Zeichen im Bytestream, uint8_t runLendie Anzahl der Bytes des Integerwertes plus 1:

    float Minus = 1.0;
    // Can we expect negative values (type O_INT)?
    if(sign)
    {
       // Yes. is the very first bit set?
       if((*(cq+1)&0x80))
       {
          // Yes, we have a negative number here
          Minus = -1.0;
       }
       else
       {
          // No, positive - we may treat it like an unsigned.
          sign = false;
       }
    }
    float f = 0.0;
    for(uint8_t j=1; j<runLen; ++j)
    {
       f *=256.0;
       if(sign)
       {
          f += ~(*(cq+j));
       }
       else
       {
          f += *(cq+j);
       }
    }
    if(sign) f += 1.0;
    f *= Minus;
    

    Ich versuche also, im Fall einer negativen Zahl jeweils die inversen Bytes zunächst zu einer positiven Zahl zusammenzurechnen und im Nachhinein das negative Vorzeichen anzuwenden, wenn nötig (und vorher 1 hinzuzuzählen, weil das Einerkomplement ja um 1 verschoben ist: z.B. bei 16bit 0xffff = -1, invertiert 0x0000 = 0)

    Allerdings kommen nur positive Werte korrekt heraus, negative sehen immer wie sehr große positive aus...

    Ich vermute den Fehler bei f += ~(*(cq+j)); - aber wo steckt der? *(cq+j) ist ein uint8_t-Byte, und ~(*(cq+j)) sollte die Bitinvertierung davon sein.



  • @Miq Nutze den Debugger.

    Dann siehst du, was mit den Werten passiert.

    Was steht eigentlich an der Stelle *cq ?



  • Debugger fällt leider aus - ich habe keinen JTAG-Hardwaredebugger und Softwaredebug geht auf dem Arduino Nano nicht. Ich werde mir Ausgaben auf die Serial-Schnittstelle selber schreiben müssen.

    *cp zeigt auf ein Typ- und Längenbyte. Da steht z. B. 0x53, was bedeutet, dass eine signed int (5) mit 2 Bytes (3 minus das TL-Byte) folgt. Es gibt noch komplexere Varianten mit einem weiteren Längenbyte, was aber bei Integer nicht vorkommen sollte. Das ist SML, ich lese da ein Datentelegramm von einem Stromzähler aus.

    Inzwischen denke ich, dass irgendwas an der Berechnung von sign schief ist.



  • Niemand zwingt dich, auf dem System zu debuggen.

    Ich würde die sowieso empfehlen, für diese Funktion einen Test zu schreiben. Schreib den Code oben in eine Funktion, mach dir ein paar Inputdaten als Byte- bzw uint8-Array und dann testen!



  • @Miq sagte in long long int in float32 umwandeln?:

    *cp zeigt auf ein Typ- und Längenbyte. Da steht z. B. 0x53, ...

    Das sollte in der Funktion (ich hoffe, das ist eine extra Funktion) gar nicht mehr sichtbar sein.

    So eine Funktion läuft dann auch auf einem beliebigen Compiler.



  • Ich habe es per Serial-Ausgaben geknackt. Ich wollte nicht riskieren, dass durch andere Bitlängen auf dem PC das Ergebnis nicht vergleichbar sein könnte. Ging aber dann trotzdem ganz flott.

    Der Grund ist, dass *(cq+j) zwar tatsächlich ein uint8_t ist, also z.B. 0xFA, dass aber beim Ausdruck ~(*(cq+j)) der Inhalt der äußeren Klammer vor der Anwendung des NOT-Bitoperators intern auf ein uint32_t umgewandelt wird.

    1. *(cq+j)    ==> 0xFA
    2. (*(cq+j))  ==> 0x000000FA
    3. ~(*(cq+j)) ==> 0xFFFFFF05 (und nicht 0x05!)
    

    Mit einem &0xFF dahinter funktioniert es dann.

    Danke für's Mitdenken!



  • Noch was ganz anderes: du scheinst intern mit floats zu rechnen, wo du doch eigentlich nur bei den ints die Byte-Reihenfolge umkehren bzw. auf eine bestimmte Weise interpretieren willst? Das erscheint mir sehr merkwürdig.



  • @wob sagte in long long int in float32 umwandeln?:

    Noch was ganz anderes: du scheinst intern mit floats zu rechnen, wo du doch eigentlich nur bei den ints die Byte-Reihenfolge umkehren bzw. auf eine bestimmte Weise interpretieren willst? Das erscheint mir sehr merkwürdig.

    Die floats sind nur für die Anzeige notwendig, weil die kleinen MCUs wie der Arduino Nano mit int64 nichts anfangen können. Mit dem händischen Umrechnen auf float32 verliere ich zwar u.U. Genauigkeit, kann aber wenigstens ungefähr mit dem Wert arbeiten und quasi beliebig lange ints verarbeiten. Die Daten kommen (s.o.) big endian an, also muss ich sie auch in der Reihenfolge verarbeiten.

    Um die Story mal komplett zu machen:

    • Der Stromzähler liefert alle Sekunde ein Datenpaket über eine IR-Schnittstelle mit den Zählerständen. Format ist SML binär, die Daten sind nach OBIS codiert.
    • Der hier thematisierte Arduino Nano liest die Pakete mit einem Fototransistor, zieht die Zählerstände heraus und schreibt sie einem zweiten Nano über I²C in den Speicher.
    • Der zweite Nano agiert als MODBUS/RTU-Server und stellt so die Daten auf dem MODBUS bereit.

    Dadurch ist der eigentlich sehr restriktive Stromzähler meines Stromnetzbetreibers abfragbar im Netz verfügbar.



  • @Miq sagte in [Problem gelöst] long long int in float32 umwandeln?:

    Die floats sind nur für die Anzeige notwendig, weil die kleinen MCUs wie der Arduino Nano mit int64 nichts anfangen können.

    Welche Anzeige denn? Soll einer der Arduino diese Werte anzeigen?
    Wenn nein, was würde dagegen sprechen ein int64 als 2 int32 (einen für die unteren 32Bit und einen für die oben 32Bit) zu speichern und via modbus bereitzustellen?

    Das ganze funktioniert halt nur wenn der "eigentliche" Empfänger mit 64Bit Zahlen umgehen kann und vor der Anzeige aus dem 2 32Bit zahlen wieder eine 64Bit zahl generiert.



  • @firefly Die Obis-Darstellung lässt 64bit Integer zu, und deshalb muss man das verarbeiten können. Allerdings ist das für Datenaustausch zwischen Stromerzeuger und Netzbetreiber gedacht, die große Mengen bewegen können. Mein Zähler läuft im Monat um so 250.000 Wattstunden hoch, so dass 32 Bit Integer so ungefähr nach 17000 Monaten überlaufen werden. Werde ich knapp nicht mehr erleben... Und ja, die Anzeige wird selber von einem Arduino getrieben.



  • @Miq: Du weißt aber schon, daß float nur eine Genauigkeit von ca. 7-8 Ziffern hat (d.h. bei Zahlen über 10 Millionen ist die erste Vorkommastelle schon ungenau), so daß du bei größeren Int64-Werten immer mehr Vorkommastellen verlierst)?!

    s.a. Einfache Genauigkeit

    Fließkommazahlen sind nicht für Darstellung von (nur) ganzen Zahlen gedacht!



  • @Miq sagte in [Problem gelöst] long long int in float32 umwandeln?:

    @firefly Die Obis-Darstellung lässt 64bit Integer zu, und deshalb muss man das verarbeiten können. Allerdings ist das für Datenaustausch zwischen Stromerzeuger und Netzbetreiber gedacht, die große Mengen bewegen können. Mein Zähler läuft im Monat um so 250.000 Wattstunden hoch, so dass 32 Bit Integer so ungefähr nach 17000 Monaten überlaufen werden. Werde ich knapp nicht mehr erleben... Und ja, die Anzeige wird selber von einem Arduino getrieben.

    Ich glaub du hast nicht verstanden was ich meinte.
    Da die Ardunio Nano keine 64Bit zahlen direkt verarbeiten können sollen diese die 64Bit zahl aufsplitten in je 32Bit Blöcke (2 Blöcke).
    Was diese auch können da du einen byte strom verarbeitest. Und statt der Wandlung in ein float werden einfach die ersten 32Bit in Block 1 gespeichert und die restlichen 32Bit der Zahl in Block 2.

    Man könnte das ganze als ein struct von 2 32bit Zahlen abbilden.

    struct EnergeyCounterValue
    {
      int32 upperbits;
      int32 lowerbits;
    };
    

    z.b. wenn der Zähler 4300000000 Wattstunden als Wert liefert.
    Binär (64bit) 0000 0000 0000 0000 0000 0000 0000 0001 0000 0000 0100 1100 1100 1011 0000 0000

    in lowerbits landen die ersten 32 Bit: 0000 0000 0100 1100 1100 1011 0000 0000 (dezimal 5032704)
    in upperbits die restlichen 32Bit: 0000 0000 0000 0000 0000 0000 0000 0001 (dezimal: 1)

    Wenn das MODBUS Protokoll 64bit Zahlen unterstützt dann kannst die Zahl wieder als 64Bit verschicken



  • @firefly Doch, das habe ich genau so verstanden. Ich wollte nur sagen, dass für meine lokalen Belange das float32 völlig ausreichend ist. Ich brauche keine Zehntelwattzählerstände, ganze kWh reichen. Und die werde ich in float32 wesentlich länger speichern können als ich lebe.


Log in to reply