PIC Code for Binary to Decimal Conversion
Part of
the Binary to Decimal Conversion Tutorial
|
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.