# PIC18F4520 math in assembly

Discussion in 'Electronic Design' started by Mook Johnson, Mar 21, 2007.

1. ### Mook JohnsonGuest

I ahve a little GPS module that I'm trying to read with a PIC and convert
the data and display on an LCD screen.

The GPS outputs speed as a 16 bit word where 1 bit - 0.1 km/h and I'd like
to convert that to MPH before displaying it on a screen. I need to multiply
the word received from the GPS by a fractional number before having
something to display on the screen.

How do I do that in a 8 bit PIC18 series part using assembly? I know there
are some routines for dealing with floating point numbers in a pIC but I
don't quite know how to start. How do you convert a 16 bit word to a float
so you can do the multiply?

2. ### BrianGuest

You over-thunk it. You need to divide by 1.6. Instead, divide by 16, a
simple shift operation.

3. ### Guest

If you don't want perfect accuracy - you need to
a) divide by 10
b) multiply by 5/8
Equivalently, you need to divide by 16 - which you can do by shifting
right 4 places.
You can use a look up table to deal with the 4 bit remainder

4. ### Spehro PefhanyGuest

There should be some routines to do this conversion (integer->float).
In general you'd normalize the number by left shifting until the ms
bit is 1, and then one more shift (no sense storing the leftmost bit
if it's always one). The exponent gets decremented from a starting
value (typically biased from zero) with each shift. When you see the
floating point format described, it should be pretty obvious. Usually
you're going to also want the reverse function to display the number.

For only a single operation you might find it easier to work entirely
in fixed point and avoid the double conversions. For example you could
use some routines that would handle 16 or 32 bit math (or write them).
Multiply by 10 and then divide by 16 (shift), more-or-less as Brian
suggested would give you 0.1 MPH resolution and ~0.6% error.

Personally, for an accurate instrument, in assembly, I'd use a 32-bit
x 32-bit -> 64-bit multiply by a fixed 32-bit constant of 0x4F9175A
(keep the most significant 32 bits) and get essentially perfect
accuracy, probably faster than the floating point calculations and
conversions, but hey, that's just me.

5. ### Jonathan KirwanGuest

In assembly, I'd just avoid floating point entirely.

I don't know for certain what you'd like to output. You mention just
converting to miles/hr, but since your input is in 1/10ths of km/hr
let's say you wanted to generate an integer that was in 1/10ths of
miles/hr. That way, you could just convert this binary integer into
ASCII output and insert a period just before the last digit. So let
me assume that so you can see.

Let's call your value 'x'. It is an integer in tenths of km/hr. To
convert this to km/hr, it is (x/10). To convert km/hr to miles/hr,
you need to:

(x/10) / [ 5280 ft/mile * 12 in/ft * 25.4 mm/in * 10^-6 km/mm ]

That is:

100000
x * -------
1609344

But to convert to 1/10ths of a mile/hr, multiply that constant by 10,
so you actually need to compute this:

1000000
x * -------
1609344

In the first case above, you can see why some suggested just dividing
by 16. Looks close enough. But let's go with the 2nd case I
mentioned and compute tenths of a mile/hr, as in integer.

Let's first remove prime factors:

15625
x * -----
25146

That helps. You could, if you have the routines handy, just multiply
your 16-bit 'x' by 15625 to compute a 32-bit numerator, then use a
32-by-16 divide routine to divide that result by 25146.

But let's say you want to look a little further. Use continued
fractions (look it up, if you want) to approximate that fraction. The
continued fraction for the above fraction is:

[ 0, 1, 1, 1, 1, 1, 3, 1, 2, 7, 1, 1, 15 ]

In terms of possible ratios, in decreasing accuracy, they are then
formed from the above continued fraction by removing final terms:

Term Fraction Decimal value
15: 15625/25146 0.621371192237334
1: 1006/1619 0.6213712168004941
1: 535/861 0.6213704994192799
7: 471/758 0.6213720316622692
2: 64/103 0.6213592233009708
1: 23/37 0.6216216216216216
3: 18/29 0.6206896551724138
1: 5/8 0.625
1: 3/5 0.6
1: 2/3 0.6666666666666666
1: 1/2 0.5
1: 1/1 1
0: 0 0

As you can see, you can pick your poison. Looking down the list, you
can see that perhaps 5/8ths isn't so bad. In this case, you multiply
your 'x' value by 5 (which is a shift left two and add) and then
divide by 8 (which is a shift right three -- and just check the carry
out for rounding, if you want.) That would take the value of 160
(which is 16 km/hr) and convert it to 100 (which is 10 mi/hr.) That
might be close enough and would be easily done in assembly.

If you need greater accuracy, work your way up the chain. But as you
can see, the divisors aren't powers of 2 anymore so a simple shift
won't work and you'll be looking for an integer division routine,
perhaps.

If you really do just want miles/hr and not tenths of miles/hr, then
the continued fraction setup looks like, in descending precision:

Term Fraction Decimal value
6: 3125/50292 0.0621371192237334
4: 503/8095 0.062137121680049416
1: 107/1722 0.062137049941927994
2: 75/1207 0.06213753106876554
2: 32/515 0.062135922330097085
1: 11/177 0.062146892655367235
10: 10/161 0.062111801242236024
16: 1/16 0.0625
0: 0 0

There you can see the (1/16) recommended elsewhere. But you can also
see other options for more precision, assuming you've got a nifty
integer division algorithm floating about and want greater accuracy.

Hope that helps.

Jon

6. ### Jonathan KirwanGuest

I mean more accuracy, here. Sorry.

Jon

7. ### Terran MelconianGuest

Nice writeup.

into the program in question and try to avoid adding more if I can.

Suppose that what I already have is a 16x16 multiply. I don't want to
introduce a divide as well, because I'm short on code space, so I want
to divide by a power of two, or even better a power of 256. 4072/65536
is 0.0621338, not too bad at all. This can be implemented as
multiplying the value by 4072 and then taking the upper two bytes of the
result, possibly rounding if desired.

Alternatively, suppose I have a 24/24 divide, but no multiply (this
has happened). I'd put the number in the highest two bytes of my
dividend and make the lowest byte zero. Then I want to divide by N such
that 256/N = 0.621371, giving N=412. This gives 256/412=0.621359.

8. ### Tony WilliamsGuest

The sum, Miles = KM*( 5/8 - 1/256 ) looks about 0.05% accuracy.

It's mainly 32 bit shifts and adds, for a 16 bit result.

9. ### Jonathan KirwanGuest

I like it. It can be observed easily by computing (5/8 - 15625/25146)
in floating point notation (32-bit is 3B6DD100) and noting the
mantissa, which is 1.11011011101.... * 2^-9. All those bits near the
leading left side spells an easy round up to 2^-8, which is your
1/256. Good call.

It's better than you give here, though. The expression is also just
KM*( (5*32 - 1)/256 ). This codes up as:

((KM + ((KM - (KM >> 5)) >> 2) + 1) >> 1)
\11 bits/

\----16 bits---/

\-------14 bits-------/

\--------------17 bits-----------/

\----------------16 bits----------------/

So 16 bits + carry are enough.

Jon

10. ### Tony WilliamsGuest

[/QUOTE]
Big software improvement. I didn't even see it. Thanks.

11. ### Ben JacksonGuest

Other people covered a bunch of fixed point techniques for you. I'll
just provide a link to some macros I wrote:

http://www.ben.com/vcc/18fdivmul.zip

which generate arbitrary bit width multiply/divide functions for PIC18,
in case you need to do something that really does multiply.

12. ### JamieGuest

convert it all to 32 bits.
and fractions you have will become wholes.

use the standard ADD SUB Binary Divides etc,.
when done. you simply generate an ASCII string
with the decimal point in the correct place.

13. ### MooseFETGuest

Or:

If you need to multiply it by (1+N/256):

X*(1+N/256) = X + (X*N)/256

This is a very handy way to get a quicker answer than a 32 bit library
would do.

It is often still worth it even if you have to code:

Y = X + (N * (X + (X*M)/256))/256

14. ### mook JohnsonGuest

Great responses guys.

Thanks.

15. ### John BarrettGuest

Isnt your GPS module capable of NMEA output ?? NMEA is a full text format,
fixed field width, newline delimited.... Just find the command to tell the
GPS to output NMEA and then decode the string and get preformatted text 16. ### Mook JohnsonGuest

Not this one. Its a Furuno GH81. Tiny little sucker at .8" x .8" but it
has its own binary protocol.
Talk about a PITA. It outputs words in 7 bit bytes with the msb set to 1.
I have to concatinate the 7 bits in each byte (eliminating the 8th bit in
each byte) to get the a 14 bit word of actual data.

I'd never seen that before and thought my unit was broken when I tried to do
a straight conversion.  