Connect with us

PIC18F4520 math in assembly

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

Scroll to continue with content
  1. Mook Johnson

    Mook Johnson Guest

    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. Brian

    Brian Guest

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

    x * -------

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

    x * -------

    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:

    x * -----

    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,

    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.

  6. I mean more accuracy, here. Sorry.

  7. 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.
  8. 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. 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.

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

    Ben Jackson Guest

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

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

    Jamie Guest

    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. MooseFET

    MooseFET Guest


    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 Johnson

    mook Johnson Guest

    Great responses guys.

  15. John Barrett

    John Barrett Guest

    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 Johnson

    Mook Johnson Guest

    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.
Ask a Question
Want to reply to this thread or ask your own question?
You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.
Electronics Point Logo
Continue to site
Quote of the day