PIC18F4520 math in assembly

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?

Reply to
Mook Johnson
Loading thread data ...

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

Reply to
Brian

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

Reply to
richard.mullens

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.

Reply to
Spehro Pefhany

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

Reply to
Jonathan Kirwan

I mean more accuracy, here. Sorry.

Jon

Reply to
Jonathan Kirwan

Nice writeup.

I usually start by seeing what arithmetic routines are already linked 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.

Reply to
Terran Melconian

In message , Mook Johnson writes

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.

--
Tony Williams
Reply to
Tony Williams

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

Reply to
Jonathan Kirwan

Big software improvement. I didn't even see it. Thanks.

--
Tony Williams.
Reply to
Tony Williams

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

--
Ben Jackson AD7GD

http://www.ben.com/
Reply to
Ben Jackson

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.

--
"I\'m never wrong, once i thought i was, but was mistaken"
Real Programmers Do things like this.
http://webpages.charter.net/jamie_5
Reply to
Jamie

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

Reply to
MooseFET

Great responses guys.

Thanks.

Reply to
mook Johnson

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 :)

Reply to
John Barrett

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.

Reply to
Mook Johnson

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.