Integral term:

i += (error * dt * ki)

Alternatively, integral could be

i += (error * dt * kp) / ti using ti as integral time instead of integral gain.

Derivative term:

d = ((error - error_last) / dt) * kd

Alternatively, derivative could be

d = ((error - error_last) * kp * td) / dt) using td as the derivative time constant instead of kd as derivative gain.

In a positional controller, I deals with steady-state error:

-If the P and D terms land the output just a little bit from where it wants to be, eventually I will wind up and push it there.

-If the output requires a constant power to hold position, the I term will find it and stay there (once again, not instantly but over time).

This has issues:

-In a pure implementation, the I term HAS to overshoot to unwind.

-Most of the complex logic in implementing PID deals with limiting the integral term.

I’ve tried various integral limiting methods including:

-Reset I when state setpoint changes (ALWAYS)

-Hold I in reset when disabled (ALWAYS)

-Limit absolute boundaries of I

-Reset I when error zero-crosses

-Limit boundaries of I based on either a linear equation or lookup curve based on error (e.g. at error 0, I boundary is set at ±1, but at error 15, I boundary is set to ±0.25, and at error 30, I is completely zeroed out).

BUT, for a speed controller, the control is slightly different. Think about it as the input parameter is the derivative of position (which it is), so to get back to a motor power output you need to integrate the output. Then, you end up with:

P becomes I -> I does the work of P in a normal controller, since it has to find steady-state power and essentially has to do the heavy lifting

I becomes double-integrated and is useless

D becomes P -> P does the work of D and deals with transient response

Simply, if you implement a normal PI controller (with feed-forward), and calibrate P as if it was D and I as if it was P, you will end up with a velocity controller.

If you feed forward, then the ideal I contribution is 0 at steady state so you can limit the I term in various ways except zero-cross reset without worrying too much. You can also cal the I gain really low since it dosen’t have to lift very much. P gain would stay the same with or without feed forward control.

As for timing, I’ve had good luck with the RT timed loops. They do eat a lot of CPU usage, but they are worth it. A few tricks I’ve learned since we switched from LV 8.6 in terms of CPU load:

-Any VI call should be considered substantial overhead and avoided when possible.

-Any VI call that can be a subroutine (set in VI Properties->Execution->Priority) is substantially less overhead than a normal VI call, and should be considered zero overhead, BUT every subVI from a subroutine call has to also be a subroutine, and you can’t debug subroutines in realtime.

-Any VI call that can be re-entrant and is smal (e.g. math functions, button mapping functions, etc.) should be inlined (set re-entrant state to ‘Preallocate clones’, uncheck error handling and allow debugging, and check Inline SubVI, all under VI Properties->Execution)

-The WPIlib should be treated as horribly inefficient and any call to it should be thought out very carefully. I’ve gone through the trouble of reworking the majority of it to be more efficient, this is a huge PITA and time waster but it does save a ton of CPU time.

–If you actually read most of the VI’s, you will likely cry.

And, while debugging in realtime

-Any front panel indicator is inefficiency when the VI is open on your laptop and the code is running. The CPU penalty goes up with faster loop times also.