Creating a map subroutine

oracacle

Senior Member
I have been doing a fair bit of C programming, and using the map function, somemthing that is missing from the picaxe system.
So i thought it will work in interger maths and breaking ti down into its seperate parts to correct for order of operations should allow it to work on the picaxe.
Well it does, providing you keep the intermedarey result (in this case stored in W2) below the 65535) mark you should be fine. It needs work, and knowing that there are some pretty clever folks here I suspect that the upper limit of what can be calulated can be increased.

I put here as its not really a code snipet yet, it needs work an i ma sure it can be steamlined to use less variables, but even its current form mamybe usfeul for some one

Code:
symbol val = w0
symbol in_min = 0
symbol in_max = 1023
symbol out_min = 0
symbol out_max = 180

symbol out_val = out_max-out_min    'pre calc out_val
symbol in_val = in_max-in_min        'pre cal in_val

'map (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min

map:
    w1 = val - in_min
    w2 = w1 * out_val        ',ust not exceed 65535 or overflow will happen
    'in this case min out val is 0 so 65535/in_max gives 64, so val should not exceed 64
    w3 = w2 / in_val + out_min 'w3 contains the output
    pause 50        'pause to follow what going on
    'section for incrementing and checking for overflow of val   
    val = val + 1
    if val > 63 then
        val = 0
    end if
    goto map
 

oracacle

Senior Member
I have done a little more workon this, cut the variable down to 2 and just 2 lines of code in a subroutine. You mamybe able to make it into a macro as its quite small.
I dont have a servo to test this with, simulation seems to show it works, but would br grateful if somemone could test it.
Code:
'testing mapping adc rading to servopos values

symbol val             = b0        'variable to recieve pot value
symbol map_val         = b1        'using word just incase, you may well be able to use a byte
symbol stored_servo     = b2        'stored mapped val to check for change

symbol in_min = 0            'min adc reading
symbol in_max = 255        'max adc reading

symbol out_min = 75        'servo min position
symbol out_max = 225        'servo max position
'these values can be with in word variable range providing overflow is avoided
'they can also be used as somftware limits without the need for an if statemnt
'using the figures 80 for min and 150 for max will mean taht full movement of the
'protentiometer will move the servo between those two positions

symbol out_val = out_max-out_min    'pre calc out_val
symbol in_val = in_max-in_min        'pre cal in_val

'map_val =  (val - in_min) * (out_max - out_min) / (in_max - in_min) + out_min

init:
    servo 0, out_min

main:
    readadc 0, val
    gosub map
    'now do stuff with mapped value
    if map_val <> stored_servo then        'check to see if pot has moved and update if needed
        servopos 0, map_val
        stored_servo = map_val
    endif
    goto main

map:
    map_val = val - in_min
    map_val = map_val * out_val / in_val + out_min        'map_val * out_val result must not overflow 65535
    return
 

Jeff Haas

Senior Member
Hi, I tested this, and it works well. I used an 08M2 so the code needed a few tweaks.

I added this at the top:
Code:
' Connections
SYMBOL  servopin   = C.2 
SYMBOL  potpin     = C.4        ' *** Originally was pin 0, which does not support ADC on 08M2
That led to a couple of other adjustments, indicated by the ***. I added a debug statement - watching the values change on the computer makes understanding things much easier.
Code:
init:
    servo servopin, out_min      ' *** Changed pin number to symbol

main:
    readadc potpin, val    ' *** Changed pin number to symbol
    gosub map
    'now do stuff with mapped value
    if map_val <> stored_servo then        'check to see if pot has moved and update if needed
        servopos servopin, map_val       '*** Changed pin number to symbol
        stored_servo = map_val
    endif
    debug       ' *** Display values on computer screen
    goto main
 

Jeff Haas

Senior Member
Just after posting that I found this older discussion of the same programming problem:
 

oracacle

Senior Member
thanks for testing that> I did have a look around for something similar, but evidentially not well enough.
Both cases should work with PWM as well which is nice.
 

AllyCat

Senior Member
Hi,

Sometimes a "picture" can be helpful, for example the "Transfer Characteristic" Graph here. Apologies for the "ASCII Art" but it's easier to edit than using a separate drawing App.

The graph (diagonal line) shows how an "input" value on the horizontal base line (the X-axis) is to be converted to an "output" value on the vertical scale (the Y-axis). Here, the (straight) line starts at 75 for the "zero" input value and ends at 225 corresponding to the highest input (X) value. The highest input value might be 255 or 256 depending whether a byte or word value is being used (setting a byte to 256 makes it overflow and read as zero). For example to "map" the input (X) value of 128, we look for 128 along the X-axis and move exactly vertically until we reach the line, then exactly horizontally to read from the Y-axis, i.e. a value of 150 here.
Code:
   Y
  /|\          
   |           !
225|-----------@225 \
   |         / !    )
   |       /   !    )
150|<----/     |    } 225-75=150
   |   / :     !    )
   | /   :     !    )
75 @-----:-----+    /
   |     :     !
   |     :     !    
 0 O+++++#+++++#++++> X
   0    128   256
A way to "simplify" the graph is to move the graph line downwards (or sideways, or upwards) until it passes through the "origin" (where X and Y are both zero). To do this here we must subtract 75, which gives a clue that at the end of the calculation we should add the 75 back in. Now, we can calculate the gradient of the line, i.e. it rises by a value of 150 as it moves across the 256 points of the X-axis, so the gradient is 150/256. To calculate the result, we multiply the input value by the gradient and add the offset that was removed, so the calculation becomes b0 = b1 * 150 / 256 + 75 . If we're working with larger numbers then we can reduce the 150 / 256 to 75 / 128 or maybe use 255 to reduce to 30 / 51, but there is still a severe risk that the intermediate calculation will overflow a word value of 65535.

However, the X2 chips (only) have the "*/" operator which performs a multiplication but then "automatically" divides by 256, so we can reduce the "Subroutine or Macro" to b0 = b1 */ 150 + 75 . The M2s (and X2s) have the "**" operator, which may seem more confusing because it multiplies and then automatically divides by 65536, but this can be very powerful. Since it divides by a value that is 256 times too large for our application here, we need to multiply by 256 to compensate, so in an M2 we could write b0 = b1 * 150 ** 256 + 75 , or better, pre-calculate the known (fixed) part of the multiplication to become w0 = w1 ** 38400 + 75 . This has two advantages; firstly, because it divides by 65536, it can never overflow, regardless of how large are the two word values. Also, we can have very "fine" control of the gradient, because it could be 38399 (/65536) or 38401, etc. However, since it divides by 65536 we must be careful to use it only with "large" numbers, or we will just end up with zero(s).

Things can get more "interesting" if we consider several of these "graphs" joined side-by-side, for example four to give a range of X values from zero to 1023 (or 1024). The X-range can be calculated (or "scaled") in many ways, but in particular it's the direct output from a READAC10 command, for example from a Pot. Then we can use the High byte of the Input Word to select which of the four sections of the larger graph to read. For example a section (numbered 0) to the left of the above map (number 1) could be a line from the origin (X=0, Y=0) to Y = 75 (i.e. a gradient = 75 / 256) to link to the original line (gradient = 150 / 256) and then the next section could have a gradient of 300 / 256 to reach a Y-level of 300 + 225 = 525 , and a final section could continue to 1023, with a gradient of 498 / 256 . That gives a "logarithmic" transfer characteristic, which might give a better subjective effect with a potentiometer controlling for example PWM for LED brightness, or as an Audio Volume control (Log or Decibel "Law").

It's not necessary to write separate Subroutines or Macros for each "section", a single routine can use a Lookup command or a Table. For example READADC10 w1 : LOOKUP b3 , (0 , 75 , 225 , 525 , 1024) , WYleft for the starting level of the line. The ending level could be read after incrementing b3, or from a second LOOKUP b3 , (75 , 225 , 525 , 1024) , WYright . Calculate the gradient as WYright - WYleft (/256) and the "Output" value becomes W0 = b2 */ gradient + WYleft . For a large number of "sections" (perhaps 8+) a READ or READTABLE structure is faster and more efficient. But unlike the LOOKUP, a Table can contain only Bytes, not Words, so WYleft and WYright each must be read from separate bytes. A useful "trick" is that (because only the low byte of any word is stored), we can convert the above Lookup to a TABLE (0 , 0 , 75 , 0 , 225 , 0 , 525 , 2 , 1023 , 3) where the bytes alternate as Low , High , Low , High , etc., but we easily can still see the content. However, we must multiply b3 by 2 before READing the first byte, and then (auto-) increment for the next 3 bytes, for example: READTABLE b3 , WYLeftLo , WYLeftHi, WYRightLo , WYRightHi .

Finally a quick "advert" that this method is further expanded in my CODE SNIPPETs to provide "advanced" mathematical functions such as Sine, Cosine, ArcTan, Log and Exponential, etc., at a higher resolution than is built into the X2. Obviously much more complex Program code, but it uses only 4 variables, three of which are Words, because it can span almost the full range of Word values.

Cheers, Alan.
 
Top