PIC Microcontroller Radix Math Method

Binary to BCD packed and ASCII, 24 bit to 8 or 9 digits with sign, and decimal point

from Tony Kübek tony.kubek at flintab.com

...it's for 24 bit signed variable, then I cannot output a variable at a time ( supposed to go inside an int handler ), therefore I'm using a 9 byte string to store the result, furthermore for the decimal point and supressing of leading zeroes I needed 1 byte and 2 bit variables. ... Its a bit of 'bloatware', requires about 290 ! instructions but thats efficient enough for me, I might get back to hammer on it when I've finished off the other bits I'm starting with now. I think it goes around 480 instruction executed worst case, and all the way down to 250 something for best case. Also I added a minor conditional that makes execution a tad faster for 20 bit numbers ( as my app will have about 99 % in this range ) to the expence of more code and additional execution for larger numbers.
;**********************************************************************
;   This file is a basic code template for assembly code generation   *
;   on the PICmicro PIC16F877. This file contains the basic code      *
;   building blocks to build upon.                                    *  
;                                                                     *
;   If interrupts are not used all code presented between the ORG     *
;   0x004 directive and the label main can be removed. In addition    *
;   the variable assignments for 'w_temp' and 'status_temp' can       *
;   be removed.                                                       *                         
;                                                                     *
;   Refer to the MPASM User's Guide for additional information on     *
;   features of the assembler (Document DS33014).                     *
;                                                                     *
;   Refer to the respective PICmicro data sheet for additional        *
;   information on the instruction set.                               *
;                                                                     *
;   Template file assembled with MPLAB V4.00 and MPASM V2.20.00.      *
;                                                                     *
;**********************************************************************
;                                                                     *
;    Filename:	    	MathTest.asm 24bit signed binary->ASCII w. dp *
;    Date: 		2000-03-31                                    *
;    File Version:  	0.1B                                          *
;                                                                     *
;    Author: 		Tony Kübek                                    *
;    Company:                                                         *
;                                                                     * 
;                                                                     *
;**********************************************************************
;                                                                     *
;    Files required:                                                  *
;                                                                     *
;                                                                     *
;                                                                     *
;**********************************************************************
;                                                                     *
;    Notes:  Tests of binary to ASCII coversion                       *
;            Routines from techref.massmind.org                       *
;	     Originators(16bit): Scott Dattalo and Nikolai Golovchenko*
;            Routine takes 24 bit signed binary number and coverts to *
;            8/9 byte ASCII(true!) decimal string with decimal point  *
;	     and minus sign as the first byte ( if negative ).        * 
;	     24 bit variabel is stored in last three string bytes     *
;	     These are 'destroyed' during processing	              *
;	     Leading zeroes are surpressed (conv. to space)           *
;    	     Resources needed:					      *
;				DpPlacement 1 byte + 1 bit	      *
;		                PrevIsChar 1 bit ,prev. zero was surpressed*
;			       	ASCII string of 9 bytes               *
;**********************************************************************


	list      p=16f877            ; list directive to define processor
	#include <p16f877.inc>        ; processor specific variable definitions
	
	__CONFIG _CP_OFF & _WDT_ON & _BODEN_ON & _PWRTE_ON & _RC_OSC & _WRT_ENABLE_ON & _LVP_OFF& _DEBUG_OFF & _CPD_OFF 

; '__CONFIG' directive is used to embed configuration data within .asm file.
; The lables following the directive are located in the respective .inc file.
; See respective data sheet for additional information on configuration word.

#define TEST_SMALL_NUMBER	; if defined then tests are performed
		                ; to see number is less than +/-1048575 ( 20 bit )
			  	; makes routine a little faster for thoose numbers
				; to the expence of more code 
				; and adds some cycles to all numbers above if used
				; but can deduct upto 62 cycles on smaller numbers  

#define DECIMAL_POINT	0x2E    ; '.'
#define BYTE_OFFSET	0x30    ; start decimal ASCII numbers
#define SIGN_NEGATIVE	0x2D    ; '-'

;***** VARIABLE DEFINITIONS
	CBLOCK	0x20			; start userram bank 0
	
	BitVars:1			; used for dp-bit variable
	OutPut:9			; 8 used if no dp, else 9
	DpPlacement:1			; where to put dp, counted from start, min=1
					; note an added bonus here ! as this var is
					; located directly after the string and will
					; 'always' be = 0x00(after conversion), 
					; you have an 'free' null terminator

	ENDC



w_temp        EQU     0x70        ; variable used for context saving 
status_temp   EQU     0x71        ; variable used for context saving


#define _DpUsed		BitVars,0 ; decimal point has been placed
#define _PrevIsSpace	BitVars,1 ; previous byte/digit is 'converted' to space

#define Sample  OutPut+6 	  ; Note !! to save ram, 24 bit variable is stored
                                  ; in highest three byte in 9 byte string






;**********************************************************************
		ORG     0x000             ; processor reset vector
		clrf    PCLATH            ; ensure page bits are cleared
  		goto    MAIN              ; go to beginning of program


		ORG     0x004             ; interrupt vector location
		movwf   w_temp            ; save off current W register contents
		movf	STATUS,w          ; move status register into W register
		movwf	status_temp       ; save off contents of STATUS register


; isr code can go here or be located as a call subroutine elsewhere


		movf    status_temp,w     ; retrieve copy of STATUS register
		movwf	STATUS            ; restore pre-isr STATUS register contents
		swapf   w_temp,f
		swapf   w_temp,w          ; restore pre-isr W register contents
		retfie                    ; return from interrupt



MAIN




;************************ Tony test 24 bit->8/9 digit ASCII with decimal point
; code with TEST_SMALL_NUMBER: (about)290 instructions ( excluding variable setup code )
;
; True ASCII conversion, variable decimalpoint, surpress leading zeroes
; 24 bit variable is handled as SIGNED!
;************************************
        CLRF 	BitVars

	; dp=1->dp=7(no dp), adds approx 50 cycles, i.e. dp=1 least cycles
	MOVLW	1	; note dp  = 1 = between first and second digit
			; dp = 7 between the last and second last digit
			; if dp >= 7, dp = 0 then last digit is = 0x00
	MOVWF	DpPlacement	

	MOVLW	BYTE_OFFSET	; preset with ASCII offset
	MOVWF	OutPut
	MOVWF	OutPut+1
	MOVWF	OutPut+2
	MOVWF	OutPut+3
	MOVWF	OutPut+4
	MOVWF	OutPut+5
	BCF	OutPut,4	; clear bit, makes first char 'space'


	; OutPut+6 - OutPut+8 will contain 24 bit sample, LSB in OutPut+8
	; ** NOTE ** Sample = OutPut+6, Sample+1 = OutPut+7, Sample+2 = OutPut+8
	; Sample is defined as a separate name as it makes the code easier to read


;	test number : 1235783 = 0x12DB47 - ok ! cycles approx: 301 -350 depending on dp placment
; 	Note ! This with TEST_SMALL_NUMBER, without deduct  10 cycles
;	MOVLW	0x47
;	MOVWF	Sample+2
;	MOVLW	0xDB
;	MOVWF	Sample+1	 
;	MOVLW	0x12
;	MOVWF	Sample

;	test number : -1235783 = 0xED24B9 - ok ! cycles approx: 311 -360 depending on dp placment
; 	Note ! This with TEST_SMALL_NUMBER, without deduct  10 cycles
;	MOVLW	0xB9
;	MOVWF	Sample+2
;	MOVLW	0x24
;	MOVWF	Sample+1	 
;	MOVLW	0xED
;	MOVWF	Sample

;	test number : 60900 = 0x00EDE4 - ok ! cycles approx: 315 -371 depending on dp placment
; 	Note ! This with TEST_SMALL_NUMBER, without add  63 cycles
;	MOVLW	0xE4
;	MOVWF	Sample+2
;	MOVLW	0xED
;	MOVWF	Sample+1	 
;	MOVLW	0x00
;	MOVWF	Sample

;	test number : -60900 = 0xFF121C - ok ! cycles approx: 325 -381 depending on dp placment
; 	Note ! This with TEST_SMALL_NUMBER, without add  56 cycles
;	MOVLW	0x1C
;	MOVWF	Sample+2
;	MOVLW	0x12
;	MOVWF	Sample+1	 
;	MOVLW	0xFF
;	MOVWF	Sample



;	test number : 231 = 0xE7 - ok !, cycles approx 244-302 depending on dp placement
; 	NOTE ! With TEST_SMALL_NUMBER, without add 62 cycles

;	MOVLW	0xE7
;	MOVWF	Sample+2
;	MOVLW	0x00
;	MOVWF	Sample+1	 
;	MOVLW	0x00
;	MOVWF	Sample

;	test number : -1 = 0xFFFFFF - ok !, cycles approx:262-326 depending on dp placement
; 	This with TEST_SMALL_NUMBER, without add 62 cycles
;	

;	MOVLW	0xFF
;	MOVWF	Sample+2
;	MOVLW	0xFF
;	MOVWF	Sample+1	 
;	MOVLW	0xFF
;	MOVWF	Sample

;	test number : 1048575 = 0x0FFFFF - ok !, cycles approx: 297-348 depending on dp placement
;	this with TEST_SMALL_NUMBER, without add 8 cycles
;	MOVLW	0xFF
;	MOVWF	Sample+2
;	MOVLW	0xFF
;	MOVWF	Sample+1	 
;	MOVLW	0x0F
;	MOVWF	Sample

;	test number : 1000000 = 0x0F4240 - ok !, cycles approx: 291-350 depending on dp placement
;	this with TEST_SMALL_NUMBER, without add 8 cycles
;	MOVLW	0x40
;	MOVWF	Sample+2
;	MOVLW	0x42
;	MOVWF	Sample+1	 
;	MOVLW	0x0F
;	MOVWF	Sample

;	test number : 8388607 = 0x7FFFFF - ok !, cycles approx: 394-445 depending on dp placement
;	this with TEST_SMALL_NUMBER, without add 8 cycles
;	MOVLW	0xFF
;	MOVWF	Sample+2
;	MOVLW	0xFF
;	MOVWF	Sample+1	 
;	MOVLW	0x7F
;	MOVWF	Sample
;	test number : -7099999 = 0x93A9A1 - ok !, cycles approx:429-480 depending on dp placement
;	this with TEST_SMALL_NUMBER, without add 8 cycles
	MOVLW	0xA1
	MOVWF	Sample+2
	MOVLW	0xA9
	MOVWF	Sample+1	 
	MOVLW	0x93
	MOVWF	Sample


TEST_NEGATIVE
	BTFSS	Sample,7	; test if negative
	IFDEF	TEST_SMALL_NUMBER
	GOTO	TEST_TOPNIBBLE	; nope
	ELSE	
	GOTO	SUB4M		; nope
	ENDIF
	
	; sample is negative
	; make two's complement 
	COMF	Sample+2,F
	COMF	Sample+1,F
	COMF	Sample,F
	MOVLW	1
	ADDWF	Sample+2,F
	SKPNC
	ADDWF	Sample+1,F
	SKPNC
	ADDWF	Sample,F

	MOVLW	SIGN_NEGATIVE
	MOVWF	OutPut		; first digit minus sign

	

	IFDEF	TEST_SMALL_NUMBER  ; test for small numbers and skips some loops
				   ; use if number is often in 0-1048575 range ( negative or not)
				   ; i.e. 0x000000 - 0x0FFFFF ( 2.5 bytes 20 bits )
	
TEST_TOPNIBBLE
	; test if top nibble is zero, i.e. number less than 1048575 ( decimal )
	; if so we can skip to sub300k routine

	MOVF	Sample,w
	ANDLW	0x70      ; Mask out top three bits
	BNZ	SUB4M	  ; nope do full conversion

	; this digit is zero, and dp is not yet set
	; then downshift to space	
	BSF	_PrevIsSpace 
	BCF	OutPut+1,4 
	
	GOTO	SUB300k	     ; continue
	
	ENDIF	 	     ; TEST_SMALL_NUMBER


SUB4M:
        ; subtract 4 million from sample
	MOVLW 	4
        ADDWF 	OutPut+1, f
        MOVLW 	low(4000000)
        SUBWF 	Sample+2, f

	MOVLW 	low(4000000>>8)
        SKPC
	MOVLW 	low(4000000>>8)+1
        SUBWF 	Sample+1, f
	
	MOVLW 	low(4000000>>16)
        SKPC
        MOVLW 	low(4000000>>16) + 1
        SUBWF 	Sample, f


        SKPNC	; overflow, i.e. negative
        GOTO 	SUB4M

ADD1M:
        ; add 1 million to sample
	DECF 	OutPut+1, f
        MOVLW 	low(1000000)
        ADDWF 	Sample+2, f

	MOVLW 	low(1000000>>8)
        SKPNC
        MOVLW 	low(1000000>>8) + 1
        ADDWF 	Sample+1, f

	MOVLW 	low(1000000>>16)
        SKPNC
        MOVLW 	low(1000000>>16) + 1
        ADDWF 	Sample, f
	
	SKPC	; done ?
        GOTO 	ADD1M

	; test for leading zeroes
	MOVLW	BYTE_OFFSET
	SUBWF	OutPut+1,w
	BNZ	SUB300k

	; this digit is zero, and dp is not yet set
	; then downshift to space	
	BSF	_PrevIsSpace 
	BCF	OutPut+1,4 
	

SUB300k:
        ; substract 300 thousand from sample
	MOVLW 	3
        ADDWF 	OutPut+3, f
        MOVLW 	low(300000)
        SUBWF 	Sample+2, f

	MOVLW 	low(300000>>8)
        SKPC
	MOVLW 	low(300000>>8)+1
        SUBWF 	Sample+1, f
	
	MOVLW 	low(300000>>16)
        SKPC
        MOVLW 	low(300000>>16) + 1
        SUBWF 	Sample, f


        SKPNC
        GOTO 	SUB300k

ADD100k
        ; add 100 thousand to sample
	DECF 	OutPut+3, f
        MOVLW 	low(100000)
        ADDWF 	Sample+2, f

	MOVLW 	low(100000>>8)
        SKPNC
        MOVLW 	low(100000>>8) + 1
        ADDWF 	Sample+1, f

	MOVLW 	low(100000>>16)
        SKPNC
        MOVLW 	low(100000>>16) + 1
        ADDWF 	Sample, f
	
	SKPC	; done
        GOTO 	ADD100k
	IFDEF	TEST_SMALL_NUMBER ; if this test enabled it is possible
				  ; to have an overflow here

	; test for overflow ( Output+3 = 10 )

	MOVLW	BYTE_OFFSET+0x0A
	SUBWF	OutPut+3,W
	BTFSS	STATUS,Z	; test if non zero
	GOTO    ADD100k_TstDp

	
	; over flow, result is = 10, second digit = 1, third = 0
	MOVLW	BYTE_OFFSET
	MOVWF	OutPut+3
	BTFSS	_DpUsed		; test if dp already used
	BSF	OutPut+1,0	; if no dp used set this digit to one
	BSF	OutPut+2,0      ; set also third digit to 1 ( in case dp is used )

	; restore previous digit
	BTFSS	_PrevIsSpace
	GOTO	ADD100k_TstDp
	
	BSF	OutPut+1,4
	BCF	_PrevIsSpace

	ENDIF

ADD100k_TstDp:

	; test for dp
	DECFSZ	DpPlacement,F ; test for dp placement
	GOTO   	ADD100k_NoDp   ; no dp yet
	; place dp
	BSF	_DpUsed	    ; dp is used
	MOVLW	DECIMAL_POINT        ;
	MOVWF	OutPut+2    ; dp is third digit	
	
	; restore zero before dp
	BTFSC	_PrevIsSpace
	BSF	OutPut+1,4

	GOTO	SUB30k	    ; continue
	
ADD100k_NoDp:
	; no dp copy back number
	MOVF	OutPut+3,W
	MOVWF	OutPut+2

	; test for leading zeroes
	MOVLW	BYTE_OFFSET
	SUBWF	OutPut+2,w
	BTFSS	STATUS,Z
	BCF	_PrevIsSpace
	BNZ	SUB30k
	
	BTFSS	_PrevIsSpace
	GOTO	SUB30k

	; this digit is zero, and dp is not yet set
	; then downshift to space	
	BSF	_PrevIsSpace 
	BCF	OutPut+2,4 


SUB30k:
      	; subtract 30'000 from sample
	MOVLW 	3
        ADDWF 	OutPut+4, f
        MOVLW 	low(30000)
        SUBWF 	Sample+2, f

	MOVLW 	low(30000>>8)
        SKPC
	MOVLW 	low(30000>>8)+1
        SUBWF 	Sample+1, f
	
	MOVLW 	low(30000>>16)
        SKPC
        MOVLW 	low(30000>>16) + 1
        SUBWF 	Sample, f


        SKPNC	; negative ?
        GOTO 	SUB30k
ADD10k:
        ; add 10'000 to sample

	DECF 	OutPut+4, f
        MOVLW 	low(10000)
        ADDWF 	Sample+2, f

	MOVLW 	low(10000>>8)
        SKPNC
        MOVLW 	low(10000>>8) + 1
        ADDWF 	Sample+1, f

	MOVLW 	low(10000>>16)
        SKPNC
        MOVLW 	low(10000>>16) + 1
        ADDWF 	Sample, f
	
	SKPC	; done ?
        GOTO 	ADD10k
	
	BTFSC	_DpUsed
	GOTO	SUB3k	; dp is already used continue
	
	; test for dp
	DECFSZ	DpPlacement,F ; test for dp placement
	GOTO   	ADD10k_NoDp   ; no dp yet
	; place dp
	BSF	_DpUsed	    ; dp is used
	MOVLW	DECIMAL_POINT        ;
	MOVWF	OutPut+3    ; dp is fourth digit	

	; restore zero before dp
	BTFSS	_PrevIsSpace
	BSF	OutPut+2,4	


	GOTO	SUB3k	    ; continue
	
ADD10k_NoDp:
	; no dp copy back number
	MOVF	OutPut+4,W
	MOVWF	OutPut+3

	; test for leading zeroes
	MOVLW	BYTE_OFFSET
	SUBWF	OutPut+3,w
	BTFSS	STATUS,Z
	BCF	_PrevIsSpace
	BNZ	SUB3k
	
	BTFSS	_PrevIsSpace
	GOTO	SUB3k

	; this digit is zero, and dp is not yet set
	; then downshift to space	
	BSF	_PrevIsSpace 
	BCF	OutPut+3,4 

SUB3k:
     	; subtract 3000 from sample
	MOVLW 	3
        ADDWF 	OutPut+5, f
        MOVLW 	low(3000)
        SUBWF 	Sample+2, f

	MOVLW 	low(3000>>8)
        SKPC
	MOVLW 	low(3000>>8)+1
        SUBWF 	Sample+1, f
	
        SKPNC	; negative ?
        GOTO 	SUB3k
ADD1k:
        ; add 1000 to sample
	DECF	OutPut+5, f
        MOVLW 	low(1000)
        ADDWF 	Sample+2, f

	MOVLW 	low(1000>>8)
        SKPNC
        MOVLW 	low(1000>>8) + 1
        ADDWF 	Sample+1, f
	
	SKPC
        GOTO 	ADD1k
	
	BTFSC	_DpUsed
	GOTO	SUB300_PreLoad	; dp is already used continue

	; test for dp
	DECFSZ	DpPlacement,F ; test for dp placement
	GOTO   	ADD1k_NoDp   ; no dp yet
	; place dp
	
	BSF	_DpUsed	    ; dp is used
	MOVLW	DECIMAL_POINT        ;
	MOVWF	OutPut+4    ; dp is fifth digit	
	
	; restore zero before dp
	BTFSC	_PrevIsSpace
	BSF	OutPut+3,4
	
	GOTO	SUB300_PreLoad	    ; continue
	
ADD1k_NoDp:
	; no dp copy back number
	MOVF	OutPut+5,W
	MOVWF	OutPut+4

	; test for leading zeroes
	MOVLW	BYTE_OFFSET
	SUBWF	OutPut+4,w
	BTFSS	STATUS,Z
	BCF	_PrevIsSpace
	BNZ	SUB300_PreLoad
	
	BTFSS	_PrevIsSpace
	GOTO	SUB300_PreLoad

	; this digit is zero, and dp is not yet set
	; then downshift to space	
	BSF	_PrevIsSpace 
	BCF	OutPut+4,4 

SUB300_PreLoad	
	MOVLW	BYTE_OFFSET
	MOVWF	OutPut+6
SUB300:
	; subtract 300 from sample
        MOVLW 	3
        ADDWF 	OutPut+6, f
        MOVLW 	low(300)
        SUBWF 	Sample+2, f

	MOVLW 	low(300>>8)
        SKPC
	MOVLW 	low(300>>8)+1
        SUBWF 	Sample+1, f

        SKPNC	; negative ?
        GOTO 	SUB300

        MOVLW 	100

ADD100:
	; add 100 to sample
        DECF 	OutPut+6, f
        ADDWF 	Sample+2, f
        SKPC
        GOTO 	ADD100
        INCF 	Sample+1, f
        BTFSC 	Sample+1, 7
        GOTO 	ADD100
		
	BTFSC	_DpUsed
	GOTO	SUB30_PreLoad	; dp is already used continue
	
	; test for dp
	DECFSZ	DpPlacement,F ; test for dp placement
	GOTO   	ADD100_NoDp   ; no dp yet
	; place dp
	BSF	_DpUsed	    ; dp is used
	MOVLW	DECIMAL_POINT        ;
	MOVWF	OutPut+5    ; dp is sixth digit	
	
	; restore zero before dp
	BTFSC	_PrevIsSpace
	BSF	OutPut+4,4

	GOTO	SUB30_PreLoad	    ; continue
	
ADD100_NoDp:
	; no dp copy back number
	MOVF	OutPut+6,W
	MOVWF	OutPut+5

	; test for leading zeroes
	MOVLW	BYTE_OFFSET
	SUBWF	OutPut+5,w
	BTFSS	STATUS,Z
	BCF	_PrevIsSpace
	BNZ	SUB30_PreLoad
	
	BTFSS	_PrevIsSpace
	GOTO	SUB30_PreLoad

	; this digit is zero, and dp is not yet set
	; then downshift to space	
	BSF	_PrevIsSpace 
	BCF	OutPut+5,4 


SUB30_PreLoad:
        MOVLW 	30
SUB30:
     	; subtract 30 from sample
	INCF 	OutPut+7, f
        SUBWF 	Sample+2, f
        SKPNC
        GOTO 	SUB30
        MOVFW 	OutPut+7
        RLF 	OutPut+7, f
        ADDWF 	OutPut+7, f
        MOVLW 	10

ADD10:
        ; add 10 to sample
	DECF 	OutPut+7, f
        ADDWF 	Sample+2, f
        SKPC
        GOTO 	ADD10
	
	MOVLW	BYTE_OFFSET
	ADDWF	OutPut+7,f

	BTFSC	_DpUsed
	GOTO	LAST_DIGIT		; dp is already used continue
	
	; test for dp
	DECFSZ	DpPlacement,F ; test for dp placement
	GOTO   	ADD10_NoDp   ; no dp yet
	; place dp
	BSF	_DpUsed	    ; dp is used
	MOVLW	DECIMAL_POINT        ;
	MOVWF	OutPut+6    ; dp is seventh digit	

	; restore zero before dp
	BTFSC	_PrevIsSpace
	BSF	OutPut+5,4
	
	GOTO	LAST_DIGIT   ; continue
	
ADD10_NoDp
	; no dp copy back number
	MOVF	OutPut+7,W
	MOVWF	OutPut+6
	; test for leading zeroes
	MOVLW	BYTE_OFFSET
	SUBWF	OutPut+6,w
	BNZ	LAST_DIGIT
	
	BTFSS	_PrevIsSpace
	GOTO	LAST_DIGIT

	; this digit is zero, and dp is not yet set
	; then downshift to space	
	BSF	_PrevIsSpace 
	BCF	OutPut+6,4 

LAST_DIGIT:

	MOVLW	BYTE_OFFSET
	ADDWF   Sample+2,w
	MOVWF	Sample+2
	
	BTFSS	_DpUsed
	MOVWF 	OutPut+7 ; save in previous byte
	BTFSC	_DpUsed
	GOTO	END_CONV
		
	DECFSZ	DpPlacement,F 	; test for dp placement
	GOTO   	CLEAR_LAST	; no dp used at all ??
	MOVLW	DECIMAL_POINT   ;
	MOVWF	OutPut+7    ; dp is eigth digit	

	; restore zero before dp
	BTFSC	_PrevIsSpace
	BSF	OutPut+6,4

	GOTO	END_CONV
CLEAR_LAST:
	; no dp used copy back number, and clear last digit
	BTFSS	_DpUsed
	CLRF	OutPut+8 ; clear last digit no dp used

END_CONV
	NOP	; done :-)
	GOTO MAIN




		END                       ; directive 'end of program'