Go Down

### Topic: From linear to exponential PWM output (Read 34140 times)previous topic - next topic

#### Techylah

#30
##### May 14, 2012, 09:45 pmLast Edit: May 15, 2012, 12:29 am by Techylah Reason: 1
I said
Quote
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:
Quote
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.

#### backbone

#31
##### May 14, 2012, 10:39 pm
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
Never to old to learn and I learn every day

#### Techylah

#32
##### May 15, 2012, 12:06 am
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.

#### Techylah

#33
##### May 15, 2012, 12:28 amLast Edit: May 15, 2012, 12:47 am by Techylah Reason: 1
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);

#### backbone

#34
##### May 15, 2012, 08:14 am
Is this what your trying to achieve?
If yes I am with you.... :-)

Paco
Never to old to learn and I learn every day

#### Techylah

#35
##### May 15, 2012, 05:52 pmLast Edit: May 15, 2012, 07:33 pm by Techylah Reason: 1
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?  :-)

#### robtillaart

#36
##### May 15, 2012, 07:42 pm
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 -
Code: [Select]
`int s[]  = {   0,  0, 10, 30, 70, 130, 180, 320, 350, 370, 380}; // 11int 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...

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

#### Techylah

#37
##### May 15, 2012, 08:40 pmLast Edit: May 15, 2012, 08:56 pm by Techylah Reason: 1
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++)
{
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;

#### Techylah

#38
##### May 15, 2012, 09:11 pm
Quote
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.
Quote
Then for the midrange,
speedValue = map (sensormappedValue, 75, 175,  speedValueMidStart, speedValueMidEnd);

#### robtillaart

#39
##### May 15, 2012, 10:13 pm
Quote
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

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

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

#### Techylah

#40
##### May 15, 2012, 10:33 pmLast Edit: May 15, 2012, 10:35 pm by Techylah Reason: 1
Quote
lookup is way faster:    lookup: 1120 ==> instantly

Great. Tnx.

byte lookup[256];

And after input is read use either of these:
byte val = (analogRead(A0) * 255  + 511)/ 1023;              // exact but slower
or
byte val = analogRead(A0) >> 2;                                     // fastest, since shifting is much faster than multiply and divide

I'm not sure how significant this speed difference would be.

#### robtillaart

#41
##### May 15, 2012, 10:41 pm
As the array is calculated only once the difference is irrelevant.

The shift instruction takes 2 or 4 clockcycli, especially the division might be 10 times as slow, the multiply and add I expect also only a few clock cycles. So a difference of a factor 10-15 I expect. (not measured as its getting late here)
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

#### backbone

#42
##### May 15, 2012, 10:49 pm
In case you need to know.
I started with a UNO rev 3 but with all the code and the MenWiz menu for the display I ran out of Sram.
So now it have a Mega 2560 to prototype further.

BTW all the calculations and assumptions about timing is already past my ability to follow......
Did not had the time to look into the code for my understanding.
Paco
Never to old to learn and I learn every day

#### Techylah

#43
##### May 15, 2012, 10:59 pmLast Edit: May 15, 2012, 11:01 pm by Techylah Reason: 1
Quote
As the array is calculated only once the difference is irrelevant.

Only in the timing test code is it calculated once.

For the real operational code, every analog read of the input controller will get converted from 10 to 8-bit,
so the shift version should be used.

Paco, no problem.  Rob and I are bit-pushing.
The end result is what we are pretty sure is the best and fastest.
Techylah

#### robtillaart

#44
##### May 15, 2012, 11:11 pm
Quote
For the real operational code, every analog read of the input controller will get converted from 10 to 8-bit,
so the shift version should be used.

Definitely, especially because the last 1-2 bits are often noizzzzzzz....
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Go Up

Please enter a valid email to subscribe