PIC Code for Binary to Decimal Conversion

The following decimal print routine for the 14-bit family of PIC microcontrollers is lifted from production code I developed. The production code comes from a commercial product, and I have verified that no substantial changes have been made in the process of extracting it from that context; nonetheless, the usual disclaimers apply!

This code is presented as inline code! In practice, most programmers will want this wrapped up as a callable function, but call-stack space on the PIC is a scarce resource, so in many cases, other wrappers will be necessary.

	; preconditions
	;   TEM0 contains least significant 8 bits of 16-bit operand
	;   TEM2 contains most significant 8 bits of 16-bit operand
	;   the 16-bit operand is positive (if it was negative, it
	;     already been negated and the sign printed)

	; resource usage
	;   TEM0 holds low nibble, is converted to low decimal digit
	;   TEM1 holds second nibble, is converted to second digit
	;   TEM2 holds third nibble, converted to third digit
	;   TEM3 holds 4th nibble, converted to 4th digit
	;   TEMPH short-term scratch
	;   TEMPL short-term scratch
	;
	;   calls PUTDIG, to prints each decimal digit
	;     when called with an ingeger from 0 to 9 in W
	;     PUTDIG may not use TEM0, TEM1, TEM2, TEM3
	;
	;   all internal labels have the prefix PUTDEC 
   
	; copyright 2002, Douglas W. Jones
	;   this code may be incorporated into any product so long
	;   this notice is preserved in the source code and so long
	;   as any copyright notice on the final software or firmware
	;   product gives credit to the author.

	; warranty
	;   This is freeware, you get what you pay for.  The author
	;   is pretty sure this code works, but can't offer anything
	;   better than that.  He who transcribes this code for
	;   incorporation into some larger program must take full
	;   responsibility for any errors.
  
        SWAPF   TEM0,W          ; get high nibble of low byte
        MOVWF   TEM1            ;   into position
        SWAPF   TEM2,W          ; get high nibble of high byte
        MOVWF   TEM3            ;   into position
        MOVLW   0x0F            ; setup to mask nibbles
        ANDWF   TEM0,F		;   in each nibble, use only low 4 bits
        ANDWF   TEM1,F
        ANDWF   TEM2,F
        ANDWF   TEM3,F

        ; TEM3 ... TEM0 hold 4 nibbles of number, TEM0 holds LSB
        ; TEM0 = (TEM3 + TEM2 + TEM1)*6 + TEM0
        ; TEM0 <=(   8 +   15 +   15)*6 +   15 = 243
        MOVF    TEM3,W
        ADDWF   TEM2,W
        ADDWF   TEM1,W          ; note, C reset because TEM1+TEM2+TEM3 <= 38
        MOVWF   TEMPL           ; TEMPL = W = (TEM3+TEM2+TEM1)
        RLF     TEMPL,F         ; TEMPL =(TEM3+TEM2+TEM1)*2
        ADDWF   TEMPL,F         ; TEMPL =(TEM3+TEM2+TEM1)*3(C reset,TEMPL<=114)
        RLF     TEMPL,W         ; W     =(TEM3+TEM2+TEM1)*6(C reset, W <= 228)
        ADDWF   TEM0,F          ; TEM0 done; TEM0 <= 243, C is still reset

        ; note TEMPH and TEMPL are no-longer needed
        ; TEMPH = TEM0 div 10 is carry into next digit
        ; TEM0  = TEM0 mod 10 is the least significant digit

        ; approximate TEM0/10 by TEM0*.000110011 (assume C already reset)
        MOVF    TEM0,W
        MOVWF   TEMPH           ; TEMPH = TEM0*1.
        RRF     TEMPH,F         ; TEMPH = TEM0*0.1 (assumes C initially zero)
        ADDWF   TEMPH,F         ; TEMPH = TEM0*1.1 (result high bit in C)
        RRF     TEMPH,F         ; TEMPH = TEM0*0.11 (high bit recovered)
        SWAPF   TEMPH,W         ; W     = TEM0*0.000011
        ANDLW   0x0F            ; (discard high bits)
        ADDWF   TEMPH,F         ; TEMPH = TEM0*0.110011 (result high bit in C)
        RRF     TEMPH,F         ; TEMPH = TEM0*0.0110011 (high bit recovered)
        RRF     TEMPH,F         ; TEMPH = TEM0*0.00110011 (bit of junk)
        RRF     TEMPH,F         ; TEMPH = TEM0*0.000110011 (2 bits of junk)
        MOVLW   0x3F            ; (discard junk bits)
        ANDWF   TEMPH,F

        ; TEM0 = TEM0 - 10*TEMPH, the remainder approximation
        RLF     TEMPH,W         ; high bits of TEMPH are zero, so this clears C
        ANDLW   0xFE            ; (clear any random carry in)
        MOVWF   TEMPL           ; TEMPL = W = TEMPH*2
        RLF     TEMPL,F         ; TEMPL = TEMPH*4
        RLF     TEMPL,F         ; TEMPL = TEMPL*8
        ADDWF   TEMPL,W         ; W = TEMPH*8 + TEMPH*2
        SUBWF   TEM0,F          ; TEM0 = TEM0 - 10*TEMPH

        ; TEMPH may be off by 1 and TEM0 off by 10
        MOVLW   -D'10'
        ADDWF   TEM0,W          ; W = TEM0 - 10 (sets C if TEM0 >= 10)
        BTFSC   STATUS,C
        MOVWF   TEM0            ; if W >= 0, TEM0 = TEM0 - 10
        BTFSC   STATUS,C
        INCF    TEMPH,F         ; if W >= 0, TEMPH++

        ; TEM0 is now the least significant decimal digit!

        ; TEM1 = TEMPH + TEM3*9 + TEM2*5 + TEM1
        ; TEM1 <=   24 +    8*9 +   15*5 +   15 = 186 (carry never set!)
        MOVF    TEMPH,W
        ADDWF   TEM3,W
        ADDWF   TEM2,W
        ADDWF   TEM1,F          ; TEM1 = TEMPH + TEM3*1 + TEM2*1 + TEM1(C reset)
        RLF     TEM3,W
        ADDWF   TEM2,W          ; W    =         TEM3*2 + TEM2*1
        MOVWF   TEMPL
        RLF     TEMPL,F         ; TEMPL =        TEM3*4 + TEM2*2
        RLF     TEMPL,W         ; W    =         TEM3*8 + TEM2*4
        ADDWF   TEM1,F          ; TEM1 = TEMPH + TEM3*9 + TEM2*5 + TEM1
        BTFSC   STATUS,Z
        GOTO    PUTDEC1         ; if rest of number is zero, print just 1 digit

        ; TEMPH = TEM1 div 10 is carry into next digit
        ; TEM1  = TEM1 mod 10 is the next to the least significant digit

        ; approximate TEM1/10 by TEM1*.000110011
        MOVF    TEM1,W
        MOVWF   TEMPH           ; TEMPH = TEM1*1.
        RRF     TEMPH,F         ; TEMPH = TEM1*0.1
        ADDWF   TEMPH,F         ; TEMPH = TEM1*1.1 (high bit is in carry)
        RRF     TEMPH,F         ; TEMPH = TEM1*0.11
        SWAPF   TEMPH,W         ; W     = TEM1*0.000011
        ANDLW   0x0F            ; (discard high bits)
        ADDWF   TEMPH,F         ; TEMPH = TEM1*0.110011
        RRF     TEMPH,F         ; TEMPH = TEM1*0.0110011
        RRF     TEMPH,F         ; TEMPH = TEM1*0.00110011
        RRF     TEMPH,F         ; TEMPH = TEM1*0.000110011
        MOVLW   0x3F            ; (discard high bits)
        ANDWF   TEMPH,F

        ; TEM1 = TEM1 - 10*TEMPH, the remainder approximation
        RLF     TEMPH,W
        ANDLW   0xFE            ; (clear any random carry in)
        MOVWF   TEMPL           ; TEMPL = W = TEMPH*2
        RLF     TEMPL,F         ; TEMPL = TEMPH*4
        RLF     TEMPL,F         ; TEMPL = TEMPL*8
        ADDWF   TEMPL,W         ; W = TEMPH*8 + TEMPH*2
        SUBWF   TEM1,F          ; TEM1 = TEM1 - 10*TEMPH

        ; TEMPH may be off by 1 and TEM1 off by 10
        MOVLW   -D'10'
        ADDWF   TEM1,W          ; W = TEM1 - 10
        BTFSC   STATUS,C
        MOVWF   TEM1            ; if W >= 0, TEM1 = TEM1 - 10
        BTFSC   STATUS,C
        INCF    TEMPH,F         ; if W >= 0, TEMPH++

        ; TEM1 is the next to the least significant decimal digit!

        ; TEM2 = TEMPH + TEM2*2
        ; TEM2 <=   18 +   15*2 = 48 (no carry, and top 2 bits unused too)
        MOVF    TEMPH,W
        ADDWF   TEM2,W
        ADDWF   TEM2,F          ; TEM2 = TEMPH + TEM2*2 (never carry out!)
        MOVF    TEM2,W          ; check if need to print high digits
        IORWF   TEM3,W          ; TEM3 must be rolled into test!
        BTFSC   STATUS,Z
        GOTO    PUTDEC2         ; if rest is zero, print just 2 digits

        ; TEMPH = TEM2 div 10 is carry into high two digits
        ; TEM2  = TEM2 mod 10 is the middle digit

        ; get exact TEM2/10 by TEM2*.0001101 (good because TEM2 < 68)
        MOVF    TEM2,W          ; W     = TEM2*1.0
        MOVWF   TEMPH           ; TEMPH = TEM2*1.0
        RRF     TEMPH,F         ; TEMPH = TEM2*0.1
        RRF     TEMPH,F         ; TEMPH = TEM2*0.01
        BCF     TEMPH,7         ; (discard high bit; next to high already clear)
        ADDWF   TEMPH,F         ; TEMPH = TEM2*1.01
        RRF     TEMPH,F         ; TEMPH = TEM2*0.101
        ADDWF   TEMPH,F         ; TEMPH = TEM2*1.101 (does not set carry!)
        SWAPF   TEMPH,W         ; W     = TEM2*0.0001101
        ANDLW   0x0F            ; (discard high bits)
        MOVWF   TEMPH           ; W = TEMPH = TEM2*0.0001101 = TEM2/10

        ; TEM2 = TEM2 - 10*TEMPH, the remainder
        RLF     TEMPH,W
        MOVWF   TEMPL           ; TEMPL = W = TEMPH*2
        RLF     TEMPL,F         ; TEMPL = TEMPH*4
        RLF     TEMPL,F         ; TEMPL = TEMPL*8
        ADDWF   TEMPL,W         ; W = TEMPH*8 + TEMPH*2
        SUBWF   TEM2,F          ; TEM2 = TEM2 - 10*TEMPH

        ; TEM2 is the middle decimal digit!

        ; TEM3 = TEMPH + TEM3*4
        ; TEM3 <=    8 +    8*4 = 40 (no carry, and top 2 bits unused too)
        BCF     STATUS,C
        RLF     TEM3,F          ; TEM3 =         TEM3*2
        RLF     TEM3,W          ; TEM3 =         TEM3*4
        ADDWF   TEMPH,W         ; W    = TEMPH + TEM3*4 (no carry!)
        BTFSC   STATUS,Z
        GOTO    PUTDEC3         ; if rest is zero, just print bottom 3 digits

        ; TEMPH = W div 10 is the high digit
        ; TEM3  = W mod 10 is the least significant digit

        ; get exact W/10 by W*.0001101 (good because W < 68)
        MOVWF   TEM3            ; TEM3  = W*1.0
        MOVWF   TEMPH           ; TEMPH = TEM3*1.0
        RRF     TEMPH,F         ; TEMPH = TEM3*0.1   (carry in is zero!)
        RRF     TEMPH,F         ; TEMPH = TEM3*0.01
        BCF     TEMPH,7         ; (discard high bit; next to high already clear)
        ADDWF   TEMPH,F         ; TEMPH = TEM3*1.01  (carry out is zero!)
        RRF     TEMPH,F         ; TEMPH = TEM3*0.101
        ADDWF   TEMPH,F         ; TEMPH = TEM3*1.101 (does not set carry!)
        SWAPF   TEMPH,W         ; W     = TEM3*0.0001101
        ANDLW   0x0F            ; (discard high bits)
        MOVWF   TEMPH           ; TEMPH = TEM3*0.0001101

        ; TEM3 = TEM3 - 10*TEMPH, the remainder
        RLF     TEMPH,W         ; W     = TEMPH*2
        MOVWF   TEMPL           ; TEMPL = TEMPH*2
        RLF     TEMPL,F         ; TEMPL = TEMPH*4
        RLF     TEMPL,F         ; TEMPL = TEMPL*8
        ADDWF   TEMPL,W         ; W = TEMPH*8 + TEMPH*2
        SUBWF   TEM3,F          ; TEM3 = TEM3 - 10*TEMPH

        ; TEM3 is the next to most significant decimal digit!
        ; TEMPH is the most significant decimal digit!

        ; conversion to decimal is done! now output digits
        MOVF    TEMPH,W
        BTFSS   STATUS,Z        ; don't print it if leading digit is zero
        CALL    PUTDIG          ; output char
        MOVF    TEM3,W
        CALL    PUTDIG          ; output char
PUTDEC3:
        MOVF    TEM2,W
        CALL    PUTDIG          ; output char
PUTDEC2:
        MOVF    TEM1,W
        CALL    PUTDIG          ; output char
PUTDEC1:
        MOVF    TEM0,W
        CALL    PUTDIG          ; output char

	; postconditions
	;   the number has been printed by a series of calls to PUTDIG
	;   TEM0, TEM1, TEM2, TEM3, TEMPH and TEMPL have been used
	;   along with any memory locations used by PUTDIG.

I have more PIC-related material elsewhere on the web.