AxesControl

AxesControl is the multi-axis orchestrator.
Current release

AxesControl is the multi-axis orchestrator. It takes the motion you command in axis space, conditions and limits it, converts it into actuator space, runs one control loop per actuator, and converts the measurements back.

It is where the distinction between an axis and an actuator lives:

  • An axis is what you command — a joint of the kinematic model, a Cartesian direction, a virtual degree of freedom.
  • An actuator is a physical drive with its own encoder, drive mode and control loop.

Two matrices relate them, so one axis may drive several actuators and one actuator may be shared by several axes — differential drives, tandem axes, belt couplings.

flowchart LR
    i1(["axesPositionsInput, axesVelocitiesInput, axesAccelerationsInput"]) --> B["AxesControl"]
    i2(["axesTorquesInput"]) --> B
    i3(["axesHomingJogVelocitiesInput, axesHomingGotoJog"]) --> B
    B --> o1(["axesPositionsActual, axesVelocitiesActual, axesAccelerationsActual"])
    B --> o2(["sensorTorquesActual"])
    B --> o3(["axesIsOpenLoop, axesIsReferencing"])
    B --> o4(["axesPositionsTargetLimited, axesPositionsReference, axesVelocitiesReference, axesAccelerationsReference"])
    B --> o5(["axesHomingJogDelta"])
    B --> o6(["axesPositionsTargetHardware, axesPositionsActualHardware"])
    B --> s1(["actuatorControlLoops — one per actuator"])
    B --> s2(["axesLimiters, smoothStop"])
    B --> s3(["axesTransducers — one per axis"])
    B --> s4(["axesJogRateLimiter, axesJogIntegrator"])
    B --> s5(["interpolators, axesSetpointJumpDetectors, axesTorqueDetectors"])

The number of axes and the number of actuators are fixed when the controller is built, and they need not be equal. So are the two matrices' dimensions.

Almost all the configuration is in the sub-trees. The per-axis limits are under axesLimiters, and every actuator has a complete control loop of its own under actuatorControlLoops. Budget commissioning time per actuator, not per machine.

axesIsOpenLoop is the signal to watch. It tells you, per axis, whether the loop is genuinely closed. An axis driven by two actuators whose contributions cancel is not closed, and this output is what says so.

Each axis has its own transducer, under axesTransducers/axesTransducer<NN>. It gives the axis a gain and a zero point of its own, so a coupled or differential axis can be referenced as an axis rather than one actuator at a time. With the defaults it is an exact identity, so a plain one-actuator-per-axis machine is unaffected and can skip it entirely.

Inside the loop

AxesControl block diagram

The round trip on one sheet. Setpoints flow right on four lanes — position, velocity, acceleration, torque — through the interpolators, the axis jog sums, axesLimiters, smoothStop, the axis transducer (position) and the transducer gain (the other three, the × squares), the axes-to-actuators product dot() and into the actuator control loops. The measured state comes back left on four lanes through the actuators-to-axes product, the same transducer (position) and the reciprocal gain (÷), leaving as axesPositionsActual, axesVelocitiesActual, axesAccelerationsActual and sensorTorquesActual. A per-axis or per-actuator array is drawn once, under its array path, because the count is a configuration choice. Square blocks are sub-modules, dot() a function call, grey rounded boxes logic in this block’s own code, the yellow square a switch resting on its default contact, and a pointed tag a signal that would otherwise cross the sheet.

Signals

Inputs

Path Unit Range Description
axesPositionsInput axis unit unbounded The commanded position per axis. Interpolated, limited, then converted to actuator space.
axesVelocitiesInput axis unit per second unbounded The commanded velocity per axis.
axesAccelerationsInput axis unit per second² unbounded The commanded acceleration per axis.
axesTorquesInput torque unit unbounded The commanded torque per axis. Only present when the actuator loops were built with torque support.
axesHomingJogVelocitiesInput axis unit per second unbounded A jog speed per axis, for homing. Maps onto every actuator that axis drives.
axesHomingGotoJog - true or false Enables that jog, per axis.

Outputs

Path Unit Description
axesPositionsActual axis unit Where each axis is, transformed back from the actuator measurements.
axesVelocitiesActual axis unit per second
axesAccelerationsActual axis unit per second²
sensorTorquesActual torque unit Per axis, when the actuator loops have torque sensors.
axesIsOpenLoop - Per axis: the position loop is not closed. True when no actuator driving that axis is closing its loop, and also when their contributions cancel — check it before trusting an axis’s position. Also true while that axis’s transducer is latched after a reference.
axesIsReferencing - Per axis: the transducer is in its post-referencing hold. While set, that axis reports open loop, and its setpoint-jump and torque detectors are disabled so the re-based setpoint cannot trip them.
axesPositionsTargetLimited axis unit The axis position target after the limiters, before the jog delta and the transducer.
axesPositionsReference axis unit The limiter output actually followed.
axesVelocitiesReference axis unit per second
axesAccelerationsReference axis unit per second²
axesHomingJogDelta axis unit Per axis: the accumulated jog offset, added to the commanded position. Exactly zero when no jog has been commanded. It is not cleared by releasing the jog or by resetErrors — see the symptom table.
axesPositionsTargetHardware axis unit The axis position target on the hardware side of the transducer — what enters the axis-to-actuator conversion. For commissioning.
axesPositionsActualHardware axis unit The measured axis position on the hardware side — what leaves the actuator-to-axis conversion.

Parameters

Path Unit Default Range Effect
interpolators/sampleFactor - — 1 upward How many control cycles pass between updates from your motion source. 1 disables interpolation. Interpolating adds one source period of delay — acceptable when this block runs faster than the planner feeding it.

That is the only parameter at this level. The rest is in the sub-trees:

Path Effect
axesLimiters/… The per-axis position, velocity, acceleration and jerk limits. The main safety configuration.
axesTransducers/axesTransducer<NN>/… Per axis: its gain, its zero point, and its referencing. <NN> is 1-based and two digits, so axis 0 is axesTransducer01. See below.
axesJogRateLimiter/rateLimit How fast a commanded axis jog velocity may change, per axis. Default 1.0 axis-unit per second per second.
axesJogIntegrator/… Bounds on the accumulated jog delta. outputLimiterEnable with outputLimiterMin / outputLimiterMax; all disabled by default, so the delta is unbounded until you bound it.
smoothStop/… How the machine is brought to rest on a stop or a detected setpoint jump.
axesSetpointJumpDetectors/windowDetector<N>/… Per axis: how large a setpoint jump must be to count as a fault.
axesTorqueDetectors/windowDetector<N>/… Per axis torque supervision, on torque-capable builds.
actuatorControlLoops/actuatorControlLoop<N>/… A complete actuator control loop per actuator. See the ActuatorControlLoop page.

All are persistent and survive a controller restart.

Axis transducers

One per axis, under axesTransducers/axesTransducer<NN>/. The stage sits between the axis coordinate your application commands — the software side — and the coordinate the axis-to-actuator conversion uses, the hardware side.

Position: hardware = gain × software + offset, and back software = (hardware − offset) / gain. Velocity, acceleration, torque and jog velocity carry the gain only, with no offset. gain = gainNum / gainDen. With gainNum 1, gainDen 1 and offset 0 the stage is an exact identity.

Path Unit Default Range Effect
gainNum - 1 non-zero Numerator of the axis gain. A zero is out of range and is silently reverted to the last accepted value every cycle, so a rejected write leaves the old gain in force with no error.
gainDen - 1 above 0 Denominator. Same silent reversion.
offset axis unit, hardware side 0 unbounded The axis zero point. Written by referencing; it survives a restart only if your configuration writes it back.
referencing/useSoftwareReferenceExternal - true - Selects which reference value a reference request uses.
referencing/softwareReferenceExternal axis unit — unbounded The reference value used when the switch above is true. An input, so it can be linked.
referencing/softwareReferenceInternal axis unit — unbounded The reference value used when the switch is false.
referencing/reconnectToleranceHardwareSide axis unit, hardware side 2 above 0 How close the recomputed hardware output must come to the frozen one before the transducer reconnects. The default is wrong for an axis — see Limits and errors.

To reference an axis:

  1. Jog it with axesHomingJogVelocitiesInput and axesHomingGotoJog. The commands reach every actuator that axis drives.
  2. Optionally snapshot a known point with referencing/:fromState.setHardwareSnapshot. Set hardwareSnapshotTarget to snapshot a specific hardware-side value instead of the live one — but note 0.0 there means “no target given” and falls back to the live input, so an exact hardware value of 0.0 cannot be snapshotted this way.
  3. At the home position, assert referencing/:fromState.setHardwareReference. The transducer computes offset = snapshot − reference × gain and the axis then reads the reference value.
  4. Watch axesIsReferencing for that axis go false. Until it does, the transducer has frozen its hardware-side output and the axis is held.
  5. referencing/:fromState.reset clears a latch that will not resolve.

Per-axis referencing status is read from axesTransducers/axesTransducer<NN>/referencing/:toState. This block’s own :toState.isReferenced is unrelated — it still reports the actuator referencing state.

Setup

  1. Commission each actuator loop individually first, under actuatorControlLoops. Get every transformation, gearbox ratio and window detector right one actuator at a time. Nothing at this level can be checked until they are.

  2. Set the two axis-to-actuator matrices from the machine’s mechanics. They are checked for dimension — a mismatched matrix is rejected.

  3. With everything disengaged, move each axis by hand and confirm axesPositionsActual reads correctly. This is the check that the matrices are the right way round.

  4. Confirm axesIsOpenLoop reads true on every axis while the drives are off.

  5. Set the per-axis limits under axesLimiters to values the machine cannot be hurt by. Start conservative.

  6. Set interpolators/sampleFactor from your motion source’s real update rate.

  7. Set the setpoint-jump detectors so a plausible planner glitch is caught but normal motion is not.

  8. Engage one axis at a time, at low speed.

    Step 8 moves the machine under your motion source’s control. With several actuators per axis, a matrix error moves more than one drive. Have the stop within reach and the axis limits tight.

  9. Confirm axesIsOpenLoop goes false on the axes you engaged.

  10. If an axis needs a zero point of its own — a coupled or differential axis — set its axesTransducers/axesTransducer<NN> gainNum and gainDen before referencing it, and set referencing/reconnectToleranceHardwareSide to a few thousandths of an axis unit. Read gainNum and gainDen back; an out-of-range write is reverted silently.

  11. Reference that axis with the procedure above and confirm axesIsReferencing goes false and axesPositionsActual reads the reference value.

  12. If you use axis-level jogging, link either the axis-level jog inputs or the actuator-level actuatorControlLoops/actuatorHomingJogVelocitiesTarget — never both. A configuration that links both applies the jog twice.

Tuning

  1. All the loop tuning is per actuator, one level down. This level is limits, interpolation and stopping behaviour.
  2. Set the axis limits from what the mechanism can take, expressed in axis units. They apply before the conversion to actuator space, so one axis limit protects every actuator that axis drives.
  3. Raise the limits in steps, re-testing each time, rather than setting them from a datasheet and hoping.
  4. Set interpolators/sampleFactor to the true ratio between this block’s rate and your motion source’s. Too low shows as a step at each source update; too high shows as staleness.
  5. If the interpolation delay matters — a machine where this block is inside a feedback path — run the source at this block’s rate and set the factor to 1.
  6. Tune the smooth stop so an emergency stop is firm but does not itself become a fault. Test it deliberately.
  7. Set the setpoint-jump detectors last, once you know what a normal setpoint stream looks like.
  8. Re-check the axis limits after any change to the matrices — the same axis limit means a different actuator motion once the coupling changes.
  9. Set the axis jog rate on axesJogRateLimiter/rateLimit, per axis. Releasing a jog does not stop the delta growing at once: the velocity ramps down at this rate and the delta keeps accumulating until the ramp reaches zero.
  10. Bound the jog delta with axesJogIntegrator/outputLimiterEnable and its min and max if a runaway jog would be a hazard. It is unbounded by default.
  11. Do not tune an axis transducer’s gainNum, gainDen or offset on an engaged axis. They are read every cycle with no hold and no smoothing, so a write steps the hardware-side target — and an engaged actuator’s target — in a single cycle. The axis setpoint-jump detectors run on the software side and never see it.
  12. Re-reference an axis after any gain change. The offset was computed against the old gain, so changing the gain afterwards moves the hardware target and the measured position jumps.

An axis commanded faster than it can move, and the limited target. Thelimiter holds the axis to its configured velocity and acceleration rather thanfollowing the command.

The gap between the two traces is motion your source asked for and did not get. If that gap is routine, your source and your limits disagree.

Symptom Cause Action
An axis moves the wrong actuators The axis-to-actuator matrix is wrong or transposed Move each axis by hand and check axesPositionsActual
A matrix was rejected Its dimensions do not match the axis and actuator counts Check both counts; they need not be equal to each other
axesIsOpenLoop stays true on an engaged axis No actuator driving that axis is closing its loop, or their contributions cancel Check each actuator loop’s own engage state
An axis reads a position but is not controlled Same cause — this is what axesIsOpenLoop exists to tell you Do not trust the axis until it reads false
The machine will not follow the commanded speed The axis limiters are clamping Compare the input and actual traces; raise the limits if the machine can take it
Motion is stepped interpolators/sampleFactor is lower than the real ratio Set it to the true ratio
Motion lags the command The interpolator adds one source period Run the source at this block’s rate and set the factor to 1
The machine stopped by itself A setpoint-jump detector fired and the smooth stop ran Check the detectors and your setpoint stream
The stop itself caused a fault The smooth stop is too aggressive for the axis limits Soften the stop, or widen the limits
A jog moved more actuators than expected Expected: a jog maps onto every actuator that axis drives Nothing
A fault will not clear The detectors latch until reset Assert resetErrors on this block’s state input. It clears every actuator and every axis at once — there is no per-axis reset
Torque signals are missing from the tree The actuator loops were built without torque support Fixed when the controller is built
I need more axes or actuators Both counts are fixed when the controller is built Rebuild
One actuator behaves differently from the rest Its own loop is configured differently Compare the two loops' sub-trees
An axis is stuck after referencing, reporting axesIsReferencing forever Nothing re-based the software side, so the reconnect condition is never met. The hardware output stays frozen — safe, but held Have the application follow axesIsReferencing and set its axis setpoint to the measured actual; or assert referencing/:fromState.reset
A reference request did nothing The transducer is still latched from the previous one; requests are ignored while disconnected Wait for axesIsReferencing to clear, or reset the latch
The axis jumped when the transducer reconnected referencing/reconnectToleranceHardwareSide is still at its default of 2, which is enormous in axis units Set it to a few thousandths of an axis unit and re-reference
An axis position jumped after a gain change Expected: offset was computed against the old gain Re-reference the axis after every gain change
A write to gainNum or gainDen had no effect and reported nothing Expected: an out-of-range value is silently reverted to the last accepted one every cycle Read both back; use a non-zero numerator and a positive denominator
A jog moved the machine twice as far as commanded Both the axis-level and the actuator-level jog paths are linked Link one of the two, never both
An axis holds a standing offset that nothing clears Expected: the jog delta is cleared only while that axis is in its post-referencing hold. Releasing the jog, an open-loop axis and resetErrors all leave it alone Read axesHomingJogDelta; reference the axis to clear it
A jog kept accumulating after being released Expected: the velocity ramps down at axesJogRateLimiter/rateLimit and the delta integrates until it reaches zero Raise the rate limit if the overshoot matters
A referenced axis reads open loop Expected while axesIsReferencing is set: a frozen hardware side has no usable closed position loop Wait for the hold to clear

A starting point, after every actuator loop is commissioned:

interpolators/sampleFactor = <control rate ÷ motion source rate>
axesLimiters/…             = <conservative per-axis limits>

Limits and errors

Limit Set by What happens Reported
Axis motion axesLimiters sub-tree Position, velocity, acceleration and jerk are limited per axis, in axis units, before conversion The limiters' own outputs
Matrix dimensions Checked A matrix whose dimensions do not match the axis and actuator counts is rejected Not applicable
Loop closure Computed axesIsOpenLoop accounts for signed cancellation — two actuators whose contributions cancel do not count as closing the axis axesIsOpenLoop
Setpoint jumps axesSetpointJumpDetectors A jump beyond the configured window triggers the smooth stop Each detector’s flags
Stopping smoothStop sub-tree Brings the machine to rest on a stop request or a detected jump Not reported
Interpolation interpolators/sampleFactor Upsamples the axis inputs and adds one source period of delay Not reported
Per-actuator limits and faults Each actuator loop’s own sub-tree Applied independently, after the axis-space limits Each loop’s outputs
Fault reset This block’s state input Asserting resetErrors there clears every actuator loop’s detectors and every axis torque detector at once. There is no per-axis or per-actuator reset Not reported
Axis gain and offset axesTransducers/axesTransducer<NN> Applied every cycle with no hold and no smoothing. A live write steps the hardware-side target in one cycle, invisibly to the axis setpoint-jump detectors Not reported; read axesPositionsTargetHardware
gainNum ≠ 0, gainDen > 0 Fixed An out-of-range value is silently reverted to the last accepted one, every cycle. A rejected write leaves the previous gain in force Not reported; read both values back
Post-referencing hold The reconnect condition The hardware-side output is frozen until the recomputed value lands within referencing/reconnectToleranceHardwareSide for more than 100 consecutive cycles. It is a re-basing wait, not a timer, so it does not time out axesIsReferencing, referencing/:toState.isHardwareDisconnected
referencing/reconnectToleranceHardwareSide Default 2 Sized for a transducer whose hardware side carries encoder ticks. An axis transducer’s hardware side carries axis units, where 2 would let a whole offset step through Not reported; set it per axis
Jog delta axesJogIntegrator Unbounded by default. Cleared only while that axis is in its post-referencing hold — not by releasing the jog, not by an open-loop axis, and not by resetErrors axesHomingJogDelta
Jog path Configuration The axis-level and actuator-level jog paths are both live. Linking both applies the jog twice Not reported
Torque signals Fixed at build time Present only when the actuator loops were built with torque support Missing paths
Axis and actuator counts Fixed at build time Independent of each other, and not changeable from the tree Not reported

This block logs nothing itself. The transducers log during referencing — both the per-axis ones and those inside each actuator loop. Every other condition above shows as a value on a trace, or not at all.


Verified against motorcortex-control3 3.33.0 (69625c1).