www.mikrocontroller.net

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


Autor: Mattias (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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:
// Umwandlung von 2 Zeichen eines strings  in ein Byte
unsigned char hex(unsigned char a, unsigned char b)
{
  unsigned char c=0;
  
  if(a < 59){ 
    c = 16 * ( a - 48);  // wenn a eine zahl ist
  }else{
    c = 16 * ( a - 87);  // wenn a eine zeichen ist
  }
  if(b < 59){ 
    c +=  ( b - 48);  // wenn b eine zahl ist
  }else{
          c +=  ( b - 87);  // wenn b eine zeichen ist
  }
  return c;
}
Jetzt hole ich mir die ganzen Zeichen aus dem String und schiebe sie in 
meine Variable:
      frequenz = 0ULL;
      frequenz += (unsigned long long)hex(display_text[6],   display_text [7])  << 48;
      frequenz += (unsigned long long)hex(display_text[8],   display_text [9])  << 32;        
      frequenz += (unsigned long long)hex(display_text[10],  display_text[11])  << 24;        
      frequenz += (unsigned long long)hex(display_text[12],  display_text[13])  << 16;        
      frequenz += (unsigned long long)hex(display_text[14],  display_text[15])  << 8;        
      frequenz += (unsigned long long)hex(display_text[16],  display_text[17])  << 0;        

Prüfen kann ich den Inhalt einfach zB. so:
      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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
      frequenz += (unsigned long long)hex(display_text[6],   display_text [7])  << 48;

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

unsigned char hex(unsigned char a, unsigned char b)
{
  unsigned char c=0;
  
  if(a < 59){ 
    c = 16 * ( a - 48);  // wenn a eine zahl ist
  }else{
    c = 16 * ( a - 87);  // wenn a eine zeichen ist
  }
  if(b < 59){ 
    c +=  ( b - 48);  // wenn b eine zahl ist
  }else{
          c +=  ( b - 87);  // wenn b eine zeichen ist
  }
  return c;
}

Warum 59, warum 48, warum 87?

Schreibs doch so
unsigned char hex(unsigned char a, unsigned char b)
{
  unsigned char c=0;
  
  if(a <= '9'){ 
    c = 16 * ( a - '0');  // wenn a eine zahl ist
  }else{
    c = 16 * ( a - 'a' + 10 );  // wenn a eine zeichen ist
  }
  if(b <= '9'){ 
    c +=  ( b - '0');  // wenn b eine zahl ist
  }else{
    c +=  ( b - 'a' + 10 );  // wenn b eine zeichen ist
  }
  return c;
}

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

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
display_text[] ist bei dir "010ff00a3001"?

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

Autor: Udo S (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Mattias (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Udo S (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Mattias (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Mattias (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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 :-)

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht 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:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <ctype.h>

unsigned char hexdigit2value( char c )
{
  switch( c )
  {
    case '0':
      return 0;
      break;

    case '1':
      return 1;
      break;

    case '2':
      return 2;
      break;

    case '3':
      return 3;
      break;

    case '4':
      return 4;
      break;

    case '5':
      return 5;
      break;

    case '6':
      return 6;
      break;

    case '7':
      return 7;
      break;

    case '8':
      return 8;
      break;

    case '9':
      return 9;
      break;

    case 'a':
    case 'A':
      return 0xA;
      break;

    case 'b':
    case 'B':
      return 0xB;
      break;

    case 'c':
    case 'C':
      return 0xC;
      break;

    case 'd':
    case 'D':
      return 0xD;
      break;

    case 'e':
    case 'E':
      return 0xE;
      break;

    case 'f':
    case 'F':
      return 0xF;
      break;

    default :
      return 0;
      break;
  }
}

void szhex2uint64( const char *p_src, uint64_t *pll_dest )
{
  // Schleifenzähler:
  uint8_t               iByte;

  // p_aktuell soll immer auf das gerade zu verarbeitende Zeichen zeigen:
  const char *p_aktuell = p_src;

  // zeigt immer auf die Stelle in der uint64_t, die als nächstes
  // gesetzt wird:
  unsigned char *p_dest = (unsigned char*)pll_dest;

  // Falls auf dem aktuellen System Zahlen little endian abgelegt
  // werden, muß unten in der uint64_t begonnen werden und nach oben
  // gewandert werden; bei einem big endian system muß man hinten
  // beginnen und nach unten wandern.

  // Nur bei little endian (PC-Prozessoren Intel, AMD):
  const int    offset = +1;

  // Nur bei big endian (fast alle anderen: AVR, Motorola, ...):
  //const int    offset = -1;
  //p_dest += sizeof(*pll_dest);


  // ab hier wieder für big- und little endian:

  // an das Stringende gehen:
  while( *p_aktuell )
  {
    p_aktuell++;
  }
  p_aktuell--; // wieder einen zurück vor die abschließende 0


  for( iByte=0; iByte<sizeof(*pll_dest); ++iByte )
  {
    if( p_aktuell>p_src )
    {
      // es können noch mindestens 2 Zeichen benutzt werden:
      *p_dest = hexdigit2value( *p_aktuell ) | hexdigit2value( p_aktuell[-1] )<<4;
      p_aktuell -= 2;
    }
    else if( p_aktuell==p_src )
    {
      // es kann nur noch ein Zeichen benutzt werden:
      *p_dest = hexdigit2value( *p_aktuell );
      --p_aktuell;
    }
    else
    {
      // String schon abgearbeitet:
      *p_dest = 0;
    }

    // weiter zu nächstem Byte in der uint64_t:
    p_dest += offset;
  }
}


int main( int nargs, char **args )
{

  char meintollerstring[] = "10ff00a3001";

  uint64_t      ganzlang = 0x0ULL;

  szhex2uint64( meintollerstring, &ganzlang );

  printf( "%llx\n", ganzlang );

  return 0;
}

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

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht 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? 
;-)

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Udo S (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Udo S. (udo)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Abspeichern in ein Array:

uint8_t get_nibble( uint8_t val )               // convert hex digit to 0..15
{
  switch( val ){
    case '0' ... '9': val -= '0'; break;

    case 'a' ... 'f': val += 10 - 'a'; break;

    case 'A' ... 'F': val += 10 - 'A'; break;

    default: val = 0xFF;                        // no valid hex digit
  }
  return val;
}


uint16_t get_hex_byte( uint8_t * ptr )          // read two digits to 0..255
{
  uint8_t h, l;

  h = get_nibble( *ptr );
  if( h > 15 ) return 0xFFFF;
  l = get_nibble( *++ptr );
  if( l > 15 ) return h;
  h <<= 4;
  return h | l;
}


uint8_t dds_data[6];


uint8_t parse_string( uint8_t *s )
{
  uint16_t val;
  uint8_t i;

  for( i = 0; i < sizeof( dds_data ); i++ ){    // convert up to 12 digits
    val = get_hex_byte( s );
    if( val & 0x8000 )                          // no valid hex digit
      break;
    dds_data[i] = val;
    s += 2;
  }
  return i;                                     // number of valid bytes
}

Ergibt 136 Byte Code.


Peter

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.