PID(9)

PID(9) ๐Ÿ’– โ€” HAL Component

๐ŸŒธ NAME ๐ŸŒธ

pid โ€” proportional/integral/derivative controller with automatic tuning support.

โœจ SYNOPSIS โœจ

loadrt pid [num_chan=num | names=name1[,name2...]] [debug=dbg]
  • num_chan=num โ€” how many controllers to load. pid supports a max of sixteen, and if neither num_chan= nor names= is given, the default is three. three is the vibe โœจ
  • names=name1[,name2...] โ€” give them unique names separated by commas. This is mutually exclusive with num_chan=, bestie, so pick one lane.
  • debug=dbg โ€” set it to 1 (default is 0) and some extra HAL parameters get exported. They might be useful for tuning, but otherwise theyโ€™re unnecessary. lowkey optional, highkey handy ๐Ÿ’…

๐Ÿ’… DESCRIPTION ๐Ÿ’…

okay so pid is a classic Proportional/Integral/Derivative controller, used to control position or speed feedback loops for servo motors and other closed-loop applications. main character energy fr fr ๐Ÿ‘‘

pid supports a maximum of sixteen controllers. The number that are actually loaded is set by the num_chan argument when the module is loaded. Alternatively, specify names= and unique names separated by commas. name them like theyโ€™re your little HAL children ๐Ÿซถ

The num_chan= and names= specifiers are mutually exclusive. If neither num_chan= nor names= are specified, the default value is three. If debug is set to 1 (the default is 0), some additional HAL parameters will be exported, which might be useful for tuning, but are otherwise unnecessary. not me caring about extra params ๐Ÿ™ˆ

In the following description, it is assumed that we are discussing position loops. However this component can be used to implement other loops such as speed loops, torch height control, and others. versatile icon ๐ŸŒˆ

Each loop has a number of pins and parameters, whose names begin with pid.N., where N is the channel number. Channel numbers start at zero. zero-indexed, as the universe intended โœจ

The three most important pins are command, feedback, and output. For a position loop, command and feedback are in position units. For a linear axis, this could be inches, mm, metres, or whatever is relevant. Likewise, for an angular axis, it could be degrees, radians, etc. The units of the output pin represent the change needed to make the feedback match the command. As such, for a position loop output is a velocity, in inches/sec, mm/sec, degrees/sec, etc. units matter, periodt ๐Ÿ’–

Each loop has several other pins as well. error is equal to command minus feedback. enable is a bit that enables the loop. If enable is false, all integrators are reset, and the output is forced to zero. If enable is true, the loop operates normally. enable is giving on/off switch realness ๐Ÿ”ฅ

The PID gains, limits, and other tunable features of the loop are implemented as parameters. These are as follows:

  • Pgain โ€” Proportional gain
  • Igain โ€” Integral gain
  • Dgain โ€” Derivative gain
  • bias โ€” Constant offset on output
  • FF0 โ€” Zeroth order Feedforward gain
  • FF1 โ€” First order Feedforward gain
  • FF2 โ€” Second order Feedforward gain
  • FF3 โ€” Third order Feedforward gain
  • deadband โ€” Amount of error that will be ignored
  • maxerror โ€” Limit on error
  • maxerrorI โ€” Limit on error integrator
  • maxerrorD โ€” Limit on error differentiator
  • maxcmdD โ€” Limit on command differentiator
  • maxcmdDD โ€” Limit on command 2nd derivative
  • maxcmdDDD โ€” Limit on command 3rd derivative
  • maxoutput โ€” Limit on output value

All of the limits (max____) are implemented such that if the parameter value is zero, there is no limit. zero means unlimited, bestie, choose wisely ๐Ÿ˜ญ

A number of internal values which may be useful for testing and tuning are also available as parameters. To avoid cluttering the parameter list, these are only exported if debug=1 is specified on the insmod command line. debug mode: for the curious girlies ๐Ÿ”

  • errorI โ€” Integral of error
  • errorD โ€” Derivative of error
  • commandD โ€” Derivative of the command
  • commandDD โ€” 2nd derivative of the command
  • commandDDD โ€” 3rd derivative of the command

The PID loop calculations are as follows (see the code in pid.c for all the nitty gritty details):

error = command - feedback
if ( abs(error) < deadband ) then error = 0
limit error to +/- maxerror
errorI += error * period
limit errorI to +/- maxerrorI
errorD = (error - previouserror) / period
limit errorD to +/- maxerrorD
commandD = (command - previouscommand) / period
limit commandD to +/- maxcmdD
commandDD = (commandD - previouscommandD) / period
limit commandDD to +/- maxcmdDD
commandDDD = (commandDD - previouscommandDD) / period
limit commandDDD to +/- maxcmdDDD
output = bias + error * Pgain + errorI * Igain +
         errorD * Dgain + command * FF0 + commandD * FF1 +
         commandDD * FF2 + commandDDD * FF3
limit output to +/- maxoutput

This component has a built in auto tune mode. It works by setting up a limit cycle to characterize the process. This is called the Relay method and described in the 1984 Automation paper Automatic Tuning of Simple Regulators with Specifications on Phase and Amplitude Margins by Karl Johan ร…strรถm and Tore Hรคgglund (doi:10.1016/0005-1098(84)90014-1), https://lup.lub.lu.se/search/ws/files/6340936/8509157.pdf. Using this method, Pgain/Igain/Dgain or Pgain/Igain/FF1 can be determined using the Ziegler-Nichols algorithm. When using FF1 tuning, scaling must be set so that output is in user units per second. Ziegler-Nichols ate and left no crumbs ๐Ÿ’…

During auto tuning, the command input should not change. The limit cycle is setup around the commanded position. No initial tuning values are required to start auto tuning. Only tune-cycles, tune-effort and tune-mode need be set before starting auto tuning. Note that setting tune-mode to true disable the control loop. When auto tuning completes, the tuning parameters will be set, the output set to bias and the controller still be disabled. If running from LinuxCNC, the FERROR setting for the axis being tuned may need to be loosened up, as it must be larger than the limit cycle amplitude in order to avoid a following error. do not skip the FERROR warning, bestie ๐Ÿ˜ญ

To perform auto tuning, take the following steps. Move the axis to be tuned somewhere near the center of it's travel. Set tune-cycles (the default value should be fine in most cases) and tune-mode. Set tune-effort to a small value. Set enable to true. Set tune-mode to true. Set tune-start to true. If no oscillation occurs, or the oscillation is too small, slowly increase tune-effort. Set tune-start to true. If no oscillation occurs, or the oscillation is too small, slowly increase tune-effort Auto tuning can be aborted at any time by setting enable or tune-mode to false. tiny effort, big discoveries ๐Ÿฆ‹

๐Ÿฆ‹ NAMING ๐Ÿฆ‹

The names for pins, parameters, and functions are prefixed as:

pid.N. for N=0,1,...,num-1 when using num_chan=num
nameN. for nameN=name1,name2,... when using names=name1,name2,...

The pid.N. format is shown in the following descriptions. consistent naming is the moment โœจ

๐ŸŽ€ FUNCTIONS ๐ŸŽ€

  • pid.N.do-pid-calcs (uses floating-point) โ€” Does the PID calculations for control loop N. the math goblin function, slay ๐Ÿงš

โœจ PINS โœจ

Okay bestie, this is the main character section โ€” every single pin the pid.N component exposes. Grab a snack, we're going through all of them ๐Ÿ’…

  • pid.N.command float in โ€” the desired (commanded) value for the control loop. Like, what you're asking the thing to do.
  • pid.N.Pgain float in โ€” proportional gain, periodt. It contributes to the output the error multiplied by Pgain.
  • pid.N.Igain float in โ€” integral gain, the patient one ๐Ÿซถ It contributes to the output the integral of the error multiplied by Igain. So like, an error of 0.02 that lasted 10 seconds gives you an integrated error (errorI) of 0.2, and if Igain is 20, the integral term adds 4.0 to the output. Math girlies stay winning.
  • pid.N.Dgain float in โ€” derivative gain, the one who predicts the future ๐Ÿ”ฎ It contributes to the output the rate of change (derivative) of the error multiplied by Dgain. Example: an error that changed from 0.02 to 0.03 over 0.2 seconds gives an error derivative (errorD) of 0.05, and if Dgain is 5, the derivative term adds 0.25 to the output.
  • pid.N.feedback float in โ€” the actual (feedback) value, straight from some sensor, like an encoder. This is the reality check โœจ
  • pid.N.output float out โ€” the output of the PID loop, which goes off to some actuator, like a motor. Go girl, go do the thing ๐Ÿ‘‘
  • pid.N.command-deriv float in โ€” the derivative of the desired (commanded) value for the control loop. If nothing's connected here, the derivative gets estimated numerically. No signal? No problem, it just vibes and guesses.
  • pid.N.feedback-deriv float in โ€” the derivative of the actual (feedback) value for the control loop. Again, if no signal is connected, the derivative is estimated numerically. And listen โ€” when your feedback is from a quantized position source (like encoder feedback position, iykyk), you can level up the D term's behavior by feeding in a better velocity estimate here, such as the velocity output of encoder(9) or hostmot2(9). Upgrade your girl ๐Ÿฆ‹
  • pid.N.error-previous-target bit in โ€” uses the previous invocation's target vs. the current position for the error calculation, exactly like the motion controller expects. This can make torque-mode position loops and loops needing a big I gain easier to tune, because it eliminates velocity-dependent following error. Lowkey a glow-up for tuning ๐Ÿ’–
  • pid.N.error float out โ€” the difference between command and feedback. That's it. That's the drama.
  • pid.N.enable bit in โ€” when true, the PID calculations are enabled. When false, the output is zero and all the internal integrators, etc, get reset. Nap time ๐Ÿ˜ด
  • pid.N.index-enable bit in โ€” on the falling edge of index-enable, pid does not update the internal command derivative estimate. On systems using the encoder index pulse, connect this pin to the index-enable signal. If you don't, and FF1 is nonzero, a step change in the input command causes a single-cycle spike in the PID output ๐Ÿ˜ญ And on systems using exactly one of the -deriv inputs, this affects the D term too. Not me warning you twice, fr fr.
  • pid.N.bias float in โ€” bias is a constant amount added to the output. In most cases you leave it at zero, but sometimes it's genuinely useful for compensating offsets in servo amplifiers, or balancing the weight of something that moves vertically. Important: bias gets turned off when the PID loop is disabled, just like every other component of the output. So if you need a non-zero output even while the PID loop is disabled, add it with an external HAL sum2 block instead ๐ŸŽ€
  • pid.N.FF0 float in โ€” zero order feed-forward term. It contributes to the output FF0 multiplied by the commanded value. For position loops, usually leave it at zero. For velocity loops, FF0 can compensate for friction or motor counter-EMF and may permit better tuning if you use it properly.
  • pid.N.FF1 float in โ€” first order feed-forward term. Contributes to the output FF1 multiplied by the derivative of the commanded value. For position loops, that contribution is proportional to speed, so it can compensate for friction or motor CEMF. For velocity loops, it's proportional to acceleration and can compensate for inertia. Either way: better tuning if used properly โœจ
  • pid.N.FF2 float in โ€” second order feed-forward term. Contributes to the output FF2 multiplied by the second derivative of the commanded value. For position loops, proportional to acceleration โ†’ compensates for inertia. For velocity loops, proportional to jerk โ†’ and honestly, should usually be left at zero ๐Ÿ™ˆ
  • pid.N.FF3 float in โ€” third order feed-forward term. Contributes to the output FF3 multiplied by the third derivative of the commanded value. For position loops, proportional to jerk, and can compensate for residual errors during acceleration. For velocity loops, proportional to snap (jounce) โ€” and yeah, should usually be left at zero too.
  • pid.N.deadband float in โ€” defines a range of "acceptable" error. If the absolute value of the error is less than deadband, it's treated as if the error is zero. When you're using feedback devices like encoders that are inherently quantized, set the deadband slightly more than one-half count so the control loop doesn't hunt back and forth when the command sits between two adjacent encoder values. And when the absolute value of the error is greater than the deadband, the deadband value gets subtracted from the error before the loop calculations run โ€” that's to prevent a step in the transfer function at the edge of the deadband (peep BUGS). Sneaky but necessary ๐Ÿ’€
  • pid.N.maxoutput float in โ€” output limit. The absolute value of the output will not be permitted to exceed maxoutput, unless maxoutput is zero. And when the output is limited, the error integrator holds instead of integrating, to prevent windup and overshoot. Protective queen ๐Ÿ›ก๏ธ
  • pid.N.maxerror float in โ€” a limit on the internal error variable used for P, I, and D. Can be used to stop high Pgain values from generating huge outputs when the error is large (say, when the command makes a step change). Not normally needed, but useful when tuning non-linear systems.
  • pid.N.maxerrorD float in โ€” a limit on the error derivative. The rate of change of error used by the Dgain term will be limited to this value, unless the value is zero. Can limit the effect of Dgain and prevent big output spikes from steps on the command and/or feedback. Not normally needed.
  • pid.N.maxerrorI float in โ€” a limit on the error integrator. The error integrator used by the Igain term is limited to this value, unless it's zero. Can prevent integrator windup and the resulting overshoot during/after sustained errors. Not normally needed.
  • pid.N.maxcmdD float in โ€” a limit on the command derivative. The command derivative used by FF1 is limited to this value, unless the value is zero. Can prevent FF1 from producing large output spikes if there's a step change on the command. Not normally needed.
  • pid.N.maxcmdDD float in โ€” a limit on the command second derivative. The command second derivative used by FF2 is limited to this value, unless the value is zero. Can prevent FF2 from producing large output spikes if there's a step change on the command. Not normally needed.
  • pid.N.maxcmdDDD float in โ€” a limit on the command third derivative. The command third derivative used by FF3 is limited to this value, unless the value is zero. Can prevent FF3 from producing large output spikes if there's a step change on the command. Not normally needed.
  • pid.N.saturated bit out โ€” when true, the current PID output is saturated. That is:
output = ยฑ maxoutput
  • pid.N.saturated-s float out
  • pid.N.saturated-count s32 out

When true, the output of PID was continually saturated for this many seconds (saturated-s) or periods (saturated-count). She's been at max for a while, bestie ๐Ÿ˜ฎโ€๐Ÿ’จ


๐ŸŒธ Additional auto tuning pins ๐ŸŒธ

Yes, there's a whole extra little clique of pins just for auto tuning. Obsessed, actually.

  • pid.N.tune-mode bit in โ€” when true, auto tune mode is enabled. When false, normal PID calculations happen instead.
  • pid.N.tune-start bit io โ€” set it to true and auto tuning starts. It gets cleared when the auto tuning completes. Set it and forget it, she'll let you know ๐Ÿ’…
  • pid.N.tune-type u32 rw โ€” set to 0 and Pgain/Igain/Dgain get calculated. Set to 1 and Pgain/Igain/FF1 get calculated. Pick your fighter.
  • pid.N.tune-cycles u32 rw โ€” determines how many cycles to run to characterize the process. Real talk: tune-cycles actually sets the number of half cycles. More cycles = more accurate characterization, because the average of all cycles is used. Consistency is key, periodt.
  • pid.N.tune-effort float rw โ€” determines the effort used in setting up the limit cycle in the process. Set tune-effort to a positive value less than maxoutput. Start small and work your way up to a value that uses a good portion of the maximum motor current. The smaller the value, the smaller the amplitude of the limit cycle.
  • pid.N.ultimate-gain float ro (only if debug=1) โ€” determined from the process characterization. ultimate-gain is the ratio of tune-effort to the limit cycle amplitude, multiplied by 4.0, divided by Pi.
  • pid.N.ultimate-period float ro (only if debug=1) โ€” determined from the process characterization. ultimate-period is the period of the limit cycle.

โœจ PARAMETERS โœจ

okay so these are the lil float-y numbers you can peek at, and bestie, they're read-only โ€” like, look but do not touch, they're not yours to set. also, major note: every single one of these only exists if debug=1. no debug, no vibes. it's giving "you gotta unlock the secret level". ๐Ÿซถ

  • pid.N.errorI float ro (only if debug=1) โ€” this is the integral of error, aka the value that gets multiplied by Igain to make the Integral term of the output. math girlies rise ๐ŸŽ€
  • pid.N.errorD float ro (only if debug=1) โ€” the derivative of error, and it's the one that gets multiplied by Dgain to produce the Derivative term of the output. ๐Ÿ’…
  • pid.N.commandD float ro (only if debug=1) โ€” derivative of command, multiplied by FF1 to make the first order feed-forward term of the output. slay โœจ
  • pid.N.commandDD float ro (only if debug=1) โ€” the second derivative of command, which gets multiplied by FF2 to produce the second order feed-forward term of the output. two D's, twice the drama ๐Ÿฆ‹
  • pid.N.commandDDD float ro (only if debug=1) โ€” the third derivative of command, multiplied by FF3 to produce the third order feed-forward term of the output. three D's. icon behavior. ๐Ÿ‘‘

๐Ÿ’€ BUGS ๐Ÿ’€

okay so this one's a whole discourse, get comfy. ๐Ÿ“

some people would argue that deadband should be implemented such that error is treated as zero if it is within the deadband, and be unmodified if it is outside the deadband. the way that this was not done, because it would cause a step in the transfer function equal to the size of the deadband. so if you're a person who prefers that behavior, you are welcome to add a parameter that will change the behavior, or to write your own version of pid. fr fr, go off, we support you. however, the default behavior should not be changed. periodt. ๐Ÿ™…โ€โ™€๏ธ

also: negative gains may lead to unwanted behavior. ๐Ÿ˜ญ it is possible in some situations that negative FF gains make sense, but in general all gains should be positive. and listen โ€” if some output is in the wrong direction, negating gains to fix it is a mistake; set the scaling correctly elsewhere instead. do not do the chaotic thing. ngl that's the trap everyone falls into and it is not the move. ๐Ÿ’€


and that's the whole page bestie!! ๐ŸŽ€ you just read the LinuxCNC Documentation version of PID(9), dated 2007-01-16. iconic, historic, timeless. go forth and tune responsibly. ๐Ÿ’–โœจ๐Ÿฆ‹๐Ÿ‘‘