From linear to exponential PWM output

New excel file added. :slight_smile:
Both formula options are used from Rob and Techyla

Paco

exponential formula Rob Tillaart and Techyla corrected.xls (56 KB)

Looking at a curve in Excel isn't going to tell you how it actually "feels" in practice. Instead of meddling with Excel why not just implement the equation I gave and see how it goes? Once you have a feel for how it really responds you can decide whether to leave it as it is or adjust it.

Replace this:

>>>>>>>> speedcurveValue = speedvalueWorked with the quotation to calculate the exponential power curve // added speedcurve value

with this:

// Pick one or the other version of the curve
// This is the "negative" curve
speedcurveValue = round(speedvalueWorked * speedvalueWorked / 255.0);
// This is the "positive" curve
// speedcurveValue = round(sqrt(speedvalueWorked * 255.0));

Pete

The excel formulas seems similar to 6 digits for almost all values of n, so you might check which one is most time / size efficient.

And yes, follow Pete's advice as "the proof is in eating the pudding"

Hi Pete and Rob

I will use the code dont worry.
As getting the formula correct and I like to learn what I am doing (not just copy and paste) I did the homework first in Excel.
Learned today again some valuable stuff in Arduino and Excel world.

I whished the slot car world would be that easy that one fixed curve would do the trick.
All cars and motors behave different on the tracks and then there is also different lanes that require different feel (set up).
So ONE fixed curve positive or negative is a no go.... :slight_smile:

The powercurve should be adjustable (currently is) by push buttons + and - for direct setting on the control handle or through menu structure by display.
The MEGA powered project is still prototype as the power board is still under construction so no real life testing with a motor or at the track.
I currently use two LED's for the PWM output (speed and brake) to see what happens while operating the trigger.
The speedcurveValue now is adjustable from 0 to125 and BYTE declared.
As I need both sides of the curves I have to switch formulas at the 1 value point.
if speedcurveValue = 0.1 to 0.99 then formula positive
if speedcurveValue = 1 to 2 then formula negative
speedcurveValue was initially set up as BYTE length but I guess I have to change all after this line and declare to FLOAT?

Paco

I hope I was helpful. Glad to help if I was.

I'm thinking of something totally new and better for these slot cars. I know only the kid controllers I used 50 years ago!
I do know programming and have linearized many processes in the prepress world.

I think I'm seeing what you want to accomplish - a perfectly linear (with speed) hand controller for forward and reverse.
Since you're going to implement a lookup table anyway for speed and flexibility, let the lookup table do the whole thing, both sides of the curve and the dead zone,
say values 121 - 135, (16 values centered on 128) are zero.

Then for both the forward and the reverse you take actual speed readings for say 10 forward and 10 reverse controller settings.
You "invert" the response curves, either mathematically or graphically, and enter those values into the lookup table, interpolating the inbetweens.

Say your top speed is measured at 50 mph at full, 255 throttle. 135 is to be 0 (top of dead zone). That means you want your controller to have 120 steps from 0 to 50 mph.
The exact middle settting, 60 + 135 = 195 should cause 25 mph. You look at your response curve (draw a hor line) and see that 25 mph actually happened at
controller position 100. Therefore you store a 100 in lookuptable[195]. lookuptable is an array of 255 bytes.
So far lookuptable of 135 is 0, 195 is 100, and 255 is 255. You do this for all 120 positive speeds and all 120 reverse speeds.
You now have your table. No on-the-fly calculations; just near instant table lookups. Outside the dead zone, speed is perfectly linear with throttle position.
(Your code makes sure to switch the voltage direction for values below 121).

This brings up an interesting question about slot car racing. Do people indeed want and implement a "velocity pedal", rather than an "acceleration pedal".
If so, is that only because it is hard to get the current velocity, which is what would be needed for a true accelerator pedal like in a car?
If you could read back velocity (wheel speed) and implement an accelerator in the mathemeematic sense of the term, would that be better or worse for slot car racing.
In other words, if you push the throttle position forward a little, the velocity starts constantly increasing at a slow constant rate. Push it further and the rat of speedup increases, like in a real car.
The above described lookuptable implements a perfect, calibrated velocity pedal.

Hello Techlya,
Thank you very much for thinking with me and the project.
You can find the project here:
http://arduino.cc/forum/index.php/topic,93528.0.html
I like to take care of some misunderstandings ?.
Slotcars only run one direction in the slot and that is forward which is typical for these cars.
There is no feedback coming what so ever from the car or track except what the driver see is happening while driving with his eyes. So this is and stays interactive control.
There is no reverse speed or do you mean this as BRAKING?
With RC modelcar transmitters the trigger has forward and brake movement for both side of 35 degrees where there is a real mechanical zero point for both directions. With slotcar race controllers they do not have a zero point like RC controllers but only 0 to 100 travel where around zero the brake is activated (deathbandzone).
The reaction speed slotracers need is much higher as RC racers so if the reverse part of the trigger is used as linear braking then this is taking too much time and response action.
I try to make a slot race controller that is capable of many settings.
Only linear response will not work on all tracks and cars. So there come the extra settings like startspeed and exponential curve in play.
Please let me know if you understand my direction.
If not let me know
Slotcars have evolved since the late 60’s ? and electronic controllers are the way to go.

Regards, Paco

Ok Paco, tnx for the brief education. I looked up slot car response curves and found a site that graphically presents what I think you describe.
http://www.jaygeeracing.com/SOFTouch_Trigger_Response.html

To implement that you need just two parameters to fill your lookup table. No dead zone; the curve takes care of that.

parameter 1 - Base level bias - The voltage percentage you want to have even at trigger level 0. (their softtouch gets as high as 40)
parameter 2 - Curvature - How much you want to curve the response, to give you increased control meaning low trigger sensitivity at low trigger levels, with the reverse at high trigger levels.

Whenever the user changes either of these parameters you calculate a new lookup table:
(Assume user sets Bias to be 0-255 and Curvature to be 0-255 (let max 255 mean a MaxGamma of 4)

float gamma = Curvature * (MaxGamma - 1.0) / 255.0 + 1.0;
for (i=0; i<256; i++)
table = (int) ( 0.5 + Bias + pow(i/255.0, gamma) * (255.0 - Bias));
When Curvature is 0, you get no curvature. When it is 255 you get the max curvature of 4.
To see the curve part, consider gamma to be 2.
Your table value for 50% would be the value for 1/2 squared (2nd power), or a value for 1/4 or 25%.
The actual table value is the bias value plus 1/4 of the remaining way from the bias to full.
Say the bias is 75. (table[0] = 75 ....)
table[127] is the value you want for a halfway trigger push.
That comes to 75 + 1/4 * 180.
So table[127] = 120
Is this what you are looking for?

Techylah,

Appreciate your guidance effort highly.

If you see what they try to achieve at JayGee they exchange probably eeproms with software curves.
Why would you do that if you just can do it by software and the arduino. :slight_smile:
This way you can make ONE universal controller. 24 bands/steps for the jayGee or 255 steps for our version :cold_sweat:.
And later no contact vurnable sensors to be used.

Some more explanation.
The trigger I use is currently coupled to a potentiometer which has some play around the zero point.
The whole trigger movement is around 45 degrees from 0 to full.
The raw value of the sensor is mapped from 0 to 255.
With this base value we go further and control deathband and speed.

If the trigger is at the mechanical 0 position due to the play the sensor value can be higher as 0.
In real world the sensor value can vary around the zero point upto 5.
So what I have done is create a deathband which is user adjustable from 0 to 15.
So if the trigger is in zero/deathband position there is no activity possible at the speed output.
In this zero/deathband zone the brakes are always activated and the PWM break output is adjustable from 0 to 255 (0 to 100%).

As soon as the trigger leaves the zero/deathband zone the PWM speed output is activated and the brakes switched OFF.
From this point on the value is mapped from 0 to 255 and linear.
If we add speedstart then the pwm starts at a higher point and not at 0.
So if speedstart is set at 45 then the trigger regulates the output from 45 to 255 in linear fashion.

If the speedstart is set to 0 and we like to curve the linear line we call this speedcurve and this is the exponential curve we like to create.
This will run then from 0 to 255.
The speedcurve should be able to be adjusted upper or lower then the linear line.
If we add speedstart and speedcurve then from 45 to 255 the curve should be exponential.
See the previous graphs I added to the responses.

I assume your parameter 1 Bias is what I call startspeed(speedstart) and parameter 2 Curvature the speedcurve.
The softtouch of the JayGee controller is a curve with startspeed intergrated.
I like to see them both as seperate adjustable parameters.

Do not fully understand the purpose of the look up table and the benefit of it.
I know that is my problem due to the lack of knowledge.
Does my code do the same or is the look up table an easier way to achieve the same?
Is not using a look up table slowing down the controller reaction.
Do I see it wrong but 16 Mhz should be enough with the code I have or is calculating slowing down the reaction of the trigger compared to the speed dramaticly. Are we talking about micro seconds or milliseconds delays?

Please let me know your valuable opinion.

Current code... for the speed brake section.
Not optimized just for the knowledge I have gained sofar.

//     +++++++++++++++ regular speed - brake mode +++++++++++++++
      speedstartValue= constrain (speedstartValue,0,255);
      speedcurveValue= constrain (speedcurveValue,0,255);
      brakeValue= constrain (brakeValue,0,255);
      modelNumber= constrain (modelNumber,1,10);
      
      // control the outputs with output cross block function to prevent speed and brake to be ON together 
      sensorValue = analogRead(sensorspeedPin); // read the raw value from the linear hall sensor/potentiometer:
      sensorValue = map(sensorValue, sensorMin, sensorMax, 0, 255);// apply the calibration to the sensor reading from the hall sensor/potentiometer
      // sensorMin and sensorMax are stored in the EEPROM during calibration.
      sensormappedValue = constrain (sensorValue,0,255); // keep value in range
      speedValue = map(sensormappedValue, 0, 255, speedstartValue, 255); //speedstart added to the speed
      //speedcurveValue = speedvalue with the quotation to calculate the exponential power curve // added speedcurve value
      //speedfinalValue = speedcurveValue
      if (speedfinalValue > 250) {speedfinalValue= 255;} //make sure we reache full speed due to play.
      speedfinalValue = speedValue;
      
      if (sensormappedValue > deathbandValue){analogWrite(pwmoutspeedPin, speedfinalValue); analogWrite(pwmoutbrakePin, 0); digitalWrite(led13Pin, LOW);} 
       else  
      {analogWrite(pwmoutspeedPin, 0); (speedfinalValue, 0);analogWrite(pwmoutbrakePin, brakeValue);digitalWrite (led13Pin,HIGH);} // startspeed and curve included
      
      if (speedfinalValue < speedfinalValueold){analogWrite(pwmoutspeedPin, 0); (speedfinalValue, 0);analogWrite(pwmoutbrakePin, brakeValue);digitalWrite (led30Pin,HIGH);} // regenerative breaking
       else
      {digitalWrite(led30Pin, LOW);}

Very good. The curve should be added before the speedstart is added.

Change:

speedValue = map(sensormappedValue, 0, 255, speedstartValue, 255); //speedstart added to the speed

to
speedValue = (int) ( 0.5 + speedstartValue + pow(sensormappedValue/255.0, gamma) * (255.0 - speedstartValue));

If gamma is 1.0 it should operate the same as the unchanged version.
When gamma is greater than 1.0, you get the Jaygee effect.
If you make gamma < 1.0, say .5 or .33, you curve the opposite way. I'm not sure why you'd want that.

If you do, you can still combine both curves into one, with precise control at low end and at the high end, but you quickly adjust with small trigger movements in the middle speeds.

The reason for the lookuptable is just for speed. A simple lookup is much faster than floating point math, especially exponential (pow) function.
You should verify this by seeing how long it takes to do 1000 lookups (for the same or different values) vs a loop to do 1000 of the "pow" version.
Just make sure you don't put any serial.print stuff in the loop, only at the end to subtract the millis times and print that out.

They didn't have or know about Arduinos! Yours indeed will be better, more flexible, and use a simple pot!

I said

If you make gamma < 1.0, say .5 or .33, you curve the opposite way. I'm not sure why you'd want that.

Now that I think about it, I see why you might want that. You want the last 25% of controller range also to be "meaningful".

I think you might be breaking new ground (in the slot car world) with this feature set.

If you do indeed want to do it you'd do something like:
Instead of

speedValue = (int) ( 0.5 + speedstartValue + pow(sensormappedValue/255.0, gamma) * (255.0 - speedstartValue));

if (sensormappedValue < 128)
speedValue = (int) ( 0.5 + speedstartValue + pow(sensormappedValue/127.0, gamma) * ((255.0 - speedstartValue) / 2.0) );
else
speedValue = (int) (0.5 + 255.0 - pow((255 - sensormappedValue)/127.0, gamma) * ((255.0 - speedstartValue) / 2.0));

(The two extra subtractions in the bottom line make the curve symmetric around the middle, thus it curves the opposite way the lower half does. It smoothly approaches maximum speedValue exactly the same way as the bottom part smoothly leaves the speedstartValue)

This might be way cool. You might even allow a different gamma for each half of the curve.

Techylah,

Your quicker with thinking then I can respond.
Yes the positive curve makes more use of the part where the power really counts when a car is already in motion.
And if we do not need it as the track, car or what ever situation is not requiring it, do not use it.
But it is always on hand and no need to add parts or any other mechanical adjustments.
Slow tracks with lot of corners might use negative powercurve and if you have a wider open track use the positive curve.
Being flexible is the key point to this controller, no contacts, no limits.

The arduino is just the interface between the trigger and the car in the slot.
If we can influence parameters by software as much as possible why not do it.
And the best of it all by display, bluetoothed PC or Smartphone you can see what your settings are.
Save them and reuse them later if needed.
No more guessing at some turn knobs with maximum 10 settings and fiddling around to find the right ones.

Paco

Yes. It's like the older phones that had hardwired tones or bells. Once it became all software, ringtone variety went wild.

I see it. You lend a newbie friend your controller, but first you download the beginner curves to it until he (she's) ready for more.
You then tweak your own curves to get increased control where you want, all the while seeing the "S" shaped response curve change instantaneously on your smart phone or a little LCD panel.

There will be a better feel if you always use the pre-computed byte[256] lookup table and never do floating point calculations during operation.
What the users changes, edits, and saves/restores can be just the parameters.
You might divide the trigger range into 3 sections: curved low end, midrange linear, and curved high end.
The locations of these might default to 25% and 75% but those would be user-changeable parameters, as would be the low end and high end gamma factors and speedStart.

It's nice that PWM and speedStartValue takes care of your not having to modify your curves just to handle startup inertia and stuff.

By the way, Paco, I didn't put speedfinalValue into my equations.
Here it is:

if (sensormappedValue < 128)
speedValue = (int) ( 0.5 + speedstartValue + pow(sensormappedValue/127.0, gammaStart) * ((speedfinalValue - speedstartValue) / 2.0) );
else
speedValue = (int) (0.5 + speedfinalValue - pow((255 - sensormappedValue)/127.0, gammaFinal) * ((speedfinalValue - speedstartValue) / 2.0));

Leave the 255 there; it relates to input obviously.
Also both gammaStart and gammaFinal are always >= 1. Start them both at 2.0.

When you're ready to make it 3 sections as I described, the above remain the same except that there will be two tests on sensormappedValue.
If the sensormappedValue (mislabeled and now corrected in my previous post!) is not within either extreme range, it is in the middle linear region and there you can use the map function.

If for the 0-255 sensor range say you want the linear mid region to be 75 thru 175,
you would use the upper equation to compute speedValueMidStart for sensormappedValue = 75 and
use the lower equation to compute speedValueMidEnd for sensormappedValue = 175
Then for the midrange,
speedValue = map (sensormappedValue, 75, 175, speedValueMidStart, speedValueMidEnd);

Is this what your trying to achieve?
If yes I am with you.... :slight_smile:

Paco

I think we agree, Backbone, but with inflections of the curves!

I assumed you wanted more precise control at the extremes, so my gammas would be > 1.
I make that assumption because that's what the JayGee controllers do.

Your curves, with gammas less than 1, have little control at the extremes and more precise control in the midrange.

The code is the same either way; it's just the setting of the two gammas that change.

Yours slams right in to 0 and 100% at a steep angle.
Mine approaches the extremes gradually. One can more precisely and easily control how close to 0% or 100% you want to be.
You could finesse a luxury car-like, smooth slow start with mine, but not yours. The slightest touch would jerk the velocity upward.
Similarly if you want to slowly approach a dangerous top speed without going over, my curve directions would be better.

My jpg should be attached. For this graph and notes, I used 0-1 for the x axis instead of 0-255
With me? :slight_smile:

Still looks like a perfect job for multimap() - http://arduino.cc/playground/Main/MultiMap - does not use float so it has a smaller footprint and faster (not tested extensively)

an example S-curve - fill in your own values -

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};
unsigned long d = 0;

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

  d = millis();
  for (int i=0; i< 10000; i++)
  {
    int val = analogRead(A0)/10;
    volatile int x = multiMap(val, in, s, 11);
  }
  d = millis() - d;
  Serial.print("a: ");
  Serial.println(d);
  
  d = millis();
  for (int i=0; i< 10000; i++)
  {
    int val = analogRead(A0)/10;
  }
  d = millis() - d;
  Serial.print("b: ");
  Serial.println(d);
  
  d = millis();
  for (int i=0; i< 10000; i++)
  {
    int val = analogRead(A0)/10;
    volatile int x = pow(val/255.0, 2.5)*255;
  }
  d = millis() - d;
  Serial.print("c: ");
  Serial.println(d);
}

void loop()
{
}

int multiMap(int val, int* _in, int* _out, uint8_t size)
{
  // 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]);
}

a: 1643
b: 1120
c: 4344

So 10000 calls took :
multimap: 1643 - 1120 = 525 millis or 53 micros() per call .
powefunc: 4344 - 1120 = 3224 millis or 322.5 micros() per call.
difference: 322/53 ~~ factor 6

Sketch with 1 call to the power func: size 3528 bytes (IDE 0.22, duemillanove)
Sketch with 1 call tothe multimap: size 2180 bytes

The diff in size here is mainly because no floats are used.

That said you need the power formula to generate the lookup table... :slight_smile:

Very good to know that, Rob. It's as I suspected. 6x
I think that using a lookup table, array will be the fastest.
There are no internal multiplies and divides as there must be in the multimap.

If it's not too much trouble, see how fast this is:

int lookup[1024];
for (int i=0; i<1024; i++)
lookup[ i ] = (i * 255 + 511)/ 1023;

d = millis();
for (int i=0; i< 10000; i++)
{
int val = analogRead(A0);
volatile int x = lookup[ val ];
}
d = millis() - d;
Serial.print("lookup: ");
Serial.println(d);

Note that using a 1024 size table eliminates the division after the analog read.
Anyway, for full and most exact 10-bit to 8-bit conversion that should be:
int val = (analogRead(A0) * 255 + 511)/ 1023;

Still looks like a perfect job for multimap()

Multimap does a piecewise linear interpolation, perfect for when you have relatively few data points.

For the curved sections, no three points are linear.

The middle section, though, is linear and that is where map() is used.

Then for the midrange,
speedValue = map (sensormappedValue, 75, 175, speedValueMidStart, speedValueMidEnd);

Multimap does a piecewise linear interpolation, perfect for when you have relatively few data points.

Or if one wants to use as little memory as possible.
As the points don't need to be equi-distant, multimap can be tuned to minimize the error by adding more points where needed. For now this tuning must be done manually, but it would be a nice sketch that given a "large" set of datapoints, and the acceptable max error, spits out the minimal multimap arrays :slight_smile:

That said, if a graph can be captured in a formula the error approaches zero, which could be the most important requirement, then one should use it.

lookup is way faster: lookup: 1120 ==> instantly
(speed traded for ram)
note: did change the type from int to byte, as 1024 ints do not fit in my duemillanove; could be a PROGMEM array to fix that, but that would slow it a bit.