Hex Zahlen aus Char Array verarbeiten - Möglichkeiten?
-
Hi Leute.
Ich würde gerne eine kleine Diskussion darüber starten, wie es am sinnvollsten ist, folgenden Fall zu bearbeiten:
Stand der Dinge: Vom seriellen Port kommen über ein Messgerät Hex Werte rein, die in einen Buffer vom Typen vector<unsigned char> gespeichert werden. Es handelt sich also nicht um echte Zahlen, sondern eher Zeichenketten, die Hex Werte darstellen..
Dann geht es natürlich darum, die Werte weiter zu verarbeiten, es sollen bestimmte Werte gesucht werden, dabei kommt es eigentlich nur auf 3 Bytes an, die benötigt werden.
Das 2. und 3. Bytes müssen zusammengesetzt werden (Big Endian), in einen Integer verwandelt und schließlich noch ein bisschen umgerechnet werden.Jetzt meine Fragen:
Ich werde jetzt keine Codeschnipsel hier reinstellen, sondern beschreiben, was ich gemacht habe. Falls erwünscht gibts natürlich auch Code.
Per find() Habe ich den Vektor nach dem ersten Hexwert suchen lassen, find gibt mir auch den korrekten Iterator aus.
Jetzt der eher eklige Teil:
Ich kann die zwei darauffolgenden Bytes in zwei temporäre int Variablen schreiben (indem ich den char nach int caste), doch wie verbinde ich die beiden Zahlen am besten so, dass eine Integer Zahl rauskommt?
Ich kann die beiden Zahlen nach Hex umwandeln, dann per switch-case A-F abfangen und dann für jede einzelne 16^x Stelle meine Zahl zusammenaddieren.
Z.b:
8F16=8*161+15*160=14310
Jetzt erscheint mir das aber etwas zu klobig, macht man das (noch?) so?Ich kann auch per fscanf die "Zahlen" aus dem Hex Buffer als Hex via "%x" einlesen, dann per strcat die beiden Zahlen verketten und dann umwandeln. Nur sind das halt alles C Funktionen, daher frage ich mich, ob es eine etwas elegantere Lösung in C++ gibt.
Allgemein fühle ich mich auch die ganze Zeit etwas "unwohl", weil es sich ja eigentlich nur um Zahlen handelt, die aber als Zeichenkette in dem char Puffer stehen. Kann mann die überhaupt vernünftig weiterverarbeiten, weil eigentlich handelt es sich ja dabei nur um ASCII Zeichen..
Ich hoffe das war verständlich, wenn nicht, fragt bitte nach ich beschreibe nochmal anders was ich meine!
Vielen Dank im Voraus,
JaKap
-
Es gibt auch Stringstreams, die du im Hex-Modus betreiben kannst
string data; // = "8F" stringstream sin(data); sin>>hex>>wert;
-
jakap schrieb:
Vom seriellen Port kommen über ein Messgerät Hex Werte rein, die in einen Buffer vom Typen vector<unsigned char> gespeichert werden. Es handelt sich also nicht um echte Zahlen, sondern eher Zeichenketten, die Hex Werte darstellen..
Hallo JaKap,
nur um das nochmal klar zustellen, es kommen lesbare Zeichen über die Schnittstelle, also z.B. die drei Zeichen "?f0" - wäre hilfreich, wenn Du mal ein, zwei Beispiele posten könntest.
jakap schrieb:
Dann geht es natürlich darum, die Werte weiter zu verarbeiten, es sollen bestimmte Werte gesucht werden, dabei kommt es eigentlich nur auf 3 Bytes an, die benötigt werden.
Das 2. und 3. Bytes müssen zusammengesetzt werden (Big Endian), in einen Integer verwandelt
[...]
daher frage ich mich, ob es eine etwas elegantere Lösung in C++ gibt.Ja die gibt es. Das ist doch im Grunde ein klassischer Input. In C++ deckt das die Input.Output.Libary ab. Die Umwandlung von einer Zeichenfolge in eine Zahl (oder allgemeiner in ein Objekt) ist die natürliche Aufgabe eines Input-Streams (std::istream). CStoll hat ja schon den Vorschlag gemacht, einen solchen zu benutzen.
Wie kommen denn die Zeichen in den vector<unsigned char> hinein? Das Lesen (und Schreiben) einzelner Zeichen ist die natürliche Aufgabe eines konkreten Streambuffers (std::streambuf).
Anbei eine kleine Demo, um die Arbeitsweise des ganzen zu veranschaulichen:
#include <iostream> #include <streambuf> // -- die folgenden drei Funktionen sind nur für Demo-Zwecke, die Zeichen werden nicht von einem seriellen Port // sondern schlicht von stdin (cin) gelesen. int serial_open( /* Parameter */ ) { std::cout << "--- Serial Input> "; return 1; } void serial_close( int /*handle*/ ) { std::cout << "\n--- close serial line ---" << std::endl; } bool serial_read( int /*handle*/, char* c ) // liest ein Zeichen vom seriellen Port { return !std::cin.get( *c ).fail(); } // --- die Skizze eines konkreten Streambufs, der von einem seriellen Port liest. class SerialCommunication : public std::streambuf { public: typedef int Handle; // irgendein Handle oder File-Descriptor für die serielle Schnittstelle SerialCommunication( /* Parameter */ ) : m_serialDevice( serial_open( /* Parameter */ ) ) , m_buf() { // ggf. Fehlerbehandlung } ~SerialCommunication() { serial_close( m_serialDevice ); } protected: virtual int_type underflow() { if( !serial_read( m_serialDevice, &m_buf ) ) // hier ein Byte von der seriellen Schnittstelle lesen return traits_type::eof(); // Fehler mit EOF melden setg( &m_buf, &m_buf, &m_buf + 1 ); return traits_type::to_int_type( m_buf ); } private: Handle m_serialDevice; char m_buf; // Buffer von einem Byte }; // -- und hier folgt jetzt die Anwendung des Streambufs int main() { using namespace std; SerialCommunication sb/*( Parameter )*/; istream in( &sb ); int messwert; if( in.ignore( 1 ) >> hex >> messwert ) // 1. Zeichen ignorieren, danach Integer im Hex-Format einlesen { cout << "Wert= " << messwert << endl; } return 0; }
Wie Du siehst fungiert der Streambuf als Wrapper um die Funktionen des seriellen Ports. Der Streambuf hat genau die Schnittstelle, auf die ein Stream zugreifen kann. Und der Stream ist jetzt dafür verantwortlich, aus einer Zeichenfolge eine Zahl zu machen.
Ein Ablauf dieses Programms könnte dann so aussehen:--- Serial Input> ?f0 Wert= 240 --- close serial line ---
Beim Einsatz eines Seriellen Port gibt es jetzt noch ein paar zusätzliche Probleme. Insbesondere muss definiert sein, wann ein Telegramm vom Messgerät 'zu Ende' ist. Der Stream liest hier nämlich nicht nur zwei Zeichen, sondern er liest solange, solange er keine verwertbaren Zeichen mehr findet. Wenn die Zahl am Ende des Telegramms steht, so muss der Streambuf hier ein EOF zurückgeben. Anschließend muss der Stream und der Streambuf für das nächste Telegramm wieder neu aufgesetzt werden. Das könnte man 'von außen' mit der Methode sync() erledigen.
Fährt das Protokoll mit Software-Handshake (STX,ETX), Hardware-Handshake oder kein Handshake?jakap schrieb:
Das 2. und 3. Bytes müssen zusammengesetzt werden (Big Endian), in einen Integer verwandelt und schließlich noch ein bisschen umgerechnet werden.
Was passiert bei der Umrechnung genau? Vielleicht lässt sich das auch mit in das Einlesen integrieren.
Gruß
Werner
-
Werner Salomon schrieb:
jakap schrieb:
Vom seriellen Port kommen über ein Messgerät Hex Werte rein, die in einen Buffer vom Typen vector<unsigned char> gespeichert werden. Es handelt sich also nicht um echte Zahlen, sondern eher Zeichenketten, die Hex Werte darstellen..
Hallo JaKap,
nur um das nochmal klar zustellen, es kommen lesbare Zeichen über die Schnittstelle, also z.B. die drei Zeichen "?f0" - wäre hilfreich, wenn Du mal ein, zwei Beispiele posten könntest.
Hi
Also ja, es werden einzelne Bytes in der Serial Port Klasse per WinAPI für ein paar Sekunden eingelesen (das Gerät sendet dauerhaft, sendet kontinuierlich einen Standardcode bestehend aus 3 Bytes: "B2" "03" "02").
Die Lesefunktion ist in einem eigenen Thread gestartet, es wird jedes einzelne gelesene Byte in den Vektor geschrieben, der per Parameter übergeben wird.
Konkret läuft es so, dass ich eine Zeichenkette aus einem 3 elementigen char Array sende, dauerhaft den Puffer schreibe und später wird der Vektor durchsucht.
Ich sende z.B. "AB" "00" "00" und erwarte als Antwort "AB" "xx" "yy". Daher kann ich in dem Vektor schön nach AB suchen, dann gucke ich, ob vor und nach (3 Bytes weiter) der gefundenen Position der Standardcode auftaucht, sodass ich davon ausgehen kann, dass die Übertragung hingehauen hat. Interessant sind jetzt die beiden Bytes "xx" und "yy"
Die müssen verkettet werden und in einen Int umgebaut werden.jakap schrieb:
Dann geht es natürlich darum, die Werte weiter zu verarbeiten, es sollen bestimmte Werte gesucht werden, dabei kommt es eigentlich nur auf 3 Bytes an, die benötigt werden.
Das 2. und 3. Bytes müssen zusammengesetzt werden (Big Endian), in einen Integer verwandelt
[...]
daher frage ich mich, ob es eine etwas elegantere Lösung in C++ gibt.Ja die gibt es. Das ist doch im Grunde ein klassischer Input. In C++ deckt das die Input.Output.Libary ab. Die Umwandlung von einer Zeichenfolge in eine Zahl (oder allgemeiner in ein Objekt) ist die natürliche Aufgabe eines Input-Streams (std::istream). CStoll hat ja schon den Vorschlag gemacht, einen solchen zu benutzen.
Wie kommen denn die Zeichen in den vector<unsigned char> hinein? Das Lesen (und Schreiben) einzelner Zeichen ist die natürliche Aufgabe eines konkreten Streambuffers (std::streambuf).
Ui hmm. Die Klasse habe ich noch nicht entdeckt. Ärgerlich, das wird in meinem Buch überhaupt nicht erwähnt : /
Sehe ich mir aber an.Anbei eine kleine Demo, um die Arbeitsweise des ganzen zu veranschaulichen:
#include <iostream> #include <streambuf> // -- die folgenden drei Funktionen sind nur für Demo-Zwecke, die Zeichen werden nicht von einem seriellen Port // sondern schlicht von stdin (cin) gelesen. int serial_open( /* Parameter */ ) { std::cout << "--- Serial Input> "; return 1; } void serial_close( int /*handle*/ ) { std::cout << "\n--- close serial line ---" << std::endl; } bool serial_read( int /*handle*/, char* c ) // liest ein Zeichen vom seriellen Port { return !std::cin.get( *c ).fail(); } // --- die Skizze eines konkreten Streambufs, der von einem seriellen Port liest. class SerialCommunication : public std::streambuf { public: typedef int Handle; // irgendein Handle oder File-Descriptor für die serielle Schnittstelle SerialCommunication( /* Parameter */ ) : m_serialDevice( serial_open( /* Parameter */ ) ) , m_buf() { // ggf. Fehlerbehandlung } ~SerialCommunication() { serial_close( m_serialDevice ); } protected: virtual int_type underflow() { if( !serial_read( m_serialDevice, &m_buf ) ) // hier ein Byte von der seriellen Schnittstelle lesen return traits_type::eof(); // Fehler mit EOF melden setg( &m_buf, &m_buf, &m_buf + 1 ); return traits_type::to_int_type( m_buf ); } private: Handle m_serialDevice; char m_buf; // Buffer von einem Byte }; // -- und hier folgt jetzt die Anwendung des Streambufs int main() { using namespace std; SerialCommunication sb/*( Parameter )*/; istream in( &sb ); int messwert; if( in.ignore( 1 ) >> hex >> messwert ) // 1. Zeichen ignorieren, danach Integer im Hex-Format einlesen { cout << "Wert= " << messwert << endl; } return 0; }
Wie Du siehst fungiert der Streambuf als Wrapper um die Funktionen des seriellen Ports. Der Streambuf hat genau die Schnittstelle, auf die ein Stream zugreifen kann. Und der Stream ist jetzt dafür verantwortlich, aus einer Zeichenfolge eine Zahl zu machen.
Ein Ablauf dieses Programms könnte dann so aussehen:--- Serial Input> ?f0 Wert= 240 --- close serial line ---
Wow, so eine dicke Hilfe habe ich garnicht erwartet. Herzlichen Dank, ich werde mir das angucken und versuchen zu verstehen.
Beim Einsatz eines Seriellen Port gibt es jetzt noch ein paar zusätzliche Probleme. Insbesondere muss definiert sein, wann ein Telegramm vom Messgerät 'zu Ende' ist. Der Stream liest hier nämlich nicht nur zwei Zeichen, sondern er liest solange, solange er keine verwertbaren Zeichen mehr findet. Wenn die Zahl am Ende des Telegramms steht, so muss der Streambuf hier ein EOF zurückgeben. Anschließend muss der Stream und der Streambuf für das nächste Telegramm wieder neu aufgesetzt werden. Das könnte man 'von außen' mit der Methode sync() erledigen.
Fährt das Protokoll mit Software-Handshake (STX,ETX), Hardware-Handshake oder kein Handshake?Uff, okay. Es gibt keinen Handshake. Man könnte doch hier auch auf den "Standardcode" syncen, oder?
jakap schrieb:
Das 2. und 3. Bytes müssen zusammengesetzt werden (Big Endian), in einen Integer verwandelt und schließlich noch ein bisschen umgerechnet werden.
Was passiert bei der Umrechnung genau? Vielleicht lässt sich das auch mit in das Einlesen integrieren.
Gruß
Werner"xx" und "yy" werden zusammengesetzt, "xx" ist das höherwertigere Bit, "yy" das Niederwertigere. Heraus soll halt eine Zahl kommen, die wird noch mit einem Faktor skaliert und fertig ist das Ergebnis, keine Großartige Berechnung mehr.
Ich bedanke mich ganz herzlich für die Umfangreiche Hilfe
Ich merke, dass es bei mir garnicht um die eigentliche Programmierung geht, sondern eher um das Konzept dahinter, so n paar Funktionen kann ich schreiben, sodass das rauskommt, was ich möchte, aber die Eleganz und vermutlich auch die Effizienz fehlt vollkommen. Woher bekomme ich in diese Richtung Tips, gibts ein gutes Buch?
Schöne Grüße,
jakap
-
jakap schrieb:
Werner Salomon schrieb:
jakap schrieb:
Vom seriellen Port kommen über ein Messgerät Hex Werte rein, die in einen Buffer vom Typen vector<unsigned char> gespeichert werden. Es handelt sich also nicht um echte Zahlen, sondern eher Zeichenketten, die Hex Werte darstellen..
Hallo JaKap,
nur um das nochmal klar zustellen, es kommen lesbare Zeichen über die Schnittstelle, also z.B. die drei Zeichen "?f0" - wäre hilfreich, wenn Du mal ein, zwei Beispiele posten könntest.
Hi
Also ja, es werden einzelne Bytes in der Serial Port Klasse per WinAPI für ein paar Sekunden eingelesen (das Gerät sendet dauerhaft, sendet kontinuierlich einen Standardcode bestehend aus 3 Bytes: "B2" "03" "02").Das heißt, da werden keine lesbaren Werte, sondern pure Binärdaten, richtig? In dem Fall ist die Hexadeimal-Umwandlung durch den Stream gar nicht notwendig und du kannst deinen Wert zusammenbauen per
[s]wert = data[0]*8 + data[1];[/s]
wert = data[0]<<8 + data[1];
Beim Auslesen solltest du darauf achten, daß deine Kennsymbole eventuell auch als Teil der Nutzdaten vorkommen könnten - aber das zu verhindern ist Aufgabe des Übertragungsprotokolls.
"xx" und "yy" werden zusammengesetzt, "xx" ist das höherwertigere Bit, "yy" das Niederwertigere. Heraus soll halt eine Zahl kommen, die wird noch mit einem Faktor skaliert und fertig ist das Ergebnis, keine Großartige Berechnung mehr.
Du kennst den Unterschied zwischen "Bit" und "Byte"?
-
CStoll schrieb:
jakap schrieb:
Werner Salomon schrieb:
jakap schrieb:
Vom seriellen Port kommen über ein Messgerät Hex Werte rein, die in einen Buffer vom Typen vector<unsigned char> gespeichert werden. Es handelt sich also nicht um echte Zahlen, sondern eher Zeichenketten, die Hex Werte darstellen..
Hallo JaKap,
nur um das nochmal klar zustellen, es kommen lesbare Zeichen über die Schnittstelle, also z.B. die drei Zeichen "?f0" - wäre hilfreich, wenn Du mal ein, zwei Beispiele posten könntest.
Hi
Also ja, es werden einzelne Bytes in der Serial Port Klasse per WinAPI für ein paar Sekunden eingelesen (das Gerät sendet dauerhaft, sendet kontinuierlich einen Standardcode bestehend aus 3 Bytes: "B2" "03" "02").Das heißt, da werden keine lesbaren Werte, sondern pure Binärdaten, richtig? In dem Fall ist die Hexadeimal-Umwandlung durch den Stream gar nicht notwendig und du kannst deinen Wert zusammenbauen per
wert = data[0]*8 + data[1];
Hey!
In wie fern meinst du jetzt "lesbare" Werte, könnteste mir das erklären?
Das data Array enthält nun die beiden Hex "Zahlen", oder die beiden Stellen einer Hex Zahl? Wieso denn *8? Wenn es jetzt *16 wäre und data[0] die höherwertige Stelle ist könnte ichs nachvollziehen. Dann muss aber auch z.B. das A als 10 identifiziert werden, so in etwa wollte ich das ja auch oben machen mit einer switch-case oder so.Beim Auslesen solltest du darauf achten, daß deine Kennsymbole eventuell auch als Teil der Nutzdaten vorkommen könnten - aber das zu verhindern ist Aufgabe des Übertragungsprotokolls.
Das ist ein sehr guter Punkt, habe ich noch nicht bedacht und muss ich noch einbauen
"xx" und "yy" werden zusammengesetzt, "xx" ist das höherwertigere Bit, "yy" das Niederwertigere. Heraus soll halt eine Zahl kommen, die wird noch mit einem Faktor skaliert und fertig ist das Ergebnis, keine Großartige Berechnung mehr.
Du kennst den Unterschied zwischen "Bit" und "Byte"?
Ohman, ja kenne ich, habe mich hier verschrieben, redete ja die ganze Zeit vorher von Byte
Danke für deine Hilfe.
-
jakap schrieb:
CStoll schrieb:
jakap schrieb:
Werner Salomon schrieb:
jakap schrieb:
Vom seriellen Port kommen über ein Messgerät Hex Werte rein, die in einen Buffer vom Typen vector<unsigned char> gespeichert werden. Es handelt sich also nicht um echte Zahlen, sondern eher Zeichenketten, die Hex Werte darstellen..
Hallo JaKap,
nur um das nochmal klar zustellen, es kommen lesbare Zeichen über die Schnittstelle, also z.B. die drei Zeichen "?f0" - wäre hilfreich, wenn Du mal ein, zwei Beispiele posten könntest.
Hi
Also ja, es werden einzelne Bytes in der Serial Port Klasse per WinAPI für ein paar Sekunden eingelesen (das Gerät sendet dauerhaft, sendet kontinuierlich einen Standardcode bestehend aus 3 Bytes: "B2" "03" "02").Das heißt, da werden keine lesbaren Werte, sondern pure Binärdaten, richtig? In dem Fall ist die Hexadeimal-Umwandlung durch den Stream gar nicht notwendig und du kannst deinen Wert zusammenbauen per
wert = data[0]*8 + data[1];
Hey!
In wie fern meinst du jetzt "lesbare" Werte, könnteste mir das erklären?
Das data Array enthält nun die beiden Hex "Zahlen", oder die beiden Stellen einer Hex Zahl? Wieso denn *8? Wenn es jetzt *16 wäre und data[0] die höherwertige Stelle ist könnte ichs nachvollziehen. Dann muss aber auch z.B. das A als 10 identifiziert werden, so in etwa wollte ich das ja auch oben machen mit einer switch-case oder so."Lesbare Werte" wären für mich die Zahlen in Textform, d.h. beschränkt auf die Ziffern '0'..'9' und 'A'..'F'. Binäre Daten können alles enthalten von 0 bis 255 (das als "Hex-Zahlen" zu bezeichnen ist auch nicht ganz korrekt).
Und das *8 war mein Fehler - ich konnte mich nicht entscheiden zwischen <<8 und *256
-
Alles klar :))
Jetzt stellt sich nur noch die Fragen, wieso das funktioniert
Also wie interpretiere ich dann die binären Werte? Es stehen also die Werte in einem char Array. Was passiert denn, wenn ich die um 8 Bits shifte, und da z.B. ein Buchstabe drin steht. Wie wird das interpretiert?
Danke
-
Wenn wir von Binärdaten ausgehen, ist es nicht mehr relevant, welches Zeichen ein Byte repräsentiert - du verwendest dort den reinen Zahlenwert zwischen 0 und 255 (oder 0x00 und 0xFF) als Daten. Durch die genannte Formel kannst du aus den beiden Teil-Bytes ein Word zusammensetzen (d.h. ein Zahlenwert zwischen 0x0000 und 0xFFFF), den du dann als deinen Messwert weiterverarbeiten kannst.
-
Wollte mich nochmal melden.
Dein Vorschlag hat natürlich funktioniert, ich brauchte etwas ähnliches in andere Richtung, das hat dann gut mit Modulo 16 bzw 256 geklappt.
Ich gucke mir jetzt nochmal die coolen Beispiele hier an und versuche die zu verstehen.
Ganz herzlichen Dank, super Forum=)
-
jakap schrieb:
Wollte mich nochmal melden.
da ist gut - Du wolltest doch eine Diskussion starten.
jakap schrieb:
In wie fern meinst du jetzt "lesbare" Werte, könnteste mir das erklären?
Über die serielle Schnittstelle kommen sogenannte Zeichen. Jedes Zeichen (auch Byte) hat einen (Zahlen-)Wert der zwischen 0 und 255 (0x00 bis 0xff) liegen kann. Was so ein Zeichen bedeutet, kann jetzt ganz unterschiedlich interpretiert werden. Wenn man annimmt, dass es sich dabei um 'lesbare Zeichen' handelt, so liegen die (meisten) Werte zwischen 32 (0x20) und 126 (0x7e). Dabei wäre eine 32 ein Leerzeichen und z.B. eine 65 der Buchstabe A. Das ganze nennt man auch ASCII-Code.
Wenn Du von Hexzahlen sprichst, so würde ich erwarten, dass z.B. zwei Byte über die Schnittstelle kommen - z.B. eine 70(0x46) für ein F und eine 48(0x30) für die Ziffer 0 - macht zusammen F0 also die (Dezimal)zahl 240.
Wenn man von Binärcode spricht, so meint man damit i.A. die selbe Art wie die Werte (in diesem Fall Integer-Werte) auch im Memory des Speichers abgelegt sind. Angenommen es liegt Big Endian vor, so bedeutet dieselbe Folge der zwei Byte 70 und 48 wie oben die Zahl 17968=256*70+48. So eine Zahl aus zwei Byte nennt man auch ein 'Word'.
Es kommt also immer auf die Interpretation bzw. die Vereinbarung zwischen Sender und Empfänger an, was die Folge von Zeichen (bzw. Byte) bedeutet.Der Stream, den ich Dir vorgeschlagen habe, ist nur für lesbare Zeichen wirklich gut geeignet. Du kannst Dir aber auch selber einen eigenen Stream schreiben, der Binärdaten 'versteht'. Hier hatte ich schon mal so ein Beispiel für binäres Lesen vorgestellt. Voraussetzung dafür ist immer ein Streambuf, der die einzelnen Zeichen (bzw. Bytes) liefert.
Gruß
Werner