Limit the rate of change of output

Hi all,

I have a little tank project using an Uno... 2 servo inputs from RC Controller. An IR sensor and two servo outputs to Electronic Speed Controllers. When an IR signal comes in, it limits the maximum value that can be sent to the outputs (simulating hits from an enemy).

That is working fine but I'd like to add a little finesse... that is to limit the rate of change of the output signals so it accelerates and decelerates smoothly. Adding a simulation of inertia and mass to my tank.

Any ideas how I could achieve a slow rate of change to the outputs?

Thanks very much. Drew

Yes, just write the code.

Properly designed code uses the "loop" function to cycle in the order of a thousand times per second. You use the "millis()" counter to determine how much time is actually passing so for your inertia function, you arrange the code to regularly - say five times a second - increment or decrement your "speed" variable which sets the PWM value to the ESCs, toward the commanded speed.

Any ideas how I could achieve a slow rate of change to the outputs?

Yes. But seeing your code would allow us to explain it to you in terms of YOUR code.

You have a current range of outputs. You have a delta to be applied to that range. You want to divide the delta by a number of steps, and then apply the smaller delta over some time.

Thanks very much.... I find this kind of thing very difficult to conceptualize, your replies have given me some impetus in the right direction!

I'll post my code when I get home from work, but it certainly helps to know the kind of approach I need.

Cheers, Drew.

The conceptually simplest way is to go back to the equations of motion(*) and implement them directly - each time round loop calculate the position from the time and update the servo.

(*) s = u t + 0.5 a t^2

Just to add yet another way of looking at the problem - separate out the demanded output (how much speed the user is requesting) from the actual output (what speed you are currently delivering). Instead of simply making the actual output match the demanded output directly, you would use the demanded output to make incremental changes to the actual output. For example, one strategy would be to run some code every 100 ms which moves the actual speed a little bit closer to the demanded speed, giving the impression of inertia. You can control how much inertia by how often you update the speed and how much you update it by.

You need a digital filter with a single pole. Suppose you start with no pole and your output y(t) is just a constant, "a", times the input x(t). Then y(t) = a*x(t). Now add a pole with time constant "tau". y(t) + tau*y'(t) = a*x(t) Now describe this in the frequency domain by taking the Laplace transform. Y(s)/X(s) = a/(tau*s +1) Find the discrete time difference equation using something like Mathematica's "ToDiscreteTimeModel" function or Matlab's "c2d" function. For a discrete loop time of 10mS, a tau of 1 sec, and a gain "a"=1, I get y[n] = 0.9905*y[n-1] + 0.00995017*x[n]

Thanks again.
I think I understand but I’m afraid that I am a bit out of my depth! This is my first Arduino project and my first attempt at programming since writing touch typing trainers and racer games on the C64! :astonished:

Here’s my attempt at code for what it’s worth!

/*
tank controller try 1
 License: CC-BY SA 3.0 - Creative commons share-alike 3.0
 use this code however you'd like, just keep this license and
 attribute. Let me know if you make hugely, awesome, great changes.
 */

#include <Servo.h>
#include <IRLib.h>


//Constant
//int RECV_PIN = 2;           // IR Sensor not required, done through interrupt - but it is in pin 2 
int led = 13;                 // Hit LED
//int hashAnswer = 84696349;  // The hashed answer of recived IR signal (IRrecv)
int hashAnswer = 22621;       // The hashed answer of recived IR signal (IRrecvPCI)
Servo myservoL;               // Servo object for ServoL
Servo myservoR;               // Servo object for ServoR
unsigned long interval=1000;  // Time we need to turn on hit LED to signal we are hit
int n = 4;


//variables
IRsend My_Sender;             //IR send object
unsigned int Signal[] = {
  3100,2800,5950}; //RAW outout code for IR
int i;                         // Counter for IR send loop
IRrecvPCI My_Receiver(0);     //IR recieve function
IRdecode My_Decoder;          //IR decode function
IRdecodeHash My_Hash_Decoder; //Decode hash function
int hashResult;               //The result of the hashed IR recived value
int chL;                      // Here's where we'll keep our left channel values
int chR;                      // Here's where we'll keep our right channel values
int chF;                      // Channel value for fire
int hitcount;                 // variable to use to count hits
int slowstage1;               // point at which we slow by 50%
int slowstage2;               // point at which we slow by 75%
unsigned long waitUntil=interval; // millis() returns an unsigned long.


void setup() {

  pinMode(5, INPUT);            // Set our left servo input pins as such
  myservoL.attach(8);           // attaches the servo on pin 9 to the servo object
  pinMode(6, INPUT);            // Set our right servo input pins as such
  myservoR.attach(9);           // attaches the servo on pin 10 to the servo object
  pinMode(7, INPUT);            // set our fire channel to pin 7

  pinMode(led, OUTPUT);         //Set hit LED pin as output
  My_Receiver.enableIRIn();     // Start IR the receiver
  Serial.begin(9600);           // Pour a bowl of Serial
  hitcount = 0;                 // initially set hitcount to 0
  slowstage1 = 1;               // number of hits to first slowstage
  slowstage2 = 5;               // number of hits to second slowstage
}

void loop() {

  chL = pulseIn(5, HIGH, 25000); // Read the pulse width of left Servo
  chR = pulseIn(6, HIGH, 25000); // Read the pulse width of Right servo
  chF = pulseIn(7, HIGH, 25000); // Read the pulse width of Fire servo

  if (hitcount >= slowstage1){
    chL = constrain(chL, 1250, 1530);  //I got hit to slowstage 1. Axis contrained to 50%
    chR = constrain(chR, 1250, 1530); 
  } 

  if (hitcount >= slowstage2) {
    chL = constrain(chL, 1125, 1510); //I got hit to slowstage 2. Axis constrained to 25%
    chR = constrain(chR, 1125, 1510); 
  }
  if (hitcount >=9) {
    chL = 1000;                       // Set both servos to zero... because I'm dead
    chR = 1000;
    myservoL.write(chL);              // tell left servo to go to position in variable 'chL'
    myservoR.write(chR);              // tell right servo to go to position in variable 'chR'    
    delay(15000);                     // I'm dead!  wait for 15 seconds to recover
    hitcount = 0;                     // reset the hit counter to zero and get going!
  }

  // there two send the outputs to the servos.
  myservoL.write(chL);              // tell left servo to go to position in variable 'chL'
  myservoR.write(chR);              // tell right servo to go to position in variable 'chR'  


  // 'Fire' routine.
  if (chF >= 1700) { // if fire is pressed.....:
    for (i = 0; i < n; i++) {
      My_Sender.IRsendRaw::send(Signal, sizeof(Signal)/sizeof(int), 38); //AnalysIR Batch Export - RAW 
      delay (10);
    }
    My_Receiver.enableIRIn();       //make sure IR reciever stays on after doing fire routine
  }

  // Got hit routine.
  if (My_Receiver.GetResults(&My_Decoder)) {//Puts results in My_Decoder
    My_Hash_Decoder.copyBuf(&My_Decoder);//copy the results to the hash decoder
    My_Decoder.decode();
    My_Hash_Decoder.decode();

    hashResult = (My_Hash_Decoder.hash);
    if (hashResult == hashAnswer) {       //compare the result to known answer
      if ((unsigned long)(millis() - waitUntil) >= interval) {  
        waitUntil =+ interval; // Increase our wait for 1000ms.
        digitalWrite(led, HIGH);
      }
      digitalWrite(led, LOW);
      hitcount ++; 
      //Serial.println (hitcount);
    }
    //Serial.println (hashResult);
    My_Receiver.resume(); 
  }

}