Handling larger numbers (multiregister mul/div)

rossw

Active Member
So, I've got this fancy sensor that returns 24-bit data with its internal sigma-delta ADC.
I've bit-bashed my own SPI subroutines to send and receive data.
 
The crunch has come while converting the raw sensor data to useful real-world numbers.
There are offsets and scaling factors that need to be applied, and the intermediate calculations require 58 bits.
I can't see a way of simplifying the calculations to remain in 32-bits integer without underflow and/or overflow, although I'm not done yet.
 
It would be "nice" to have a long multiply and divide, that could take a register-pair (lets say, ram1.ram2).
So I could do (lets say)
 
   mul32  var1 ram3   (implied result into ram1 (bits 63-32) and ram2 (bits 31-0))
 
and
 
  div ram3 ram4   (divides long register pair ram1.ram2 by ram3 and puts the result in ram4)
 
While we're at it, a divide that can put the non-integer part in a register would be good. (Wait, did we end up with a working modulo?
so     modulo 17 3  would put the remainder (17/3=5, remainder=2) somewhere?)
 
Is this realistic or simply too much for the poor WC8 ?
 
The largest variable the compiler can allocate for this processor is 32bit.  For having a number larger than 32bit, there is no other way than move to WC32 board.
 
CAI_Support said:
The largest variable the compiler can allocate for this processor is 32bit.  For having a number larger than 32bit, there is no other way than move to WC32 board.
 
The compiler might limit you to that, but any processor can do arbitary precision maths. Over 35 years ago I wrote 32-bit floating point routines for 8-bit Z80 processors. Even today, I've got 32-bit machines capable of several thousand digits of precision. It's a coding question, not an architecture limitation!
 
That said, I spent some time last night and reworked their original equations to move the significant bits into a 32-bit range.
I just uploaded the code to an old board, using arbitary-precision floating point maths on another machine, the "correct" barometer reading should have been 972.655 hPa. The PLC, even having lost some low-end bits of data during intermediate calculations, spat out 97266 (which is a 100-times scaled answer) for 972.66 hPa.
 
I think that's "good enough", my target was to within 0.1 hPa and I've managed an order of magnitude better than that!
 
Hi Ross,
 
It is very interesting to see how did you handle this large number in this existing PLC handling logic.  Are you getting the value through WEBSET return or SETVAR?
 
CAI_Support said:
Hi Ross,
 
It is very interesting to see how did you handle this large number in this existing PLC handling logic.  Are you getting the value through WEBSET return or SETVAR?
 
Seriously??  Well, ok, you asked for it!
This is some seriously ugly code - it is the "first cut of the first proof of concept" code, on an old board (3.02.07) without lots of the nicer additional commands, so cut me some slack :)
 
Note, it also includes a number of "hardware trigger points" used to trigger my digital storage 'scope to watch and understand what the hardware was doing, and that is also why some of the long delays (to give me time to stop the scope, or to easily identify a particular section of code by time offsets from triggers)
 
Herewith the full commented source:
 

Code:
START
        set op7 1       # remove chip select
        delay 100       # let things settle
        set ram1 30     # reset command
        callsub send
        delay 500

        set op1 1
        set op1 0
        set ram1 160    # read PROM0
        callsub read24

        set ram1 162    # read PROM1
        callsub read24
        set var8 ram1

        set ram1 164    # read PROM2
        callsub read24
        set var7 ram1

        set ram1 166    # read PROM3
        callsub read24
        set var6 ram1

        set ram1 168    # read PROM4
        callsub read24
        set var5 ram1

        set ram1 170    # read PROM5
        callsub read24
        set var4 ram1

        set ram1 172    # read PROM6
        callsub read24
        set var3 ram1

        set ram1 174    # read PROM7
        callsub read24
        delay 500

loop:
        set op1 1
        set op1 0

        delay 250
        set ram1 88     # convert D2 (temperature)
        callsub send
        set ram1 0      # command 0 to read answer
        callsub read

        mul var4 256 ram2       # ram2 has c5*2^8
        sub ram1 ram2 ram5      # ram5 has dT

        div ram5 4 ram1         # ram1 has dT/4 for scaling later
        div var3 4 ram2         # C6/4
        mul ram1 ram2 ram3      # dT*C6
        div ram3 524288 ram1    # dT*C6 / 2^19
        add ram1 2000 ram1      # 2000+ (dT*C6/2^23)
        add ram7 ram1 ram7      # add temperature*100 to accumulater


        set ram1 72     # convert D1 (pressure)
        callsub send
        set ram1 0      # command 0 to read answer
        callsub read
        set ram4 ram1   # D1

# ram4=D1 ram5=dT  var2=TEMP  var8=C1 var7=C2 var6=C3  var5=C4  var4=C5  var3=C6



        div var5 8 ram2         # P2 = C4 / 2^3
        div ram5 4 ram1         # P3 = dT / 2^2
        mul ram2 ram1 ram1      # P4 = p2 *  p3
        div ram1 131072 ram1    # p4 / 2^17
        add ram1 var7 ram1
        add ram1 var7 ram1      # add C2*2 to calculate OFF

        div var6 8 ram2         # Q2 = C3 / 2^3
        div ram5 8 ram3         # Q3 = dT / 2^3
        mul ram2 ram3 ram2      # Q4 = Q2 * Q3
        div ram2 131072 ram2    # Q4 / 2^17
        add ram2 var8 ram2      # add Q1
# ram1 has OFF, ram2 has SENS

        div ram4 512 ram3       # R1 = D1 / 2^9
        mul ram3 ram2 ram2      # R3 = R1 * SENS
        div ram2 4096 ram2      # R3 / 2^12
        sub ram2 ram1 ram1      # calculate pressure*100

        add ram8 ram1 ram8      # accumulate pressure in averaging register
        inc ram6        # bump counter
        tstlt ram6 10           # every 10th scan, update output
        goto loop       # not the 10th scan, so just loop
        div ram8 10 var1   # set VAR1 and VAR2 with pressure*100 and temperature*100
        div ram7 10 var2
        set ram6 0    # clear counter
        goto loop   # and start all over again.
end



send:
        # send 8 bits in ram1, most significant bits first
        set ram2 8
        set op6 0       # data low
        set op7 0       # enable chip

sendbit:
        set op8 0       # clock low
        tstge ram1 128 op6      # is bit7 high? (put bit7 on output)
        sub ram1 128 ram1       # and clear bit7
        set op8 1       # assert clock
        mul ram1 2 ram1 # shift left 1 bit
        dec ram2        # decr count
        bnz sendbit     # send more bits if required
        set op8 0       # clock low
        set op6 0       # data low
        delay 10        # wait for command to complete
        set op7 1       # disable chip
        ret

read24:
        set ram4 24     # clock 24 pulses, send first 8 bits of ram1 out, return last 16 bits in ram1. Destroys ram1, 2, 3, 4.
        tsteq 1 0       # skip next instruction
read:
        set ram4 32     # clock 32 pulses, send first 8 bits of ram1 out, return last 24 bits in ram1. Destroys ram1, 2, 3, 4.
        set ram2 0
        set op8 0       # clock low
        set op6 0       # data low
        set op7 0       # enable chip

readbit:
        tstge ram1 128 op6      # is bit7 high? (put bit7 on output)
        sub ram1 128 ram1       # and clear bit7
        mul ram1 2 ram1         # shift left 1
        set op8 1               # assert clock
        add ram3 ram3 ram3      # shift bits
        add ip7 ram3 ram3       # add data bit to register
        inc ram2                # bump count
        tsteq ram2 8            # are we 8 bits in?
        set ram3 0
        set op8 0               # remove clock
        tstlt ram2 ram4         # done yet?
        goto readbit
        set op8 0       # clock low
        set op7 1       # disable
        set ram1 ram3   # return final answer
        delay 100
        ret
 
For those masochistic enough to read through the above code and wondering how the hell it is supposed to actually DO anything...
 
The spec sheet for the sensor is here; http://general.rossw.net/MS5611-01BA03.pdf
And it is connected to the WC board thus:
 
# Barometer chip.
# OP8 - SCL
# OP7 - CSB
# OP6 - SDA
# IP7 - SDO
 
 
CAI_Support said:
Amazing. You actually read directly from the sensor and form the larger number yourself in PLC logic.
 
I never backed away from anything because it was hard or unconventional :)
 
rossw said:
Seriously??  Well, ok, you asked for it!
This is some seriously ugly code - it is the "first cut of the first proof of concept" code, on an old board (3.02.07) without lots of the nicer additional commands,
Neat. 
 
I've bit banged a lot of interfaces in assembler but it never occurred to me you could do I2C on the WebControl. I haven't read through your code but I assume you implemented the SPI interface since it is simpler then I2C.
 
This forum has been fantastic learning experience for me.
 
/tom
 
Tschmidt said:
Neat. 
 
I've bit banged a lot of interfaces in assembler but it never occurred to me you could do I2C on the WebControl.
 
This forum has been fantastic learning experience for me.
 
/tom
 
Heh, glad to have contributed, Tom.
Just for clarification, I bit-bashed the SPI interface, as it didn't need any additional parts.
I am tempted to do I2C just to prove it can be done, but it'll take two transistors or fets. I considered doing it with diodes but that really is a bit nasty :)
 
Back
Top