One of the main applications for the Arduino board is measuring. Often the raw measurements e.g. from the analogRead() function must be converted to get a physical meaning. This convertion function is often linear but can also be more complex. In fact it can even be no function: meaning that a measured value can have two meanings. This is the case with the - SHARP 2Y0A02 F 9Y - distance sensor. Almost every output voltage can mean 2 different distances [graph]. Fortunely only the part from 20-150 cm is the "working range", so there is a function. In practice - using real measured points from the sensor - this function is not approximatable accurate enough with one mathematical function. I found the reMap() function on the playground http://interface.khm.de/index.php/lab/experiments/nonlinear-mapping/ but I wrote a new version to optimize it for speed and to make it a bit more robust.
The code:
{
// take care the value is within range
// val = constrain(val, _in[0], _in[size-1]);
if (val <= _in[0]) return _out[0];
if (val >= _in[size-1]) return _out[size-1];
// search right interval
uint8_t pos = 1; // _in[0] allready tested
while(val > _in[pos]) pos++;
// this will handle all exact "points" in the _in array
if (val == _in[pos]) return _out[pos];
// interpolate in the right segment for the rest
return map(val, _in[pos-1], _in[pos], _out[pos-1], _out[pos]);
}
code for floats see at the end.
As I only needed integers the function returns an int. The parameters are the int val which comes from the analogRead() function, an array of input values and an array of corresponding output values and a parameter to indicate the size of the array's used. Note that the arrays need to be equal in length (at least the part used). Furthermore the input array must be a monotone increasing array of values.
First the input value is constrained to the input range, so we can do a search for the right interpolation segment (while loop). If the input value equals the lower bound of the array (index 0), the mapping in the last line cannot be one. So its handled separately. In fact this check is expanded to all known exact values. Finally a call to the well known map() function is made to do a linear interpolation in the right segment.
In a small test with an input array of 14 elements, 10.000 worst case calls took 1003 millis, 10.000 best case calls took 358 millis so on average 1361/2 = 680 micros per call. This performance is most affected by the size of the array it has to search, so the general advice is keep the array as small as possible. Note these numbers are only indicative.
Some snippets shows how multiMap() can be used:
// out[] holds the values wanted in cm
int out[] = {150,140,130,120,110,100, 90, 80, 70, 60, 50, 40, 30, 20};
// in[] holds the measured analogRead() values for defined distances
int in[] = { 90, 97,105,113,124,134,147,164,185,218,255,317,408,506};
val = analogRead(A0);
cm = multiMap(val, in, out, 14);
// Some "sinus" approximation
int out[] = {0,316,601,827,972,1023,972,827,601,316,0,-316,-601,-827,-972,-1023,-972,-827,-601,-316, 0 }; // 21
int in[] = {0,50,100,150,200,250,300,350,400,450,500,550,600,650,700,750,800,850,900,950,1000};
val = analogRead(A0); // connect a potmeter?
x = multiMap(val, in, out, 21);
// A normal distribution
int out[] = { 0, 5, 20, 50, 80, 95, 100, 95, 80, 50, 20, 5, 0 }; // 13
int in[] = {0,80,160,240,320,400,480,560,640,720,800,880,960};
val = analogRead(A0);
x = multiMap(val, in, out, 13);
y = multiMap(val/2, in , out, 7); // using only left halve of the array.
// S curve
int s[] = { 0, 0, 10, 30, 70, 130, 180, 320, 350, 370, 380}; // 11
int in[] = { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
val = analogRead(A0)/10;
x = multiMap(val, in, s, 11);
// trapezium
int trapx[] = { 0, 100, 100, 0}; // 4
int trapy[] = { 0, 100, 200, 0}; // 4
int trapz[] = { 0, 400, 200, 0}; // 4
int in[] = { 0, 100, 700, 1000};
val = analogRead(A0);
x = multiMap(val, in, trapx, 4);
y = multiMap(val, in, trapy, 4);
z = multiMap(x, in, trapx, 4); // can you see what it does?
The multiMap function is not completely optimized, see TODO section. Note the parameter: - size - can be a smaller as the size of the array, in the normal distribution snippet it uses only half the array for the y value. I considdered using a 2 dimensional array as that guarantees the arrays have equal length, but it would prevent reuse of arrays like in the trapezium snippet. Think an input array {0 - 1023} in ten steps would be very reusable.
Enjoy tinkering,
rob.tillaart@removethisgmail.com
{
// take care the value is within range
// val = constrain(val, _in[0], _in[size-1]);
if (val <= _in[0]) return _out[0];
if (val >= _in[size-1]) return _out[size-1];
// search right interval
uint8_t pos = 1; // _in[0] allready tested
while(val > _in[pos]) pos++;
// this will handle all exact "points" in the _in array
if (val == _in[pos]) return _out[pos];
// interpolate in the right segment for the rest
return (val - _in[pos-1]) * (_out[pos] - _out[pos-1]) / (_in[pos] - _in[pos-1]) + _out[pos-1];
}
See discussion on forum thread