Forum: Compiler & IDEs Umwandlung eines Strings (hex) in Long Long int


von Mattias (Gast)


Lesenswert?

Hallo Liebe Leute,
ich habe ein kleines Problem mit der Umwandlung von hex asciizeichen in 
eine Variable.
Ich habe einen String mit zB. folgendem Inhalt:
"010ff00a3001". Das sind 12 Zeichen, also 6 Byte. Die möchte ich in eine 
Variable packen. Dazu brauche ich eine long long int, die ist dann 8 
Byte lang.
Ich habe schon eine Funktion, die mir aus 2 Zeichen eine Zahl macht.
hier die Funktion:
1
// Umwandlung von 2 Zeichen eines strings  in ein Byte
2
unsigned char hex(unsigned char a, unsigned char b)
3
{
4
  unsigned char c=0;
5
  
6
  if(a < 59){ 
7
    c = 16 * ( a - 48);  // wenn a eine zahl ist
8
  }else{
9
    c = 16 * ( a - 87);  // wenn a eine zeichen ist
10
  }
11
  if(b < 59){ 
12
    c +=  ( b - 48);  // wenn b eine zahl ist
13
  }else{
14
          c +=  ( b - 87);  // wenn b eine zeichen ist
15
  }
16
  return c;
17
}
Jetzt hole ich mir die ganzen Zeichen aus dem String und schiebe sie in 
meine Variable:
1
      frequenz = 0ULL;
2
      frequenz += (unsigned long long)hex(display_text[6],   display_text [7])  << 48;
3
      frequenz += (unsigned long long)hex(display_text[8],   display_text [9])  << 32;        
4
      frequenz += (unsigned long long)hex(display_text[10],  display_text[11])  << 24;        
5
      frequenz += (unsigned long long)hex(display_text[12],  display_text[13])  << 16;        
6
      frequenz += (unsigned long long)hex(display_text[14],  display_text[15])  << 8;        
7
      frequenz += (unsigned long long)hex(display_text[16],  display_text[17])  << 0;
Prüfen kann ich den Inhalt einfach zB. so:
1
      if(frequenz == 0xa000000000000000ULL) TCCR1B = 0x04;    // Timer1 starten
Erst dachte ich es Funktioniert. Aber nach eingehender Prüfung habe ich 
feststellen müssen, dass es mit dem höchsten Byte, also die ersten 
beiden Zechen, nicht geht. Wenn also mein String so aussieht, wie in dem 
obigen Vergleich, wird der Timer nicht gestartet. Ich weiss nicht mehr 
weiter, woran kann das liegen ?
Gibt es vielleicht eine Möglichkeit das Problem anders zu lösen ?
Danke schon im voraus.
Grüße Mattias

von Karl H. (kbuchegg)


Lesenswert?

1
      frequenz += (unsigned long long)hex(display_text[6],   display_text [7])  << 48;

48 ist zuviel. Die nächste 8-er Grenze ist bei 40

1
unsigned char hex(unsigned char a, unsigned char b)
2
{
3
  unsigned char c=0;
4
  
5
  if(a < 59){ 
6
    c = 16 * ( a - 48);  // wenn a eine zahl ist
7
  }else{
8
    c = 16 * ( a - 87);  // wenn a eine zeichen ist
9
  }
10
  if(b < 59){ 
11
    c +=  ( b - 48);  // wenn b eine zahl ist
12
  }else{
13
          c +=  ( b - 87);  // wenn b eine zeichen ist
14
  }
15
  return c;
16
}

Warum 59, warum 48, warum 87?

Schreibs doch so
1
unsigned char hex(unsigned char a, unsigned char b)
2
{
3
  unsigned char c=0;
4
  
5
  if(a <= '9'){ 
6
    c = 16 * ( a - '0');  // wenn a eine zahl ist
7
  }else{
8
    c = 16 * ( a - 'a' + 10 );  // wenn a eine zeichen ist
9
  }
10
  if(b <= '9'){ 
11
    c +=  ( b - '0');  // wenn b eine zahl ist
12
  }else{
13
    c +=  ( b - 'a' + 10 );  // wenn b eine zeichen ist
14
  }
15
  return c;
16
}

Das hat mehrere Vorteile
* man kann im Code ablesen, was da eigentlich passiert
* Du musst dir keine ASCII Codes merken
* Es ist nicht so verwirrend warum du den ASCII Code von 'W' abziehst

von Klaus W. (mfgkw)


Lesenswert?

display_text[] ist bei dir "010ff00a3001"?

Wenn der 12 Zeichen lang ist, wieso nimmst du dann Zeichen bis
display_text[17]?

von Udo S (Gast)


Lesenswert?

Und warum nimmst du nicht einfach ein Byte Array? Dann könnte man die 
Funktion universell und unabhängig von der Länge des Hex Strings 
implementieren, ausserdem sind shifts von 32, 40 (oder noch mehr) nicht 
unbedingt performant zumal je nachdem was der Prozessor in Hardware kann 
bei so grossen Variablen entsprechende overflow Überprüfungen per 
software gemacht werden müssen.

Udo

von Klaus W. (mfgkw)


Lesenswert?

Wenn man es so macht, wie ich glaube daß du es vorgeschlagen hast,
muß man aber die Endianness des jeweiligen Rechners kennen und
berücksichtigen.

von Mattias (Gast)


Lesenswert?

Hallo,
ja das mit der 48 ist natürlich Banane. Jetzt sollte es Funktionieren.
@Udo S
wie meinst Du das mit dem Byte Array ?
Könntest Du mal ein Beispiel geben ?
Wenn ich nicht shifte, wie soll ich die Werte sonnst dort hin bekommen. 
Ich denke mal multiplizieren ist schlimmer.

Gruß Mattias

von Udo S (Gast)


Lesenswert?

@Klaus Wachtler

Der Poster hat nur gesagt er möchte den Hex String ein eine Variable 
packen. Das hiess für mich, daß der String nicht einen bestimmten 
numerischen Wert beinhaltet, sondern er nur Hex in numerische Werte 
wandeln will.
Wenn der String genau eine Zahl darstellt hast Du natürlich recht, dann 
müsstest Du wirklich berücksichtigen wie diese Zahl gespeichert wird.

Trotzdem sind diese Shift Operationen abhängig von Prozessor und 
Compiler nicht unbedingt effizient, ein Mul ist bei modernen Prozessoren 
oft schneller, wobei aber mancher Compiler das wieder optimiert.

udo

von Mattias (Gast)


Lesenswert?

Hallo,
es handelt sich um einen Mega8 und als Compiler den Winavr.
Es ist wirklich so, dass der String eine einzige Zahl darstellt.
Wenn jemand eine Idee hat, wie man das effizienter macht, dann bitte 
melden.
@Klaus Wachtler
Was sind Endianness ?

Gruß Mattias

von Karl H. (kbuchegg)


Lesenswert?

Mattias schrieb:
> Hallo,
> es handelt sich um einen Mega8 und als Compiler den Winavr.
> Es ist wirklich so, dass der String eine einzige Zahl darstellt.

Die Frage ist: muss die Zahl so dermassen gross sein?

Da deine Variable Frequenz heisst, rate ich mal, dass du einen 
Frequenzgenerator baust.
Dazu nimmst du einen Timer her.
Jetzt die Gretchenfrage: Ist die mit dieser Zahl einstellbare Frequenz 
wirklich realistisch? Kannst du mit dem Timer speziell die grossen 
Frequenzen wirklich aufs Herz genau einstellen? Oder gaukelst du mit 
diesen großen Zahlen und den vielen Stellen nicht eine Genauigkeit vor, 
die du nicht hast.

Es kann natürlich schon sein, dass ich falsch geraten habe. Allerdings 
kommt ein long long schon sehr selten vor, dass man sich automatisch 
frägt: Was will er machen? Will er die Atome im Universum zählen?

von Peter D. (peda)


Lesenswert?

Mattias schrieb:
> es handelt sich um einen Mega8 und als Compiler den Winavr.

Dann ist 64Bit nur mit der Kneifzange anzufassen.
Die 64Bit-Lib ist extrem groß (~5kB) und extrem langsam, also noch 
deutlich schlechter als float.

> Wenn jemand eine Idee hat, wie man das effizienter macht, dann bitte
> melden.

Wurde schon gesagt, nimm ein Byte-Array.
Die Zahl liegt ja schon als HEX vor, also muß Du nix rechnen:
- wandle ein Zeichen in ein Nibble
- füge 2 Nibble zu einem Byte zusammen
- usw. bis alle Bytes fertig sind


Peter

von Mattias (Gast)


Lesenswert?

Hallo,
@Karl heinz Buchegger
ja, ich brauche so große Zahlen. Das ist für ein DDS, 48bit Auflösung.
@Peter Dannegger
wie geht das mit dem Byte-Array ?
Habe sowas noch nicht gemacht.

Gruß Mattias

von Karl H. (kbuchegg)


Lesenswert?

Mattias schrieb:
> Hallo,
> @Karl heinz Buchegger
> ja, ich brauche so große Zahlen. Das ist für ein DDS, 48bit Auflösung.

OK.
Du musst aber nicht damit selber rechnen, oder?
D.h. du kriegst die 'Zahl' und gibst sie einfach nur weiter.

(Denk gut nach: Wenn du eine Anzeige hast auf die die Zahl rauf muss, 
musst du rechnen)

> @Peter Dannegger
> wie geht das mit dem Byte-Array ?
> Habe sowas noch nicht gemacht.

wo liegt das Problem?
uint8_t Bytes[6];

Im Grunde hast du das ja schon fast mit deinem String :-)

von Klaus W. (mfgkw)


Lesenswert?

Mattias schrieb:
> ...
> @Klaus Wachtler
> Was sind Endianness ?

Das ist dann relevant, wenn man die Zahl nicht mit shift-Operatoren
zusammenschrauben will, sondern direkt Byte für Byte im Speicher
ablegt.
Dann muß man nämlich wissen, ob das niederwertige Byte der
long long an der Adresse der Variablen liegt und die höherwertigen
oberhalb (little endian), oder die höherwertigen unten und
die niederwertigen oben (big endian).
Grob gesagt sind Intel+AMD littele endian, der Rest (inkl. AVR)
big endian.

Siehe http://de.wikipedia.org/wiki/Endianness

Wenn man weiß, wie es auf dem aktuellen Rechner aussieht und man
in Kauf nimmt, den Quelltext für einen anderen Fall entsprechend
ändern muß, könnte man die Umwandlung deutlich effizienter
machen.
Mein Vorschlag:
1
#include <stdio.h>
2
#include <stdlib.h>
3
#include <stdint.h>
4
#include <ctype.h>
5
6
unsigned char hexdigit2value( char c )
7
{
8
  switch( c )
9
  {
10
    case '0':
11
      return 0;
12
      break;
13
14
    case '1':
15
      return 1;
16
      break;
17
18
    case '2':
19
      return 2;
20
      break;
21
22
    case '3':
23
      return 3;
24
      break;
25
26
    case '4':
27
      return 4;
28
      break;
29
30
    case '5':
31
      return 5;
32
      break;
33
34
    case '6':
35
      return 6;
36
      break;
37
38
    case '7':
39
      return 7;
40
      break;
41
42
    case '8':
43
      return 8;
44
      break;
45
46
    case '9':
47
      return 9;
48
      break;
49
50
    case 'a':
51
    case 'A':
52
      return 0xA;
53
      break;
54
55
    case 'b':
56
    case 'B':
57
      return 0xB;
58
      break;
59
60
    case 'c':
61
    case 'C':
62
      return 0xC;
63
      break;
64
65
    case 'd':
66
    case 'D':
67
      return 0xD;
68
      break;
69
70
    case 'e':
71
    case 'E':
72
      return 0xE;
73
      break;
74
75
    case 'f':
76
    case 'F':
77
      return 0xF;
78
      break;
79
80
    default :
81
      return 0;
82
      break;
83
  }
84
}
85
86
void szhex2uint64( const char *p_src, uint64_t *pll_dest )
87
{
88
  // Schleifenzähler:
89
  uint8_t               iByte;
90
91
  // p_aktuell soll immer auf das gerade zu verarbeitende Zeichen zeigen:
92
  const char *p_aktuell = p_src;
93
94
  // zeigt immer auf die Stelle in der uint64_t, die als nächstes
95
  // gesetzt wird:
96
  unsigned char *p_dest = (unsigned char*)pll_dest;
97
98
  // Falls auf dem aktuellen System Zahlen little endian abgelegt
99
  // werden, muß unten in der uint64_t begonnen werden und nach oben
100
  // gewandert werden; bei einem big endian system muß man hinten
101
  // beginnen und nach unten wandern.
102
103
  // Nur bei little endian (PC-Prozessoren Intel, AMD):
104
  const int    offset = +1;
105
106
  // Nur bei big endian (fast alle anderen: AVR, Motorola, ...):
107
  //const int    offset = -1;
108
  //p_dest += sizeof(*pll_dest);
109
110
111
  // ab hier wieder für big- und little endian:
112
113
  // an das Stringende gehen:
114
  while( *p_aktuell )
115
  {
116
    p_aktuell++;
117
  }
118
  p_aktuell--; // wieder einen zurück vor die abschließende 0
119
120
121
  for( iByte=0; iByte<sizeof(*pll_dest); ++iByte )
122
  {
123
    if( p_aktuell>p_src )
124
    {
125
      // es können noch mindestens 2 Zeichen benutzt werden:
126
      *p_dest = hexdigit2value( *p_aktuell ) | hexdigit2value( p_aktuell[-1] )<<4;
127
      p_aktuell -= 2;
128
    }
129
    else if( p_aktuell==p_src )
130
    {
131
      // es kann nur noch ein Zeichen benutzt werden:
132
      *p_dest = hexdigit2value( *p_aktuell );
133
      --p_aktuell;
134
    }
135
    else
136
    {
137
      // String schon abgearbeitet:
138
      *p_dest = 0;
139
    }
140
141
    // weiter zu nächstem Byte in der uint64_t:
142
    p_dest += offset;
143
  }
144
}
145
146
147
int main( int nargs, char **args )
148
{
149
150
  char meintollerstring[] = "10ff00a3001";
151
152
  uint64_t      ganzlang = 0x0ULL;
153
154
  szhex2uint64( meintollerstring, &ganzlang );
155
156
  printf( "%llx\n", ganzlang );
157
158
  return 0;
159
}

In dieser Form für little endian probiert.
Durch Aus- bzw. Einkommentieren von drei Zeilen sollte es
auf AVR korrekt arbeiten (nicht probiert).

von Klaus W. (mfgkw)


Lesenswert?

PS: Diese Version funktioniert bei allen Stringlängen (bei mehr
als 16 Zeichen werden die führenden ignoriert) ebenso wie
für ungerade Stringlängen.
Ungültige Ziffern (also nicht 0-9 oder a-f oder A-F) werden
zu 0 angenommen.

von Stefan E. (sternst)


Lesenswert?

Klaus Wachtler schrieb:

> Grob gesagt sind Intel+AMD littele endian, der Rest (inkl. AVR)
> big endian.

Das mit dem AVR = big endian war nicht so wirklich ernst gemeint, oder? 
;-)

von Peter D. (peda)


Lesenswert?

Vermutlich werden die Bytes per SPI an den DDS-Chip gesendet.
Dann ist die Byteorder des Compilers egal, wenn er den Wert in ein Array 
ablegt und dieses Array sendet.
Nur beim Umweg über uint64_t muß man die Byteorder wissen.


Peter

von Klaus W. (mfgkw)


Lesenswert?

Stefan Ernst schrieb:
> Klaus Wachtler schrieb:
>
>> Grob gesagt sind Intel+AMD littele endian, der Rest (inkl. AVR)
>> big endian.
>
> Das mit dem AVR = big endian war nicht so wirklich ernst gemeint, oder?
> ;-)

ok, war ein Scherz.
Wobei genau genommen das ja eigentlich nicht mal vom AVR vorgegeben
wird, wenn ich das richtig sehe. Oder kennt der AVR überhaupt
2 Byte oder mehr? Wenn nicht, hat der Compiler ja eigentlich freie
Auswahl, wie er es macht.

Aber tatsächlich: zumindest die Kombination AVR+gcc ist little endian.

von Stefan E. (sternst)


Lesenswert?

Klaus Wachtler schrieb:

> Wobei genau genommen das ja eigentlich nicht mal vom AVR vorgegeben
> wird, wenn ich das richtig sehe. Oder kennt der AVR überhaupt
> 2 Byte oder mehr? Wenn nicht, hat der Compiler ja eigentlich freie
> Auswahl, wie er es macht.

Die 16-Bit-Register sind Little-Endian, und die Opcodes liegen auch 
Little-Endian im Flash. So gesehen ist der AVR Little-Endian. Aber es 
gibt ausschließlich 8-Bit Speicherzugriffe, also hätte der Compiler 
theoretisch die freie Wahl.

von Udo S (Gast)


Lesenswert?

Der AVR kennt insofern 2 Byte Werte, als er die z.B. beim Timer in zwei 
Register hat. Wenn das HighByte in dem Register mit der höheren Adresse 
ist (ich kenn den AVR nicht so gut) benutzt er Big Endian.
Aber im 8 Bit Controllerbereich ist das eh egal, weil du die Werte 
Byteweise in ein Display oder eine weiterverarbeitende Peripherie 
steckst, und da musst Du eh die Doku bemühen ober LSB oder HSB zuerst 
kommt. Und für das zuammenbauen oder Multiplizieren der Werte musst du 
es eh von Hand machen.

Also insofern wäre ein Byte Array wesentlich schneller zumal Du sonst 
diese LongLong Bibliothek brauchst.
Ich persönlich mag bei so kleinen Controllern eher Assembler, da weiss 
man wenigstens was passiert und hats beim Debuggen viel einfacher, ist 
aber Geschmackssache.

Nochmal zu langsam / schnell: Moderne Prozessoren brauchen für eine 
Multiplikation nur einen Takt, genausolange wie für einen Shift um 1 
Bit. Das heisst im Extremfall für eine Multiplikation einer Tahl mit 
2^24 brauchst Du nur einen Takt (32Bit Prozessor), für das Schieben von 
24 Bit 24 Takte.
Früher zur Zeit des 8088 brauchte eine Multiplikation zweier 16 Bit 
Zahlen zu einem 32 Bit Ergebnis bis zu 100 Takte, da waren 
Schiebeoperationen deutlich schneller. Aber das ist hochgradig 
Prozessorabhängig und im Falle einer Hochsprache wie C auch noch 
Compilerabgängig.

Gruß, Udo

von Udo S. (udo)


Lesenswert?

> Autor: Udo S (Gast)
> Datum: 20.11.2009 16:15

> Der AVR kennt insofern 2 Byte Werte, als er die z.B. beim Timer in zwei
> Register hat. Wenn das HighByte in dem Register mit der höheren Adresse
> ist (ich kenn den AVR nicht so gut) benutzt er Big Endian.
> Aber im 8 Bit Controllerbereich ist das eh egal, weil du die Werte
> Byteweise in ein Display oder eine weiterverarbeitende Peripherie
> steckst, und da musst Du eh die Doku bemühen ober LSB oder HSB zuerst
> kommt. Und für das zuammenbauen oder Multiplizieren der Werte musst du
> es eh von Hand machen.

> Also insofern wäre ein Byte Array wesentlich schneller zumal Du sonst
> diese LongLong Bibliothek brauchst.
> Ich persönlich mag bei so kleinen Controllern eher Assembler, da weiss
> man wenigstens was passiert und hats beim Debuggen viel einfacher, ist
> aber Geschmackssache.

> Nochmal zu langsam / schnell: Moderne Prozessoren brauchen für eine
> Multiplikation nur einen Takt, genausolange wie für einen Shift um 1
> Bit. Das heisst im Extremfall für eine Multiplikation einer Tahl mit
> 2^24 brauchst Du nur einen Takt (32Bit Prozessor), für das Schieben von
> 24 Bit 24 Takte.
> Früher zur Zeit des 8088 brauchte eine Multiplikation zweier 16 Bit
> Zahlen zu einem 32 Bit Ergebnis bis zu 100 Takte, da waren
> Schiebeoperationen deutlich schneller. Aber das ist hochgradig
> Prozessorabhängig und im Falle einer Hochsprache wie C auch noch
> Compilerabgängig.

> Gruß, Udo

Auch hier möchte ich Festellen, dass nicht ich diesen Beitrag 
geschrieben habe.

@ Gast (Udo S)

Schaff dir bitte einen anderen Nick an.

Gruß
Udo

von Stefan E. (sternst)


Lesenswert?

Udo S schrieb:
> Der AVR kennt insofern 2 Byte Werte, als er die z.B. beim Timer in zwei
> Register hat. Wenn das HighByte in dem Register mit der höheren Adresse
> ist (ich kenn den AVR nicht so gut) benutzt er Big Endian.

High-Byte in höherer Adresse ist Little-Endian, nicht Big-Endian.

von Peter D. (peda)


Lesenswert?

Abspeichern in ein Array:
1
uint8_t get_nibble( uint8_t val )               // convert hex digit to 0..15
2
{
3
  switch( val ){
4
    case '0' ... '9': val -= '0'; break;
5
6
    case 'a' ... 'f': val += 10 - 'a'; break;
7
8
    case 'A' ... 'F': val += 10 - 'A'; break;
9
10
    default: val = 0xFF;                        // no valid hex digit
11
  }
12
  return val;
13
}
14
15
16
uint16_t get_hex_byte( uint8_t * ptr )          // read two digits to 0..255
17
{
18
  uint8_t h, l;
19
20
  h = get_nibble( *ptr );
21
  if( h > 15 ) return 0xFFFF;
22
  l = get_nibble( *++ptr );
23
  if( l > 15 ) return h;
24
  h <<= 4;
25
  return h | l;
26
}
27
28
29
uint8_t dds_data[6];
30
31
32
uint8_t parse_string( uint8_t *s )
33
{
34
  uint16_t val;
35
  uint8_t i;
36
37
  for( i = 0; i < sizeof( dds_data ); i++ ){    // convert up to 12 digits
38
    val = get_hex_byte( s );
39
    if( val & 0x8000 )                          // no valid hex digit
40
      break;
41
    dds_data[i] = val;
42
    s += 2;
43
  }
44
  return i;                                     // number of valid bytes
45
}
Ergibt 136 Byte Code.


Peter

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.