Forum: Mikrocontroller und Digitale Elektronik Möglichst kurze Routine für Hex-to-ASCII-Conversion


von Alex V. (bastel_alex) Benutzerseite


Lesenswert?

Worum es geht:

auf einem AVR controller einen empfangenen 16 Bit Hexwert (hier vom ADC) 
zB. 0xA2FD in 4 ASCII Werte umwandeln: "A" "2" "F" "D" (zum Senden über 
UART).

Gibt es da EINFACHE KURZE routinen? Wenn ich jetzt anfange das 
umzusetzen wird es sicher wieder komplizierter als es sein muss...

von Alex V. (bastel_alex) Benutzerseite


Lesenswert?

Außerdem noch eine nebenfrage:
Ich schicke alles per ASCII, damit ich es dann einfach im CSV format 
speichern kann.

Schicke ich dann am ende jeder Zeile ein
- Carriage Return oder
- Line Feed oder
- Carriage Return + Line Feed?

von micha (Gast)


Lesenswert?

Du nimmst ein 16 char Array mit [0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F] und 
nutzt jeweils ein Nibble des Datenwortes als index. Ist nicht schwer

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

micha schrieb:
> Du nimmst ein 16 char Array mit [0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F] und
> nutzt jeweils ein Nibble des Datenwortes als index. Ist nicht schwer

Hier mal die praktische Umsetzung:
1
// main conversion routine 
2
// table unfortunately needs to reside in RAM for now
3
char hextable[16] = "0123456789ABCDEF";
4
// very simple output to hex 
5
static void printbyte(const unsigned char data)
6
{
7
  uart_put(hextable[data >> 4]);
8
  uart_put(hextable[data & 0x0f]);
9
}
10
static void printword(const unsigned int data)
11
{
12
  printbyte(data >> 8);
13
  printbyte(data);
14
}
Es wäre auch möglich, die Hextabelle ins Flash zu setzen, und über 
pgm_read_byte drauf zuzugreifen. So kostet der Spass 16 Bytes im RAM für 
die Tabelle.

: Bearbeitet durch User
von Alex V. (bastel_alex) Benutzerseite


Lesenswert?

Dankesehr, das waren doch mal klare ansagen! :)

Und wie ist es konventionell mit den CR /LF am ende?

von Max G. (l0wside) Benutzerseite


Lesenswert?

Windows-Welt: CR+LF
Unix-Welt: nur LF

Ist letztendlich Geschmackssache.

Max

von Alex V. (bastel_alex) Benutzerseite


Lesenswert?

Danke!!

von Falk B. (falk)


Lesenswert?

CR + LF, das ist das Standardformat aus DOS-Zeiten. Unix macht nur CR.

von innerand i. (innerand)


Lesenswert?

Alex v. L. schrieb:
> Und wie ist es konventionell mit den CR /LF am ende?

Das hängt vom Betriebssystem ab.
Bei Windows Systemen ist es zum Beispiel CR + LF, bei Unixioden nur der 
LF.

von Cyblord -. (cyblord)


Lesenswert?

innerand innerand schrieb:
> bei Unixioden nur der LF

Typisch, und wie kommt dann der Cursor wieder an den Anfang der Zeile?

von Udo S. (urschmitt)


Lesenswert?

Falk Brunner schrieb:
> CR + LF, das ist das Standardformat aus DOS-Zeiten. Unix macht nur CR.

CR LF auch Windows (auch neue Apple?)
Nur CR hatte Apple früher
IBM (vor allem Host, aber auch AIX teilweise) unterscheidet zwischen 
Line Feed (LF) und New Line (NL)

: Bearbeitet durch User
von innerand i. (innerand)


Lesenswert?

cyblord ---- schrieb:
> Typisch, und wie kommt dann der Cursor wieder an den Anfang der Zeile?

Magie.

von ich (Gast)


Lesenswert?

cyblord ---- schrieb:
> innerand innerand schrieb:
>> bei Unixioden nur der LF
>
> Typisch, und wie kommt dann der Cursor wieder an den Anfang der Zeile?

Stimmt schon, was "innerand" sagt.
Bei Unix: LF
Bei Mac: CR
Bei DOS/Windoofs: CR+LF

von Peter D. (peda)


Lesenswert?

1
char buff[4+2+1];
2
3
sprintf( buff, "%04X\r\n", val );

von Luther B. (luther-blissett)


Lesenswert?

Array ist viel zu lang!
1
/*
2
$ avr-gcc -mmcu=atmega8 -DHEX_FUN -c -Os h.c && avr-size h.o
3
   text     data      bss      dec      hex  filename
4
     52        0        0       52       34  h.o
5
$ avr-gcc -mmcu=atmega8 -DHEX_TABLE -c -Os h.c && avr-size h.o
6
   text     data      bss      dec      hex  filename
7
     60       16        0       76       4c  h.o
8
$ avr-gcc -mmcu=atmega8 -DHEX_PROGMEM -c -Os h.c && avr-size h.o
9
   text     data      bss      dec      hex  filename
10
     76        0        0       76       4c  h.o
11
*/
12
#include <stdint.h>
13
#include <avr/pgmspace.h>
14
15
extern void put(char c); 
16
17
#if defined(HEX_TABLE)
18
19
 char hextable[16] = "0123456789ABCDEF";
20
 #define N2C(X) (hextable[X])
21
22
#elif defined(HEX_FUN)
23
24
 char n2c(uint8_t n) { return (n<10?n+'0':n+('A'-10)); }
25
 #define N2C(X) (n2c(X))
26
27
#elif defined(HEX_PROGMEM)
28
29
 char hextable[16] PROGMEM = "0123456789ABCDEF";
30
 #define N2C(X) (hextable[X])
31
32
#else 
33
#error "define HEX_FUN or HEX_TABLE or HEX_PROGMEM"
34
#endif
35
36
void put8(uint8_t n)
37
{
38
  put(N2C(n>>4)); 
39
  put(N2C(n&0xf)); 
40
}
41
void put16(uint16_t n) 
42
{
43
  put8(n>>8);
44
  put8(n);
45
}

: Bearbeitet durch User
von Luther B. (luther-blissett)


Lesenswert?

char n2c(uint8_t n)
{
   n+='0';
   if (n>'9') n+=('A'-'0'-10);
   return n;
}

spart noch mal 2 byte.

von LTC1043 (Gast)


Lesenswert?

Luther Blissett schrieb:
> char hextable[16] = "0123456789ABCDEF";

Sollte das nicht char hextable[17] = "0123456789ABCDEF";
                                ^
--------------------------------|

sein? Im String "0123456789ABCEDF" wird ja wohl noch die '/0' als 
Terminierung dazu gepackt

Cheers

von Karl H. (kbuchegg)


Lesenswert?

LTC1043 schrieb im Beitrag #3499442:
> Luther Blissett schrieb:
>> char hextable[16] = "0123456789ABCDEF";
>
> Sollte das nicht char hextable[17] = "0123456789ABCDEF";
>                                 ^
> --------------------------------|
>
> sein? Im String "0123456789ABCEDF" wird ja wohl noch die '/0' als
> Terminierung dazu gepackt

Die allerdings keiner braucht.
Das Teil wird hier als ein Array von Charactern benutzt und nicht als 
String.

(Und es gibt eine Sonderregel in C: Ist die Initialisierung eines 
Char-Arrays  genau so groß, das alle Zeichen in das Array passen, dann 
hängt der COmpiler kein \0 hinten drann. Jetzt hab ich endlich mal ein 
Beispiel dafür, wo dieses Verhalten nützlich ist :-)

: Bearbeitet durch User
von Axel S. (a-za-z0-9)


Lesenswert?

In vielen meiner Projekte findet sich diese kurze Funktion:

1
void hexout(uint8_t x)                                                          
2
{                                                                               
3
    register uint8_t d= (x>>4) & 0x0F;                                          
4
    putc(d + (d>9 ? 'A'-10 : '0'));                                         
5
    d= x & 0x0F;                                                                
6
    putc(d + (d>9 ? 'A'-10 : '0'));                                         
7
}

Da man einen längeren Integer i.d.R. problemlos in Bytes zerlegen kann, 
ruft man die Funktion einfach für jedes Byte einmal auf, beginnend mit 
dem höchstwertigen.

Selbstverständlich kann man statt putc() auch sendRS232() o.ö. 
verwenden. Und wenn man Klein- statt Großbuchstaben will, tauscht man 
das 'A' gegen ein 'a'.


XL

von Peter II (Gast)


Lesenswert?

Axel Schwenke schrieb:
> In vielen meiner Projekte findet sich diese kurze Funktion:
>     register uint8_t d= (x>>4) & 0x0F;

hast du dir mal den erzeugten ASM code angeschaut? Das Register hatte 
bei mir mehr negative Auswirkungen, weil es dann dafür extra ein neues 
Register verwendet hat. Dabei steht der übergebene wert ja bereits in 
einem Register.

Schneller wird es damit auf jeden Fall nicht.

von Peter D. (peda)


Lesenswert?

Karl Heinz schrieb:
> (Und es gibt eine Sonderregel in C: Ist die Initialisierung eines
> Char-Arrays  genau so groß, das alle Zeichen in das Array passen, dann
> hängt der COmpiler kein \0 hinten drann.

Ich bin immer zu faul zum Zählen.
Ich lass daher die Klammer leer und opfere das Byte.

Aber es ginge auch so:
1
#define STR "1245"
2
3
char y[sizeof(STR)-1] = STR;

von Axel S. (a-za-z0-9)


Lesenswert?

Peter II schrieb:
> Axel Schwenke schrieb:
>> In vielen meiner Projekte findet sich diese kurze Funktion:
>>     register uint8_t d= (x>>4) & 0x0F;
>
> hast du dir mal den erzeugten ASM code angeschaut? Das Register hatte
> bei mir mehr negative Auswirkungen, weil es dann dafür extra ein neues
> Register verwendet hat. Dabei steht der übergebene wert ja bereits in
> einem Register.

Der übergebene Wert darf aber nicht direkt verändert werden, weil er ja 
zweimal gebraucht wird. Unten wird er daher nach r17 gerettet und die 
eigentliche Rechenarbeit wird in r24/r25 gemacht. Was sinnvoll ist, weil 
r24 ja gleich das Argument für den Aufruf von putc() enthalten muß. Ein 
weiterer Grund, das hexout()-Argument nach r17 zu retten.

> Schneller wird es damit auf jeden Fall nicht.

Nein. Es schadet aber auch nicht. avr-gcc erzeugt bei mir mit oder oder 
ohne register den gleichen Code. Interessant ist aber, daß man einen 
Maschinenbefehl sparen kann, wenn man den Ausdruck
1
putc(d + (d>9 ? 'A'-10 : '0'))

ausführlicher (aber nicht unbedingt lesbarer) so schreibt:
1
d+= '0';
2
if (d > '9')
3
  d+= 'A'-10-'0';
4
putc(d)

Hier ist was avr-gcc im Optimierungslevel -Os erzeugt:
1
void hexout(uint8_t x)                                                          
2
{                                                                               
3
 3e4:   1f 93           push    r17                                             
4
 3e6:   18 2f           mov     r17, r24                                        
5
    register uint8_t d= (x>>4) & 0x0F;                                          
6
 3e8:   98 2f           mov     r25, r24                                        
7
 3ea:   92 95           swap    r25                                             
8
 3ec:   9f 70           andi    r25, 0x0F       ; 15                            
9
    lcd_putc(d + (d>9 ? 'A'-10 : '0'));                                         
10
 3ee:   9a 30           cpi     r25, 0x0A       ; 10                            
11
 3f0:   10 f4           brcc    .+4             ; 0x3f6 <hexout+0x12>           
12
 3f2:   80 e3           ldi     r24, 0x30       ; 48                            
13
 3f4:   01 c0           rjmp    .+2             ; 0x3f8 <hexout+0x14>           
14
 3f6:   87 e3           ldi     r24, 0x37       ; 55                            
15
 3f8:   89 0f           add     r24, r25                                        
16
 3fa:   fe d2           rcall   .+1532          ; 0x9f8 <lcd_putc>              
17
    d= x & 0x0F;                                                                
18
 3fc:   91 2f           mov     r25, r17                                        
19
 3fe:   9f 70           andi    r25, 0x0F       ; 15                            
20
    d+= '0';                                                                    
21
 400:   89 2f           mov     r24, r25                                        
22
 402:   80 5d           subi    r24, 0xD0       ; 208                           
23
    if (d>'9') d+= 'A'-10-'0';                                                
24
 404:   8a 33           cpi     r24, 0x3A       ; 58                            
25
 406:   08 f0           brcs    .+2             ; 0x40a <hexout+0x26>           
26
 408:   89 5f           subi    r24, 0xF9       ; 249                           
27
    lcd_putc(d);                                                                
28
 40a:   f6 d2           rcall   .+1516          ; 0x9f8 <lcd_putc>              
29
}                                                                               
30
 40c:   1f 91           pop     r17                                             
31
 40e:   08 95           ret

In Assembler hätte ich den Teil übrigens anders geschrieben:
1
  andi r24, 0x0F
2
  ldi r25, '0'
3
  cpi r24, 10
4
  brcs +2
5
  ldi r25, 'A'-10
6
  add r24, r25
7
  rcall putc

genauso kurz wie die kürzere Variante oben, aber IMHO lesbarer. Und am 
Ende der Funktion hätte ich das ret noch weggespart ;)
1
  pop r17
2
  rjmp putc


XL

von W.S. (Gast)


Lesenswert?

Peter Dannegger schrieb:
> sprintf( buff, "%04X\r\n", val );

Tja Pete, Thema verfehlt ürde ich sagen. Es ging nicht um eine 
minimalistische Quellzeile, sondern um eine "EINFACHE KURZE" Routine.
Ich denke, der TO meinte sowas:
1
const char HexChars[17]   = {"0123456789ABCDEF"};
2
/* ein Hex-Zeichen ausgeben */
3
void Nibble_Out (int aHex)
4
{ V24_CharOut (HexChars[aHex & 0x0F]); }
5
6
/* ein Byte als Hexa ausgeben */
7
void HexB_Out (byte aHex)
8
{ Nibble_Out (aHex >> 4);
9
  Nibble_Out (aHex);
10
}
11
12
/* ein Word als Hexa ausgeben */
13
void HexW_Out (word aHex)
14
{ HexB_Out (aHex >> 8);
15
  HexB_Out (aHex);
16
}
17
18
/* ein DWORD als Hexa ausgeben */
19
void HexL_Out (long aHex)
20
{ HexW_Out (aHex >> 16);
21
  HexW_Out (aHex);
22
}
23
24
/* ein QWORD als Hexa ausgeben */
25
void HexQ_Out (int64* aHex)
26
{ HexL_Out (*aHex >> 32);
27
  HexL_Out (*aHex);
28
}
zitiert aus der Lernbetty..  ;-)

W.S.

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


Lesenswert?

Axel Schwenke schrieb:
> Und am Ende der Funktion hätte ich das ret noch weggespart ;)
>   pop r17
>   rjmp putc

Das hätte der GCC 4.7+ auch ;)

von Luther B. (luther-blissett)


Lesenswert?

W.S. schrieb:
> Peter Dannegger schrieb:
>> sprintf( buff, "%04X\r\n", val );
>
> Tja Pete, Thema verfehlt ürde ich sagen. Es ging nicht um eine
> minimalistische Quellzeile, sondern um eine "EINFACHE KURZE" Routine.
> Ich denke, der TO meinte sowas:
>
> const char HexChars[17]   = {"0123456789ABCDEF"};

Lies den Thread noch mal durch. Die Kovertierung über einen Arraylookup 
ist schon diskutiert worden. Du findest dort auch einen Post der 
erläutert, warum du für den HexChar Array keine 17 bytes brauchst.

von Peter D. (peda)


Lesenswert?

W.S. schrieb:
> Tja Pete, Thema verfehlt ürde ich sagen. Es ging nicht um eine
> minimalistische Quellzeile, sondern um eine "EINFACHE KURZE" Routine.

Nö, es ging ihm genau um den Quelltext, daß der nicht zu kompliziert 
ist:

Alex v. L. schrieb:
> Wenn ich jetzt anfange das
> umzusetzen wird es sicher wieder komplizierter als es sein muss...

Das mit "wenig Flash" haben nur andere fälschlich hinein interpretiert.

von Luther B. (luther-blissett)


Lesenswert?

Peter Dannegger schrieb:

> Das mit "wenig Flash" haben nur andere fälschlich hinein interpretiert.

Das stimmt allerdings :)

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.