The motion sequencer defines and executes short deterministic workflows inside the ecmc realtime loop. Sequences may be defined from IOC startup commands or edited at runtime through a dedicated asyn port and EPICS records.
This implementation is experimental. It is intended for workflows such as:
Existing axis behavior is unchanged unless a sequence is explicitly created, compiled, armed, and started.
Use addMotionSequence.cmd to create an empty sequence and its EPICS interface before defining steps:
${SCRIPTEXEC} ${ecmccfg_DIR}scripts/addMotionSequence.cmd, \
"SEQ_ID=0,MAX_STEPS=32,DB_PREFIX=$(IOC):,RECORD_PREFIX=Seq0-"
Defaults:
SEQ_ID=0MAX_STEPS=32ASYN_PORT=ECMC_SEQ<SEQ_ID>DB_PREFIX=$(IOC):RECORD_PREFIX=Seq<SEQ_ID>-SCAN=.1 secondPREC=3LOAD_PVS=1REPORT=0SOFT_TRG_FLNK optionally defines a forward link from the soft-trigger counter.
The wrapper first executes:
Cfg.CreateMotionSeq(seqIndex,maxSteps,portName)
and then loads ecmcMotionSequence.template. Creation must happen before the
records connect to the dedicated sequence port.
Each call also registers the sequence in the IOC configuration namespace:
$(IOC):MCU-Cfg-SEQ-Cnt
$(IOC):MCU-Cfg-SEQ-FrstObjId
$(IOC):MCU-Cfg-SEQ<ID>-Pfx
$(IOC):MCU-Cfg-SEQ<ID>-NxtObjId
$(IOC):MCU-Cfg-SEQ<ID>-PrvObjId
The linked list preserves creation order and supports sparse sequence IDs.
The caQtDM overview follows this list, so it only includes configured
sequences. Open it through ecmcMain.ui or directly with:
python3 /ioc/modules/qt/ecmc_start_motion_sequence_overview.py <IOC>
A sequence follows this lifecycle:
create -> define/edit -> compile -> arm -> start
Typical startup definition:
Cfg.SeqMoveAbs(0,0,1,10.0,5.0,20.0,20.0,5000)
Cfg.SeqWaitInPos(0,1,1,2000)
Cfg.CompileMotionSeq(0)
Cfg.ArmMotionSeq(0)
The sequence can then be started through:
Cfg.StartMotionSeq(0)
or the Seq0-Cmd-Start record.
Compile validates steps and resolves data-item bindings in a low-priority EPICS thread. Arm copies the compiled plan into the active realtime plan.
Steps can also be inspected and changed through records. Select a step for
readback with Read-Index and process Read-Cmd, or use Read-Prev and
Read-Next:
caput IOC:Seq0-Read-Index 3
caput IOC:Seq0-Read-Cmd 1
caget IOC:Seq0-Read-Action IOC:Seq0-Read-Axis IOC:Seq0-Read-Name
Read-Prev and Read-Next skip disabled/unconfigured step slots. Direct
Read-Index plus Read-Cmd can still inspect any slot, including empty gaps
left for sparse step numbering.
Read-CmdLine exposes the selected step as a one-line text row. It is a
read-only waveform so navigating with Read-Prev and Read-Next does not
overwrite the editable command buffer. Process Read-ToCmdLine to copy the
selected row into CmdLine and the editable step fields:
caget -S IOC:Seq0-Read-CmdLine
caput IOC:Seq0-Read-ToCmdLine 1
To edit a step, write the Edit-* fields and then process Edit-Apply.
Recompile and arm the sequence before starting it:
caput IOC:Seq0-Edit-Index 3
caput IOC:Seq0-Edit-Enable 1
caput IOC:Seq0-Edit-Action 5
caput IOC:Seq0-Edit-Axis 1
caput IOC:Seq0-Edit-Position 0.2
caput IOC:Seq0-Edit-Velocity 0.5
caput IOC:Seq0-Edit-Acceleration 2
caput IOC:Seq0-Edit-Deceleration 2
caput IOC:Seq0-Edit-TimeoutMs 5000
caput -S IOC:Seq0-Edit-Name "rel +0.2"
caput IOC:Seq0-Edit-Apply 1
caput IOC:Seq0-Cmd-Compile 1
caput IOC:Seq0-Cmd-Arm 1
To insert or delete a configured step at Edit-Index, process Edit-Insert
or Edit-Delete:
caput IOC:Seq0-Edit-Index 4
caput IOC:Seq0-Edit-Insert 1
caput IOC:Seq0-Edit-Delete 1
Insert shifts steps at and after the index up by one and clears the inserted step. Delete shifts later steps down by one. Both operations invalidate the compiled/armed plan. Branch and goto targets are updated automatically when they are shifted. Delete fails if another step targets the deleted step.
The caQtDM sequence detail panel exposes the same readback and edit records.
Edit-Action, Read-Action, and the runtime Action record are numeric
action IDs. Use Read-CmdLine or CmdLine when a decoded text representation
is more useful.
For common edits, use the one-line command waveform instead of writing every field manually:
caput -S IOC:Seq0-CmdLine "4: wait_item ec.s2.ai01 gt 10.5 timeout=5000 name='wait ai01'"
caput IOC:Seq0-CmdLine-Apply 1
caget -S IOC:Seq0-CmdLine-Result
caget -S IOC:Seq0-Read-CmdLine
CmdLine-Result reports parse/apply feedback. Read-CmdLine exposes the
selected readback row in the same one-line syntax.
The command line syntax is:
<step>: <action> key=value key=value ...
Use enabled=0 to define a disabled step. For power, enable=0|1
controls the axis power state.
Supported initial actions include:
nop
reset axis=<id> timeout=<ms> wait=<0|1>
power axis=<id> enable=<0|1> timeout=<ms> wait=<0|1>
home axis=<id> timeout=<ms> wait=<0|1>
move_abs axis=<id> pos=<pos> vel=<vel> acc=<acc> dec=<dec> timeout=<ms> wait=<0|1>
move_rel axis=<id> dist=<dist> vel=<vel> acc=<acc> dec=<dec> timeout=<ms> wait=<0|1>
move_vel axis=<id> vel=<vel> acc=<acc> dec=<dec>
halt axis=<id> timeout=<ms> wait=<0|1>
wait_inpos axis=<id> timeout=<ms>
set_enc_homed axis=<id> homed=<0|1>
wait_time ms=<ms>
run_seq seq=<childSeq> timeout=<ms>
set_item item=<dataItem> value=<value>
wait_item item=<dataItem> op=<eq|ne|gt|gte|lt|lte> value=<value> timeout=<ms>
exit_item item=<dataItem> op=<eq|ne|gt|gte|lt|lte> value=<value> timeout=<ms>
branch_item item=<dataItem> op=<eq|ne|gt|gte|lt|lte> value=<value> true_step=<step> [false_step=<step>]
goto_step target=<step>
| ID | Action |
|---|---|
| 0 | NOP |
| 1 | MC_Reset |
| 2 | MC_Power |
| 3 | MC_Home |
| 4 | MC_MoveAbsolute |
| 5 | MC_MoveRelative |
| 6 | WaitInPosition |
| 7 | SetItem |
| 8 | WaitItem |
| 9 | WaitTime |
| 10 | RunSequence |
| 11 | ArmPositionTrigger |
| 12 | WaitTriggerDone |
| 13 | ArmTimeTrigger |
| 14 | MC_MoveVelocity |
| 15 | MC_Halt |
| 16 | ExitItem |
| 17 | SetEncHomed |
| 18 | BranchItem |
| 19 | GotoStep |
Prefer the action-specific Cfg.Seq* commands when defining sequences in a
startup script.
Motion and waits:
Cfg.SeqNop(seq,step)
Cfg.SeqWaitTime(seq,step,waitMs)
Cfg.SeqReset(seq,step,axis,timeoutMs)
Cfg.SeqReset(seq,step,axis,timeoutMs,wait)
Cfg.SeqPower(seq,step,axis,enable,timeoutMs)
Cfg.SeqPower(seq,step,axis,enable,timeoutMs,wait)
Cfg.SeqHome(seq,step,axis,homeSeq,homePos,velToCam,velOffCam,acc,dec,timeoutMs)
Cfg.SeqHome(seq,step,axis,homeSeq,homePos,velToCam,velOffCam,acc,dec,timeoutMs,wait)
Cfg.SeqMoveAbs(seq,step,axis,pos,vel,acc,dec,timeoutMs)
Cfg.SeqMoveAbs(seq,step,axis,pos,vel,acc,dec,timeoutMs,wait)
Cfg.SeqMoveRel(seq,step,axis,distance,vel,acc,dec,timeoutMs)
Cfg.SeqMoveRel(seq,step,axis,distance,vel,acc,dec,timeoutMs,wait)
Cfg.SeqMoveVel(seq,step,axis,vel,acc,dec,timeoutMs)
Cfg.SeqMoveVel(seq,step,axis,vel,acc,dec,timeoutMs,wait,tolerance)
Cfg.SeqHalt(seq,step,axis,timeoutMs)
Cfg.SeqHalt(seq,step,axis,timeoutMs,wait)
Cfg.SeqWaitInPos(seq,step,axis,timeoutMs)
Cfg.SeqSetEncHomed(seq,step,axis,homed)
SeqSetEncHomed sets the homed flag on the axis primary/current encoder and
then advances immediately.
Data items:
Cfg.SeqSetItem(seq,step,item,value,timeoutMs)
Cfg.SeqWaitItem(seq,step,item,op,value,timeoutMs)
Cfg.SeqExitItem(seq,step,item,op,value,timeoutMs)
Cfg.SeqBranchItem(seq,step,item,op,value,trueStep)
Cfg.SeqBranchItem(seq,step,item,op,value,trueStep,falseStep)
Cfg.SeqGotoStep(seq,step,targetStep)
Cfg.InsertMotionSeqStep(seq,step)
Cfg.DeleteMotionSeqStep(seq,step)
SeqWaitItem and SeqExitItem support ==, !=, >, >=, <, <=,
eq, and ne. SeqExitItem exits the current sequence as Done when the
condition is fulfilled; if the condition is false, the sequence continues with
the next step.
SeqBranchItem uses the same operators. It jumps to trueStep when the
condition is fulfilled. In one-line commands these targets are named
true_step and false_step. If falseStep/false_step is omitted, a false
condition continues with the next enabled step; otherwise it jumps to the false
target. Branch and goto targets are configured step IDs and must point to
enabled steps.
Nested sequences:
Cfg.SeqRunSeq(parentSeq,step,childSeq,timeoutMs)
Triggers:
Cfg.SeqArmPosTrigger(seq,step,id,axis,item,startPos,interval,endPos,value,pulseMs)
Cfg.SeqArmTimeTrigger(seq,step,id,item,delayMs,periodMs,count,value,pulseMs)
Cfg.SeqWaitTriggerDone(seq,step,id,timeoutMs)
Control and inspection:
Cfg.CompileMotionSeq(seq)
Cfg.ArmMotionSeq(seq)
Cfg.StartMotionSeq(seq)
Cfg.StopMotionSeq(seq)
Cfg.ResetMotionSeq(seq)
Cfg.ReportMotionSeq(seq)
Cfg.ReportMotionSeq(seq,step)
The short SeqMoveVel form is nonblocking. It issues the velocity command and
advances immediately:
Cfg.SeqMoveVel(0,0,1,10.0,20.0,20.0,1000)
Cfg.SeqWaitItem(0,1,ec0.s1.stopInput,==,1,30000)
Cfg.SeqHalt(0,2,1,5000)
The extended form optionally waits for actual velocity:
Cfg.SeqMoveVel(0,0,1,10.0,20.0,20.0,5000,1,0.1)
This waits until actual velocity is within 10.0 +/- 0.1. Internally the generic
step arguments are:
wait=1;tol=0.1
This representation is also available through the Edit-Args record.
All motion helpers support an optional final wait argument. The short forms
keep their existing defaults:
wait=1wait=0With wait=0, the command is issued and the sequence advances immediately. This
allows multiple axes to be started in consecutive realtime cycles:
Cfg.SeqMoveAbs(0,0,1,100.0,10.0,20.0,20.0,1000,0)
Cfg.SeqMoveAbs(0,1,2,50.0,5.0,10.0,10.0,1000,0)
Cfg.SeqWaitInPos(0,2,1,15000)
Cfg.SeqWaitInPos(0,3,2,15000)
The generic step representation stores this as:
wait=0
It can therefore also be configured through Edit-Args. A later wait step
should be used when completion must be synchronized.
Sequence 1 can execute sequence 0, wait, and execute it again:
Cfg.SeqRunSeq(1,0,0,10000)
Cfg.SeqWaitTime(1,1,500)
Cfg.SeqRunSeq(1,2,0,10000)
step is the step position in the parent sequence. childSeq selects the
sequence being called.
The child must reach DONE before the parent step timeout. Child errors and
stops propagate to the parent. Direct self-call is rejected during compile.
Position triggers are armed before a later motion step and execute in the background:
Cfg.SeqArmPosTrigger(0,0,0,1,ec0.s1.output01,10.0,1.0,29.0,1,5)
Cfg.SeqMoveAbs(0,1,1,50.0,10.0,20.0,20.0,10000)
Cfg.SeqWaitTriggerDone(0,2,0,1000)
This pulses the output at positions 10, 11, through 29. The range is startPos:interval:endPos; negative intervals support reverse motion, for example 80,-1,20 fires at 80,79,...,20.
Time triggers use elapsed time after the arm step:
Cfg.SeqArmTimeTrigger(0,0,0,ec0.s1.output01,10,5,100,1,1)
This fires first after 10 ms, then every 5 ms, for 100 pulses. Each pulse remains high for 1 ms.
Use soft instead of a data-item name:
Cfg.SeqArmPosTrigger(0,0,0,1,soft,10.0,1.0,29.0,1,0)
Cfg.SeqArmTimeTrigger(0,1,1,soft,10,5,100,1,0)
Each soft trigger event increments:
stat.soft_trigger_count
All trigger events, both hard and soft, also update:
stat.trigger_count
stat.trigger_id
Trigger IDs 0..7 expose dedicated counters and pulse-active status:
stat.soft_trigger.<id>.count
stat.soft_trigger.<id>.pulse
The internal asyn names keep the original soft_trigger prefix for
compatibility, but these per-ID counters now count both hard and soft trigger
IDs. The ecmccfg database maps them to Trigger0Count/Trigger0Pulse through
Trigger7Count/Trigger7Pulse, and keeps the old
SoftTrig0Count/SoftTrig0Pulse aliases for compatibility. Use the counters as
the reliable EPICS event source. The pulse flags are only high while a trigger
pulse is active and therefore require a nonzero pulseMs.
and updates:
stat.soft_trigger_id
The corresponding soft-only records are SoftTriggerCount and SoftTriggerId.
SoftTriggerEvent is processed through a CP link when the counter posts a
changed value. SOFT_TRG_FLNK can forward-link that event to another record.
The counter value should still be used to detect the number of new events.
With defaults for sequence 0, records use prefix $(IOC):Seq0-.
Editing:
Edit-Index
Edit-Enable
Edit-Action
Edit-Axis
Edit-Position
Edit-Velocity
Edit-Acceleration
Edit-Deceleration
Edit-TimeoutMs
Edit-Name
Edit-Transition
Edit-OnError
Edit-Args
Edit-Apply
CmdLine
CmdLine-Apply
CmdLine-Result
Readback navigation:
Read-Index
Read-Cmd
Read-Prev
Read-Next
Read-Enable
Read-Action
Read-Axis
Read-Position
Read-Velocity
Read-Acceleration
Read-Deceleration
Read-TimeoutMs
Read-Name
Read-Transition
Read-OnError
Read-Args
Commands and status:
Cmd-Compile
Cmd-Arm
Cmd-Start
Cmd-Stop
Cmd-Reset
State
Valid
Armed
Running
CompileBusy
StepIndex
Action
ErrorId
StepCount
ElapsedMs
StepName
ErrorText
ValidationText
TriggerId
TriggerCount
TriggerEvent
Trigger0Count..Trigger7Count
Trigger0Pulse..Trigger7Pulse
Trigger0Event..Trigger7Event
SoftTriggerId
SoftTriggerCount
SoftTriggerEvent
SoftTrig0Count..SoftTrig7Count
SoftTrig0Pulse..SoftTrig7Pulse
SoftTrig0Event..SoftTrig7Event
Status and readback records poll at SCAN because realtime sequence updates do
not currently generate asyn callbacks.
The ecmc PLC library exposes sequence control and status helpers. Command-style
functions are rising-edge triggered on the execute argument.
seq_arm(seq,execute)
seq_set_step(seq,execute,step_id)
seq_arm arms an already compiled sequence. seq_set_step requests a jump to a
configured step ID. The sequence must be armed or running; missing target steps
put the sequence in error.
Status getters:
seq_get_state(seq)
seq_get_valid(seq)
seq_get_armed(seq)
seq_get_running(seq)
seq_get_compile_busy(seq)
seq_get_step(seq)
seq_get_action(seq)
seq_get_error(seq)
seq_get_step_count(seq)
seq_get_elapsed_ms(seq)
seq_get_step returns the configured step ID of the active step. It returns
-1 when no step is active.
Example:
arm_err := seq_arm(0, arm_exec);
jump_err := seq_set_step(0, jump_exec, 20);
seq_running := seq_get_running(0);
seq_step := seq_get_step(0);
Define sequence 0 as one X scan line:
Cfg.SeqArmPosTrigger(0,0,0,1,soft,10.0,1.0,109.0,1,0)
Cfg.SeqMoveAbs(0,1,1,110.0,20.0,50.0,50.0,20000)
Cfg.SeqWaitTriggerDone(0,2,0,2000)
Cfg.CompileMotionSeq(0)
Cfg.ArmMotionSeq(0)
Sequence 1 composes scan lines with Y steps:
Cfg.SeqRunSeq(1,0,0,25000)
Cfg.SeqMoveRel(1,1,2,0.1,2.0,10.0,10.0,5000)
Cfg.SeqRunSeq(1,2,0,25000)
Separate forward and backward child sequences can implement a snake/raster scan without an extra return move.