Conversion from HEX to ASCII

GNU GPL  Obsah  Memory model
This page discusses conversion from HEX to ASCII and tries to find an optimal algorithm.

In embedded programming, the basic problem is user interface. Besides reading data from the keyboard, printing to the display, etc., various conversions are also encountered.

Option 1 - Look-up table

Although it may seem primitive, this option is widely used. First, it is simple, and it can achieve high speed that does not depend on the number being converted. It can also convert into a representation different than 0123456789ABCDEF, for example to the Hewlett-Packard representation of 0123456789ACFHPU (used in signature analysis etc.), which is more legible on 7-segment displays.

Table lookup corresponds roughly to the following C-like fragment:

const code char   HEXstring[16] = "0123456789ABCDEF";

print_hex (BYTE num)
{
   put_nibble (num >> 4);
   put_nibble (num);


put_nibble (BYTE nibble)
{
   char_from_Acc (HEXstring[nibble & 0x0F]);
}

In ASM51 the following code might be used:

print_hex1:
   PUSH   ACC           ;store LSB for later
   SWAP   A             ;move MSB from bits 7..4 to 3..0
   ACALL  put_nibble1   ;print MSB part
   POP    ACC           ;restore value
   ACALL  put_nibble1   ;print LSB part
   RET
;
put_nibble1:
   ANL    A,#0FH        ;use only LSB
   ADD    A,#HEXstring1-mvcoff1 ;read table offset
   MOVC   A,@A+PC       ;use it as an index to retrieve the ASCII value
mvcoff1:
   ACALL  char_from_Acc ;print fetched value
   RET
HEXstring1:
   DB    '0123456789ABCDEF'
;
; 34 bytes, execution time 31 µs at 12 MHz
;

After removing unnecessary CALL and RETURN, the following modification is obtained:

print_hex2:
   PUSH   ACC
   SWAP   A
   ACALL  put_nibble2
   POP    ACC
;
put_nibble2:
   ANL    A,#0FH
   ADD    A,#HEXstring2-mvcoff2
   MOVC   A,@A+PC
mvcoff2:
   AJMP   char_from_Acc
HEXstring2:
   DB     '0123456789ABCDEF' ;'standard' hex string
;  DB     '0123456789ACFHPU' ;HP signature analysis

;
; 30 bytes, execution time 23 µs at 12 MHz
;

Simple elimination of redundant procedure calls shortens execution time from 31 top 23 µs and even shortens the code by 4 bytes. To make it even faster, we have to eliminate the put_nibble call, placing the procedure inline instead of the call.

print_hex3:
   PUSH   ACC
   SWAP   A
   ANL    A,#0FH
   ADD    A,#HEXstring3-mvcoff3a
   MOVC   A,@A+PC
mvcoff3a:
   ACALL  char_from_Acc
   POP    ACC
   ANL    A,#0FH
   ADD    A,#HEXstring3-mvcoff3b
   MOVC   A,@A+PC
mvcoff3b:
   AJMP   char_from_Acc
HEXstring3:
   DB     '0123456789ABCDEF' ;'standard' hex string
;  DB     '0123456789ACFHPU' ;HP signature analysis

;
; 35 bytes, execution time 21 µs at 12 MHz
;

Inline procedures shorten execution time by another 2 µs but the code is 5 bytes longer.

Option 2 - compare

This is used quite often because it is easy to understand.

The conversion algorithm corresponds to the following C-like fragment

print_hex (BYTE num)
{
   put_nibble (num >> 4);
   put_nibble (num);


put_nibble (BYTE nibble)
{
   nibble = nibble & 0x0F;        //True C lovers would write nibble &= 0x0F
   if (nibble > 10) nibble = nibble + 'A' - ('9' + 1);
   char_from_Acc (nibble + '0');
}

In ASM51, the following code might be used:

print_hex4:
   PUSH   ACC
   SWAP   A
   ACALL  put_nibble4
   POP    ACC
put_nibble4:
   ANL    A, #0Fh
   ADD    A, #-10     ;processor cannot directly compare to a constant
                      ;so we'll use subtraction instead
   JNC    phex_n
   ADD    A, #7
phex_n:
   ADD    A, #'0'+10  ;+10 is added since storing a value before comparison
                      ;and restoring itwould be less effective
   AJMP   char_from_Acc

;
; 19 bytes, execution time 25,75 µs at 12 MHz
;
   Even this algorithm could be improved, for example by substituting
   ADD   A,#7
with
   ADD   A,#'A'+10
   AJMP  char_from_Acc

Option 3 - using DAA instruction

This option is simple, short and elegant; however, it is not that easy to understand.

print_hex5:
   PUSH   ACC
   SWAP   A
   ACALL  put_nibble5
   POP    ACC
put_nibble5:
   ANL    A,#0Fh
   ADD    A,#90h  ;conversion from 00h..0Fh range to 90h..9Fh
   DA     A       ;does nothing for 90h..99h range
                  ;for 9Ah..9Fh range: 
                  ; 1) adds 06h, converting to A0h..A5h
                  ; 2) adds 60h to the result, converting to 00h..05h and setting Carry
   ADDC   A,#40h  ;converts from 90h..99h to D0h..D9h + Carry
                  ;converts from 00..05h(+Carry) to 41h..45h
   DA     A       ;for range D0h..D9h(+Carry) adds 60h, converting to 30h..39h
                  ;does nothing for 41h..45h
   AJMP   char_from_Acc
;
; 17 bytes, execution time 25 µs at 12 MHz
;
char_from_Acc:
   RET   ;dummy procedure

;

Conclusion

Each option has its advantages as well as disadvantages, as the following table shows:
  Code size
(bytes)
Execution time
(µs at 12 MHz)
Note
Table lookup ver. 1 34 31  
Table lookup ver. 2 30 23 Universal
Table lookup ver. 3 35 21 Fastest
Comparisons 19 25,75  
DAA trick 17 25 Smallest
Note: Execution time is the average for all 256 possible values.




Sponzored by LPhard Ltd. Graphics by GIMP Created by EasyPad

(c)Copyright 2000 - 2002, HW server & Radek Benedikt
Web51@HW.cz, Web51.HW.cz
GNU GPL  Obsah  Memory model