Umgang mit signierten Int-Überläufen

Kurzer Hintergrund zuerst; Ich habe Daten vom CAN-Bus eines Lenkwinkels, der offensichtlich in Hex ist. Der Lenkwinkel umfasst zwei Bytes einer Nachricht. Das Spezifikationsdokument, das ich habe, besagt, dass diese beiden Bytes ein 16-Bit-Int mit Vorzeichen bilden, das der Lenkwinkel ist, der ein Prescaler von 1/1024 ist, das ist alles, was ich habe (ich habe keinen Zugriff auf die Quelle). Was ich versuche, ist, diese Hex-Werte in signed int umzuwandeln, aber ich bin mir nicht sicher, wie ich es richtig machen soll.

Ein kleiner Ausschnitt der CAN-Nachricht in kurzer Zeit (wir konzentrieren uns auf Byte 2 und 3):

can0  700   [8]  00 00 99 93 55 0B EF BD
can0  700   [8]  00 00 95 95 10 0C 17 BE
can0  700   [8]  00 00 6F 97 FB 0A 17 BE
can0  700   [8]  00 00 39 99 5C 0A 40 BE
can0  700   [8]  00 00 AD 9A 62 08 EF BD
can0  700   [8]  00 00 EF 9B B5 08 40 BE
can0  700   [8]  00 00 CA 9D 9A 09 17 BE
can0  700   [8]  00 00 3E 9F 55 09 40 BE
can0  700   [8]  00 00 91 A0 ED 09 17 BE

Soweit ich weiß, folgen Daten in CAN-Nachrichten normalerweise diesem Format: ein Byte für die eigentlichen Daten, ein Byte für die Anzahl der Überläufe.

Nehmen wir zum Beispiel ein unsigned int mit einem Wert von 2000. Angenommen, Byte Nr. 0 ist für Überläufe, Byte Nr. 1 für tatsächliche Daten, erhalten wir:

CAN message -> [07, D0, x, x, x, x, x, x]

07 zeigt an, dass es 7 Überläufe gegeben hat, D0 zeigt an, dass der Rest 208 ist, daher:

7*255 + 208 = 2000

Ich verstehe, wie man es mit vorzeichenlosen Werten macht. Aber diesmal habe ich es in meinem Szenario mit vorzeichenbehafteten Werten zu tun. Ich gehe davon aus, dass ein Byte für Überläufe ist, ein Byte für den Rest, aber ich bin mir nicht sicher.

  1. Wie werden Überläufe für vorzeichenbehaftete Werte berechnet? Ist es Überlauf += 1 bei Wert > 127 und Überlauf -= bei Wert < -128? Macht es überhaupt Sinn, dass Überläufe Vorzeichen haben?

  2. Wie kann ich diese Bytes in C/C++ in vorzeichenbehaftete Dezimalzahlen umwandeln? Nehmen wir an, mein Bytewert in Hex ist 91. Als ich das letzte Mal versucht habe, ihn in int zu speichern und auszudrucken, wurde 145 (normale Binärzahl) und nicht -111 (2er-Komplement) ausgedruckt. Wie kann ich das 2er-Komplement (falls das Sinn macht) in meinem Code erzwingen?

  3. Könnte ich das Byte-Format falsch interpretieren? Alles, was es sagt, dass diese zwei Bytes den Lenkwinkel darstellen und dass der Lenkwinkel int16_t ist. Ich habe beobachtet, wie sie sich in Echtzeit ändern, und einer von ihnen ändert sich unregelmäßig, fast so, als würde er von 00 auf FF springen, während der andere langsam und fast linear mit der Zeit zunimmt/abnimmt. Ideen?

Ich stecke hier wirklich fest, ich weiß nicht einmal, ob ich in die richtige Richtung gehe.. Alle Vorschläge und Hilfe sind sehr willkommen!

Sind sie binär oder hex? Hex ist eine Technik zur Darstellung von Binärdaten mit druckbaren Zeichen. Erhalten Sie eine Textnachricht, die von Hex in Binär konvertiert werden muss, oder erhalten Sie die rohen Binärdaten?
„Überläufe“ werden nur über den gesamten Wertebereich für die Wortgröße berücksichtigt. Höhere Bytes innerhalb desselben Wortes sind genau das, höhere Bytes.
@mkeith, wenn Sie sich mein kurzes Beispielsegment ansehen, ist es eindeutig in Hex. Die Bytes 2 und 3 stellen zusammen einen einzelnen 16-Bit-Int-Wert dar. Ich will nur wissen, wie man das richtig macht.
@IgnacioVazquez-Abrams, nicht 100% sicher, was Sie zu sagen versuchen. Meinen Sie damit, dass wir anstelle von Überläufen Byte 3 haben, das die höheren 8 Bits des 16-Bit-Werts sind, während Byte 2 die unteren 8 Bits sind?
"Lower" und "Higher" hängt von der Endianness der Daten ab, aber Sie haben (zum Beispiel) nicht 0x12 0x34, Sie haben 0x1234.
@IgnacioVazquez-Abrams, das ist mir bewusst. Es ist übrigens ein Little-Endian-System. Das wäre also Byte 2 ist LH, Byte 3 ist UH? Wird es nicht normalerweise stattdessen mit Überläufen gemacht? Zum Beispiel sind Byte 3 Ihre Daten, und wenn es überläuft, würde der Wert in Byte 2 inkrementiert?
Nichts für ungut, aber die ganze Frage erweckt in mir kein Vertrauen, dass Sie wissen, wovon Sie sprechen. Mein Verdacht besteht weiterhin darin, dass die an Sie übergebenen Werte tatsächlich binär und nicht hexadezimal sind. Sie zeigen sie im Hexadezimalformat an, da sie nicht im Binärformat angezeigt werden können, da die Binärdatei nicht druckbar ist. Ihre Frage bezieht sich darauf, wie Sie Einzelbyte-Binärdaten in eine korrekte vorzeichenbehaftete Ganzzahl mit einer Breite von zwei Bytes umwandeln.
Mit Überläufen ist es nie getan. Es ist eine einzelne Zahl .
@mkeith sicher, sie sind binär. Alles in der physikalischen Schicht wird binär ausgeführt. Die Daten, die ich habe, befinden sich in einer Excel-Datei, die aus einem Skript gezogen wurde, das die Daten in Hex darstellt. Meine Frage mehr zur Interpretation der Bytes in der Nachricht, die den physikalischen Wert darstellt. Wie Ignacio erwähnt hat, könnten es zwei Bytes sein, die höhere und untere Hälften eines 16-Bit-Werts darstellen. Meine anfängliche Vermutung war, dass ein Byte für die Überlaufzählung verwendet wird, während das andere Byte für die untere Hälfte verwendet wird. Außerdem tut es mir leid, dass ich deine Frage falsch interpretiert habe.
@IgnacioVazquez-Abrams, danke dafür, das macht eigentlich sehr viel Sinn. Ich weiß nicht, woher ich die Idee von Überläufen habe, muss es mit etwas anderem verwechselt haben!
@hypomania Overflows sind das, was Sie erhalten, wenn Sie zwei Zahlen addieren und einen Übertrag erhalten.
Überlauf entsteht, wenn die Größe des numerischen Ergebnisses zu groß für den Datentyp der Variablen ist, in der das Ergebnis gespeichert werden soll.
„Soweit ich weiß, folgen Daten in CAN-Nachrichten typischerweise diesem Format: ein Byte für die eigentlichen Daten, ein Byte für die Anzahl der Überläufe.“ Wovon in aller Welt redest du? Aus allgemeiner CAN-Sicht ist das völliger Unsinn. Aus der Perspektive einer Standard-2er-Komplementzahl ist dies ebenfalls völliger Unsinn. Und was ist diese Besessenheit von Überläufen überhaupt? Was lässt Sie glauben, dass es überhaupt Überläufe gibt?
Was ist das für ein CAN-Protokoll? Was ist das Format? Was bedeuten diese Bytes? Wenn Sie dies nicht angeben, kann die Frage nicht einfach beantwortet werden. Es sieht nicht nach einem gemeinsamen Standard aus.

Antworten (6)

Wahrscheinlich hilft es, einige Missverständnisse auszuräumen.

Erstens sind Ihre Daten ein 16-Bit-Wert. Es gibt keine "Überläufe" und "tatsächlichen Daten" - die 16 Bits werden nur in zwei 8-Bit-Stücke (Bytes) geteilt. Um den richtigen Binärwert zu erhalten, müssen Sie die Bytes verketten. In C können Sie dies tun, indem Sie mit vorzeichenlosen Werten beginnen und bitweise Operatoren wie folgt verwenden:

uint16_t highbyte, lowbyte, data; 
highbyte = get_can_byte();   //Do whatever you normally do to get the bytes
lowbyte = get_can_byte();

data = highbyte<<8 | lowbyte;

Jetzt haben Sie den richtigen 16-Bit-Wert. Wenn Sie möchten, dass das Ergebnis signiert wird, können Sie den Wert einfach in einen signierten Typ umwandeln:

int16_t signed_data;

signed_data = (int16_t)(highbyte<<8 | lowbyte);

Um Ihre konkreten Fragen zu beantworten:

  1. Überläufe erzeugen die gleichen Binärwerte, unabhängig davon, ob Ihre Variable vorzeichenbehaftet oder vorzeichenlos ist. Zum Beispiel 0x7fff + 1 == 0x8000. Ob Sie 0x8000 als 32768 oder -32768 interpretieren, hängt vom Datentyp ab. (Beachten Sie, dass der Überlauf von vorzeichenbehafteten Ganzzahlen im C-Standard technisch nicht definiert ist – dies wird Ihre CPU tun.)

  2. Wie gesagt, es hängt alles vom Datentyp ab:

    uint16_t ui = 0xffff;    //65535
    int16_t   i = 0xffff;    //   -1
    
  3. Was Sie beschreiben, klingt für eine hochpräzise Messung vernünftig. Eine Änderung von +1 oder -1 in Ihrem oberen Byte repräsentiert nur 1/256 Ihres gesamten Steuerbereichs. Das Low-Byte reagiert extrem empfindlich auf den genauen Winkel.

Beachten Sie, dass es am besten ist, so spät wie möglich in einen signierten Wert zu konvertieren und dies mit einer expliziten Umwandlung zu tun. Dinge wie bitweise Operatoren können sich anders verhalten oder ein undefiniertes Verhalten erzeugen, wenn sie für vorzeichenbehaftete Werte verwendet werden. Verwenden Sie im Allgemeinen bei jeder Bitmanipulation vorzeichenlose Werte.

Siehe Jonks Kommentar zu meiner Antwort. Ich glaube, C garantiert nicht, dass die Umwandlung von unsigned zu signed das Richtige tut. Ich glaube jedoch, dass es tatsächlich auf den meisten modernen Architekturen mit modernen Compilern funktionieren wird. Wenn das OP es portabel machen möchte, würde ich vorschlagen, in einem C-Forum zu fragen, nicht hier.
Ich habe darüber nachgedacht, eine umständliche standardkonforme Version hinzuzufügen, aber ich möchte den armen Fragesteller nicht verwirren. :-) Wenn es ihnen gelingt, eine Maschine zu finden, an der sie versagt, werden sie es sehr schnell entdecken. Meiner Meinung nach haben Sie, wenn Sie mit Zahlenformaten und endianness-abhängigem Code herumspielen, die universelle Portabilität sowieso aufgegeben.
Ja, Adam. Zustimmen.
Vielen Dank für eine ausführliche und klare Erklärung. Sie haben mir die richtige Richtung gezeigt und ich glaube, es ist mir klar, was als nächstes zu tun ist, danke!

Du verkomplizierst das zu sehr.

can0  700   [8]  00 00 *99 93* 55 0B EF BD -> 0x9399 -> -27751  
can0  700   [8]  00 00 *95 95* 10 0C 17 BE -> 0x9595 -> -27243  
can0  700   [8]  00 00 *6F 97* FB 0A 17 BE -> 0x976F -> -26769  
can0  700   [8]  00 00 *39 99* 5C 0A 40 BE -> 0x9939 -> -26311  

usw ...

Langer Arbeitstag, meine Gedanken waren chaotisch. Danke dafür, ich habe das Ganze zu kompliziert gemacht :)

Die wahrscheinlichste Erklärung dafür, warum ein Byte zufällig erscheint und sich linear ändert, ist, dass das lineare Byte das Byte höherer Ordnung ist (die Überläufe). Das niederwertige Byte erscheint zufällig, da es sich im Vergleich zur Aktualisierungsrate schnell ändert.

Ich würde sagen, dass Byte 3 das höherwertige Byte und Byte 2 das niederwertige Byte ist. In vorzeichenbehafteten Ganzzahlen mit Zweierkomplementdarstellung (die heutzutage fast universell ist) werden bei negativen Zahlen höherwertige Bits auf 1 gesetzt. Wenn Sie sich das Hexadezimal ansehen, wenn das höherwertige Byte größer als 0x80 ist, ist es eine negative Zahl. In Ihrem Beispiel sind also alle Zahlen negative Zahlen. 0xffff ist -1, 0xfffe ist -2 usw. Was Sie in Ihrem Snippet haben, ist:

  • 0x9399
  • 0x9995
  • 0x976f
  • usw...

Da alle diese Zahlen größer als 0x8000 sind, sind sie alle negativ.

Das Beste, was Sie tun können, ist, sich mit der Zweierkomplementdarstellung von vorzeichenbehafteten ganzen Zahlen zu beschäftigen. Es hört sich so an, als wüssten Sie, wie man eine uint16_t-Konvertierung durchführt. Was für Sie funktionieren könnte, ist, dies einfach zu tun und den Wert dann einer Variablen von int16_t zuzuweisen. Der Compiler konvertiert es möglicherweise korrekt für Sie (es wird nicht durch die C-Spezifikation garantiert, aber viele Compiler machen es so).

Ich hoffe, das hilft dir ein bisschen.

Für Speicher gleicher Größe garantiert C die Umwandlung von signed int in unsigned int. (Was nicht nach der Richtung klingt, die das OP will.) C garantiert NICHT die Konvertierung von unsigned int in signed int - es garantiert es nur, wenn der unsigned int-Wert in der signed int dargestellt wird (positiv und passt.) Größere Werte können in zufälligen Müll umgewandelt werden, soweit der Standard dies zulässt. Soweit mir bekannt ist, tut das natürlich kein C-Compiler. Aber laut Spezifikation könnten sie, wenn sie wollten. So wie du es sagst, denke ich.
@jonk, ja. Ich glaube, es ist die Implementierung definiert. Könnte undefiniert sein. Aber es funktioniert die meiste Zeit auf Zweierkomplementmaschinen. Es so zu machen, dass es vollständig tragbar ist, ist ein Schmerz im Arsch. Könnte in unit32_t konvertieren, dann einige Tests an der vorzeichenlosen Zahl durchführen und sie ohne implementierungsdefinierte Schritte in die korrekte int16_t konvertieren. Aber es ist ein Schmerz.
Sie haben völlig Recht, Byte 2 ist das niedrigere Byte, Byte 3 ist das höhere (es ist ein Little-Endian-System). Deine und all die anderen Kommentare haben mir die Dinge sehr klar gemacht, es ist viel einfacher als ich dachte, dass es sein würde. Um ehrlich zu sein, habe ich keine Ahnung, woher ich die Idee habe, dass Zahlenüberläufe im Byte in der Nähe gespeichert werden. Es macht irgendwie Sinn und liefert die gleichen Ergebnisse, aber das Konzept ist völlig anders, Überläufe werden für den gesamten Wert ausgeführt, anstatt für das untere Byte. Jedenfalls weiß ich jetzt, was ich tue, danke für deine Geduld!
Schön, dass meine Kommentare geholfen haben! Viele von uns machen das schon seit langem, also ist es jetzt einfach. Aber wir alle mussten es auf dem Weg herausfinden, normalerweise mit der Hilfe von Leuten, die es schon einmal gemacht haben. Am Anfang ist es nie einfach. Du musst es herausfinden.

Ich weiß nichts über tatsächliche Lenkwinkelsensoren in Autos ...,

Beachten Sie jedoch, dass ein Winkelsensor möglicherweise mehrere Kurvenwerte meldet. dh eine Umdrehung könnte 0-1024 sein, und der Sensor kann +/-32 Umdrehungen in einer 16-Bit-Zahl melden.

Warum sollte jemand das tun?

Stellen Sie sich vor, das Lenkrad befindet sich genau am 0/360-Grad-Punkt und dreht sich zwischen 0 und 360 mit Straßenvibrationen. Was passiert, wenn Sie Rauschen herausfiltern? Sie erhalten 180 Grad, was völlig falsch ist.

Dies ist immer ein Problem, das bei der Verwendung von frei drehbaren Winkelsensoren irgendwo behandelt werden muss. Einige Systeme geben zwei Winkelwerte bei 90 Grad (sin+cos) zurück, um diese Mehrdeutigkeit aufzulösen. Andere erweitern den Winkelwert auf 360 + 180 Grad mit Hysterese, während einige eine Multiturn-Winkelakkumulation durchführen.

Vielen Dank für den Punkt, ich werde dies im Hinterkopf behalten, sobald ich die eigentliche Konvertierung durchgeführt und mir die Handlung angesehen habe!

Die anderen Antworten erklären die Nachrichtenformatierung gut, aber ich würde es wahrscheinlich so auffassen:

union
{
    struct
    {
        uint8_t hi;    //the order of these two depends on the endian-nesss of your specific micro
        uint8_t lo;    //swap if the data is garbled
    };
    uint16_t all;      //or sint16_t if you like: the only difference so far is at what value it wraps to the opposite end of its range
} reading;

uint16_t get_reading()
{
    reading.hi = get_hi_byte();
    reading.lo = get_lo_byte();
    return reading.all;
}

Der Vorteil dabei ist, dass die scheinbare Berechnung (shift + or) tatsächlich ohne wirklichen Aufwand vollständig von der Speicherstruktur erledigt wird, sodass selbst ein dummer Compiler immer noch effizienten Code generiert. Und Sie schreiben es direkt so, wie es tatsächlich passiert, was meiner Meinung nach ein großes Plus für die Lesbarkeit ist.


Wenn Sie dies häufig tun, können Sie:

typedef union
{
    struct
    {
        uint8_t hi;
        uint8_t lo;
    };
    uint16_t all;
} byte_int;

byte_int steering;
byte_int throttle;
//etc.
Das Problem bei dieser Lösung besteht darin, dass der Code endianess-abhängig wird und die CPU-Endianess nicht unbedingt mit der Protokoll-Endianess identisch ist. Daher besteht die bessere Lösung darin, Bitverschiebungen zu verwenden. Es ist keine gute Idee, Endianess-abhängigen Code zu schreiben, wenn Sie stattdessen Endianess-unabhängigen Code für wenig bis gar keinen zusätzlichen CPU-Overhead erhalten können.
@Lundin Ja, es wird Endian-abhängig und das Protokoll könnte sich von der CPU unterscheiden. (Letzteres ist der Grund, warum wir jeweils ein Byte aus dem Protokoll kopieren.) Aber ich habe viele Projekte gesehen, die ihre ursprüngliche Plattform nie verlassen (Endianess ändert sich nicht) und viele idiotische Compiler. Zum Beispiel ist genau diese Operation - 16-Bit-Verschiebung um 8 und dann bitweises Oder - genau das, was ich im Objektcode gesehen habe: Er durchläuft eine Compiler-Bibliotheksfunktion 8 Mal, um 16 Bit um 1 zu verschieben, und dann ist es eins Byte auf einmal.
Wenn die Plattform auf die entgegengesetzte Endianess wechselt, hat sogar meine Version genau eine Stelle, um dies zu beheben, wenn Sie die typedef verwenden.
Es ist selten, dass ganze Projekte die Plattform verlassen, aber einzelne Quelldateien tun es häufig - oder Sie machen es falsch und erfinden das Rad immer wieder neu.
@Lundin Ich habe meine typedefs in eine separate Header-Datei eingefügt, die eine von zwei ist, die dem jeweiligen Chip entsprechen. (der andere hat eine Reihe chipspezifischer Definitionen für E / A und grundlegende Funktionen) Dann wird der obige Quellcode portabel.

Wenn Sie eine Dezimalzahl wie 106 hätten, könnten Sie sagen, dass sie einen Überlauf hatte, bei dem Sie über 99 hinausgegangen sind und einmal auf 100 "übergelaufen" sind. Sie könnten zustimmen, dass dies besser als "Übertrag" bezeichnet wird. Sie verstehen das, weil Sie in Ihrem Beitrag 7 * 256 multipliziert haben, bevor Sie 208 addiert haben, um die Antwort von 2000 zu erhalten. (Sie haben 255 geschrieben, aber ich denke, das ist ein Ausrutscher, da die Antwort richtig war).

Sie haben nicht gesagt, ob Sie in C oder einer anderen Sprache arbeiten. Es ist möglich, dass Sie nur auf einen CAN-Dump starren und die Dinge auf Papier ausarbeiten. Aus dieser Sicht ist der einfachste Weg zu einer Antwort, dass Sie, wenn die Zahl negativ ist (d. h. 2^15 (32768) oder größer ist), 2^16 (65536) vom Messwert abziehen, um die Antwort zu erhalten. Das heißt, wenn Sie F8 33 hätten und F8 * 256 + 33 berechnen, erhalten Sie 63539. Dann ist 63539 - 65536 = -1997, was der Wert ist, der von F833 dargestellt wird, wenn er als vorzeichenbehaftete Zahl betrachtet wird.

Die Informationen in den anderen Antworten sind gültig und werden meistens empfohlen, aber dies kann Ihnen den Einstieg erleichtern und möglicherweise für jemanden geeignet sein, der sich noch nicht mit dem Programmierende befasst hat. Es ist auch eine gute Möglichkeit, die Antwort zu überprüfen, die Ihr Programm Ihnen gibt, nachdem Sie es ausprobiert haben.