PID woes with large and slow inertia system

Hi,

A 25v geared brushed motor driven by 16 bit PWM (timer configured from default 8 bit) into a darlington, turning a 2kg steel disc. At 100% duty applied at zero standstill takes 5 seconds or so to reach full speed.

The load on the disc is variable and unpredictable. The disc RPM peaks at about 300RPM and three magnets to a hall sensor increase the read resolution to 900 pulses/minute.

I'm using the FastPid library but found integral windup was impossible to avoid overshoot on cold start, instead I waited till it had stabilised (after 20 seconds or so), read the integral sum and pre-load the FastPid c library with it (PID_INTERAL_WINDUP). Now what I do is apply 25% duty until the base speed is attained then FastPid is enabled and takes over.

The PID routine is called at 31Hz and the period is calculated by timer in period capture via interrupt, so both are fast and accurate.

static float Kp=0.1, Ki=0.2, Kd=0.1;
FastPID myPID(Kp, Ki, Kd, MOTION_PID_FREQUENCY, 16U, false);

void motionBalancer (void) {

  if (! motion.targetPeriod) {
    return;
  }

  if (! motion.ignited) {
    if (motion.period && motion.period < MOTION_IGNITE_PERIOD) {
      motion.ignited = true;
      myPID.clear();
      myPID._sum = PID_INTERAL_WINDUP;
      return;
    }
    MOTION_PWM = MOTION_IGNITE_PWM;
    return;
  }

  MOTION_PWM = myPID.step(motion.targetPeriod, motion.period);
}

I have played with the P, I, D variables but they have little effect or tend to result in no motion at all.

The problem is the PID is nowhere near reactive enough. As load on the system increases or decreases there is a a dramatic slow-down or over-shoot which can be well over 50% of the speed range.

Three questions come to mind:

  1. which P/I/D values are responsible for this and could I focus on?

  2. is PID going to be suitable? Perhaps I should just use a 0/100 duty instead? Although the mass is considerable it would make it a little jumpy, or might generate more heat in the motor but I can see this being the best approach for reactiveness.

  3. detach PID when speed is outside of a window and use 0/100% duty, i.e < 90% or > 110%. If I do this how would integral sum work? If I set it to zero it goes nuts which is why I pre-load it for a known speed (see earlier paragraph).

Cheers, Andrew

There are systematic approaches to PID tuning, and plenty of tutorials on the web explaining them.

However, if your electromechanical system is very nonlinear (for example, with inadequate motor driver or power source), then PID won't work well.

You may need a motor driver with brake mode, which has a more linear RPM versus PWM value relationship.

A brake mode is for a brushless motor right (reverse the current)? Mine's DC and brush based.

No.

PWM brake mode on a brushed DC motor driver connects the motor terminals directly together, as a "short circuit", which provides braking action instead of coasting.

Using this mode the RPM versus PWM response is much more linear.

I would expect that PID properly tuned could take care of this, but don't forget that PID is a generic controller that knows nothing of the system it's controlling. It may be better to write something that does know. Or a combination.

Perhaps run PID until the system gets slowed down too much, switch to manual mode and apply maximum power until you're back in range and hand control back to the PID.

A long time ago I was taught
a) Turn off I and D, increase P until unit oscillates, have P value
b) Set integral time to oscillation period
c) never use D

Another option is a free running oscillator that triggers a correction pulse proportional to the error. We used this for Hyro governor control for KW set points. The beauty about it is that you can alter the oscillator frequency ( period) for different conditions, ie run up, run down, sudden load change, normal operation.. A poor man's fuzzy PID logic.

PID tuning is a black art and worth reading up on .
Bear in mind with control systems such as yours the human is able to compensate for non linear loops than a PID controller, so you will have to be a bit sub optimal and slower to respond .
As previous tune with p only first , it will control away from your set point and you are looking for slight oscillation around some offset value . try with different loads etc to get the best you can .
I’d then start adding integral to get to the set point ( you might have to readjust P to keep it stable) .
If it overshoots a lot when you step change the load add a bit of derivative and see how it goes .

If the system is very non linear you might have to set the P lower and start over to keep it always stable
You need to devote some time to this aspect . You really need to get a feel for it and the effect of twiddling each term.

In my mind if the speed is 10% below reference it shouldn't gradually increase but get it back within 10% as quickly as possible, so 100% duty. Given the mass is so large this alone won't produce overshoot.

From this my thoughts are on disabling PID above or below the threshold, but the problem, which may be a quirk in FastPID library, is once reset the PID goes beserk with overshoot if the integral isn't preloaded with a value. It's this I need to figure out.

100% duty makes the motor jittery (torque), it's OK for start-up and out of threshold but I wouldn't want to be doing it inside the window thus I'd like to retain PID.

The regular PID library has been written to deal smoothly with transition from manual back to automatic. No idea about FastPID though.

Hello!

There's good advice in this thread about tuning. I wrote FastPID but I'm not an expert in mechanical systems. I can tell you this: If the velocity of the wheel is all that matters and you don't care about the distance it has traveled you don't want an I term (and probably not D either). The FastPID library doesn't include some of the features of ArduinoPID because I believe they're better implemented outside the library.

I have a code suggestion. If you want the PID to be very reactive (i.e. have a big P term) it should only be allowed to act when the motor is near the set point (say 10%). Outside of that range you can command "all on" or "all stop". I would suggest having your code do something more like this:

​if (speed < (target * 0.9)) {
 full_power();
 my_pid.clear();
}
else if (speed > (target * 1.1)) {
 no_power();
 my_pid.clear();
}
else {
 set_power(my_pid.step(...));
}
[pre]​

[/pre]

I've got it working well, very pleased.

  1. Increased resolution of reads from 3 to 26/rpm.
  2. Included a slight step on the lower window to prevent ripping the gearbox up!
  3. Increased timer frequency for steps to 60Hz.
void motionBalancer (void) {

  if (! motion.target) {
    return;
  }

  if (motion.kph < pidWindow[0]) {
    if (MOTION_PWM < MOTION_PWM_CEIL-500U) {
      MOTION_PWM += 500U;
    }
    myPID.clear();
    return;
  }

  if (motion.kph > pidWindow[1]) {
    MOTION_PWM = 0;
    myPID.clear();
    return;
  }

  MOTION_PWM = myPID.step(motion.target, motion.kph);
}

Playing with the PID values, I settled on

static float Kp=200, Ki=30, Kd=3;

Playing around shows that Kp had to be very high to be reactive. If Ki=0 then it wouldn't vary when the load changed, so that went up, Kd seems to dampen Ki to prevent overshoot/oscillations.

With a large amount of inertia in the system you ideally want to measure the torque (motor current)
and have an inner current-control PID loop (running way faster than 31Hz).

Then the main speed control loop drives the set-point for the inner loop, effectively controlling torque
directly.

Having a proper encoder on the disc so that speed can be measured with much lower latency would
be good. Currently you have very few counts per rev, so at slow speeds you have high latency judging
the disc speed, leading to unstable or sluggish control.

Another approach if you want to control speed is to abandon angle-based sensors entirely and use a
DC tachogenerator as a direct speed sensor - this has no inherent latency.

You haven't actually stated that you are trying to control speed rather than position (angle), so
I'm assuming that.