ActuatorControlLoop

ActuatorControlLoop is the control loop for a single axis.
Current release

ActuatorControlLoop is the control loop for a single axis. It converts sensor readings into engineering units, decides what the axis should be doing, converts that back into drive units, and watches everything for faults.

Everything else in this library exists to feed or be fed by this block.

flowchart LR
    i1(["inputPosition, inputVelocity, inputAcceleration, inputTorque"]) --> B["ActuatorControlLoop"]
    i2(["motorPositionActual, motorVelocityActual, motorTorqueActual, sensorTorqueActual"]) --> B
    i3(["gotoEngaged, gotoJogging, inputJogVelocity"]) --> B
    i4(["driveMode, doInstantSwitchToggle, disable"]) --> B
    i5(["impedance/inputExternalTorque, impedance/usePositionTarget, impedance/disable"]) --> B
    B --> o1(["motorPositionTarget, motorVelocityTarget, motorTorqueTarget, motorTorqueOffsetTarget"])
    B --> o2(["actuatorPositionTarget, actuatorVelocityTarget, actuatorAccelerationTarget, actuatorTorqueTarget"])
    B --> o3(["actuatorPositionActual, actuatorVelocityActual, actuatorTorqueActual, actuatorPowerActual"])
    B --> o4(["actuatorPositionError, actuatorVelocityError"])
    B --> o5(["…ActualFiltered outputs, gearboxGain, isEnabled"])
    B --> s1(["positionTransformation, velocityTransformation, torqueTransformation"])
    B --> s2(["positionController, velocityController, vibrationController"])
    B --> s3(["six window detectors, engagedSwitch, limiters, feedforwardControl"])
    B --> s4(["positionTargetFilter, velocityTargetFilter, torqueTargetFilter, torqueOffsetTargetFilter, vibrationFilter"])

Four things determine how this block behaves. Get these right first.

gotoEngaged decides whether the axis follows you or follows itself. While it is false the target tracks the measured position, so nothing jumps when the drive powers on. While it is true the target follows inputPosition. The transition between the two is faded.

driveMode decides where the additive torque goes. In a position or velocity mode it leaves through motorTorqueOffsetTarget; in a torque mode it leaves through motorTorqueTarget. Exactly one of the two carries it at a time, deliberately, so that a drive whose reported mode lags your commanded mode cannot receive the same torque twice. This is about the additive torque only — the controller torques stay on motorTorqueTarget in every mode, so that output is generally non-zero in position and velocity mode too.

gearboxLoadSide and gearboxMotorSide scale everything. They are the single most consequential pair of numbers in this block. Neither is checked for zero.

The block does not ship configured. The limiters, the window detectors, the transformations and the controllers all live in sub-trees and all need setting. An unconfigured loop will not move an axis usefully and may not protect it.

Inside the loop

ActuatorControlLoop block diagram

Every sub-module on one sheet. Square blocks are sub-modules — the type inside, the parameter-tree name below — and grey rounded boxes are logic in the loop’s own code, quoting the leaves it reads. Fills mark the family: blue controllers (the Impedance block among them), light orange limiters, darker orange filters, green transformations, light red window detectors, yellow switches. The FeedforwardController and Impedance blocks hide several sub-modules each; only their external ports are named — the feedforward’s inside is drawn in feedforward-controller.md. The left half is the control system, top to bottom in the order the code runs; the right half is the motor side, where each DUAL transformation is one tall block shared by the target lane flowing right to the drive and the actual lane flowing left from it. A pointed tag is a signal that would otherwise cross the sheet: it leaves at one tag and resumes at every tag of the same name.

Signals

Inputs

Path Unit Range Description
inputPosition actuator unit unbounded The commanded position. Followed only while engaged.
inputVelocity actuator unit per second unbounded The commanded velocity, used as feedforward and as the target in a velocity mode.
inputAcceleration actuator unit per second² unbounded The commanded acceleration, used by the feedforward path.
inputTorque torque unit unbounded An additive torque. Filtered, then routed by driveMode.
motorPositionActual ticks unbounded From the drive.
motorVelocityActual ticks per second unbounded From the drive.
motorTorqueActual drive unit unbounded From the drive.
sensorTorqueActual sensor unit unbounded From a separate force or torque sensor, if fitted.
gotoEngaged - true or false Follow the input, or track the measured position.
doInstantSwitchToggle - true or false Make that transition a step instead of a fade.
driveMode - drive enumeration Which mode the drive is in. Decides the torque routing and gates impedance mode.
gotoJogging - true or false Add inputJogVelocity to the commanded motion.
inputJogVelocity actuator unit per second unbounded The jog speed. Watchdogged: if it stops being written it falls to 0 after a timeout.
disable - true or false Disables the loop.
impedance/inputExternalTorque torque unit unbounded The external torque for impedance mode.
impedance/usePositionTarget - true or false Whether impedance limiting works from the target or the measured position.
impedance/disable - true or false Disables impedance mode.

Outputs

Path Unit Description
motorPositionTarget ticks To the drive.
motorVelocityTarget ticks per second To the drive.
motorTorqueTarget drive unit To the drive, in a torque mode only — otherwise 0.
motorTorqueOffsetTarget drive unit To the drive, in a position or velocity mode only — otherwise 0.
actuatorPositionTarget actuator unit What the loop decided the axis should do, before conversion. Trace this first when commissioning.
actuatorVelocityTarget actuator unit per second Zeroed when the position limiter is limiting in the direction of travel.
actuatorAccelerationTarget actuator unit per second² Zeroed when either limiter is limiting.
actuatorTorqueTarget torque unit The torque path’s output: the position, velocity and impedance controller torques in every mode, plus the additive torque in a torque mode only.
actuatorPositionActual actuator unit Where the axis is.
actuatorVelocityActual actuator unit per second
actuatorTorqueActual torque unit
actuatorPowerActual power unit Torque times velocity.
actuatorPositionError actuator unit Target minus actual. The main health signal for the axis.
actuatorVelocityError actuator unit per second
actuatorPositionActualFiltered actuator unit The filtered measurements. These are what the loop uses internally while idle, and what the window detectors watch.
actuatorVelocityActualFiltered actuator unit per second
actuatorAccelerationActualFiltered actuator unit per second²
actuatorTorqueActualFiltered torque unit
actuatorSensorTorqueActual torque unit The separate sensor, converted to actuator units.
actuatorSensorTorqueActualFiltered torque unit
gearboxGain - gearboxLoadSide ÷ gearboxMotorSide. Read it back to check the ratio.
isEnabled - The loop is running.
impedance/isEnabled - Impedance mode is active. It requires a torque drive mode — enabling it in a position mode does nothing.

Parameters

Path Unit Default Range Effect
enable - — - Enables the loop.
gearboxLoadSide teeth — non-zero Load-side tooth count. Not checked for zero.
gearboxMotorSide teeth — non-zero Motor-side tooth count. Not checked for zero.
windowDetectorsEnable - — - Turns all six fault detectors on. Leave this on in service.
usePositionActualFilteredForVelocity - — - Derive velocity from the filtered position instead of the drive’s own velocity.
usePvaActualForCompensation - — - Feed the feedforward path the measured motion instead of the commanded motion.
torqueTargetDeadband torque unit — 0 upward Suppresses small torque targets.
impedance/enable - — - Enables impedance mode, subject to the drive being in a torque mode.
positionController/parameter — — — The position PID’s gains and limits.
positionController/usePositionControlForTorque - — - Route the position PID’s output to torque instead of adding it to the velocity target.
positionController/delayForControlError/numberOfSamplesTarget cycles — 0 upward Delays the target before the error is formed, to match the fieldbus round trip. Typically 4 for EtherCAT.
positionController/delayForControlError/numberOfSamplesActual cycles — 0 upward The same for the measurement.
velocityController/parameter — — — The velocity PID’s gains and limits.
vibrationController/parameter — — — The vibration damper’s gains. Its derivative gain is forced to 0 whatever you write.

Everything else is in the sub-trees — the transformations, the limiters, the six window detectors, the feedforward controller and the backlash compensation. All parameters are persistent and survive a controller restart.

Which sub-trees exist depends on how the loop was built. A loop built without torque support has no torqueTransformation, no torqueWindowDetector and no feedforward controller.

The drive modes

driveMode follows mcx::drive::DriveMode, plus a few values specific to this block:

Value Mode What the drive follows
8 cyclic synchronous position motorPositionTarget, with motorTorqueOffsetTarget as additive torque
9 cyclic synchronous velocity motorVelocityTarget, with motorTorqueOffsetTarget as additive torque
10 cyclic synchronous torque motorTorqueTarget
-3 cyclic synchronous current motorTorqueTarget, motor constant in the torque transformation
-110 / -109 / -108 Sensodrive torque / velocity / position as 10 / 9 / 8
0, 1..7 other DriveMode values treated like a position or velocity mode: no torque command

The three controllers, leaf by leaf

positionController/, velocityController/ and vibrationController/ have the same shape. Each has a parameter/ folder of gains, an input/ folder of runtime inputs and an output/ folder:

input/ node Description
disable The loop writes this every cycle from the engage state and the impedance mode; write it yourself only through the block’s own API.
integratorFreeze Hold the integrator.
integratorReset Zero the integrator; clears itself.
controllerReset Zero every state; clears itself.
controlError The error the module computed this cycle. Published for reading.
output/ node Description
controllerOutput The saturated controller output.
integratorOutput The integrator state.
derivativeOutput The filtered derivative term.
isEnabled parameter/enable && !input/disable. All outputs are zero while this is false.

<controller>/parameter/

All three controllers share these; the vibration controller ignores derivativeGain (it is forced to zero).

Node Default Description
enable 0 The controller’s own enable. Off by default: nothing is closed until you set it.
proportionalGain 0.0 P gain.
integratorGain 0.0 I gain, on the error outside controlErrorDeadBand. Integration stops in the direction that would push further into a saturated output.
backCalculationGain 0.0 Anti-windup: drives the integrator so the unsaturated output returns to the saturated one.
derivativeGain 0.0 D gain.
derivativeFilter 10.0 Bandwidth of the derivative filter.
controlErrorDeadBand 0.0 Error below which the integrator does not integrate.
controlIntegrationMin / controlIntegrationMax 0.0 / 0.0 Hard clamp on the integrator state. Both zero means the integrator is clamped to zero — set them before using integratorGain.
controlOutputMin / controlOutputMax -100.0 / 100.0 Saturation of controllerOutput.

Target shaping filters

Four IIRFilter sub-trees shape the outgoing targets. Each is a full IIR of up to order 6 — enough for two notches plus a lowpass — and each sits in front of its limiter, which in turn sits in front of the transformation:

Filter Shapes Order in the chain
positionTargetFilter the position target engaged switch → filter → positionLimiter → positionTransformation
velocityTargetFilter the velocity target engaged switch → filter → velocityLimiter → velocityTransformation
torqueTargetFilter the torque target torque sum → filter → torqueLimiter → torqueTransformation
torqueOffsetTargetFilter the additive torque offset sum → filter → torqueOffsetLimiter → torqueOffsetTransformation

All four default to pass-through (order 1, num[0] = den[0] = 1), so a loop you have not tuned behaves exactly as it did before these filters existed.

A filter that is switched off is a bypass, not a mute. IIRFilter on its own outputs zero when it is disabled, when order is 0, or when den[0] is 0 — which in a setpoint path would be a command to zero. This block therefore guards every one of the four: whenever a filter is not actually filtering, the unfiltered signal is passed to the limiter instead. You can disable any of these four safely. That guard is specific to this block; an IIRFilter you place in a setpoint path yourself has no such protection.

The two torque filters are inside the feedback loops. Their input is the sum of the feedforward and the position and velocity controller outputs, so tuning them changes loop stability. That is exactly what you want from a notch on a mechanical resonance, but treat these as stability parameters, not as cosmetic smoothing. The position and velocity filters shape the reference only and are outside every loop.

Filtering happens before the limiter, and that is deliberate. An IIR response is not amplitude-bounded: a filter placed after a limiter can ring past the limit and hand the drive a target outside the allowed envelope, and it would also decouple the limiters' own limiting flags — which drive the velocity and acceleration zeroing — from the signal actually commanded. The price is that clipping is not smoothed, which only matters in saturation.

Feedforward does not see these filters. The feedforward path taps the setpoints before the filters but after the same limiting, so retuning a filter never changes the feedforward torque. One second-order effect remains: positionLimiter is adaptive, and its active window is computed from the filtered signal, so a tuned position filter shifts that window slightly and the feedforward tap inherits the shift.

The acceleration target is not filtered.

The vibration damper’s own filter

vibrationFilter is a fifth IIRFilter, in the vibration controller’s feedback path rather than a target path:

sensorTorqueActual → sensorTorqueHighPass → vibrationFilter → vibrationController

sensorTorqueHighPass is unchanged and still does the DC rejection that decides what counts as vibration. vibrationFilter sits behind it, pass-through by default, and is where you put notches to keep higher-frequency content out of the damper. It is guarded the same way as the four target filters, so switching it off bypasses it instead of zeroing the feedback.

Remember where the damper’s output goes: it is added to the additive torque channel only, so it is inactive in a torque mode whatever this filter is set to.

Setup

  1. Configure the transformations first, under positionTransformation and velocityTransformation. Nothing else can be checked until the axis reports its position correctly in engineering units.

  2. Set gearboxLoadSide and gearboxMotorSide, and read gearboxGain back.

  3. With the drive disabled, move the axis by hand and confirm actuatorPositionActual reads correctly over a long move.

  4. Set the limiters under positionLimiter and velocityLimiter to values the machine cannot be hurt by.

  5. Set the six window detectors' levels from a trace of normal operation, and set windowDetectorsEnable true.

  6. Leave gotoEngaged false and enable the drive. Confirm actuatorPositionTarget tracks actuatorPositionActual and the axis does not move.

  7. Set positionController/delayForControlError/numberOfSamplesTarget and …Actual to match your fieldbus — 4 samples is typical for EtherCAT.

  8. Feed inputPosition the axis’s current measured position, then set gotoEngaged true.

    Step 8 hands the axis to your setpoint source. If inputPosition does not match where the axis is, the loop will move it there over the fade. Match them first, and keep the limiters tight.

  9. Leave the four target filters at their pass-through default for now. Tune a notch only once you have measured the resonance you are aiming at.

  10. Tune the controllers — go to Tuning.

Tuning

  1. Get the measurement chain right before touching a gain. Trace actuatorPositionActual and actuatorVelocityActual at rest and in motion; noise here becomes torque later.
  2. Set the two control-error delays to your fieldbus round trip. Getting these wrong makes the position error look like a lag, and tempts you into gains that oscillate.
  3. Tune the velocity controller first, then the position controller around it. The usual order: raise proportional gain until the axis is stiff, add integral until steady-state error clears, add derivative only if you must.
  4. Watch actuatorPositionError throughout. It is the single best indicator of whether the loop is healthy.
  5. Add feedforward once the feedback loop is stable. It reduces the error the controllers have to work on rather than replacing them.
  6. Set usePvaActualForCompensation only if the feedforward is fighting the commanded motion — normally the commanded motion is the right input.
  7. Set the window detector levels from the error you actually see, with margin. They are your protection, not a diagnostic.
  8. Re-check everything after a task-rate change: the control-error delays are in cycles, so their meaning in seconds changes with the rate.

Engaging the loop. While idle the target follows the measured position; whengotoEngaged goes true it fades onto the commandedposition.

The fade is what keeps the axis from stepping at the moment you take control.

Symptom Cause Action
The axis jumped when engaging inputPosition did not match the measured position Match them before engaging
The axis stepped rather than faded doInstantSwitchToggle is true Set it false
The axis moves while idle Something downstream is not respecting motorPositionTarget, or the drive is in the wrong mode Check driveMode
Nothing moves when engaged enable is false, disable is true, or the limiters are clamping Check isEnabled and the limiters
Position error grows with speed Feedforward is missing or under-tuned, or the control-error delays are wrong Set the delays first
The axis oscillates Controller gains too high, or the measurement is noisy Lower the gains; check the measurement filtering
The axis oscillates only at one frequency A structural mode Use the IIR filter in the transformation, or the vibration controller
The vibration controller’s derivative gain does nothing It is forced to 0 in this version Use proportional and integral only
Torque appears on the wrong output Expected: the routing follows driveMode Check which mode the drive reports
The feedforward torque blips to zero for a moment when the drive changes mode Expected: the routing switches on the commanded mode, a few cycles before the drive reports the new one Nothing to fix; sequence mode changes at rest if the blip matters
The axis received torque twice during a mode change Should not happen — the two channels are gated so only one is live Report it
Impedance mode does nothing It requires a torque drive mode Check driveMode and impedance/isEnabled
The axis keeps jogging after the operator lets go Should not happen — the jog velocity is watchdogged to 0 Check the writer is really stopping
A fault will not clear The window detectors latch. The reset is not on this block Assert resetErrors on the parent’s state input — it clears every axis at once
Everything is scaled wrongly The gearbox ratio, or the transformations Read gearboxGain back and check the transformations first
The loop misbehaves after writing a gearbox value A zero tooth count is not refused here Read both back
A sub-tree I expected is missing The loop was built without that feature Fixed when the controller is built
The torque detector’s thresholds seem to be in the wrong units It watches the raw sensor value, unlike the other five Set its levels from a trace of that signal

A starting point once the transformations are correct:

gearboxLoadSide       = <from the drivetrain>
gearboxMotorSide      = <from the drivetrain>
windowDetectorsEnable = true
positionController/delayForControlError/numberOfSamplesTarget = 4
positionController/delayForControlError/numberOfSamplesActual = 4
usePvaActualForCompensation = false

Worked configuration — cyclic synchronous position

A drive in mode 8, with a 2000-count encoder behind a 50:1 gearbox, the position loop closed in the drive and the module supplying the position target and a feedforward torque on the offset channel. Values not listed stay at their defaults.

Parameter Set to Why
gearboxLoadSide 50 50:1 reduction
gearboxMotorSide 1 "
positionTransformation/ticksPerRevolution 2000 encoder counts per motor revolution
positionTransformation/gainNum, gainDen 1, 6.283185307179586 gain = gainNum / gainDen * ticksPerRevolution ticks per unit of position: 2000 ticks per 2π of motor rotation; the gearbox ratio is applied on top automatically
velocityTransformation/ticksPerRevolution, gainNum, gainDen as above same scaling for the drive’s velocity unit
torqueOffsetTransformation/gainNum, gainDen drive-specific actuator torque → the drive’s torque unit; the gearbox ratio is applied automatically
positionLimiter/enable 1 travel limits on the setpoint
positionLimiter/lowerLimit, upperLimit the travel "
velocityLimiter/enable 1
velocityLimiter/lowerLimit, upperLimit -2.0, 2.0 what the mechanism may do
torqueOffsetLimiter/enable 1 never ask the drive for more than the motor delivers
torqueOffsetLimiter/lowerLimit, upperLimit motor rating "
engagedSwitch/fadeInTime 1.0 one second from the actual position onto the setpoint
feedforwardControl/enable 1 model-based additive torque
feedforwardControl/externalMass, gravity, … the model see feedforward-controller.md
positionController/parameter/enable 0 (default) the drive closes the position loop
velocityController/parameter/enable 0 (default) "
windowDetectorsEnable 1
positionErrorWindowDetector/low, high -0.01, 0.01 following-error warning
positionErrorWindowDetector/tooLow, tooHigh -0.05, 0.05 following-error error

Operate it with driveMode = 8, then gotoEngaged = 1 once the drive is operational and the transformation is referenced.

Worked configuration — cyclic synchronous torque, loops closed here

The same actuator in mode 10: the drive follows motorTorqueTarget, and the module closes both loops as a position-over-velocity cascade.

Parameter Set to Why
transformations, gearbox, limiters, engagedSwitch as the position example
torqueTransformation/gainNum, gainDen drive-specific actuator torque → the drive’s torque unit
torqueLimiter/enable 1 the torque channel is now the command
torqueLimiter/lowerLimit, upperLimit motor rating "
velocityController/parameter/enable 1 inner loop
velocityController/parameter/proportionalGain tuned
velocityController/parameter/integratorGain tuned
velocityController/parameter/controlIntegrationMin, controlIntegrationMax -10, 10 must be set — both at zero clamps the integrator to zero
velocityController/parameter/controlOutputMin, controlOutputMax motor rating saturation of the controller torque
positionController/parameter/enable 1 outer loop
positionController/parameter/proportionalGain tuned velocity per unit of position error
positionController/parameter/controlOutputMin, controlOutputMax -2.0, 2.0 the outer loop may not ask for more than velocityLimiter allows
positionController/usePositionControlForTorque 0 (default) cascade, not a direct position-to-torque loop
feedforwardControl/enable 1 in a torque mode the feedforward goes out on motorTorqueTarget

The feedforward moves from the offset channel to the torque channel by itself when driveMode changes to 10.

Worked configuration — impedance

Torque mode as the torque example, and the actuator made compliant to an external controller writing impedance/inputExternalTorque, with a soft fence of ±0.1 around wherever the actuator is:

Parameter Set to Why
as the torque example
impedance/enable 1
impedance/externalTorqueLimiter/enable 1 bound what the outside may ask
impedance/externalTorqueLimiter/lowerLimit, upperLimit motor rating "
impedance/positionLimiter/enable 1 the fence
impedance/positionLimiter/lowerLimit, upperLimit -0.1, 0.1 the fence, around the actual position (impedance/usePositionTarget = 0)
positionController/parameter/proportionalGain tuned this is the stiffness of the fence: only the position controller pushes back, on the undelayed error and without integrator

impedance/isEnabled confirms the mode is active; it needs driveMode = 10, impedance/enable and impedance/disable = 0. While it is active getPositionClosedLoop() reports the loop as open.

Limits and errors

Limit Set by What happens Reported
Position and velocity positionLimiter, velocityLimiter sub-trees Targets are clamped. The velocity target is zeroed when the position limiter is limiting in the direction of travel, and the acceleration target when either is limiting The limiters' own outputs
Target filter switched off Guarded in this block The unfiltered signal is passed on. A bare IIRFilter would output 0 here The filter’s isEnabled
Target filter overshoot The limiter downstream of it Clamped, because every target filter runs before its limiter The limiters' own outputs
Fault detection The six window detector sub-trees Latching warning and error flags per quantity Each detector’s noError
Fault reset The parent’s state input This block has no reset path of its own. Inside AxesControl the reset arrives through its state channel and clears every actuator at once. A standalone loop can be reset only by an application Not reported
Jog velocity loss Watchdog Falls to 0 if the writer stops Not reported
Engage transition The engaged switch sub-tree Faded, unless doInstantSwitchToggle Not reported
Additive torque routing driveMode Exactly one of the two torque outputs carries it; the other contribution is 0. The gate follows the commanded mode, so on a change into a torque mode the additive torque drops out for the few cycles the drive takes to report the new mode Both outputs
Impedance mode driveMode Silently inactive unless the drive is in a torque mode impedance/isEnabled
Gearbox ratio Nothing A zero tooth count is not refused and makes the conversion invalid gearboxGain
Values that are not numbers Nothing Not checked anywhere in this block Not reported
Control error delays Fixed in cycles Their meaning in seconds changes with the task rate Not reported
Vibration controller derivative Fixed Forced to 0 every cycle, whatever you write Read it back
Features present Fixed at build time Torque, feedforward, sensor torque and the two controllers are each compiled in or out Missing sub-trees
Axis count Fixed One axis per instance, always Not reported

This block logs nothing itself. Sub-modules below it may log — the transformation’s transducer logs during referencing. Every other condition above shows as a value on a trace, or not at all.


Verified against motorcortex-control3 3.32.1 (340db23).