PIC Code for Binary to Decimal Conversion

Part of the Binary to Decimal Conversion Tutorial
by Douglas W. Jones
THE UNIVERSITY OF IOWA Department of Computer Science

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.