Questions about Encoders and PIDControllers

I’m attempting to implement a PID loop using Encoders and Jaguars by using the PIDController class. We’ve set up the Encoders to report the rate when PIDGet(); is called. (We currently don’t have CAN set up, so that’s not an option).

What units should the reported rate be in? Should they be calibrated using ‘SetDistancePerPulse()’ so they return a range from -1.0 to 1.0 (the same range the Jaguars use)? Or should I not calibrate them at all and leave the encoders alone?

If I use ‘SetSetpoint(1.0)’, does that mean the PIDController class will try and output some number to the Jaguars so that the encoders eventually report ‘1.0’?

The simple answer is that if you do what you’ve described, the pid loop will try, but fail to reach you speed setpoint. I’m not sure if you’re trying to do speed or position, but if you’re doing speed it isn’t as easy. The problem is that the pid loop won’t work if you’re trying to control speed. With a positional PID loop, when the setpoint is reached, the pid loop will output a zero command to the motor. This makes sense because the motor has reached where it wants to be, and should stop. If you’re trying to control speed, you don’t want the pid loop to set the motor speed to zero once you’ve reached the right speed, but instead, you should keep the motor output it is at now. What you have to do is add the error calculated from the pid loop to the current motor output. To do this, you need a class which implements pidoutput. Here’s an example in java.


public class VelocityControl extends Subsystem implements PIDOutput{
    private PIDController pid;
    private Encoder encoder;
    private Jaguar jaguar;
    public VelocityControl(){
        encoder = new Encoder(1, 2);
        jaguar = new Jaguar(1);
        encoder.setDistancePerPulse(0.01);//set this normally
        encoder.setPIDSourceParameter(Encoder.PIDSourceParameter.kRate);
        pid = new PIDController(1, 0.1, 0.02, encoder, this);//set/tune your pid gains as normal
        pid.setOutputRange(0.03, -0.03); //For our shooter, these values worked well with the default 50 ms loop time for the pid loop
        //They work even though the jaguar range is 1 to -1 because 0.03 gets added to itself many times
    }

    public void initDefaultCommand() {
    }

    public void pidWrite(double output) {
        jaguar.set(jaguar.get()+output);
    }
}

No, that’s not how PID works. If you set the constant for I (Integral) to an appropriate value, the PID output will be a good value to maintain the setpoint. A zero output is what would happen if you used a Proportional only control.

Controlling speed and position aren’t really different, once you get your units of measurement and conversion factors (converting motor input to desired output) right. Position seems different because when the system reaches the setpoint, typically the output should go to zero. If you think of controlling position on an incline, then you still need a non-zero output when the system reaches the setpoint, so it’s about the same.

Here are some good threads on PID control:

Robodox Guide to velocity control using PID

2012: Tuning the Jaguar’s Speed Control PID Loop

Alternative to PID speed control

No, that’s not how positional P-only works. With P-only, there would still need to be an output of sufficient magnitude to maintain a position against any external forces. With P-only, this output comes from the steady-state positional error.

Controlling speed and position aren’t really different, once you get your units of measurement and conversion factors (converting motor input to desired output) right.

Yes, they really are that different. If you are controlling position, then the plant is an integrating plant. If you are controlling speed, then the plant is not an integrating plant. They require different strategies.

http://www.chiefdelphi.com/forums/showthread.php?t=101285?p=1115090&postcount=5

We actually agree on this. I said the output would go to zero if the system reaches the setpoint. With P-only, the system will never reach and hold the setpoint; it can hold a steady error.

That assumes you are always measuring distance, whether controlling position or speed. If you measure speed for speed control, and set your P, I, and D constants accordingly, it works out to the same concept.

Maybe we have different interpretations of the word “same”.

Unless you integrate the output of the PID controller, P, I, and D behave differently when tuning for speed instead of position. In that sense, it is not the same.

You can make a simple velocity control by integrating the output of the pid loop. As Ether has said before, if you have JUST an I gain, the pid loop will be able to maintain the right speed. But with the Java implementation of PID, the whole output is multiplied by P. So, when you make p 0, the output will be zero and without modifying the code, you’ll never get just an I gain. So, if you just add the error to the current speed, it will integrate the output. On our robot, our shooter wheel will not work with a normal pid control. In order to make the pid loop work with velocity, you must integrate the output. Other software I’ve used has separate functions for positional/velocity pid.

We achieved reasonably good velocity control using PID, but it required making a custom PIDOutput sink. Basically, what we did is - instead of treating the output to the PID loop as a value to send to the motor, we treated it as a difference to apply to the current value being sent to the motor, and limited this change to +/- .25 in order to keep it from changing too erratically.

As a rough sketch:


//global variables just for demonstration purposes
//set these up!
Jaguar jag;
Encoder enc;
float p, i, d;

class spd_control : public PIDOutput {
   float speed;
public:
   void PIDWrite(float offset) {
      speed = coerce(speed + offset, -1.0f, 1.0f);
      jag.Set(speed);
   }
};

spd_control jag_spd_ctl;

PIDController control(p, i, d, enc, jag_spd_ctl);
control.SetInputRange(0.0, MAX_SPEED);
control.SetTolerance(TOLERANCE);
control.SetOutputRange(-0.25, 0.25);

Horrible code, but you get the idea.

As has been pointed out the problem with PID controlling velocity directly is that as the motor speed reaches the setpoint, the motor speed goes to zero.

New for 2013 is the addition of a feedforward term (4th parameter on the constructors) that is a baseline value that is added to the output value. This term is multiplied by the setpoint so that it scales with differing speeds. The feedforward term will prevent the motor speed oscillation as the motor speed approaches the setpoint.

Here’s the code that actually computes the final value in the PID controller class:


m_result = m_P * m_error
               + m_I * m_totalError
               + m_D * (m_error - m_prevError) 
               + m_setpoint * m_F;

where m_P, m_I, m_D, and m_F are the P, I, D, and F constants. The m_error is the difference between the current value and the setpoint, and the m_prevError is the value of the error from the previous iteration.

Brad

Somehow I don’t think that’s what you meant.

Maybe that was a little confusing…
I meant that as the actual speed reaches the setpoint speed, the error term goes to zero. Suppose there was only a proportional term, then the output value from the PID controller would go towards zero (proportional constant times zero is zero, similar for the other terms).

That’s what the feedforward term attempts to fix.

Much better :slight_smile:

Suppose there was only a proportional term, then the output value from the PID controller would go towards zero (proportional constant times zero is zero, similar for the other terms).

It’s not similar for the other terms. The I term can be non-zero at the setpoint. In fact, that’s one of the purposes for the I term, to provide a non-zero output at the setpoint (if required).

That’s what the feedforward term attempts to fix.

Yes, feedforward is one way to address that issue.

Another is to use the I term, when controlling speed, like you would use the P term when controlling position.

Yet another is to integrate the output of the PID.

This. I think it’s more intuitive to tune it that way than using the I term itself. This is basically what my example earlier does.

Does the Java implementation of PID multiply the whole loop output by Kp, or does it just multiply the first term?

I couldn’t find a link to download the 2013 Java WPILib source, but here’s the 2012 C++ WPILib:

m_result = m_P * m_error + m_I * m_totalError + m_D * (m_error - m_prevError);

What do you mean by integrate the output? I take calculus and know the concepts behind PID but this wording is confusing me a bit. Could you please provide an example?

The WPILibJ source code is in the sunspotfrcsdk/lib directory in two zip files. One has the full project and the other has the source directory only.

Brad

Normally the output from the PID is the command that gets sent to the motor every control iteration.

Instead of doing that, keep an accumulator which gets incremented each control iteration by the PID output times some gain.

e.g.

accumulator += gain * PID_output

Use the value in the accumulator as the command to the motor.

(you’ll also want to clamp the value in the accumulator)

I couldn’t find a link to download the 2013 Java WPILib source

That assumes I already have Java installed here. I don’t. Is there a link somewhere to download just the 2013 Java WPILib source, for folks (like me) who just want the source for reference, not for Java development?

For one thing I never understood why they don’t just offer anonymous svn access to the repos WPILib on FirstForge. In addition to seeming a little against the ethos of FIRST, is the repo metadata that dangerous? Seriously?

It seems like WPILib development should be a little more open to the community… I understand if they want to keep a few files secret (e.g. kinect last year), but I see no reason that the incremental changes that they published this year needed to be secret until kickoff. If there had been something really new, then they could just develop that separately and have a script commit it at kickoff. This might be better suited to a DVCS like git where you could have a local branch so you don’t loose commit history, but I digress.