Hey guys! My first post!
I was roaming around the internet for code snippets to convert a binary integer to BCD in PIC Assembly.
There are some cool approaches over at
http://www.piclist.com/techref/microchip/math/radix/index.htm. These inspired me to have a go at my own and share it with you all.
So here we go. Three subroutines. One 8-bit and one 10-bit. These work using successive subtraction (successive overflow) to test if each BCD bit should be set.
I also included my take on the 16-bit Double Dabble algorithm with a little trick to avoid a loop counter register.
; Converts an 8 bit integer in WREG to binary coded decimal.
; 26 instructions, 1 register, no loops. Works on PIC10 and up.
;
; Initial state:
; WREG contains the integer
; TEMP is cleared
;
; Final state:
; TEMP 7:4 contains 100s
; TEMP 3:0 contains 10s
; WREG contains 1s
TEMP = h'40' ; Assign as you please.
BCD8 CLRF TEMP ; Clear TEMP.
ADDLW d'56' ; Set carry if WREG >= 200. WREG is now WREG - 200.
RLF TEMP, F ; Push carry onto TEMP.
BTFSS TEMP, 0 ; If carry was set, leave the new value in WREG.
ADDLW d'200' ; If carry was clear, revert WREG to the previous value.
ADDLW d'156' ; Set carry if WREG >= 100. WREG is now WREG - 100.
RLF TEMP, F ; Push carry onto TEMP.
BTFSS TEMP, 0 ; If carry was set, leave the new value in WREG.
ADDLW d'100' ; If carry was clear, revert WREG to the previous value.
ADDLW d'176' ; Set carry if WREG >= 80. WREG is now WREG - 80.
RLF TEMP, F ; Push carry onto TEMP.
BTFSS TEMP, 0 ; If carry was set, leave the new value in WREG.
ADDLW d'80' ; If carry was clear, revert WREG to the previous value.
ADDLW d'216' ; Set carry if WREG >= 40. WREG is now WREG - 40.
RLF TEMP, F ; Push carry onto TEMP.
BTFSS TEMP, 0 ; If carry was set, leave the new value in WREG.
ADDLW d'40' ; If carry was clear, revert WREG to the previous value.
ADDLW d'236' ; Set carry if WREG >= 20. WREG is now WREG - 20.
RLF TEMP, F ; Push carry onto TEMP.
BTFSS TEMP, 0 ; If carry was set, leave the new value in WREG.
ADDLW d'20' ; If carry was clear, revert WREG to the previous value.
ADDLW d'246' ; Set carry if WREG >= 10. WREG is now WREG - 10.
RLF TEMP, F ; Push carry onto TEMP.
BTFSS TEMP, 0 ; If carry was set, leave the new value in WREG.
ADDLW d'10' ; If carry was clear, revert WREG to the previous value.
RETURN ; Done!
; Converts a 10 bit integer to binary coded decimal.
; 35 instructions, 1 register, no loops. Works on PIC12 and up.
; For PIC18, replace RLF with RLCF.
;
; Initial state:
; WREG contains the MSBs of the integer
; TEMP 1:0 contains the LSBs of the integer
; TEMP 7:2 is ignored
;
; Final state:
; TEMP 7:4 contains 100s
; TEMP 3:0 contains 10s
; WREG 3:0 contains 1s
; WARNING: There is no 1000s! So if the integer >= 1000 (decimal), TEMP 7:4 will be 10 (decimal).
TEMP = h'40' ; Assign as you please.
BCD10 ADDLW d'56' ; Set carry if WREG >= 200. WREG is now WREG - 200.
RLF TEMP, F ; Push carry onto TEMP.
BTFSS TEMP, 0 ; If carry was set, leave the new value in WREG.
ADDLW d'200' ; If carry was clear, revert WREG to the previous value.
ADDLW d'156' ; Set carry if WREG >= 100. WREG is now WREG - 100.
RLF TEMP, F ; Push carry onto TEMP.
BTFSS TEMP, 0 ; If carry was set, leave the new value in WREG.
ADDLW d'100' ; If carry was clear, revert WREG to the previous value.
ADDLW d'206' ; Set carry if WREG >= 50. WREG is now WREG - 50.
RLF TEMP, F ; Push carry onto TEMP.
BTFSS TEMP, 0 ; If carry was set, leave the new value in WREG.
ADDLW d'50' ; If carry was clear, revert WREG to the previous value.
ADDLW d'231' ; Set carry if WREG >= 25. WREG is now WREG - 25.
RLF TEMP, F ; Push carry onto TEMP.
BTFSS TEMP, 0 ; If carry was set, leave the new value in WREG.
ADDLW d'25' ; If carry was clear, revert WREG to the previous value.
ADDLW d'236' ; Set carry if WREG >= 20. WREG is now WREG - 20.
RLF TEMP, F ; Push carry onto TEMP.
BTFSS TEMP, 0 ; If carry was set, leave the new value in WREG.
ADDLW d'20' ; If carry was clear, revert WREG to the previous value.
ADDLW d'246' ; Set carry if WREG >= 10. WREG is now WREG -10.
RLF TEMP, F ; Push carry onto TEMP.
BTFSS TEMP, 0 ; If carry was set, leave the new value in WREG.
ADDLW d'10' ; If carry was clear, revert WREG to the previous value.
ADDLW d'251' ; Set carry if WREG >= 5. WREG is now WREG - 5.
RLF TEMP, F ; Push carry onto TEMP.
RLF WREG, W ; Multiply WREG by 2 and add bit 1.
BTFSS TEMP, 0 ; If carry was set, leave the new value in WREG.
ADDLW d'10' ; If carry was clear, revert WREG to the previous value.
ADDLW d'251' ; Set carry if WREG >= 5. WREG is now WREG - 5.
RLF TEMP, F ; Push carry onto TEMP.
RLF WREG, W ; Multiply WREG by 2 and add bit 0.
BTFSS TEMP, 0 ; If carry was set, leave the new value in WREG.
ADDLW d'10' ; If carry was clear, revert WREG to the previous value.
RETURN ; Done!
; Converts a 16 bit integer to binary coded decimal.
; This is an implementation of the Double Dabble algorithm.
; 29 instructions, 5 registers, ~400 operations.
; Works on PIC10 and up.
; A little trick is used to avoid the need for a counter register.
;
; Initial state:
; LREG contains the LSBs of the integer.
; HREG contains the MSBs of the integer.
;
; Final state:
;
; AREG 3:0 contains 1s
; AREG 7:4 contains 10s
; BREG 3:0 contains 100s
; BREG 7:4 contains 1000s
; CREG 3:0 contains 10000s
; WREG and LREG are cleared.
; HREG = 128
AREG = h'50'
BREG = h'51'
CREG = h'52'
LREG = h'53'
HREG = h'54'
BCD16 CLRF AREG ; Initialise BCD registers
CLRF BREG
CLRF CREG
BSF STATUS, C ; Set carry flag. This single bit gets pushed onto the integer registers.
; Once it is moved to the MSB, the loop will exit.
BCD16_LOOP RLF LREG, F ; Rotate integer registers left
RLF HREG, F
RLF AREG, F ; Rotate BCD registers left
RLF BREG, F
RLF CREG, F
RLF HREG, W ; Once the integer registers have been rotated out, the bit
IORWF LREG, W ; pushed on at the start of the routine will now be at
BTFSC STATUS, Z ; the MSB, with all other bits cleared.
RETURN ; Test for this case, and exit if true.
MOVLW d'51' ; Additions to set bits 3 and 7 if respective nibbles >= 5
ADDWF AREG, F
ADDWF BREG, F
;MOVLW d'51' ; Prior addition to be subtracted. This value is already in WREG.
BTFSC AREG, 3 ; If bit 3 is set, don't subtract from the low nibble.
ADDLW d'253' ; WREG = WREG - 3
BTFSC AREG, 7 ; If bit 7 is set, don't subtract from the high nibble.
ADDLW d'208' ; WREG = WREG - 48
SUBWF AREG, F
MOVLW d'51' ; Prior addition to be subtracted.
BTFSC BREG, 3 ; If bit 3 is set, don't subtract from the low nibble.
ADDLW d'253' ; WREG = WREG - 3
BTFSC BREG, 7 ; If bit 7 is set, don't subtract from the high nibble.
ADDLW d'208' ; WREG = WREG - 48
SUBWF BREG, F
BCF STATUS, C ; Clear C.
GOTO BCD16_LOOP
Your thoughts / suggestions / tweaks and different approaches are always welcome!
Edit 1: NorthGuy's suggestions on PIC18 and fix a bug.
Edit 2: A bit more tweaking got the BCD10 subroutine down to 35 instructions.
Edit 3: My take on the 16-bit Double Dabble routine. 35 instructions.
Edit 4: Slight tweak to 16-bit Double Dabble. 34 instructions.
Edit 5: Updated 16-bit Double Dabble again. It is now 29 instructions. I realised there was no need to adjust the 10K register, as this will never be above 6 for a 16-bit integer.
Dabbot.