Strange behavior adding I term to P only PID for Spark velocity Control

We are tuning PID for velocity control on a spark, using this code. The unit on the graph is rpm. We tuned p-only until it flattened out at 750 RPM. The setpoint is 1000 RPM. Then, we introduced an I gain, which caused the rpm to oscillate, as shown in the graph. However, if we turn the I gain to 0 after making it a non-zero value, the rpm maxes out at ~5,200 RPM. Why doesn’t the controller settle back to it’s original 750 RPM, as kP and kI are the same as they were in that intial state?

This might be due to a really high kI value causing integral windup but I am not sure. Also, I’d recommend just using a PF loop and integrating the output, as the output voltage corresponds roughly to the motor RPM. In this case, your proportional value behaves like how “kI” would behave in a position PID controller.

Doing this naively (i.e. integrating after applying the feedforward) will result in a wildly unstable loop that rapidly shoots off to max output.

In general, integrator-driven velocity loops are not particularly effective in FRC applications (though they are definitely useful elsewhere), and a P loop with an accurate feedforward is all that is needed.


What are the terms you’re using? Integral windup is definitely the most likely culprit, and it would be really helpful to see your terms to check. It looks like you’re tuning a flywheel, if so, a normal PF controller is a better choice to get it up to 7500, but a low integral term in PI should also work ok.

I’m not sure you’ve given us enough information about what “turn the I gain to 0” means.

Are you changing the gains through smartDashboard or by changing code & restarting the robot?
You’ve described a Spark, but then linked SparkMax Closed Loop Velocity Control example code.
Are you changing the value through the Rev Client? If you restart the robot with I at 0, does it still hit 5200 or does it settle back at 750 the way it did before?

Agreed w/ other posters - looks like integral windup - you may need to restart the robot in order to reset the error accumulator on the SparkMax (or look for a method in the API to reset the accumulator).

Tuning methods (PI vs FP control)

Unrelated, it seems like you’re settling for 70% from P before using an I term to correct the final setpoint. Where is that method coming from? Is there some public tutorial that uses that method that we need to rewrite? It’s suitable for turbine generator spinup with high costs of failure, but not optimal for 99% of FRC mechanisms. Very vulnerable to integral windup (as you’ve just found) and slow to spin up.

The onboard Rev control loop has a Feed Forward term available - you could use that kFF Feed Forward term first to get ~95% of your goal, then add a P term and start trying to disturb the system (run balls through the flywheel?..) to see how it responds. Add P until you have some overshoot, then if necessary damp it back out with D. You should be able to get good enough results with just PF or maybe PFD.

If you do add an I term, I terms are generally on the order of 0.0001-0.001 - good for going from 98%-102% to 100% of setpoint, when more P just introduces instability. But what problem are you solving with I? For example - if you are going to 98% every time, you can just move the set point to 102% instead of putting in an I term.

We dealt with exactly this problem last night. What’s actually happening is that the Spark Max keeps track of an Integral Accumulator value while I is non-zero, but if you set I back to zero, it continues to use the last value of the I Accumulator. This is problematic because as long as I is zero, the I accumulator will never change, and you will continue to see behavior that looks like integral windup with P control. We solved the issue by adding the following code to our teleopPeriodic() method in our test project:

if(kI == 0) {

In the above snippet, kI is our I gain for the PID controller, and pidController is an instance of com.revrobotics.CANPIDController.

This may or may not be the case for you, but we also found that calling setIMaxAccum() to limit the maximum value of the I accumulator worked really well to reduce integral windup. For our system we ended up clamping it to 0.9, but it would probably be different for yours.

1 Like

That sounded like what OP was reporting but I didn’t want to jump to conclusions. I bet if you email your details of the problem to support@revrobotics they’ll put a bugfix for that in a firmware release ASAP. @Will_Toth

Yeah, I was planning to do that over the weekend. It’s probably an easy bug to fix.

1 Like

Yup, this is correct. I’ve added a fix we will need to test before any release. For now your workaround will work just fine. Another workaround for now when setting kI from a non-0 value to a 0 value is to set a small iZone value which will reset the integral state. This is guaranteed to work, since if the stored integral value is pushing the error, it will reset.

Example: pidController.setIZone(0.01);

You can see the code here:


Ah great, that makes a lot of sense. Aligns with the testing we did. Thank you for the help!

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.