Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Motion Axes: Creating and Managing

Getting a motor to move is, for most people, the hardest part of bringing up a new machine. This chapter is the authoritative guide to axes in AutoCore: what they are, how to create them, how to configure their behavior, and how to avoid the handful of mistakes that cost people a day each. Chapter 8 walks through the EtherCAT-specific scan/PDO steps with a concrete Teknic example; this chapter is the reference that example points back to.

What an axis is

An axis is a high-level motion abstraction. It pairs:

  • a motion core — the CiA-402 state machine plus scaling, homing, and limit configuration (autocore_std::motion::Axis + AxisConfig), and
  • a backend — where the drive actually lives.

The motion core is fieldbus-agnostic. The generic Axis controller talks to its drive through a narrow AxisView trait (control word, status word, target, feedback, modes), so the same control-program code drives:

  • an EtherCAT servo (the AxisView is generated PDO wiring), or
  • a virtual / simulated drive (the AxisView is an in-process SimDrive), with no hardware at all.

This is why an axis is defined separately from the slave. The slave entry describes the physical EtherCAT device and its PDOs; the axis entry describes the motion abstraction layered on top. Swapping the motor to a different drive is a one-line change to the axis (link), and the control program never changes.

Key idea. You write your process logic against the axis, not the drive. The axis is the stable contract; the backend is an implementation detail.

Where axes live

Axes are listed in an axes array. There are two homes, both read by codegen:

LocationForNotes
modules.ethercat.config.axesEtherCAT-backed axesThe legacy/default home. Every existing project uses it. An untagged axis here is an EtherCAT axis.
modules.motion.config.axesBackend-neutral axes (e.g. virtual)The home for axes that aren’t tied to the EtherCAT bus.

You normally don’t choose by hand — acctl add-axis puts each axis in the right place. EtherCAT axes can stay untagged in the ethercat config (fully back-compatible); virtual axes carry an explicit backend tag.

Creating an axis

The fast way: acctl add-axis

# An EtherCAT axis bound to a scanned slave:
acctl add-axis --name Press --link AKD_3

# A virtual / simulated axis (no fieldbus):
acctl add-axis --name SimShuttle --backend virtual
FlagDefaultMeaning
--name(required)Axis name → the generated handle struct (e.g. Press). Describe the function, not the hardware.
--linkSlave name to bind to. Required for ethercat, omitted for virtual.
--typeppCiA-402 profile type (Profile Position).
--backendethercatethercat or virtual.

The command is idempotent on --name and writes the axis into the correct home. It does not seed drive-behavior defaults — see Drive-behavior defaults below — because the CLI can’t read the EtherCAT device library; the IDE or a hand edit fills those in.

After adding an axis, run acctl codegen to generate its drive handle.

By hand

Add an object to the appropriate axes array. A minimal EtherCAT axis:

"modules": { "ethercat": { "config": {
    "axes": [
        { "name": "Press", "link": "AKD_3", "type": "pp" }
    ],
    "slaves": [ /* ... */ ]
} } }

A virtual axis goes in the neutral home and carries the backend tag:

"modules": { "motion": { "enabled": false, "config": {
    "axes": [
        { "name": "SimShuttle", "type": "pp", "backend": { "kind": "virtual" } }
    ]
} } }

modules.motion is a config-only namespace, not a runtime module — set "enabled": false. Codegen reads its config.axes, but there is no motion module binary; leaving it enabled (the default) makes the server’s supervisor try to spawn a nonexistent “motion” executable. acctl add-axis sets this for you.

The axis schema

FieldTypeRequiredDescription
namestringyesAxis name → generated handle struct.
linkstringethercat onlySlave name this axis binds to (matches a slaves entry).
typestringyesCiA-402 profile, currently "pp".
backendobjectno{ "kind": "ethercat" } (default if omitted) or { "kind": "virtual" }. An optional link inside the ethercat backend overrides the top-level link.
optionsobjectnoSensor/limit wiring and drive-behavior traits — see below.
outputsobjectnoAxis status values published to GlobalMemory each tick.

options

FieldTypeDefaultDescription
positive_limitstringGM bool for the positive limit switch
negative_limitstringGM bool for the negative limit switch
home_sensorstringGM bool for the home reference sensor
error_codestringGM u16 for the drive error code
maximum_pos_limitstringGM numeric: dynamic max software position limit (user units)
minimum_pos_limitstringGM numeric: dynamic min software position limit (user units)
invert_directionboolfalseNegate position targets and feedback (reverse direction in software)
halt_blocks_setpoint_ackboolfalseDrive trait. See Drive-behavior defaults.
soft_home_methodint37CiA-402 homing method (0x6098) for “current position = home”. E.g. 35 for the Inovance SV660N.

The string-valued options name GM variables; the bool/int options are baked directly into the generated handle’s AxisConfig. Software-position-limit behavior is covered in Chapter 8.

outputs

Each field names a GM variable that the generated tick() writes every cycle (all optional):

FieldGM typeFieldGM type
positionf64in_motionbool
raw_positioni64moving_positivebool
speedf64moving_negativebool
is_busyboolat_max_limitbool
is_errorboolat_min_limitbool
error_codeu32/i32at_positive_limit_switchbool
error_messagestringat_negative_limit_switchbool
motor_onboolhome_sensorbool

You do not hand-create these variables. Name them in outputs/options, and acctl codegen scaffolds the missing ones into the shared-memory layout with the right types (see The workflow).

Drive-behavior defaults (the thing that bites people)

Different CiA-402 drives disagree on subtle protocol behavior. The one that costs people a day is the halt cancel handshake, surfaced as halt_blocks_setpoint_ack.

Symptom

You command a stop (e.g. move-to-load reaches its target and calls halt()). The motor physically stops, but the axis reports an error:

Halt timeout: cancel not acknowledged

Cause

To cleanly close out a halt, the axis issues a new set-point (current position, zero velocity) and waits for the drive to acknowledge it (status-word bit 12). Drives disagree on whether they’ll acknowledge a set-point while Halt (control word bit 8) is still asserted:

  • Kollmorgen AKD, Inovance SV660N — will not acknowledge while halted, so the handshake must clear halt first. This is the CiA-402-standard behavior; set halt_blocks_setpoint_ack: true.
  • Teknic ClearPath — acknowledges while halted, and resumes the prior move if you clear halt early, so it needs halt held through the handshake. It is the oddball; leave halt_blocks_setpoint_ack: false.

The default is false — chosen as the safe fallback, not the common case. On an unknown drive, a wrong false merely times out with the motor already stopped (loud and safe); a wrong true could provoke unexpected motion. So if you hit the halt timeout on an AKD/Inovance, the fix is to set the option true.

soft_home_method

The CiA-402 method (0x6098) used when declaring “current position = home”. The default 37 works on modern drives (Teknic ClearPath); the Inovance SV660N’s range stops at 35. Set it per drive when homing won’t latch.

Seeding defaults instead of memorizing them

These values are properties of the drive model, recorded in the EtherCAT device library (device_definitions.json, via ext_definitions/<vendor>.json). You don’t have to remember which drive needs what:

  • In the IDE — run autocore: Seed axis drive defaults. It asks the live target (ethercat.list_devices) for each bound drive’s profile and writes the matching options into project.json. It is sparse (only writes what the drive declares — Teknic gets nothing, so it stays at the safe default) and non-destructive (never overwrites a value you set, so re-running is safe).
  • By hand — set halt_blocks_setpoint_ack / soft_home_method in the axis options per the table above.

Either way the value ends up in project.json options and is baked into the generated handle by acctl codegen.

Virtual / simulated axes

A virtual axis has no fieldbus. Its AxisView is an in-process SimDrive (autocore_std::motion::sim::SimDrive) that emulates a CiA-402 Profile Position drive: the enable state machine, the set-point-acknowledge handshake, and motion integration. Because Axis is generic over AxisView, your process code is identical whether the axis is real or simulated.

acctl add-axis --name SimPress --backend virtual
acctl codegen

Use them to:

  • develop and test process logic with no hardware (CI-friendly), and
  • A/B a process against sim vs. a real drive by swapping the axis backend.

Notes and limits:

  • The generated virtual handle’s sync() advances the sim by a fixed SIM_DT (default 10 ms). Match it to your control program’s scan period if timing matters.
  • SDO and persistent-position methods are omitted on the virtual handle (they’re meaningless without a fieldbus).
  • Virtual axes require autocore-std ≥ 3.3.55 (the version that introduced SimDrive). acctl codegen checks this for you — see The workflow.

The workflow

acctl add-axis --name Press --link AKD_3   # 1. create the axis
#   (IDE: "Seed axis drive defaults")       # 2. seed drive-behavior options
acctl codegen                               # 3. generate gm.rs + scaffold vars
acctl push control --start                  # 4. build + deploy

What acctl codegen does for axes, server-side:

  • Generates the drive handle in control/src/gm.rs (an EtherCAT DriveHandle or a SimDrive-backed handle), baking in the options (invert_direction, halt_blocks_setpoint_ack, soft_home_method).

  • Scaffolds the axis variables. The outputs/options variable names are injected into the shared-memory layout automatically (via the server’s Project::normalize), for axes in both homes. You do not run ethercat.generate_variables for axis variables — that command now handles only PDO variables.

  • Checks the autocore-std floor. Codegen emits constructs that need a minimum autocore-std (GmCompat → 3.3.52, halt_blocks_setpoint_ack → 3.3.54, SimDrive → 3.3.55). If control/Cargo.lock pins an older version, acctl codegen stops before writing gm.rs with an actionable message rather than letting you hit a cryptic Rust compile error:

    ✗ autocore-std too old for this server's codegen output.
      control/Cargo.lock pins 3.3.51, but the generated gm.rs needs ≥ 3.3.55.
      Bump it, then re-run codegen:
          (cd control && cargo update -p autocore-std --precise 3.3.55)
    

Using the generated handle

The handle bundles the Axis state machine with its backend (a PDO snapshot or a SimDrive). Each tick:

  1. sync(gm) — pull feedback (EtherCAT: TxPDO from shared memory; virtual: advance the sim).
  2. Issue commandsenable(), move_absolute(pos, vel, accel, decel), move_relative(...), home(method), halt(), reset_faults(), set_position(...), disable().
  3. tick(gm, client) — advance the state machine and publish outputs.

Status reads mirror the outputs: position(), speed(), is_busy(), is_error(), error_code(), error_message(), motor_on(), in_motion(). See Chapter 8 for a full control-program example.

Managing axes

  • Swap the drive — change the axis link to the new slave name and re-run acctl codegen. The control program is untouched.
  • Change behavior — edit options and re-run acctl codegen. Re-running the IDE seed command won’t clobber a value you set by hand.
  • Rename — changing name renames the generated handle struct; update the control program’s references.
  • Remove — delete the axis object and re-run acctl codegen. (Orphaned output variables can be removed from variables if you no longer want them.)

Adding, removing, or re-binding an axis does not change the shared-memory layout hash unless it adds or removes output variables — so an axis edit is generally a recompile, not a server-restart.

Common pitfalls

SymptomCauseFix
Halt timeout: cancel not acknowledged (motor stops, axis errors)Drive won’t ack a set-point while halted (AKD/Inovance)Set halt_blocks_setpoint_ack: true (or run the IDE seed command).
Homing never latchesWrong CiA-402 homing method for the driveSet soft_home_method (e.g. 35 for SV660N).
gm.<axis var> not found when buildingAxis output variable not scaffoldedRun acctl codegen (it injects them). Confirm the outputs name is spelled the same everywhere.
cannot find SimDrive / no field halt_blocks_setpoint_ack / GmCompat not foundControl program’s autocore-std is older than the codegen output needsBump autocore-std (the acctl codegen gate prints the exact --precise version).
Move rejected / quick-stop near a limitDynamic software position limit trippedCheck maximum_pos_limit / minimum_pos_limit GM values; a move away from the limit is always allowed.