From linear to exponential PWM output

This worked. :slight_smile:

if (speedstartValue != speedstartValueold)
     {
     tempMillis = millis() + 3000;
     digitalWrite(led11Pin,HIGH);
     }
if (tempMillis != 0  &&  millis() > tempMillis) 
{
     //EEPROM.write(2,speedstartValue);
     speedstartValueold = speedstartValue;
     tempMillis = 0;
     digitalWrite(led11Pin,LOW);
}

If I add the previous code snippet to other values I thougth it would perform with the same tempMillis lines OK.

  // +++++ Write values to EEPROM when they change after 3 seconds
  if (speedstartValue != speedstartValueold)
  {
    tempMillis = millis() + 3000;
    digitalWrite(led11Pin,HIGH);
  }
  if (tempMillis != 0  &&  millis() > tempMillis) 
  {
    EEPROM.write(2,speedstartValue);
    speedstartValueold = speedstartValue;
    tempMillis = 0;
    digitalWrite(led11Pin,LOW);
  }

  if (speedcurveValue != speedcurveValueold)
  {
    tempMillis = millis() + 3000;
    digitalWrite(led11Pin,HIGH);
  }
  if (tempMillis != 0  &&  millis() > tempMillis) 
  {
    EEPROM.write(3,speedcurveValue);
    speedcurveValueold = speedcurveValue;
    tempMillis = 0;
    digitalWrite(led11Pin,LOW);
  }   

  if (brakeNValue != brakeNValueold)
  {
    tempMillis = millis() + 3000;
    digitalWrite(led11Pin,HIGH);
  }
  if (tempMillis != 0  &&  millis() > tempMillis) 
  {
    EEPROM.write(4,brakeNValue);
    brakeNValueold = brakeNValue;
    tempMillis = 0;
    digitalWrite(led11Pin,LOW);
  }

Although the led is ON the changed values are not saved except for the speedstartValue.

But I have to declare temp(Xnumber up)Millis for each extra value I like to save to memory.
Is this a correct behaviour? If these extra millis() are running 10 in total could the program freeze as a result of the large number of millis()
Free Sram 5389 still.
Any workaround if this is the case?

  // +++++ Write values to EEPROM when they change after 3 seconds
  if (speedstartValue != speedstartValueold)
  {
    tempMillis = millis() + 3000;
    digitalWrite(led11Pin,HIGH);
  }
  if (tempMillis != 0  &&  millis() > tempMillis) 
  {
    EEPROM.write(2,speedstartValue);
    speedstartValueold = speedstartValue;
    tempMillis = 0;
    digitalWrite(led11Pin,LOW);
  }

  if (speedcurveValue != speedcurveValueold)
  {
    temp2Millis = millis() + 3000;
    digitalWrite(led11Pin,HIGH);
  }
  if (temp2Millis != 0  &&  millis() > temp2Millis) 
  {
    EEPROM.write(3,speedcurveValue);
    speedcurveValueold = speedcurveValue;
    temp2Millis = 0;
    digitalWrite(led11Pin,LOW);
  }   

  if (brakeNValue != brakeNValueold)
  {
    temp3Millis = millis() + 3000;
    digitalWrite(led11Pin,HIGH);
  }
  if (temp3Millis != 0  &&  millis() > temp3Millis) 
  {
    EEPROM.write(4,brakeNValue);
    brakeNValueold = brakeNValue;
    temp3Millis = 0;
    digitalWrite(led11Pin,LOW);
  }

Paco

You do not need to keep reading the time with repeated calls to millis().
Read it once at the beginning of the loop and put the value in a variable. Everything in the loop then references currentTime.
Also a separate, reasonably-named variable for each turn off time is a reasonable expense.

currentTime = millis();
....

if (brakeNValue != brakeNValueold) 
{
      brakeOffTime = currentTime  + 3000;
      digitalWrite(led11Pin,HIGH);
}
if (brakeOffTime != 0  &&  currentTime   > brakeOffTime ) 
{
      EEPROM.write(4,brakeNValue);
      brakeNValueold = brakeNValue;
      brakeOffTime = 0;
      digitalWrite(led11Pin,LOW);
}

Techylah,

I declared in the init part:
long currentTime = millis();
long brakeOffTime = 0;

With your code after power up the led11Pin goes on and not off.
With serial monitor I chekced and see brakeOffTime stays 3000 what ever you do.
So I changed currentTime into millis() and then when I power on the led11Pin is on for 3 secs and after that when I press the key the led goes ON and OFF after the 3 secs. So that works then.....
So why putting millis() into the variable is not working and is of influence on the fact the led11Pin is high always?

Paco

The declaration and setting of currentTime goes only in the loop routine, at the beginning.
Throughout that passage through the loop, that is the current time.
The beginning of each loop sets a new currentTime.

All of the turn off times, like long brakeOffTime need to be declared outside of of the loop routine, as global variables,
since they must remain the same for multiple passes of the loop.

So the loop routine starts with:
long currentTime = millis();

Outside both the init and loop routines, you have:
long brakeOffTime = 0;
long speedcurveOfftime = 0;
...

Techylah,

Sorry for no response.
Been busy to finalize the hardware so we can test drive the controller this coming friday evening at a track.
I put your recommendations in the code and that works fine now. Thanks again.

I checked all about the look up table discussions but still do not understand.
For my understanding I like to go back to an other version of the speedcurve which is more simple to understand for me I guess and progress from there.
See the attachement.
I also have to make the guys clear what they are doing when they set up speed curve.
Friday I receive a Android phone which I like to connect the controller to display the curve settings (and all other settings).
If the hardware is functioning fine I can always adapt the software settings.

Paco

scan06062012_00000.pdf (242 KB)

Got some sort of working code.

   if (sensormappedValue < switchpointValue)
    speedValue = map(sensormappedValue, 0, 255, speedstartValue, 255); // can be switched off later stage when curve section works.
    else
  //  speedValue = (int) (0.5  +  speedstartValue + pow(sensormappedValue/127.0, gammaSValue) * ((speedfinalValue - speedstartValue) / 2.0) );    
    speedValue = (int) (0.5  +  speedfinalValue - pow((255 - sensormappedValue)/127.0, gammaSValue) * ((speedfinalValue  - speedstartValue) / 2.0));

I added switchpointValue variable instead of the fixed 128 to change the point on the trigger baseline where the linear line is starting to bend. Both options earlier supplied give a drop down in speedfinalvalue after switchpointValue is reached.
I tried to figure out how to change the line to linear climbing after the switchpointValue but I failed sofar.

Paco

Techylah and Rob,

The maiden trip of the controller was made yesterday evening.
It ran 3 hours without any electronic/mechanical hick up.
I had two fast club drivers trying the controller.
They were very content about the fact they could see what they adjusted for startspeed and break and now knew what they were doing in numbers.

They had only one remark :~ at the end of the evening and that is that the controller is not following the trigger movement exactly.
As the controller was programmed for a linear line (curving not working yet) they say there was a lack in response when going from 0 to full speed.
They described it as a delay.
I can not confirm their FEELING but have to believe them.
These guys run rounds measured in 1/1000 of a second!
The difference with my controller and an other known one was 3 tenth of a second. :blush:

Could this be caused due to coding delaying the PWM to respond in this case?

Other thoughts and suggestions are welcome.

Paco

They described it as a delay.
I can not confirm their FEELING but have to believe them.

I believe them, too!

I'm pretty sure it is because you are doing calculations on the fly instead of the lookup table.

Replace:

speedValue = (int) (0.5  +  speedfinalValue - pow((255 - sensormappedValue)/127.0, gammaSValue) * ((speedfinalValue  - speedstartValue) / 2.0));

with

speedValue = lookupTable[sensormappedValue];

and then, in your init code:

for (int i=0; i<256; i++)
   lookupTable[i] = (int) (0.5  +  speedfinalValue - pow((255 - i)/127.0, gammaSValue) * ((speedfinalValue  - speedstartValue) / 2.0));

speedValue will be exactly the same but practically instantaneous, instead of after slogging through the really slow floating point implementation of pow(), along with three also slow floating point multiply/divides.

Agree with TechyIah, a lookup table would improve speed, you might place it in PROGMEM to minimize using RAM.
That means you need to print it and make C-code of it ( make a small sketch that prints a C-array)

Techylah,

for (int i=0; i<256; i++)

Gives and error in de INIT section.
Expected unqualified-id before "for"
expects contructor,destructor or type conversion before < or ++ token
I googled for it and the code is used more so code is correct.
Must be something in conjunction of the existing code I assume.

Also "lookupTable" needs to be declared as array?

Rob, do not make it more difficult as it is. 8) One step at the time.

Will look for PROGMEM and what it does.

Paco

Yes,

char lookupTable[256];

It needs to be declared outside both the init and the loop code.
And yes, get that working first and then go for saving the 256 bytes of RAM by using PROGMEM as Rob points out, if you won't be changing the curve on the fly.

Techylah,

So no error codes anymore.

I ran some test but all are different then desired.

I reduced some lines to get the basic without any switchpointValue and only used the lookupTable line.
speedstartValue is set at 1
gammaSValue is set at 1.0

speedfinalValue however in this case is always starting at 171 regardless all other settings.
Also ispeedfinalValue runs through and passing the 255 value going to 0 at the end of full trigger.

Current code for the output.

void setup()
for (int i=0; i<256; i++)
lookupTable[i] = (int) (0.5  -  speedfinalValue + pow((255 - i)/127.0, gammaSValue) * ((speedfinalValue  - speedstartValue) / 2.0));

void loop()
//  +++++++++++++++  regular speed - brake mode  +++++++++++++++

  // in regular mode 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:
  sensorValue = map(sensorValue, sensorMin, sensorMax, 0, 255);// apply the calibration to the sensor reading from the hall sensor
  sensormappedValue = constrain (sensorValue,0,255); // keep value in range
  
  //  ++++  curve section  +++++++
   //if (sensormappedValue < (switchpointValue - speedstartValue)) // switchpoint for test set at 120
   //speedValue = map(sensormappedValue, 0, 255, speedstartValue, 255);
   //else
   //speedValue = 0;
   speedValue = lookupTable[sensormappedValue];
  //  ++++  curve section  +++++++

  speedfinalValue = constrain (speedfinalValue, 0, 255);
  speedfinalValue = speedValue;
  

  if (sensormappedValue > deathbandValue)
  {
    analogWrite(pwmoutspeedPin, speedfinalValue); 
    analogWrite(pwmoutbrakePin, 0); 
    digitalWrite(ledDBPin, LOW);
    digitalWrite(ledBPPin, HIGH);
  } 
  else
  {
    analogWrite(pwmoutspeedPin, 0); 
    (speedfinalValue, 0);
    analogWrite(pwmoutbrakePin, brakeNValue);
    digitalWrite (ledDBPin,HIGH);
    digitalWrite(ledBPPin, LOW);
  }

Paco

Techylah,

For my cofirmation. with the before the init code you mean "void setup()" section?

Paco

For my confirmation. with the before the init code you mean "void setup()" section?

Yes.

Paco,
After looking at your hand-drawn curves, it occurs to me that perhaps you should implement it exactly as they described, i.e. linear until 0.75 and then
curving upwards to maximum. (I had wanted to make the top end curve down!) This can be implemented with a [ warning math follows ] curve called a cubic Bezier curve, the formula for which is:
http://www.moshplant.com/direct-or/bezier/math.html

The attached image uses 3 instead of 4 control points for the curve section after the up to 75% linear part. (I made point 1 and point 2 the same, and both at x = 1).
This greatly simplifies calculating the 6 coefficients ax, bx, cx and ay, by, cy
See if this is what you/they want.

Interestingly, it lends itself to implementation only with the lookup table, since as parametric functions of 't', you don't plug in X and get Y, or plug in Y and get X. Rather,
you plug in values of 't' from 0 to 1.0 and get any number of points in between. You make the increment small enough so it gives you all the values in the lookup table
from 3/4 (index 192) through full (index 255).

It's not simple math, but once you get it, it's very powerful and it's easy to implement. Your setup code can take any arbitrary point to be the transition point from linear to curve, and then compute the curve part of the lookup table with the two parametric equations. For example you might want the first 80% of control position to give you linear control from 0 to 60% power, and then curve to max fr the last 20%
You would make the X0, Y0 point then be (0.8, 0.6) and compute the table.

This is the math behind the curves in postscript, PDF and drawing packages (like google doc)

Techylah,

Can we go three steps back?
I have been reading articles about LUT's and how they work and how to implent them in code.
I also saw Robs multimap playground article but to technical for me.
Sofar I still have to understand the code you provided for my knowledge.

lookupTable[i] = (int) (0.5  -  speedfinalValue + pow((255 - i)/127.0, gammaSValue) * ((speedfinalValue  - speedstartValue) / 2.0));

I first have to sort out if the delay is really on the fly calculation.
So I wanted to make it linear taken from a lookuptable (instead of the current map) and test this first by the same guys.
Last week theya lso test the linear only version.
If they say the delay is gone, then I want to start to add any curve.
If your suggested end after 75 % just go up or down is up to the drivers and their feeling and a later issue.

I can take the laptop with me and load a prepared sketch with curve the same evening if the delay with a linear curve is OK and approved.
But I first have to get known to working/set up of the linear lookuptable.

lookupTable[i] = (int) (A , B);
or 
lookupTable[i] = (int) (speedfinalValue , (speedfinalValue  - speedstartValue));

If I simplify the code to this is that correct way of thinking?
Or did I misjudged the "," as a separator?

BTW, I know the Bezier curve. Work daily with it in Corel Draw to design the decals for my decal company Custom Racing Decal.

Paco

Ok. The lookupTable is a 1-dimensional array of char values (the compiler changes an int to a char automatically).
Yes, the comma was to separate the two arguments to pow().
So a straight-through linear test would be:

char lookupTable[256];
void setup()
{
     for (int i=0; i<256; i++)
          lookupTable[i] = i;
}

Techylah,

The straight full linear lookupTable works.
The lookupTable setup is put in void setup() but the excecution is put in void loop () so once the controller has started it only performs it once to fill the lookupTable with values?
So when you put things like startspeedValue in there too in void setup () with the lookupTable action are they taken into the process of the void loop() even if variables like startspeedValue are changed in runtime?

If you start the controller it reads the last saved value in void setup () for startspeedValue from the EEPROM to the variable with a value for exmaple 95.
The void loop() actions will work with that value and even can be changed by the pushbuttons up or down in value as long as the controller is ON.

Is my understanding correct?

Sorry for being a slow learner.

Paco

Paco, yes.
I would make a separate subroutine that fills the lookup table and is called with 3 values: speedStart, switchPointX, switchPointSpeed.
You could even have 3 pots that set those values, since pots are so easy to read, cheap, and add flexibility and feeling finesse to the controller.
The routine would be called once in the setup code, after setting those three variables to default values.
In your loop code, you would quickly read the pots and if and only if any of the values changed, call the fillLookupTable routine.
Also, the curved part of the table (from switchpointC to 100), can be implemented first as simple linear.
You might not even need the spline curve, and it's parametric equations. Just two simple map() calls, one for before the switchPointX and one for afterswitchPontX.
Something like:

char lookupTable[256];
int speedStart = 95;                          // out of 255,  default power level
int switchPointX = 80  * 255 / 100;     // the switch is at 80% (default) of the full input range (0 to this is linear)
int switchPointSpeed = 60 * (255 - speedStart)  / 100;  // the power or speed at the switch point is lower, only 60% of the remaining power level
void setup()
{
   fillLookupTable();
}
void fillLookupTable()
{
     for (int i=0; i<256; i++)
         if (i < switchPointX)
             lookupTable[i] = map(i, 0, switchPointX, speedStart, switchPointSpeed);
         else
             lookupTable[i] = map(i, switchPointX, 255, switchPointSpeed, 255 );       // this could get replaced by a curve after all is working
}

Then in your loop code, any time any of these three values change, by pot or button or remote,
you just simply call fillLookupTable() and you get a whole new feel and sensitivity, yet keep the fast response by having no on-the-fly calculations.