Linear Interpolation and Mapping

Hello all.

In the following code I am trying to interpolate a value from 0 to 300 in 1 second. However, if I print the mapping and interpolation values do not seem to ramp from a value to the other, only the start and the end values.

Can someone have a look and let me know what I am doing wrong?

Thanks

float oldValue;
float finalValue;
float counterTime;
float counter;
float percentage;
float interpolate;
float interpolateTime = 1000;

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  finalValue = 300;
  if (finalValue != oldValue) counterTime = interpolateTime;
  if (counterTime != 0) counter++;
  if (counter > counterTime) counter = counterTime;
  percentage = map (counter, 0, interpolateTime, 0., 1.);
  interpolate = lerp (oldValue, finalValue, percentage);
  if (percentage == 1.) oldValue = finalValue;
  counterTime = 0;  

  Serial.println(percentage);
  Serial.println(interpolate);
}

float lerp(float a, float b, float x)
{ 
  return a + x * (b - a);
}

Question, what is it supposed to look like and why is finalValue = 300; within the loop?

Does percentage give you the correct values? Adding comments helps too.

Is there a reason the two variables involved are floats? One is a counter and the other is equal to 1000. Could they be ints?

From http://arduino.cc/en/Reference/Map

The map() function uses integer math so will not generate fractions, when the math might indicate that it should do so. Fractional remainders are truncated, and are not rounded or averaged.

I'm not sure map is going to work right with floats.

EDIT: It definitely won't work here because you are mapping onto the range 0 to 1. So the only possible answers map can give you are 0 and 1. Map returns a long int, so it cannot possibly give you any fractional values.

And oldValue is only set in one statement.

My apologies if the code was not clear, but I thought it was straightforward.
Here I post it again, I hope the issue I want to solve makes sense now to you too.

//oldValue is going to store one value before the current
int oldValue = 0;
//Current value (i.e. new incoming number)
int newValue = 100;
//Counter to hit the limit
int counter = 0;
//counterTime checks to see if counter hit the limit
float counterTime = 0;
//Time Limit
int interpolateTime = 1000;
//Percentage goes from 0. to 1. When it hits 1. the interpolation is
//finished. The time is according to interpolateTime
float percentage = 0;
//Variable to calculate the interpolation between oldValue and newValue
float interpolate = 0;

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  //If there is a new value present, assign interpolation time
  //and start the timer
  if (newValue != oldValue) counterTime = interpolateTime;
  if (counterTime != 0) counter++;
  
  //When the timer starts it goes from 0 to interpolationTime (1000)
  //Map this ramp from 0 to 1, and assign it to percentage variable
  percentage = map (counter, 0, interpolateTime, 0., 1.);
  
  //Interpolation function has to go from the old to the new value
  //in the ramp that is used in percentage
  //This works fine in Processing for example that has its own lerp function
  //I'm not sure if this one is correct too
  interpolate = lerp (oldValue, newValue, percentage);
  
  //Reset time
  //if (counter > counterTime) counter = counterTime;
  
  //If percentage reaches 1, assign the new value to old, and reset timer
  if (percentage == 1.) {
    oldValue = newValue;
    counterTime = 0;  
  }
  
  Serial.println("Percentage: ");
  Serial.println(percentage);
  Serial.println("Interpolation: ");
  Serial.println(interpolate);
}

float lerp(float a, float b, float x)
{ 
  return a + x * (b - a);
}

percentage = map (counter, 0, interpolateTime, 0., 1.);

This will never give you what you want, unless you maybe make it an int, with reasonable values, not 0.0 / 1.0 and then cast it as a float.

newValue != oldValue

When do newValue and oldValue ever change?

Louis, I have to wonder what you are trying to do. It looks like every time through loop() you add 1 to counter and then I’m not sure but it involves scaling.

Do you know and understand fixed-point math as opposed to floating-point math?
How are you with the metric system?

HazardsMind:

percentage = map (counter, 0, interpolateTime, 0., 1.);

This will never give you what you want, unless you maybe make it an int, with reasonable values, not 0.0 / 1.0 and then cast it as a float.

newValue != oldValue

When do newValue and oldValue ever change?

HazardsMind, isn't this the normal function of map? What's the difference of using int and cast it later? I don't get it.

newValue and oldValue they do change here:

//If percentage reaches 1, assign the new value to old, and reset timer if (percentage == 1.) { oldValue = newValue; counterTime = 0; }

GoForSmoke: Louis, I have to wonder what you are trying to do. It looks like every time through loop() you add 1 to counter and then I'm not sure but it involves scaling.

Do you know and understand fixed-point math as opposed to floating-point math? How are you with the metric system?

GoForSmoke the counter counts for a second and then goes back to 0. If a new value arrives it will start again.

I don't understand what you mean concerning fixed/floating-point math.

Fixed point math......

If 1 meter is 1000 millimeters then 300 millimeters is .3 meters. Your code works with 300 and when you print, you set the decimal point if the label is meters or not if it's millimeters. If you want meters to 6 places then work in micrometers, millionths of a meter.

It's like counting pennies instead of dollars.

Look it up. AVR's do not have Floating Point Units so floats are slooooooooowwwwwwww. And they are 32-bit whether you use float or double, only good to 6 or 7 places.

You can also use fractions and remainders to generate decimals if you have math skills.

Thank you GoForSmoke for the short math tutorial. Again, Im not sure where all these things relate to this post. First of all I have a value of 0 and a second value of 100. Based on my counter, I want to interpolate between these two values. The code I have presented I believe is correct. Up to the point I have to use the map and interpolate functions, which seem that they do not do what they supposed to do (that is run from 0 to 100 - 0,1,2,3... in a specific timeframe I have requested i.e. 1000 milliseconds). I can understand though the problem with floats, although this is not an optimized code.

Delta_G told you why your map function won't work. The map function returns a long int not a float. So what you will need to do is change it to may be 0 - 10, cast the mapped data as a float, divide it by 10.0 and then you will have your range.

And no, only oldValue changes, once, then the condition is false and it does not go into it again.

Thanks for the suggestions. Now it works. I was carried away with Processing that uses a different map function. :)

If you want to map a number between 0 and 1 using a function that returns an INTEGER then the way you do it is to map 0 to 1000, use that and divide your results by 1000 simply by printing the decimal point before the last 3 digits. Or 0 to 100 for 2 places, 0 to 1000000 for 6 places, etc.

If you 'got' the math lesson then you would have applied it to fit your needs, not questioned it.