SetpointGenerator4thOrder
SetpointGenerator4thOrder generates a motion profile bounded in four derivatives: velocity, acceleration, jerk and snap.8 minute read
SetpointGenerator4thOrder generates a motion profile bounded in four
derivatives: velocity, acceleration, jerk and snap. Bounding the fourth means
jerk itself ramps instead of stepping, which is what a machine with a
lightly damped structure needs — a stepped jerk rings it, a ramped one does
not.
It moves to a position, or holds a velocity as a jog, and switches between the two while running.
flowchart LR
i1(["command/start"]) --> B["SetpointGenerator4thOrder"]
i2(["command/abort"]) --> B
i3(["command/type"]) --> B
i4(["command/jogVelocity"]) --> B
p1(["targetProfile/position, velocity, acceleration, jerk, snap"]) --> B
B --> o1(["outputProfile/position, velocity, acceleration, jerk, snap"])
B --> o2(["status/isDone, isAborted"])
B --> o3(["status/isMovingToPosition, isMovingToVelocity, isAtConstantVelocity"])
B --> o4(["status/currentSample, currentIndex"])
B --> o5(["calc/sampleArray, snapArray, numberOfSamples, totalTime"])
The four numbers under
targetProfileother thanpositionare bounds, not targets. The generator uses as much of each as it needs and no more, so a short move may never reach any of them.
Two things about this block will catch you out. Read them before using it.
command/startmust be held true for the whole move. It is not a pulse. Clearing it mid-move aborts the move as soon as the axis reaches its cruise speed.
command/abortonly works in jog mode. To abort a position move, clearcommand/start. And an abort of either kind only takes effect during a constant-speed phase — requested during acceleration or braking, it waits. This is not an emergency stop.
Signals
Inputs
| Path | Unit | Range | Description |
|---|---|---|---|
command/start |
- | true or false | Starts a position move, and must stay true until it finishes. The block clears it when the move ends. |
command/abort |
- | true or false | Aborts a jog only. Ignored during a position move. Cleared by the block once acted on. |
command/type |
- | 0, 1, 2 | 0 relative, 1 absolute, 2 velocity (jog). See the table below. |
command/jogVelocity |
axis unit per second | any | The jog target speed. Only read every hundredth cycle, and only while the axis is at constant speed, so a jog change can take that long to act. |
Outputs
| Path | Unit | Description |
|---|---|---|
outputProfile/position |
axis unit | The setpoint to follow. Held where it stopped when a move ends. |
outputProfile/velocity |
axis unit per second | Its speed — feed this to a feedforward path. |
outputProfile/acceleration |
axis unit per second² | Its acceleration. |
outputProfile/jerk |
axis unit per second³ | Its jerk. Continuous, which is the point of this block. |
outputProfile/snap |
axis unit per second⁴ | The driving quantity, which steps between sixteen fixed values. |
status/isDone |
- | The move has finished. Reads false until the first move completes, including straight after a restart. |
status/isAborted |
- | The last move was aborted rather than completed. |
status/isMovingToPosition |
- | A position move is running. |
status/isMovingToVelocity |
- | A jog is running. |
status/isAtConstantVelocity |
- | Acceleration, jerk and snap are all zero. An abort can only act while this is true. |
status/currentSample |
- | How many cycles into the profile the axis is. |
status/currentIndex |
- | Which of the eighteen profile segments is playing, 0 to 17. |
calc/numberOfSamples |
- | How many cycles the whole move will take. |
calc/totalTime |
s | How long it will take. Check this after starting — a very small bound makes it enormous. |
calc/sampleArray |
- | How many cycles each profile segment lasts. Diagnostic. Shows 17 of the 18 segments. |
calc/snapArray |
axis unit per second⁴ | The snap value of each segment. Diagnostic. Shows 17 of the 18. |
Parameters
| Path | Unit | Default | Range | Effect |
|---|---|---|---|---|
targetProfile/position |
axis unit | 0.0 | unbounded | The target. Absolute or relative depending on command/type. In relative mode a value of exactly 0 is refused. |
targetProfile/velocity |
axis unit per second | 0.5 | above 0 | Speed bound. A zero refuses the move. |
targetProfile/acceleration |
axis unit per second² | 5 | above 0 | Acceleration bound. A zero refuses the move. |
targetProfile/jerk |
axis unit per second³ | 100 | above 0 | Jerk bound. A zero refuses the move. |
targetProfile/snap |
axis unit per second⁴ | 1000 | above 0 | Snap bound — how fast jerk may change. This is the parameter that makes the profile gentle. A zero refuses the move. |
All five are persistent and survive a controller restart. Negative values are used as their magnitude. No parameters exist below this block.
command/type |
What the machine does | What targetProfile/position means |
|---|---|---|
0 RELATIVE |
Moves a given distance from where it is | The distance to travel. Exactly 0 is refused |
1 ABSOLUTE |
Moves to a given position — the default | The position to arrive at |
2 VELOCITY |
Jogs at command/jogVelocity until aborted |
Not used |
Setup
-
Set the four bounds from what the machine can take. All four have non-zero defaults, so the block will move without being configured — check them before you start it.
-
Set
command/typeto 0 for a first move, so the distance is relative to wherever the axis already is. -
Set
targetProfile/positionto a small distance. -
Set
command/starttrue and leave it true.Step 4 moves the machine, immediately and at the bounds you configured. Set them well below the machine’s capability first, and use a distance you can afford to overshoot.
-
Watch
status/isMovingToPositiongo true, thenstatus/isDonego true when it arrives. The block clearscommand/startitself. -
Read
calc/totalTimeand check it against the distance and the speed bound. A much larger number means a bound is far smaller than you meant. -
Confirm
outputProfile/positionends exactly at the target. It should be exact, not close.
Tuning
- Set
targetProfile/velocityandtargetProfile/accelerationfirst — they are what the machine’s mechanics allow. - Set
targetProfile/jerkto what the drivetrain tolerates. Lower means gentler load reversals. - Set
targetProfile/snaplast, and use it to trade smoothness against move time. It is the only bound the third-order generators do not have, and the reason to use this block at all. - Run a move and watch
outputProfile/jerk. With snap bounded it ramps between levels; the flatter the ramps, the gentler the move. - If the machine rings at a known frequency, lower
targetProfile/snapuntil the ringing stops. Every reduction lengthens the move. - Compare
calc/totalTimebefore and after each change, so you can see what the smoothness cost. - Short moves take longer than the bounds imply. Every profile segment is rounded up to a whole cycle, which matters most when the move is only a few cycles long.
The gentler the curve leaves zero, the less the machine is excited — and the longer the move takes.
| Symptom | Cause | Action |
|---|---|---|
| The move stopped part-way | command/start was cleared, which aborts |
Hold command/start true for the whole move |
command/abort did nothing |
It only aborts a jog | Clear command/start to stop a position move |
| An abort did not act for a while | Aborts only take effect at constant speed | Expected. Do not use this as an emergency stop |
| The axis stopped in the wrong place after an abort | An abort plays out the braking half, wherever that ends | Expected. The stopping point is not predictable |
| Nothing moved and an error was logged | A bound is 0, or a relative move of exactly 0 was requested | Set all four bounds above 0 |
| The same error keeps repeating | The block retries every hundredth cycle | Fix the bound or the move type |
| The first absolute move went to the wrong place | The block starts from position 0, and cannot be told the machine’s real position from the parameter tree | Use relative moves, or drive the block from an application |
status/isDone reads false after a restart |
Expected: it only becomes true when a move finishes | Run one move |
calc/totalTime is enormous |
A bound is far smaller than intended | Check all four |
| A short move took much longer than expected | Each segment is rounded up to a whole cycle | Expected. It matters only for very short moves |
| A jog change took a long time to act | Jog targets are read every hundredth cycle, at constant speed only | Expected |
| The machine rings during acceleration | targetProfile/snap is too high |
Lower it |
| The move is smooth but too slow | A bound is limiting it — read calc/totalTime |
Raise snap first, then jerk |
calc/snapArray shows 17 entries, not 18 |
Expected: the last segment is not published | The missing entry is always 0 |
| A bound reads back positive after writing a negative one | Expected: the magnitude is used | Write positive values |
| I need this on several axes | Not possible — this block is single axis | Use one instance per axis |
A starting point for an axis that may move at 0.5 units per second:
targetProfile/velocity = 0.5
targetProfile/acceleration = 5.0
targetProfile/jerk = 100.0
targetProfile/snap = 1000.0
command/type = 1
Limits and errors
| Limit | Set by | What happens | Reported |
|---|---|---|---|
| The four bounds | targetProfile |
Respected exactly. The profile uses as much of each as it needs; a short move reaches none of them | calc/totalTime and calc/numberOfSamples |
| A bound of 0 | Nothing | The move is refused and the output does not change | Logged, and repeated every hundredth cycle while it persists |
| A relative move of exactly 0 | Checked | Refused as an incorrect move command | Logged, and repeated |
| Bound signs | Fixed | Negative bounds are used as their magnitude | Read the values back |
| A very small bound | Nothing | Accepted, and produces an enormous move time | calc/totalTime |
| Arrival | The profile | The move lands exactly on the target, not within a rounding error | outputProfile/position |
| Segment durations | Fixed | Rounded up to whole cycles, so short moves take longer than the bounds imply | calc/totalTime |
| Position move abort | command/start |
Clearing it aborts, but only once the axis is at constant speed | status/isAborted |
| Jog abort | command/abort |
Aborts, but only once the axis is at constant speed | status/isAborted |
| Abort stopping point | Nothing | The braking half plays out and the axis stops wherever it ends. Not the target, and not predicted | Not reported |
| Starting position | Not settable from the tree | An absolute move always starts from 0 unless the block is driven from an application | Not reported |
| Jog latency | Fixed | A new jog target is read every hundredth cycle, at constant speed only | Not reported |
| Axis count | Fixed | One axis per instance, always | Not reported |
The block logs an error, repeatedly, when a bound is 0 or the move command is not valid. Every other condition above shows as a value on a trace, or not at all.
Verified against motorcortex-control3 3.30.0 (bc348fd).