# N-BitMath

Author: Daryl Taylor

I recently came across these great routines from Alexander Avtanski, on the PicList ... http://avtanski.net/projects/math/ All credit goes to Alexander!

Everything was there, it just needed a few tweaks to be used with PicBasic Pro.

They're so cool ...'

8, 16, 24, 32, 64, 128-bit Integer Math or more. You want BIG numbers?
.... you got 'em. N-Bit means any number of bits.

16F, 18F ... doesn't matter (other than RAM space), and you must be using MPASM.

The number of bytes used in the math depends on the PRECISION constant.
If PRECISION = 4, then all math is done as 32-bit operations.

So you need to have variables of the same size, which is easy to do with ...

PRECISION CON 4 SYSTEM ' 4 bytes = 32-bit
SetPoint VAR BYTE[PRECISION]
Position VAR BYTE[PRECISION]
Error VAR BYTE[PRECISION]
Current VAR BYTE[PRECISION]
MyVar VAR BYTE[PRECISION]

Then you can do the math on those variables with ...

@ MATH_SUB _SetPoint, _Position, _Error ; subtract Position from setpoint\\

Which subtracts Position from Setpoint and puts the result in Error.
All I've done is put a "wrapper" around the original routines from Alexander, so they can still be used exactly like the webpage says.
I've also added some macros to make things easier with PBP.

@ MATH_CLR _Pvar ; clear an N-Bit variable
@ MATH_INC _Pvar ; increment an N-Bit variable
@ MATH_DEC _Pvar ; decrement an N-Bit variable
@ MATH_ROL _Pvar ; rotate LEFT
@ MATH_ROR _Pvar ; rotate RIGHT
@ MATH_CMP _A, _B ; Compare Pvars - result in M_EQ and M_GTE bits
@ MATH_ADD _A, _B, _Res ; Res = A + B
@ MATH_SUB _A, _B, _Res ; Res = A - B
@ MATH_MUL _A, _B, _Res ; Res = A * B
@ MATH_DIV _A, _B, _Res ; Res = A / B - Remainder in REG_Z\\

And the inverse of those puts them back into PBP variables again.

@ MOVE?PB _Pin, _Bout ; truncate a Pvar into a BYTE var
@ MOVE?PW _Pin, _Wout ; truncate a Pvar into a WORD var
@ MOVE?PL _Pin, _Lout ; truncate a Pvar into a LONG var (PBPL only)

So let's say you were getting a 16-bit value from the QEI counter, and the upper 16-bits from a Rollover interrupt. You could move them all into a Pvar with two lines ...

RollOvers VAR WORD ; QEI rollovers (from interrupt)

@ MOVE?WP POSCNTL, _Position ; copy QEI count to Position.Word0
@ MOVE?WW _RollOvers, _Position + 2 ; copy rollovers to Position.Word1

;-- Calculate the Error --
@ MATH_SUB _SetPoint, _Position, _Error ; Error = Setpoint - Position
@ MOVE?PW _Error, _pid_Error ; copy Pvar result to a WORD variable

This way you can do 32-bit math without PBPL. Or 64-bit if you needed. It's not as easy as PBPL, but can be very useful.

Keep in mind that the math is all Software. Multiplication does not use the hardware multiplier. So with 18F's, MATH_MUL at 32-bits will take longer than PBPL with 32-bits.

However, you can still use all of the math statements within PBP, and they will be faster without PBPL, or BIGGER with PBPL.

Another recent request from someone in this forum was to have a counter that keeps track of the number of milliseconds since the power was turned on.
That can be a pretty big number ... if it's been powered-up for a decade or two.
No problem ...

'----[N-Bit Math]-----------------------------------------------------------
PRECISION CON 5 SYSTEM ; 5 bytes=40-bit
INCLUDE "N-Bit_MATH.pbp"

Powered_MS VAR BYTE[PRECISION]

MS_intHandler: ; this routine should be called every mS via a timer interrupt
@ FSRSAVE ; Save FSR value before interrupt code
@ MATH_INC _Powered_MS ; increment # of mS since PWR_ON
@ FSRREST ; Restore FSR
; return from interrupt, depends on the type of interrupt.
; interrupt code and context saving not shown.

That will happily count the number of milliseconds passed for over 34 years. 2<sup>^</sup>40/(1000*60*60*24*365)
If you need it to count longer ... go with 6 byte PRECISION (8,925 years).

The routines are completely ASM, and do not use any of PBP's system variables. So it's OK to use them within ASM type Interrupts if desired.
But you'll need to save the FSR value before doing any MATH because it uses the FSR and would disrupt the flow of the Main program if not saved.

The @ FSRSAVE and @ FSRREST can be used to make it easy.

Note: DT_INTS already saves/restores the FSR's at the beginning/end of an interrupt, so you don't have to. For ASM interrupts, you will need to save it. FSR's don't matter with ON INTERRUPT.
----- Please understand that these are "Advanced Level" math routines.
I can not debug everyone's program that ends up not compiling because of syntax errors.

If you truely believe that something is wrong with the routines, I would love to hear it. And I will do my best to fix it.
If you can't get it to compile ... keep trying ... it will.

Here's the Include File.

* N-Bit_Math Include File

Current Version = 1.4 Beta (8/4/2010); Workaround for MPASMs shift > 32-bits

History:
1.3 Beta 1/7/2010 ; compatability with 16F1xxx chips (enhanced core)
1.2 Alpha 1/4/2010 ; adds ability to turn Case Sensitivity off in MPASM
1.1 Alpha 1/4/2010 ; fixes FSR conflict with DT_INTS-18
1.0 Alpha 1/3/2010 ; initial release

'***************************************************************************
'* Name : N-Bit_Math.pbp
'* Modified by : Darrel Taylor
'* Date : 1/3/2010
'* Version : 1.4 Beta (8/4/2010); Workaround MPASM's bad shift >32-bits
'* 1.3 Beta (1/7/2010) ; now compatable with 16F19xx chips
'* Notes : This is simply a PBP wrapper around the EXCELLENT
'* : routines from ... http://avtanski.net/projects/math/
'***************************************************************************
'* See this webpage for more information ...
'************************************************************************* **

'PRECISION CON 4 SYSTEM ' should be declared in the Main program
' prior to the include file
REG_X VAR BYTE[PRECISION] BANK0 SYSTEM
REG_Y VAR BYTE[PRECISION] BANK0 SYSTEM
REG_Z VAR BYTE[PRECISION] BANK0 SYSTEM
REG_COUNTER VAR BYTE BANK0 SYSTEM
REG_STATUS VAR BYTE BANK0 SYSTEM
REG_T1 VAR BYTE BANK0 SYSTEM
REG_T2 VAR BYTE BANK0 SYSTEM
REG_ROT_COUNTER VAR BYTE BANK0 SYSTEM
FSRsaveLoc VAR WORD ; place to save FSR during interrupts
M_EQ VAR BIT ; Equal to, for CMP
M_GTE VAR BIT ; Greater than or Equal to, for CMP

GOTO OverNbit

ASM ;---(add underscore to allow use with PBP's CALL statement)-----------
#define _M_CLR M_CLR
#define _M_INC M_INC
#define _M_DEC M_DEC
#define _M_ROL M_ROL
#define _M_ROR M_ROR
#define _M_CMP M_CMP
#define _M_SUB M_SUB
#define _M_MUL M_MUL
#define _M_DIV M_DIV
ENDASM

ASM
ifdef BSR ; make 14-bit rotate compatible with 18F's
ifdef INDF2
#define rrf rrcf
ifndef RRF
#define RRF rrcf
endif
#define rlf rlcf
ifndef RLF
#define RLF rlcf
endif
endif
endif

;---------------------------
PadZeros macro Pout ; fill upper part of Pvars with 0's
while ByteCount < PRECISION
MOVE?CB 0, Pout + ByteCount
ByteCount = ByteCount + 1
endw
endm

;---------------------------
FSR16 = 0 ; determine if FSR is 8 or 16-bit
ifndef FSR
#define Fptr FSR0L
#define INDF INDF0
FSR16 = 1
else
#define Fptr FSR
endif

SETFSRL macro ; Copy W reg to FSR, Clear high byte (FSR16)
if (FSR16 == 1)
MOVE?AB FSR0L
MOVE?CB 0, FSR0H
else
MOVE?AB FSR
endif
endm

CLRFSR macro ; Clear FSR value
if (FSR16 == 1)
MOVE?CW 0, FSR0L
else
MOVE?CB 0, FSR
endif
endm

FSRSAVE macro ; Save FSR before Interrupt
if (FSR16 == 1)
MOVE?WW FSR0L, _FSRsaveLoc
else
MOVE?BB FSR, _FSRsaveLoc
endif
endm

FSRREST macro ; Restore FSR after Interrupt
if (FSR16 == 1)
MOVE?WW FSR0L, _FSRsaveLoc
else
MOVE?BB FSR, _FSRsaveLoc
endif
endm
;---------------------------------------------------------------------------
MOVE?CP macro Cin, Pout ; copy a CONSTANT to an N-bit variable
ByteCount = 0
while (ByteCount < PRECISION) && (ByteCount < 4)
MOVE?CB low(Cin >>(8*ByteCount)), Pout + ByteCount
ByteCount = ByteCount + 1
endw
endm

;---------------------------
MOVE?BP macro Bin, Pout ; copy a BYTE to an N-bit variable
MOVE?BB Bin, Pout
ByteCount = 1
endm

;---------------------------
MOVE?WP macro Win, Pout ; copy a WORD to an N-bit variable
MOVE?WW Win, Pout
ByteCount = 2
endm

;---------------------------
MOVE?LP macro Lin, Pout ; copy a LONG to an N-bit variable (PBPL only)
MOVE?NN Lin, Pout
ByteCount = 4
endm

;---------------------------
MOVE?PP macro BeerIN, Pout ; copy an N-bit variable to an N-bit variable
BeerCount = 0
while BeerCount < PRECISION
MOVE?BB BeerIN + BeerCount, Pout + BeerCount
BeerCount = BeerCount + 1
endw
endm

;---------------------------
MOVE?PB macro Pin, Bout ; truncate a Pvar into a BYTE var
MOVE?BB Pin, Bout
endm

;---------------------------
MOVE?PW macro Pin, Wout ; truncate a Pvar into a WORD var
MOVE?WW Pin, Wout
endm

;---------------------------
MOVE?PL macro Pin, Lout ; truncate a Pvar into a LONG var (PBPL only)
MOVE?NN Pin, Lout
endm

;---------------------------------------------------------------------------
MATH_CLR macro Pout ; clear an N-Bit variable
MOVE?CP 0, Pout
endm

MATH_INC macro Pout ; increment an N-Bit variable
MOVE?PP Pout, REG_X
MOVE?CA REG_X
L?CALL M_INC
MOVE?PP REG_X, Pout
endm

MATH_DEC macro Pout ; decrement an N-Bit variable
MOVE?PP Pout, REG_X
MOVE?CA REG_X
L?CALL M_DEC
MOVE?PP REG_X, Pout
endm

MATH_ROL macro Pout ; rotate LEFT
MOVE?PP Pout, REG_X
MOVE?CA REG_X
L?CALL M_ROL
MOVE?PP REG_X, Pout
endm

MATH_ROR macro Pout ; rotate RIGHT
MOVE?PP Pout, REG_X
MOVE?CA REG_X
L?CALL M_ROR
MOVE?PP REG_X, Pout
endm

MATH_CMP macro Zin, Xin ; Compare Pvars - result in M_EQ and M_GTE bits
MOVE?PP Zin, REG_Z ; Z <=> X -> STATUS(C,Z)
MOVE?PP Xin, REG_X
CLRFSR ; clear FSR first
L?CALL M_CMP
MOVE?TT STATUS,Z, _M_EQ ; STATUS,Z set if Z == X
MOVE?TT STATUS,C, _M_GTE ; STATUS,C set if Z => X;
endm

MATH_ADD macro Zin, Xin, Pout
MOVE?PP Zin, REG_Z ; Z + X -> Z
MOVE?PP Xin, REG_X
CLRFSR
MOVE?PP REG_Z, Pout
endm

MATH_SUB macro Zin, Xin, Pout
MOVE?PP Zin, REG_Z ; Z - X -> Z
MOVE?PP Xin, REG_X
CLRFSR
L?CALL M_SUB
MOVE?PP REG_Z, Pout
endm

MATH_MUL macro Xin, Yin, Pout
MOVE?PP Xin, REG_X ; X * Y -> Z
MOVE?PP Yin, REG_Y
CLRFSR
L?CALL M_MUL
MOVE?PP REG_Z, Pout
endm

MATH_DIV macro Zin, Xin, Pout
MOVE?PP Zin, REG_Z ; Z / X -> Y; remainder -> Z
MOVE?PP Xin, REG_X
CLRFSR
L?CALL M_DIV
MOVE?PP REG_Y, Pout
endm
ENDASM

ASM
;----[original code]--------------------------------------------------------
M_STOR_STATUS macro WHERE
movf STATUS,w
movwf WHERE
endm

M_RETR_STATUS macro WHERE
movf WHERE,w
movwf STATUS
endm

;M_TEST ; Original -- Test subroutine
; movlw REG_X
; call M_CLR ; clear register X

; movlw REG_Y
; call M_CLR ; clear register Y

; movlw REG_Z
; call M_CLR ; clear register Z

; movlw 0x8A
; movwf REG_X ; set register X (lowest byte)

; movlw 0xFC
; movwf REG_Z ; set register Z (lowest byte)

; call M_ADD ; adds X and Z, result in Z

; movlw 0x05 ; set register X (lowest byte)
; movwf REG_X

; call M_DIV ; divide Z by X, result in Y, remainder in Z
; sleep ; end of test

;---------------------------------------------------------------------------
M_CLR ; clear a register
SETFSRL
; movwf FSR
movlw PRECISION
movwf REG_COUNTER
M_CLR_loop
clrf INDF
incf Fptr,f
decf REG_COUNTER,f
btfss STATUS,Z
goto M_CLR_loop
return

;---------------------------------------------------------------------------
M_INC ; increment a register
SETFSRL
; movwf FSR
movlw PRECISION
movwf REG_COUNTER
M_INC_loop
incf INDF,f
btfss STATUS,Z
return
incf Fptr,f
decf REG_COUNTER,f
btfss STATUS,Z
goto M_INC_loop
return

;---------------------------------------------------------------------------
M_DEC ; decrement a register
SETFSRL
; movwf FSR
movlw PRECISION
movwf REG_COUNTER
M_DEC_loop
decf INDF,f
movlw 0xFF
subwf INDF,w
btfss STATUS,Z
return
incf Fptr,f
decf REG_COUNTER,f
btfss STATUS,Z
goto M_DEC_loop
return

;---------------------------------------------------------------------------
M_ROL ; rotate a register to the left
SETFSRL
; movwf FSR
M_STOR_STATUS REG_STATUS
clrf REG_COUNTER
M_ROL_loop
M_RETR_STATUS REG_STATUS
rlf INDF,f
M_STOR_STATUS REG_STATUS
incf Fptr,f
incf REG_COUNTER,f
movlw PRECISION
subwf REG_COUNTER,w
btfss STATUS,Z
goto M_ROL_loop
return
Page last modified on February 17, 2018, at 07:56 AM