Fast increase, delayed return function

Hello everyone!
I have had a function in my mind-sketchbook for some time and decided to just start trying.

I would greatly appreciate a nudge in the right direction.

The general idea is;

  • There is one input axis, in this case a pot analogRead
  • There is one output value, in this case just a value
  • If the input value is over 512 and holding or increasing, the output should be set to that positive value
  • If the input value is under 512 and holding or decreasing, the output should be set to minus that value

So far it is pretty straightforward.

  • If the input value decreases (or rather goes closer to 512), I need the output value to follow it but slowly
  • The output value should follow the input slower the further away from 512 it is.

Does this make any sense?

This is what I have so far (and it doesn't work very well at all):

int pot = 0;
int left = 0;
int right = 0;
int tot = 0;
int oldLeft = 0;
int oldRight = 0;

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

void loop() {
  
pot = analogRead(A0);

if (pot < 512) {
  left = map(pot, 0, 511, 1023, 0);
  right = 0; 
  }
  else if (pot > 512) {
  left = 0;
  right = map(pot, 513, 1023, 0, 1023);
  } else {
  left = 0;
  right = 0;
}

if (left >= oldLeft)
  {
     tot = (left);
  }
  else if (left < oldLeft)
  {
     tot = (oldLeft - (left/(oldLeft*100000)));

     
  } 
  
if (right >= oldRight)
  {
     tot = (-right);
  }
  else if (right < oldRight)
  {
     tot = (-(oldRight - (right/(oldRight*100000))));
  }


oldLeft = left;
oldRight = right;
  
  // print out the value you read:
  Serial.print("left: ");
  Serial.println(left);
  Serial.print("right: ");
  Serial.println(right);
  Serial.print("tot: ");
  Serial.println(tot);
  Serial.println("_____________");
  delay(100);        // delay in between reads for stability
}

Thanks in advance! :smiley:

/Chris

You may be better off telling us what your objective is.

Your description is fine for what it is, but why?

There are multiple unknowns in your idea, and a lot of relative concepts which don’t sit well with computer logic.

That doesn't make sense. Did you mean the further away the FASTER it should move? Sounds like you could implement that with a PID controller. There is a library available with examples. The larger the "error" the more correction applied.

It’s the start of a countersteering algorithm for a motorcycle game controller.
Not sure that makes more sense though :wink:

No, slower.

not clear what you expect.

the following approaches the target slowly.
the code switches at 30

#include <stdio.h>
#include <math.h>

float position = 0;

#define K       50
#define Target  50

#define Sgn(x)  (0 < x ? 1 : -1)

float
track (
    float target )
{
    float delta = target - position;
    float gain  = fabs(K / delta);

    gain  = 3 < gain ? 3 : gain;

    if (30 < delta)
        position += Sgn(delta) * gain;
    else
        position += delta / 10;

    return position;
}

int
main ()
{
    for (int i = 0; i < 100; i++)
        printf (" %6d %6.2f\n", i, track (Target));
}

So the the bigger the error, the slower the correction?

The target is the input signal, and when that is rising or is steady that should set the output instantly. If the input is lower than the output it should follow it, but the bigger the delta the slower it should go.
This is true for both “left and right”.

Since the input is an analogRead, center or “zero” is at 512. Anything less is “right” and anything more is “left”.

Not sure this is understandable though.

Yes, exactly! Preferably with a definable gain value.

Ok, so I removed the somewhat confusing left/right stuff and thought I'd just break it up in spans.

If the signal is moving away from the center value, I simply set the signal as output value.
Then I thought I'd do all the PID magic in the "else if" > the signal is decreasing.

If the delta value is high I want the pid to work slowly and vice versa.

Am I on to something here?

#include <PID_v1.h>

int sig = 0;
int out = 0;
int oldSig = 0;
int oldOut = 0; 
const int center = 512;
const int deadZone = 2;
int delta = 0;

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

void loop() {
  
sig = analogRead(A0);
  
  if (sig < (center - deadZone)) {
    delta = (center - sig);
    }
    else if (sig > (center + deadZone)) {
    delta = (sig - center);
    } else {
    delta = 0;
    }

if (sig > center) {
  if (sig >= oldSig) {                          // If input is steady or increasing
  out = sig;                                    // Set output to input
  } 
  else if  (sig < oldSig) {                     // If input is decreasing
                                                // Follow sig in magic way
  }

} 
else if (sig < center) {
  if (sig <= oldSig) {                          // If input is steady or increasing
  out = sig;                                    // Set output to input
  } 
  else if  (sig > oldSig) {                     // If input is decreasing
                                                // Follow sig in magic way
  }
}



oldSig = sig;
oldOut = out;

  // print out the values:
  Serial.print("Delta: ");
  Serial.println(delta);
  Serial.print("Sig: ");
  Serial.println(sig);
  Serial.println("_____________");
  delay(600);        // delay in between reads for stability
}

Doesn't that lead to an unstable system? The goal of a PID controller is to bring the error down to zero. If the error is growing and you are slowing your response, the error will continue to increase.

That's the goal here too. To bring the "delta" value down to zero, but the bigger it is the slower it should happen. Probably a PID is overkill here..

Have you checked the Adaptive Tunings example? Similar idea, except you could use aggressive tuning parameters for a small delta and conservative parameters for a large delta and add more stages if necessary.

i don't think PID is going to work if you want correction rates to be inversely proportional to error.

I think this could work, but forgot to mention a few things:

  • Decrease the PID sample time (default is 100ms). For example, you could make it 25x faster by using something like myPID.SetSampleTime(5); This will attenuate any overshoot that could occur with a slower rate mixed with aggressive gains.
  • Determine the max allowed overshoot (25%?), then determine the max aggressive gains (Kp, Ki) that you could use without exceeding your overshoot requirement.
  • Then just scale the gains lower higher in stages as the delta becomes lower.
1 Like

you mean make the gains inversely proportional to delta?

Yes, (whoops) ... fixed previous post.

That looks neat! I’ll definitely try it!

I got it working to some extent yesterday. I divided the delta value into spans and set the ‘magic equation’ to set values instead. Works good enough for a proof of concept I think.
I’ll get back to you with results!

Ok, so this is where I am right now. It kind of behaves the way I wanted it to.
I'll see it is at all usable, and then maybe implement the PID stuff.
Thank you for your help so far!

#include <Servo.h>

int sig = 0;
int out = 0;
int oldSig = 0;
int center = 512;
int deadZone = 2;
int delta = 0;
int ret = 0;
int servo = 0;

Servo myservo;

void setup() {
  Serial.begin(57600);
  myservo.attach(10);  // attaches the servo on pin 10 to the servo object
}

void loop() {
  
sig = analogRead(A0);


// Delta calculations
  if (sig < center) {
    delta = (sig - out);
    }
    else if (sig > center) {
    delta = (out - sig);
    } else {
    delta = 0;
    }

// The return rate, lower values the higher delta is,
// Would be nice with an equation instead..

if (delta > 400){
ret = 1;
}else if (delta > 300){
ret = 2;
}else if (delta > 200){
ret = 3;  
}else if (delta > 150){
ret = 4;  
}else if (delta > 100){
ret = 7;  
}else if (delta < 50){
ret = 10;  
}

// If turning right
if (sig > center) {
  if (sig > (out + deadZone)) {           // If input is increasing
  out = sig;                                // Set output to input
  } 
  else if  (sig < out) {                    // If input is decreasing
  out = out - ret;                        // Follow sig in ret way
  }
} 

// If turning left
else if (sig < center) {
  if (sig < (out - deadZone)) {          // If input is increasing
  out = sig;                                // Set output to input
  } 
  else if  (sig > out) {                    // If input is decreasing
  out = out + ret;                        // Follow sig in ret way
  }
}

// If not turning
else {
  out = center;
}
servo = map(out, 0, 1023, 2300, 560);
myservo.writeMicroseconds(servo);

oldSig = sig;

  // print out the value you read:
  Serial.print("Delta: ");
  Serial.print(delta);
  Serial.print("     ||      Signal: ");
  Serial.print(sig);
  Serial.print("     ||      Output: ");
  Serial.print(out);
  Serial.print("     ||      Servo: ");
  Serial.print(servo);
  Serial.print("     ||      Return: ");
  Serial.println(ret);

  
  delay(2);        // delay in between reads for stability
}

Edit; The servo thing is just for visualizing when testing.