Picaxe 32 Bit Maths

Jeremy Leach

Senior Member
Hi
This is an old project (2006) that I'm putting on here as I no longer have a website, and someone might find it useful.

See attached PDF.

This article describes a 370-byte PICAXE code
'module' that allows simple maths to be performed to
32-bit precision. The code executes maths
expressions, which are stored as simple 'strings' in
data EEPROM.

EDIT: Uploaded the code, because it's tricky getting it from the document!
 

Attachments

Last edited:

BCJKiwi

Senior Member
Not sure if its quite the same thing or not but PICAXE does actually support 32bit Math inherently.
This is a small part of the demonstrated code at http://www.picaxeforum.co.uk/showthread.php?t=8624

Code:
' Sensor ADC
    ReadADC10 1,SensADC                         '1 is the ADC pin# sensor output is connected to
sertxd ("Uncorrected Sensor     ADC ",#SensADC,cr,lf)
'
' Corrected Sensor ADC
    w3 = SensADC  *100 /RefADC *VADC
    w4 = SensADC **100 /RefADC *VADC            ' Use High word and Low word 32 bit math to maintain resolution
    w0 = w3 + w4 /100
SerTxd ("Corrected   Sensor     ADC ",#w0,cr,lf)
From Manual2 page20
Code:
Multiplication and Division
When multiplying two 16 bit word numbers the result is a 32 bit (double word)
number. The multiplication (*) command returns the low word of a word*word
calculation.  The ** command returns the high word of the calculation and */
returns the middle word.
Therefore in normal maths $aabb x $ccdd = $eeffgghh
In PICAXE maths
$aabb * $ccdd = $gghh
$aabb ** $ccdd = $eeff
The X1 and X2 parts also support return of the middle word
$aabb */ $ccdd = $ffgg
The division (/) command returns the quotient (whole number) word of a
word*word division.  The modulus (// or %) command returns the remainder of
the calculation.
 

Jeremy Leach

Senior Member
Well, it's not really the same - I mean, yes there is the ** operator to get the high word of a multiplation, but beyond that there can be overflow problems that this module tries to overcome by providing a way of calculating using integer maths for all addiiton/subtraction/multiplication/division to 32 bit not 16 bit. It was a bit experimental at the time, but worked.
 

xzoqick

New Member
I get a 'file damaged and can't be repaired' message on the .pdf file. Is it just me or could you please re-upload?
 

xzoqick

New Member
Thanks tiscando'
As a result of your reply I tried a heap of things and got a correct download, so all OK now.

Thanks again.
 

Mawrob

New Member
Divide by 0 error on 28X2?

This looks like a very useful piece of code. I can simulate it correctly on an 18X but cannot run it or simulate correctly on a 28X2. (I get a divide by zero error ERROR: 2) I have tried to work out why with no luck yet. Thought it might be differences in system variables and scratchpad but cannot see why since the code peeks and pokes into registers also used by the 28X2.

Any thoughts as to why this code does not run on the X2?
 
Last edited:

hippy

Technical Support
Staff member
Welcome to the PICAXE forum

I don't think the code will run on the 20X2 as there is a difference in mapping of SFR (RAM) on the 18X and 20X2; SFR locations 50 to 59 ( $32 to $3B ) are storage locations for variables 'b0' to 'b9' on an 18X but not on the 20X2. Anything using those locations and expecting variable values there will find they are not. The tricks used to make this code work won't work on the X1 or X2 PICAXE's without being changed.
 

Mawrob

New Member
Code modified for 28X2

Here is the code modified for the 28X2. It appears to work:

Code:
'Modified for X2 by Mawrob, 2010 based on feedback from hippy
'
'I don't think the code will run on the 20X2 as there is a difference 
'in mapping of SFR (RAM) on the 18X and 20X2; SFR locations 50 to 59 ( $32 to $3B ) 
'are storage locations for variables 'b0' to 'b9' on an 18X but not on the 20X2. 
'Anything using those locations and expecting variable values there will find they are not. 
'The tricks used to make this code work won't work on the X1 or X2 PICAXE's without being changed.
'
'Changed code to map peek and poke pointers 50->59 to 0->9
'
'Not fully tested.
'
'Original parameters left as comments
'

 '**********************************************
 '*     32-Bit (unsigned) maths on a PICAXE    *
 '*     J.Leach 2006                           *
 '**********************************************

 '****************************
 '**** PROCEDURE POINTERS ****
 '****************************
Symbol Procedure0StartAddress = 0

 '********************************
 '**** VIRTUAL PROGRAM MEMORY ****
 '********************************
Eeprom 0,("+B-C*D/E=A")

 '**************************
 '**** PICAXE VARIABLES ****
 '**************************
 'Word 0 (b0 and b1)
      Symbol OperandLSW = w0
      Symbol OperandLSWLSB = b0
      Symbol OperandLSWMSB = b1
 'Word 1 (b2 and b3)
      Symbol T = w1
      Symbol Temp2Word = w1
      Symbol InstructionOperand = b2
 'Word 2 (b4 and b5)
      Symbol AccumulatorLSW = w2
      Symbol AccumulatorLSWLSB = b4
      Symbol AccumulatorLSWMSB = b5
 'Word 3 (b6 and b7)
      Symbol AccumulatorMSW = w3
 'Word 4 (b8 and b9)
      Symbol S = w4
      Symbol Temp1Word = w4
 'Word 5 (b10 and b11)
      Symbol OperandMSW = w5
      Symbol Address = b10
      Symbol Address1 = b10
      Symbol Address2 = b11
 'Word 6 (b12 and b13)
      Symbol InstructionCode = b12
      Symbol Temp1Byte = b12
      Symbol ProgramCounter = b13
      Symbol Temp2Byte = b13
      Symbol ErrorFlag = b13
      Symbol Index = b13

 '*******************
 '**** CONSTANTS ****
 '*******************
Symbol LCDOutPin = 6 'LCD used for alerting, but other methods can be used.

 'Addresses
Symbol RTStartAddress = 80
Symbol RTEndAddress = 83
Symbol DenominatorLSBAddress = 84
Symbol DenominatorMSBAddress = 85
Symbol ErrorFlagAddress = 86
Symbol GPRStartAddress = 87
Symbol ProgramCounterAddress = 97
 'Error constants
Symbol ERROR_Overflow = 0
Symbol ERROR_NegativeResult = 1
Symbol ERROR_DivideByZero = 2
 'Other
Symbol RTStartLessb4Address = 76'26 '(80 - 54)
Symbol GPRStartAddressLessb0Address = 87'37 '(87 - 50)

 '**************
 '**** MAIN ****
 '**************
Main:
'Serout LCDOutPin,N2400,(254,1) 'Clear LCD. NOT really part of this module.
Pause 10000

 'Example calculation: (64321 - 1052 * 761 / 4523 )
w1 = 64321
w2 = 1052
w3 = 761
w4 = 4523
ProgramCounter = Procedure0StartAddress
Gosub ExecuteProcedure
sertxd (#w5,":",#w0,CR,LF)
End

 '*************************************************
 '**** VIRTUAL ARITHMETIC AND LOGIC UNIT (ALU) ****
 '*************************************************

Add:
      'Performs : Accumulator = Accumulator + Operand
      'Jumps to error routine on overflow
      'Uses both words of Operand (because used by Multiply and Divide routines)
      'ON EXIT: Operand is same as on entry

      poke ErrorFlagAddress,ERROR_Overflow

      'Add LSW
      AccumulatorLSW = AccumulatorLSW + OperandLSW
      If AccumulatorLSW >= OperandLSW Then Add_1
      'Add Carry to MSW and jump to error routine if overflow
      AccumulatorMSW = AccumulatorMSW + 1
      If AccumulatorMSW = 0 Then CPU_Error

      'Add MSW
      Add_1:
      AccumulatorMSW = AccumulatorMSW + OperandMSW
      'Jump to error routine if overflow
      If AccumulatorMSW < OperandMSW Then CPU_Error
Return

Subtract:
      'Performs : Accumulator = Accumulator + OperandLSW
      'Jumps to error routine if the result is less than zero
      'Uses only the LSW of Operand
      'ON EXIT: Operand is same as on entry

      poke ErrorFlagAddress,ERROR_NegativeResult

      'Subtract LSW
      Temp1Word = AccumulatorLSW
      AccumulatorLSW = AccumulatorLSW - OperandLSW

      If Temp1Word >= AccumulatorLSW Then Subtract_1
      'Borrow from MSW and jump to error routine if this will make the overall result
      'negative.
      If AccumulatorMSW = 0 Then CPU_Error
      AccumulatorMSW = AccumulatorMSW - 1

      'Note: No need to Subtract MSW as only OperandLSW is being used
      Subtract_1:
Goto Fetch

Multiply:
      'Performs : Accumulator = Accumulator * OperandLSW
      'Jumps to error routine on overflow
      'Uses only the LSW of Operand
      'ON EXIT: Operand is corrupted

      poke ErrorFlagAddress,ERROR_Overflow

      'Calculate the higher multiple and keep in Accumulator
      Temp1Word = AccumulatorLSW
      Temp2Word = AccumulatorMSW
      AccumulatorLSW = 0
      AccumulatorMSW = OperandLSW * Temp2Word
      Temp2Word = OperandLSW ** Temp2Word
      'Check for overflow
      If Temp2Word > 0 Then CPU_Error

      'Calculate the lower multiple and poke in Operand
      OperandMSW = Temp1Word ** OperandLSW
      OperandLSW = Temp1Word * OperandLSW

      'Add the multiples to peek the final result
      Gosub Add
Goto Fetch

Divide:
      'Performs : Accumulator = Accumulator / OperandLSW
      'Jumps to error routine if divide by zero
      'Uses only the LSW of Operand
      'ON EXIT: Operand is corrupted

      poke ErrorFlagAddress,ERROR_DivideByZero

      'Check for error
      If OperandLSW = 0 Then CPU_Error

      'Zero the Running Total
      For Address = RTStartAddress To RTEndAddress
            poke Address,0
      Next

      'Calculate the quotient and remainder for 65535/OperandLSW
      S = 65535 / OperandLSW
      T = 65535 // OperandLSW

      'Store the denominator
      poke DenominatorLSBAddress,OperandLSWLSB
      poke DenominatorMSBAddress,OperandLSWMSB

      Divide_1:
      'Calculate S * AccumulatorMSW in the Operand, and add to Running Total.
      'Note: uses variables very carefully !
      OperandLSW = AccumulatorMSW
      Gosub SwapAccumulatorWithRT
      OperandMSW = S ** OperandLSW
      OperandLSW = S * OperandLSW
      Gosub Add
      'Update the running total
      Gosub SwapAccumulatorWithRT

      'Calculate the new Numerator
      OperandMSW = 0
      OperandLSW = AccumulatorLSW + AccumulatorMSW
      If OperandLSW > AccumulatorMSW Then Divide_2
      OperandMSW = 1
      Divide_2:
      AccumulatorLSW = AccumulatorMSW * T
      AccumulatorMSW = AccumulatorMSW ** T
      Gosub Add

      'Check to see if the new numerator is a single word. Loop back if it isn't
      If AccumulatorMSW > 0 Then Divide_1

      Temp1Word = AccumulatorLSW
      'Retrieve the running total
      Gosub SwapAccumulatorWithRT
      'Retrieve the denominator
      peek DenominatorLSBAddress,OperandLSWLSB
      peek DenominatorMSBAddress,OperandLSWMSB
      'Calculate AccumulatorLSW/Demominator and store in Operand
      OperandLSW = Temp1Word / OperandLSW
      OperandMSW = 0
      'and add to the Running total to give the final result
      Gosub Add
Goto Fetch

SwapAccumulatorWithRT:
      'Swaps the Accumulator value with the Running Total value
      For Address1 = RTStartAddress To RTEndAddress
            Address2 = Address1 - RTStartLessb4Address
            peek Address1,Temp1Byte
            peek Address2,Temp2Byte
            poke Address1,Temp2Byte
            poke Address2,Temp1Byte
      Next
Return

StoreAccumulatorLSW:
      'ON EXIT : The specified w0-w4 variable is loaded with the LSW of the Accumulator.
      'The 'Return' statement will end the use of the Virtual CPU
      'and return to 'normal' PICAXE code.

      Address = InstructionOperand - "A" * 2 '+ 50
    
      poke Address,AccumulatorLSWLSB
      Address = Address + 1
      poke Address,AccumulatorLSWMSB
Return

 '*******************************
 '**** VIRTUAL CPU ROUTINES *****
 '*******************************

CPU_Error:
      peek ErrorFlagAddress,ErrorFlag
      Sertxd("ERROR: ",#ErrorFlag," ",CR,LF)
End

ExecuteProcedure:
      'ON ENTRY: ProgramCounter has been set to the start of the Procedure

      'Save the Program Counter
      poke ProgramCounterAddress,ProgramCounter 'Save the ProgramCounter

      'Load General Purpose registers with corresponding w0 to w4 values
      For Address1 = 0 to 9'50 To 59
            Address2 = Address1 + GPRStartAddressLessb0Address
            peek Address1,Temp1Byte
            poke Address2,Temp1Byte
      Next

      'Set the Accumulator to 0
      AccumulatorMSW = 0
      AccumulatorLSW = 0

 '*************************
 '**** VIRTUAL DECODER ****
 '*************************

Fetch:
      'Fetch an Instruction code and operand from Program Memory
      peek ProgramCounterAddress,ProgramCounter 'Retrieve the ProgramCounter
      Read ProgramCounter,InstructionCode
      ProgramCounter = ProgramCounter + 1
      Read ProgramCounter,InstructionOperand
      ProgramCounter = ProgramCounter + 1
      poke ProgramCounterAddress,ProgramCounter 'Save the ProgramCounter

      'Load the contents of the specified Register into OperandLSW
      Address = InstructionOperand - "A" * 2 + GPRStartAddress
      
      peek Address,OperandLSWLSB
      Address = Address + 1
      peek Address,OperandLSWMSB
      OperandMSW = 0

Execute:
      'Execute the loaded Instruction

      Lookdown InstructionCode,("+","-","*","/","="),Index
      Branch Index,(Instruction_Add,Subtract,Multiply,Divide,StoreAccumulatorLSW)

Instruction_Add:
      Gosub Add
      Goto Fetch
 

hippy

Technical Support
Staff member
Good work Mawrob - hadn't examined the code so wasn't sure how much would need to change.
 

matherp

Senior Member
This version works for the example on 20X2 - clever man that Jeremy Leach!

Code:
#picaxe20x2
setfreq m64
 '**********************************************
 '*     32-Bit (unsigned) maths on a PICAXE    *
 '*     J.Leach 2006                           *
 '**********************************************

 '****************************
 '**** PROCEDURE POINTERS ****
 '****************************
Symbol Procedure0StartAddress = 0

 '********************************
 '**** VIRTUAL PROGRAM MEMORY ****
 '********************************
Eeprom 0,("+B-C*D/E=A")

 '**************************
 '**** PICAXE VARIABLES ****
 '**************************
 'Word 0 (b0 and b1)
      Symbol OperandLSW = w0
      Symbol OperandLSWLSB = b0
      Symbol OperandLSWMSB = b1
 'Word 1 (b2 and b3)
      Symbol T = w1
      Symbol Temp2Word = w1
      Symbol InstructionOperand = b2
 'Word 2 (b4 and b5)
      Symbol AccumulatorLSW = w2
      Symbol AccumulatorLSWLSB = b4
      Symbol AccumulatorLSWMSB = b5
 'Word 3 (b6 and b7)
      Symbol AccumulatorMSW = w3
 'Word 4 (b8 and b9)
      Symbol S = w4
      Symbol Temp1Word = w4
 'Word 5 (b10 and b11)
      Symbol OperandMSW = w5
      Symbol Address = b10
      Symbol Address1 = b10
      Symbol Address2 = b11
 'Word 6 (b12 and b13)
      Symbol InstructionCode = b12
      Symbol Temp1Byte = b12
      Symbol ProgramCounter = b13
      Symbol Temp2Byte = b13
      Symbol ErrorFlag = b13
      Symbol Index = b13

 '*******************
 '**** CONSTANTS ****
 '*******************
Symbol LCDOutPin = 6 'LCD used for alerting, but other methods can be used.

 'Addresses
Symbol RTStartAddress = 80
Symbol RTEndAddress = 83
Symbol DenominatorLSBAddress = 84
Symbol DenominatorMSBAddress = 85
Symbol ErrorFlagAddress = 86
Symbol GPRStartAddress = 87
Symbol ProgramCounterAddress = 97
 'Error constants
Symbol ERROR_Overflow = 0
Symbol ERROR_NegativeResult = 1
Symbol ERROR_DivideByZero = 2
 'Other
Symbol RTStartLessb4Address = 76 '(80 - 4)
Symbol GPRStartAddressLessb0Address = 87 '(87 - 0)

 '**************
 '**** MAIN ****
 '**************
Main:
Pause 5000

 'Example calculation: (64321 - 1052 * 761 / 4523 )
w1 = 64321
w2 = 1052
w3 = 761
w4 = 4523
ProgramCounter = Procedure0StartAddress
Gosub ExecuteProcedure
sertxd(#w0,cr,lf)
End

 '*************************************************
 '**** VIRTUAL ARITHMETIC AND LOGIC UNIT (ALU) ****
 '*************************************************

Add:
      'Performs : Accumulator = Accumulator + Operand
      'Jumps to error routine on overflow
      'Uses both words of Operand (because used by Multiply and Divide routines)
      'ON EXIT: Operand is same as on entry

      Poke ErrorFlagAddress,ERROR_Overflow

      'Add LSW
      AccumulatorLSW = AccumulatorLSW + OperandLSW
      If AccumulatorLSW >= OperandLSW Then Add_1
      'Add Carry to MSW and jump to error routine if overflow
      AccumulatorMSW = AccumulatorMSW + 1
      If AccumulatorMSW = 0 Then CPU_Error

      'Add MSW
      Add_1:
      AccumulatorMSW = AccumulatorMSW + OperandMSW
      'Jump to error routine if overflow
      If AccumulatorMSW < OperandMSW Then CPU_Error
Return

Subtract:
      'Performs : Accumulator = Accumulator + OperandLSW
      'Jumps to error routine if the result is less than zero
      'Uses only the LSW of Operand
      'ON EXIT: Operand is same as on entry

      Poke ErrorFlagAddress,ERROR_NegativeResult

      'Subtract LSW
      Temp1Word = AccumulatorLSW
      AccumulatorLSW = AccumulatorLSW - OperandLSW

      If Temp1Word >= AccumulatorLSW Then Subtract_1
      'Borrow from MSW and jump to error routine if this will make the overall result
      'negative.
      If AccumulatorMSW = 0 Then CPU_Error
      AccumulatorMSW = AccumulatorMSW - 1

      'Note: No need to Subtract MSW as only OperandLSW is being used
      Subtract_1:
Goto Fetch

Multiply:
      'Performs : Accumulator = Accumulator * OperandLSW
      'Jumps to error routine on overflow
      'Uses only the LSW of Operand
      'ON EXIT: Operand is corrupted

      Poke ErrorFlagAddress,ERROR_Overflow

      'Calculate the higher multiple and keep in Accumulator
      Temp1Word = AccumulatorLSW
      Temp2Word = AccumulatorMSW
      AccumulatorLSW = 0
      AccumulatorMSW = OperandLSW * Temp2Word
      Temp2Word = OperandLSW ** Temp2Word
      'Check for overflow
      If Temp2Word > 0 Then CPU_Error

      'Calculate the lower multiple and put in Operand
      OperandMSW = Temp1Word ** OperandLSW
      OperandLSW = Temp1Word * OperandLSW

      'Add the multiples to get the final result
      Gosub Add
Goto Fetch

Divide:
      'Performs : Accumulator = Accumulator / OperandLSW
      'Jumps to error routine if divide by zero
      'Uses only the LSW of Operand
      'ON EXIT: Operand is corrupted

      Poke ErrorFlagAddress,ERROR_DivideByZero

      'Check for error
      If OperandLSW = 0 Then CPU_Error

      'Zero the Running Total
      For Address = RTStartAddress To RTEndAddress
            Poke Address,0
      Next

      'Calculate the quotient and remainder for 65535/OperandLSW
      S = 65535 / OperandLSW
      T = 65535 // OperandLSW

      'Store the denominator
      Poke DenominatorLSBAddress,OperandLSWLSB
      Poke DenominatorMSBAddress,OperandLSWMSB

      Divide_1:
      'Calculate S * AccumulatorMSW in the Operand, and add to Running Total.
      'Note: uses variables very carefully !
      OperandLSW = AccumulatorMSW
      Gosub SwapAccumulatorWithRT
      OperandMSW = S ** OperandLSW
      OperandLSW = S * OperandLSW
      Gosub Add
      'Update the running total
      Gosub SwapAccumulatorWithRT

      'Calculate the new Numerator
      OperandMSW = 0
      OperandLSW = AccumulatorLSW + AccumulatorMSW
      If OperandLSW > AccumulatorMSW Then Divide_2
      OperandMSW = 1
      Divide_2:
      AccumulatorLSW = AccumulatorMSW * T
      AccumulatorMSW = AccumulatorMSW ** T
      Gosub Add

      'Check to see if the new numerator is a single word. Loop back if it isn't
      If AccumulatorMSW > 0 Then Divide_1

      Temp1Word = AccumulatorLSW
      'Retrieve the running total
      Gosub SwapAccumulatorWithRT
      'Retrieve the denominator
      Peek DenominatorLSBAddress,OperandLSWLSB
      Peek DenominatorMSBAddress,OperandLSWMSB
      'Calculate AccumulatorLSW/Demominator and store in Operand
      OperandLSW = Temp1Word / OperandLSW
      OperandMSW = 0
      'and add to the Running total to give the final result
      Gosub Add
Goto Fetch

SwapAccumulatorWithRT:
      'Swaps the Accumulator value with the Running Total value
      For Address1 = RTStartAddress To RTEndAddress
            Address2 = Address1 - RTStartLessb4Address
            Peek Address1,Temp1Byte
            Peek Address2,Temp2Byte
            Poke Address1,Temp2Byte
            Poke Address2,Temp1Byte
      Next
Return

StoreAccumulatorLSW:
      'ON EXIT : The specified w0-w4 variable is loaded with the LSW of the Accumulator.
      'The 'Return' statement will end the use of the Virtual CPU
      'and return to 'normal' PICAXE code.

      Address = InstructionOperand - "A" * 2 
      Poke Address,AccumulatorLSWLSB
      Address = Address + 1
      Poke Address,AccumulatorLSWMSB
Return

 '*******************************
 '**** VIRTUAL CPU ROUTINES *****
 '*******************************

CPU_Error:
      Peek ErrorFlagAddress,ErrorFlag
      Sertxd("ERROR: ",#ErrorFlag," ")
End

ExecuteProcedure:
      'ON ENTRY: ProgramCounter has been set to the start of the Procedure

      'Save the Program Counter
      Poke ProgramCounterAddress,ProgramCounter 'Save the ProgramCounter

      'Load General Purpose registers with corresponding w0 to w4 values
      For Address1 = 0 To 9
            Address2 = Address1 + GPRStartAddressLessb0Address
            Peek Address1,Temp1Byte
            Poke Address2,Temp1Byte
      Next

      'Set the Accumulator to 0
      AccumulatorMSW = 0
      AccumulatorLSW = 0

 '*************************
 '**** VIRTUAL DECODER ****
 '*************************

Fetch:
      'Fetch an Instruction code and operand from Program Memory
      Peek ProgramCounterAddress,ProgramCounter 'Retrieve the ProgramCounter
      Read ProgramCounter,InstructionCode
      ProgramCounter = ProgramCounter + 1
      Read ProgramCounter,InstructionOperand
      ProgramCounter = ProgramCounter + 1
      Poke ProgramCounterAddress,ProgramCounter 'Save the ProgramCounter

      'Load the contents of the specified Register into OperandLSW
      Address = InstructionOperand - "A" * 2 + GPRStartAddress
      Peek Address,OperandLSWLSB
      Address = Address + 1
      Peek Address,OperandLSWMSB
      OperandMSW = 0

Execute:
      'Execute the loaded Instruction

      Lookdown InstructionCode,("+","-","*","/","="),Index
      Branch Index,(Instruction_Add,Subtract,Multiply,Divide,StoreAccumulatorLSW)

Instruction_Add:
      Gosub Add
      Goto Fetch
 
Top