Jerky Motion During Acceleration

I have a motion trajectory class (uses discrete integration for efficiency. i.e. all calculations are addition/subtraction) which generates theoretical position of a motor each time the function is called.

The theoretical position/set point is calculated 500/sec (I've also tried 250/sec and 1000/sec) and the result and encoder count is used by the Arduino PID library to generate a PWM output -Arduino Playground - PIDLibrary.

During acceleration the motor jerks quite a bit as the velocity increases. I've found that the motion is more jerky at the beginning of acceleration and smoothes out towards the middle and end.

My possible explanations are as follows:

  1. PID parameters need tuning. I've tried a variety of different values and the effect is negligible - in all cases little to no change in jerkiness.

  2. PWM resolution (8-bits?) is too low.

  3. Set point is calculated too frequently or infrequently. Again, I've tried several values and there's little impact.

  4. Encoder library is missing counts.

Are there any other possible explanations you guys have? Which seems most likely to you?
And, can anyone explain why the jerkiness eases as the velocity increases? Could this be to do with the momentum of the motor increasing as it speeds up...?

Any help is greatly appreciated.

Thanks :slight_smile:

Unless the encoder count actually changes from one PID cycle to the next, there is no information to process. Also, trying to derive timing information from encoder transitions is noisy and error prone.

If you suspect the encoder is missing counts, fix that.

Post your code, using code tags.

#include "MotionProfile.hpp"
#include <PID_v1.h>
#include <Encoder.h>
#include <PID_AutoTune_v0.h>

int flag = 0;

Encoder myEnc(6,7);


//Define Variables we'll be connecting to
double Setpoint, Input, Output;
double Input2, Output2;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint,0.26,0.02,0, DIRECT);


MotionProfile a(0,0.00001,0.3,100000); //start pos, accel/decel, max_vel, target position



void setup() {
  // put your setup code here, to run once:

  TCCR1B=TCCR1B&0xf8|0x01;    // Pin9,Pin10 PWM 31250Hz
  TCCR2B=TCCR2B&0xf8|0x01;    // Pin3,Pin11 PWM 31250Hz

  OCR0A = 0xAF; //setup timer
  TIMSK0 |= _BV(OCIE0A);

  pinMode(LED_BUILTIN, OUTPUT);
  
   pinMode(9, OUTPUT);
   pinMode(8, OUTPUT);

   pinMode(6, INPUT);
   pinMode(7, INPUT);

   myPID.SetTunings(0.26,0.02,0);
   myPID.SetMode(AUTOMATIC);
   myPID.SetSampleTime(1);

}

SIGNAL(TIMER0_COMPA_vect) 
{
  flag++;
} 

void loop() {
  
  while (!a.complete()){
    digitalWrite(LED_BUILTIN, HIGH);
    if (flag%2 == 0){
      //digitalWrite(LED_BUILTIN, HIGH);
    Setpoint = a.move();
    
    Input = myEnc.read();
    //Input2 = myEnc2.read();
    
    myPID.Compute();
   // myPID2.Compute();

 
  digitalWrite(8, HIGH); //direction
  //digitalWrite(11, HIGH); //direction
 
  analogWrite(9,Output); //pwm
  //analogWrite(10,Output2);

  
    }
  
  }

if (a.complete()){
  digitalWrite(LED_BUILTIN, HIGH);
}
  analogWrite(9,0);

  
  

 

  

}

jremington:
Unless the encoder count actually changes from one PID cycle to the next, there is no information to process. Also, trying to derive timing information from encoder transitions is noisy and error prone.

If you suspect the encoder is missing counts, fix that.

Post your code, using code tags.

Shouldn't the PID output be computed every time the setpoint is generated?
Not sure what you mean by trying to derive timing information from encoder positions. Could you elaborate please? Thanks :slight_smile:

If you are not trying to derive timing information from the encoder counts (e.g. velocity) then you can ignore that comment.

By "the setpoint is generated" do you mean the desired angular position of the shaft? If so, what is the expected rate of change of that position and what is the expected rate of change of encoder counts? If not, please explain what you mean by the following quote and why you chose such high calculation rates. In other words, clearly describe your system model.

The theoretical position/set point is calculated 500/sec (I've also tried 250/sec and 1000/sec) and the result and encoder count is used by the Arduino PID library to generate a PWM output -Arduino Playground - HomePage.

What is this?

MotionProfile a(0,0.00001,0.3,100000); //start pos, accel/decel, max_vel, target position

Forum members usually won't go through your code to try to figure out what you are doing. If you want informed help, you need to explain what the program should do and what it does instead, providing data to back up your points.

jremington:
If you are not trying to derive timing information from the encoder counts (e.g. velocity) then you can ignore that comment.

I have no idea what you mean by "the setpoint is generated". Please explain what you mean by the following quote and why you chose such high calculation rates. In other words, clearly describe your system model.
Forum members usually won't go through your code to try to figure out what you are doing. If you want informed help, you need to explain what the program should do and what it does instead, providing data to back up your points.

I chose a high calculation rate because I assumed the higher the frequency, the smoother the motion - I've also noticed that similar systems (e.g. LM628, which I am essentially re-implementing in an Arduino) have similarly high call rates.

I'm using a timer interrupt (Timer1) which increments an int flag by 1.

Inside the loop() method, I check to see if "flag mod 2 == 0". If so, 2ms have passed since previous calculation and setpoint is regenerated - by "the setpoint is generated", I mean calculate the theoretical position (in encoder count units) where the motor should be. This is calculated using: "pos += (accel/2) + vel;, etc".

Note that I substantially edited my comment and questions for clarity.

I'm using a timer interrupt (Timer1) which increments an int flag by 1.

Why and what for?

Note that choice of PID loop time is a critical aspect of the control system design. Plenty of info on how to choose that on line, e.g. Sample Time is a Fundamental Design and Tuning Specification – Control Guru

jremington:
Note that I substantially edited my comment and questions for clarity.
Why and what for?

MotionProfile a(0,0.00001,0.3,100000) generates a trapezoidal motion profile with starting position, accel/decel, max_vel and target position. The idea is for the motor to accelerate up to max_vel and coast until it reaches the decel point at which point it decelerates (at the required rate) to a the target position.

a.move() returns the position we should be at, at each step.

Example output (run with Xcode):

0.00000250000000000
0.00001000000000000
0.00002250000000000
0.00004000000000000
0.00006250000000000
0.00009000000000000
0.00012250000000000
0.00016000000000000
0.00020250000000000
0.00025000000000000
0.00030250000000000
......keeps running

I used the timer interrupt to effectively call the a.move() method, etc, ever 2ms. Alternative was for flag to be a boolean set to true every time the ISR is called, and check in loop whether flag == true (and then set to false), but this would have required me to change the Timer1 frequencies.

Hope that makes sense.

Still missing major pieces of the puzzle.

0.00000250000000000

Assuming that your setpoint corresponds to the desired motor shaft angular position, how do those tiny fractions relate to that position, to the encoder counts and to the output PWM values?

Does your motor run well at constant speed settings (for various speeds from min to max)?

When running at constant speed, what is the system time constant in response to a perturbation, and how does that relate to the sample period and PID loop time?

Have you optimized the PID settings for proper response to a perturbation, when running at constant speed?

jremington:
Still missing major pieces of the puzzle.
Assuming that your setpoint corresponds to the desired motor shaft angular position, how do those tiny fractions relate to that position, to the encoder counts and to the output PWM values?

Does your motor run well at constant speed settings (for various speeds from min to max)?

When running at constant speed, what is the system time constant in response to a perturbation, and how does that relate to the sample period and PID loop time?

Have you optimized the PID settings for proper response to a perturbation, when running at constant speed?

The setpoint itself is what the encoder value should be at that point in time. I.e. Suppose we want to accelerate at 5 feet/sec/sec. We know that 1000 encoder counts = 20 feet (totally made up values here) --- 1000/20 = 50, so 5 feet/sec/sec amounts to 50*5 = 250 encoder counts/sec/sec. Does that make sense?

The motor generally runs well from half way to max speed.

Unsure of the terms you've used above. Will have to read up on them.

What other pieces are you missing? :slight_smile:

Does that make sense?

Nope. Your scenario has no useful relationship to the problem at hand, which is shaft angular position. It is pointless to make up analogies in response to specific questions.

If you don't understand the concepts of "system time constant" and "system response to a perturbation", then you are missing out on the key aspects of PID tuning.

ed13:
Inside the loop() method, I check to see if "flag mod 2 == 0". If so, 2ms have passed since previous calculation and setpoint is regenerated.

Not a good assumption. There could be other things going on which cause you to miss the desired millisecond and then you have 4 milliseconds between calculations. And no way to tell that you missed a calculation, either.

Note that the normal Arduino millis() function can skip a millisecond under rare circumstances too.

jremington:
Nope. Your scenario has no useful relationship to the problem at hand, which is shaft angular position. It is pointless to make up analogies in response to specific questions.

If you don't understand the concepts of "system time constant" and "system response to a perturbation", then you are missing out on the key aspects of PID tuning.

Isn't shaft angular position proportional to the encoder resolution?
What is not making sense? I was giving you an explanation of what the setpoint value represented and how it related to encoder counts.

MorganS:
Not a good assumption. There could be other things going on which cause you to miss the desired millisecond and then you have 4 milliseconds between calculations. And no way to tell that you missed a calculation, either.

Note that the normal Arduino millis() function can skip a millisecond under rare circumstances too.

I opted to use "millis() - timeold >= 3" instead.
Can you suggest a more suitable alternative? I understand it's generally not a good idea to write too much code in the ISR.

Hi,
What is your encoder?
Do the outputs of the encoder need pullup resistors, that is, are the outputs open collector?

Thanks.. Tom.. :slight_smile:

ed13:
I opted to use "millis() - timeold >= 3" instead.
Can you suggest a more suitable alternative? I understand it's generally not a good idea to write too much code in the ISR.

Where? Did you post the new code and I missed it?

My advice is not to re-invent millis(). That code looks like it came from somebody who is used to programming microcontrollers and isn't used to Arduino. Just use the millis() function that is provided for you.

See Nick Gammon's excellent page for the full writeup.

Now that I look at your original code again, it looks like it will run several times in the same millisecond. That's really going to screw up the PID.

TomGeorge:
Hi,
What is your encoder?
Do the outputs of the encoder need pullup resistors, that is, are the outputs open collector?

Thanks.. Tom.. :slight_smile:

Hi,

Don't know for sure. Believe it or not, the bot manual didn't specify.
From what I can see, the motor is a faulhaber m124-y2002. I imagine the encoder is embedded...

As for your second question, honestly no idea; The manual is very vague.

MorganS:
Where? Did you post the new code and I missed it?

My advice is not to re-invent millis(). That code looks like it came from somebody who is used to programming microcontrollers and isn't used to Arduino. Just use the millis() function that is provided for you.

See Nick Gammon's excellent page for the full writeup.

Now that I look at your original code again, it looks like it will run several times in the same millisecond. That's really going to screw up the PID.

Sorry, didn't post it here. It did seem to have a slightly positive impact on the smoothness though, although still not as smooth as I need.

Which code are you referring to (code I wrote?)? I believe I did use the Arduino millis() provided.

ed13:

SIGNAL(TIMER0_COMPA_vect) 

{
  flag++;
}

That looks like a re-invention of millis().

MorganS:
That looks like a re-invention of millis().

Ahhhh yes, I've removed all of that now.

Replaced with:

void loop() {
 
 while (!a.complete()){
   digitalWrite(LED_BUILTIN, HIGH);
   if (millis() - timeold >= 3){
     //digitalWrite(LED_BUILTIN, HIGH);
     timeold - millis();
   Setpoint = a.move();
   
   Input = myEnc.read();
   //Input2 = myEnc2.read();
   
   if (myPID.Compute()){
  // myPID2.Compute();


 digitalWrite(8, HIGH); //direction
 //digitalWrite(11, HIGH); //direction

 analogWrite(9,Output); //pwm
 //analogWrite(10,Output2);
   }
 
   }
 
 }