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.pidsupports a max of sixteen, and if neithernum_chan=nornames=is given, the default is three. three is the vibe โจnames=name1[,name2...]โ give them unique names separated by commas. This is mutually exclusive withnum_chan=, bestie, so pick one lane.debug=dbgโ set it to1(default is0) 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 gainIgainโ Integral gainDgainโ Derivative gainbiasโ Constant offset on outputFF0โ Zeroth order Feedforward gainFF1โ First order Feedforward gainFF2โ Second order Feedforward gainFF3โ Third order Feedforward gaindeadbandโ Amount of error that will be ignoredmaxerrorโ Limit on errormaxerrorIโ Limit on error integratormaxerrorDโ Limit on error differentiatormaxcmdDโ Limit on command differentiatormaxcmdDDโ Limit on command 2nd derivativemaxcmdDDDโ Limit on command 3rd derivativemaxoutputโ 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 errorerrorDโ Derivative of errorcommandDโ Derivative of the commandcommandDDโ 2nd derivative of the commandcommandDDDโ 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 loopN. 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.commandfloat in โ the desired (commanded) value for the control loop. Like, what you're asking the thing to do.pid.N.Pgainfloat in โ proportional gain, periodt. It contributes to the output the error multiplied byPgain.pid.N.Igainfloat in โ integral gain, the patient one ๐ซถ It contributes to the output the integral of the error multiplied byIgain. So like, an error of 0.02 that lasted 10 seconds gives you an integrated error (errorI) of 0.2, and ifIgainis 20, the integral term adds 4.0 to the output. Math girlies stay winning.pid.N.Dgainfloat in โ derivative gain, the one who predicts the future ๐ฎ It contributes to the output the rate of change (derivative) of the error multiplied byDgain. Example: an error that changed from 0.02 to 0.03 over 0.2 seconds gives an error derivative (errorD) of 0.05, and ifDgainis 5, the derivative term adds 0.25 to the output.pid.N.feedbackfloat in โ the actual (feedback) value, straight from some sensor, like an encoder. This is the reality check โจpid.N.outputfloat 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-derivfloat 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-derivfloat 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 ofencoder(9)orhostmot2(9). Upgrade your girl ๐ฆpid.N.error-previous-targetbit 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.errorfloat out โ the difference betweencommandandfeedback. That's it. That's the drama.pid.N.enablebit 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-enablebit in โ on the falling edge ofindex-enable, pid does not update the internal command derivative estimate. On systems using the encoder index pulse, connect this pin to theindex-enablesignal. If you don't, andFF1is 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-derivinputs, this affects the D term too. Not me warning you twice, fr fr.pid.N.biasfloat in โbiasis 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:biasgets 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 HALsum2block instead ๐pid.N.FF0float in โ zero order feed-forward term. It contributes to the outputFF0multiplied by the commanded value. For position loops, usually leave it at zero. For velocity loops,FF0can compensate for friction or motor counter-EMF and may permit better tuning if you use it properly.pid.N.FF1float in โ first order feed-forward term. Contributes to the outputFF1multiplied 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.FF2float in โ second order feed-forward term. Contributes to the outputFF2multiplied 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.FF3float in โ third order feed-forward term. Contributes to the outputFF3multiplied 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.deadbandfloat in โ defines a range of "acceptable" error. If the absolute value of the error is less thandeadband, 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.maxoutputfloat in โ output limit. The absolute value of the output will not be permitted to exceedmaxoutput, unlessmaxoutputis zero. And when the output is limited, the error integrator holds instead of integrating, to prevent windup and overshoot. Protective queen ๐ก๏ธpid.N.maxerrorfloat in โ a limit on the internal error variable used for P, I, and D. Can be used to stop highPgainvalues 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.maxerrorDfloat in โ a limit on the error derivative. The rate of change of error used by theDgainterm will be limited to this value, unless the value is zero. Can limit the effect ofDgainand prevent big output spikes from steps on the command and/or feedback. Not normally needed.pid.N.maxerrorIfloat in โ a limit on the error integrator. The error integrator used by theIgainterm 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.maxcmdDfloat in โ a limit on the command derivative. The command derivative used byFF1is limited to this value, unless the value is zero. Can preventFF1from producing large output spikes if there's a step change on the command. Not normally needed.pid.N.maxcmdDDfloat in โ a limit on the command second derivative. The command second derivative used byFF2is limited to this value, unless the value is zero. Can preventFF2from producing large output spikes if there's a step change on the command. Not normally needed.pid.N.maxcmdDDDfloat in โ a limit on the command third derivative. The command third derivative used byFF3is limited to this value, unless the value is zero. Can preventFF3from producing large output spikes if there's a step change on the command. Not normally needed.pid.N.saturatedbit out โ when true, the current PID output is saturated. That is:
output = ยฑ maxoutput
pid.N.saturated-sfloat outpid.N.saturated-counts32 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-modebit in โ when true, auto tune mode is enabled. When false, normal PID calculations happen instead.pid.N.tune-startbit 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-typeu32 rw โ set to 0 andPgain/Igain/Dgainget calculated. Set to 1 andPgain/Igain/FF1get calculated. Pick your fighter.pid.N.tune-cyclesu32 rw โ determines how many cycles to run to characterize the process. Real talk:tune-cyclesactually 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-effortfloat rw โ determines the effort used in setting up the limit cycle in the process. Settune-effortto a positive value less thanmaxoutput. 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-gainfloat ro (only ifdebug=1) โ determined from the process characterization.ultimate-gainis the ratio oftune-effortto the limit cycle amplitude, multiplied by 4.0, divided by Pi.pid.N.ultimate-periodfloat ro (only ifdebug=1) โ determined from the process characterization.ultimate-periodis 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.errorIfloat ro (only ifdebug=1) โ this is the integral of error, aka the value that gets multiplied byIgainto make the Integral term of the output. math girlies rise ๐pid.N.errorDfloat ro (only ifdebug=1) โ the derivative of error, and it's the one that gets multiplied byDgainto produce the Derivative term of the output. ๐pid.N.commandDfloat ro (only ifdebug=1) โ derivative of command, multiplied byFF1to make the first order feed-forward term of the output. slay โจpid.N.commandDDfloat ro (only ifdebug=1) โ the second derivative of command, which gets multiplied byFF2to produce the second order feed-forward term of the output. two D's, twice the drama ๐ฆpid.N.commandDDDfloat ro (only ifdebug=1) โ the third derivative of command, multiplied byFF3to 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. ๐โจ๐ฆ๐
๐ฆ where this page comes from
- debian bookworm: linuxcnc-uspace 2.9.0~pre1+git20230208.f1270d6ed7-1+deb12u2 ยท GFDL-1.1-or-later
- debian trixie: linuxcnc-uspace 1:2.9.4-2+deb13u1 ยท GFDL-1.1-or-later
- ubuntu questing: linuxcnc-uspace 1:2.9.4-2ubuntu2
- ubuntu resolute: linuxcnc-uspace 1:2.9.4-2ubuntu4 ยท GFDL-1.1-or-later
- ubuntu stonking: linuxcnc-uspace 1:2.9.4-2ubuntu4 ยท GFDL-1.1-or-later
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms