Overview: From One-Shot to Continuous Cyclic Execution
A common first failure point for engineers moving from ladder logic to structured PLC programming is the assumption that a Function Block (FB) automatically repeats its internal logic. In Siemens S7-1200 and S7-1500 controllers running on TIA Portal, OB1 executes the user program cyclically at the configured scan cycle (or by interrupt-driven OBs for time-critical sections). The FB instance, however, only progresses through its internal state once per rising edge of its execute input. When the FB is wired such that its EN/EN0 input is dropped, or its Execute condition never re-asserts, the motor sequence appears to run exactly once and then sit in the last state forever.
This article explains why a directional motor FB stops after a single forward/reverse cycle, how to redesign the call interface so the cycle repeats, how to leverage the Siemens LAxisControl and LAcycCom application examples, and how to resolve a secondary fault where the drive fails to reach the target down position before reversing.
Cyclic OB1 Execution Model in S7-1200/1500 PLCs
OB1 is the main cyclic organization block in Siemens S7 controllers. The CPU reads inputs, executes OB1 from the first network to the last, writes outputs, and then immediately restarts the cycle. The cycle time is reported in the CPU diagnostics and is influenced by the user program length and any configured minimum cycle time or watchdog.
| Parameter | Path in TIA Portal | Typical Value | Notes |
|---|---|---|---|
| Scan cycle time | CPU Properties > Cycle | 1 ms to 60000 ms | Watchdog default 6000 ms; OB1 overrun generates OB80. |
| OB1 priority class | Fixed at 1 | 1 | Cannot be changed; lower priority OBs may interrupt OB1. |
| Process image (PII / PIQ) | CPU Properties > I/O Addresses | Auto-update at OB1 start/end | Inputs are consistent for the entire cycle. |
| OB35 / OB82 / OB121 | Project tree > Add new OB | Hardware / time / error interrupts | Use OB35 for cyclic axes at fixed slice; OB82 for diagnostics. |
Inside OB1 the FB is invoked with its instance DB as a parameter. Two boolean inputs are typically present: the EN enable (always TRUE if you want OB1 to drive the call) and the Execute (rising edge) which is the formal start trigger. The state of these inputs is what determines whether the FB begins, continues, or finishes its internal sequence.
Why a Function Block Stops After One Cycle
The user's described symptom - motor runs forward, runs reverse, then stops, executing the motion sequence only once - is almost always caused by one of three design errors:
- Single-shot Execute wired to OB1 scan. The Execute condition is a one-cycle pulse generated by a flank on a global start tag. After the FB finishes it goes to Done and never restarts because Execute stays FALSE.
- EN input tied to a sequencer flag. A common error is to put the busy flag on the EN input. As soon as the FB starts, Busy becomes TRUE, but Done is also asserted by the same path, so EN/EN0 toggles and the call is skipped on the next cycle.
- No state memory. The FB relies entirely on its local TEMP variables, which are reinitialised each call. Without a STAT field that records "I have already executed the reverse stroke" the FB cannot decide what to do next time Execute is pulsed.
The fix is to keep the EN input TRUE continuously and route the explicit start command to the Execute input. The internal logic of the FB must be written as a state machine that loops back from the last state to the first when both Done and a continuous-run flag are TRUE.
EN Input vs Execute Input: The Critical Distinction
Siemens blocks differentiate the enable line and the trigger line. The following table summarises the contract:
| Input | Function | Edge sensitivity | Recommended use |
|---|---|---|---|
EN / EN0
|
Switches the call itself. If FALSE, the FB is not processed and outputs hold their last value (LAD/FBD) or are not assigned (SCL). | Level-sensitive | Permissives such as drive ready, safety circuit closed, controller in RUN. |
Execute / REQ / Start
|
Initiates a new job. A rising edge transitions the FB from idle to busy. | Rising-edge sensitive | Operator start pushbutton, recipe step transition, FB-internal state advance. |
For a motor FB, the rule is: EN must be TRUE for every cycle in which you want the FB logic evaluated. The forward/reverse sequence is controlled by a state machine that internally pulses Execute to the next motion command. The outer call should look like this in SCL:
// OB1 - Main
IF "blockntw" AND NOT "disable_power" THEN
"MotorInstance".EN := TRUE;
ELSE
"MotorInstance".EN := FALSE;
END_IF;
"MotorInstance".Continuous := g_bRunContinuous; // continuous-run flag
"MotorInstance".TargetTop := g_rTargetTop;
"MotorInstance".TargetBot := g_rTargetBot;
"MotorInstance"(); // call the FB
Notice the absence of an Execute wire to a one-shot pulse. The FB is responsible for sequencing its own motion jobs.
Designing a Bidirectional Motor State Machine
A four-state machine is sufficient for forward, reverse, stop and idle. The states and transitions are shown below.
State transition table:
| Current state | Trigger | Next state | Action |
|---|---|---|---|
| IDLE (0) | Start = TRUE | FORWARD (10) | Issue MC_MoveAbsolute to TargetTop |
| FORWARD (10) | Done = TRUE AND Error = FALSE | HOLD (30) | Start dwell timer (e.g. 1 s) |
| HOLD (30) | Dwell elapsed | REVERSE (20) | Issue MC_MoveAbsolute to TargetBot |
| REVERSE (20) | Done = TRUE AND Error = FALSE | IF continuous THEN FORWARD (10) ELSE IDLE (0) | Loop or stop |
Step-by-Step SCL Implementation in TIA Portal
The following SCL code implements the state machine inside a single-instance FB. Drop it into TIA Portal under Program Blocks > Add new block > Function Block > SCL.
FUNCTION_BLOCK "MotorCycleFB"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
VAR_INPUT
EN : BOOL; // call enable - hold TRUE
Continuous : BOOL; // TRUE = loop forever
Start : BOOL; // rising edge starts sequence
TargetTop : LREAL; // upper target position (LU)
TargetBot : LREAL; // lower target position (LU)
Velocity : LREAL; // mm/s or rpm per axis scaling
DwellTime : TIME; // pause between strokes
END_VAR
VAR_OUTPUT
Busy : BOOL;
Done : BOOL;
Error : BOOL;
ErrorID : WORD;
ActualState : INT;
ActualPos : LREAL;
END_VAR
VAR
State : INT; // STAT - survives the cycle
DwellInst : TON; // STAT timer
MC_Power_0 : MC_Power;
MC_MoveTop : MC_MoveAbsolute;
MC_MoveBot : MC_MoveAbsolute;
Axis : AXIS_REF;
R_TrigStart : R_TRIG;
END_VAR
VAR_STAT
CycleCount : DINT;
END_VAR
BEGIN
IF NOT EN THEN
Busy := FALSE; Done := FALSE; Error := FALSE;
ActualState := 0;
RETURN;
END_IF;
// Power the axis continuously while EN is high
MC_Power_0(Axis := Axis, Enable := TRUE, StartMode := FALSE);
R_TrigStart(CLK := Start);
CASE State OF
0: // IDLE
ActualState := 0;
Busy := FALSE; Done := FALSE;
IF R_TrigStart.Q THEN
State := 10;
CycleCount := CycleCount + 1;
END_IF;
10: // FORWARD
ActualState := 10; Busy := TRUE;
MC_MoveTop(Axis := Axis,
Execute := TRUE,
Position := TargetTop,
Velocity := Velocity,
Done => , Busy => , Error => ,
ErrorID => );
IF MC_MoveTop.Done THEN
MC_MoveTop(Axis := Axis, Execute := FALSE);
DwellInst(IN := FALSE);
State := 30;
ELSIF MC_MoveTop.Error THEN
Error := TRUE; ErrorID := MC_MoveTop.ErrorID;
State := 99;
END_IF;
20: // REVERSE
ActualState := 20; Busy := TRUE;
MC_MoveBot(Axis := Axis,
Execute := TRUE,
Position := TargetBot,
Velocity := Velocity,
Done => , Busy => , Error => ,
ErrorID => );
IF MC_MoveBot.Done THEN
MC_MoveBot(Axis := Axis, Execute := FALSE);
IF Continuous THEN
State := 10; // loop back to FORWARD
ELSE
Done := TRUE; Busy := FALSE;
State := 0; // stay IDLE
END_IF;
ELSIF MC_MoveBot.Error THEN
Error := TRUE; ErrorID := MC_MoveBot.ErrorID;
State := 99;
END_IF;
30: // DWELL
ActualState := 30; Busy := TRUE;
DwellInst(IN := TRUE, PT := DwellTime);
IF DwellInst.Q THEN
DwellInst(IN := FALSE);
State := 20;
END_IF;
99: // ERROR - latch until reset
ActualState := 99;
// require rising edge on Start to clear
IF R_TrigStart.Q THEN State := 0; Error := FALSE; ErrorID := 0; END_IF;
END_CASE;
// Read current position for HMI / diagnostics
ActualPos := MC_Power_0.ActualPosition;
END_FUNCTION_BLOCK
Key points that make the cycle run continuously:
-
Stateis declared as aVAR(static) field, so it retains its value between OB1 cycles. - The transition from REVERSE to FORWARD or IDLE is decided by the
Continuousinput, not by an external signal. - EN is used as the only enable; Execute is generated internally inside the state machine.
- MC_MoveAbsolute is held Execute = TRUE for the entire motion. Dropping Execute on Done is correct and prevents a new motion being requested when state loops back to 10.
Reusing the Siemens LAxisControl Library
For a fully pre-built solution, Siemens publishes the LAxisControl application example as a free download in the Siemens Industry Online Support. It contains:
| Block | Function | Notes |
|---|---|---|
| LAxisControl | Sequences jog, homing, absolute and relative moves through a unified command interface. | Replaces hand-written CASE statements with a single command word. |
| LAxisControl_Com | PLC side of the HMI faceplate. | Receives commands from the WinCC Comfort/Advanced faceplate. |
| LAcycCom | Cyclic drive parameter read/write over PROFIdrive acyclic channel. | Useful for adjusting drive-side dynamic parameters at runtime. |
To integrate, install the example library in TIA Portal via Options > Support Packages > Add and drag the blocks from the master copy library into your project. The continuous-loop behaviour is already implemented; the only parameters you need to set on the HMI faceplate are Continuous = ON and TargetPositionTop / TargetPositionBottom in LU units of the configured technology object.
Resolving Position Accuracy Faults on the Down Stroke
The user reported that the motor returns from the down position before reaching it, then immediately reverses up. Three root causes are typical:
- Positioning window too tight. The default MC_Power/MC_MoveAbsolute behaviour uses the axis' configured positioning tolerance. If the tolerance is tighter than the actual mechanical resolution (backlash, encoder pitch, lead-screw pitch error), the FB never receives Done. Reduce the tolerance in the technology object under Position monitoring > Positioning tolerance or set Follow-up mode appropriately.
- Velocity / acceleration exceeds the drive's tracking capability. When the controller issues Done on the basis of the command position reaching the setpoint but the load has not yet arrived, the FB reverses and the actual position overshoots. Lower the velocity and the acceleration in the technology object or in the drive (SINAMICS p1120, p1121).
- Wrong direction sign on the down axis. If TargetBot is set to the same sign as TargetTop, the move finishes in one step and the FB appears to skip the down stroke. Confirm the unit and sign convention: 1 LU = 1 mm (or 1°) as defined in the axis configuration.
Diagnostic sequence to apply in TIA Portal online mode:
- Open the watch table AxisMonitor from the project library and observe
ActualPosition,ActualVelocityand the status wordSW1 / SW2of the technology object. - If
SW1 bit 15 (PositioningComplete)never asserts, the drive never reports the axis as in position. Check drive-side telegram 1 bit 14 on PROFIdrive. - Force
Continuous = FALSEand run a single stroke. Capture the trace of ActualPosition vs. time to see whether the axis oscillates around the target (mechanical / control loop) or never reaches it (limit switch / wiring).
Block Interface Best Practices: STAT, INPUT, OUTPUT, IN_OUT
The original post mentions that the tags blockntw and disable_power are defined as STAT with default value 0. STAT is the right location for retentive internal state. The following checklist applies when designing a continuous-run FB:
| Section | Holds its value across cycles? | Used for |
|---|---|---|
VAR_INPUT |
No - set by caller each cycle | Start command, targets, velocities, flags from the HMI. |
VAR_OUTPUT |
No - written by FB | Busy, Done, Error, current state, position feedback. |
VAR_IN_OUT |
No - passed by reference | AXIS_REF, large data blocks, or structs you want both sides to modify. |
VAR (static, the default) |
Yes - retained in the instance DB | State machine variable, edge detectors, internal flags, dwell timer instance. |
VAR_TEMP |
No - reinitialised each call | Loop counters, intermediate calculations. |
Optimised access (S7-Optimized_Access = TRUE, the default in TIA Portal V17 and later) treats all instance variables symbolically. STAT values are placed in the optimised instance DB and are guaranteed to retain their value between OB1 cycles; TEMP values are placed on the local stack and start at 0 on every call.
Verification, Monitoring and Online Watch Tables
After downloading the project, run the following checks before the system is approved for production:
- OB1 scan time. Open Online & Diagnostics > Cycle / clock memory and verify that the worst-case OB1 time stays below 80% of the configured watchdog. The cyclic motor block should add at most a few milliseconds per cycle.
-
State trace. In the online watch table, add the instance DB tags
State,ActualPosition,BusyandDone. Set the trigger on State and confirm that the sequence reads 0 → 10 → 30 → 20 → 10 → 30 → 20 ... when Continuous = TRUE. - Force / simulation. Use PLCSIM or PLCSIM Advanced to step through OB1 with a single-step task. The state machine must advance one state per scan when an MC_Move job returns Done.
- Error latch. Force the drive offline (pull the PROFIdrive plug). The Error and ErrorID outputs must latch. A new rising edge on Start must clear them and return the FB to IDLE.
- Retentivity test. Stop the CPU, power-cycle the controller and restart. The state should resume at IDLE (default 0), not mid-stroke, because the technology object will home again.
Diagnostic and Troubleshooting Matrix
| Symptom | Likely root cause | Where to look | Fix |
|---|---|---|---|
| Sequence runs once, FB latches in Done | Continuous flag is FALSE or has been reset | Instance DB tag Continuous | Hold the operator tag TRUE, or move it to a STAT flag in the HMI tag set. |
| Sequence never starts | EN is FALSE or the call is being skipped | Watch EN and the call instance DB | Wire EN to a TRUE constant inside OB1; verify the call DB is the same DB shown in the project tree. |
| Down stroke aborts early | TargetBot unreachable - tolerance or wiring | Technology object - position monitoring; drive telegram diagnostics | Widen positioning tolerance or correct TargetBot sign. |
| OB1 watchdog trip (SF LED, OB80 entry) | Scan time too long for OB1 budget | CPU diagnostic buffer | Move slow operations (logging, file I/O) to a lower-priority cyclic OB (OB35) or a separate task. |
| State variable resets every cycle | Declared as TEMP or in a wrong instance DB | FB interface | Move the State to a STAT field in the FB instance DB. |
| MC_Power returns ErrorID 0x8001 | Axis is not ready or encoder not configured | Axis configuration, drive diagnostics | Check the encoder telegram and PROFIdrive connection. |
| FB appears to hang in state 99 | Error latched, Start rising edge missing | Online watch table for Start | Confirm Start is a momentary pushbutton, not a maintained switch. |
Performance and Safety Considerations
When extending the pattern to multi-axis machines, mind these constraints:
- OB priority. OB1 is class 1; a higher-priority cyclic OB such as OB35 (class 16) can interrupt OB1. Put coordination state machines in OB1 and time-critical axis processing in OB35 so they can be scheduled deterministically.
- Multi-instance FBs. Where multiple motors share the same logic, place the cycle FB inside another parent FB and call it as a multi-instance. The state of each instance is then isolated in the parent DB.
- Safety integration. When the FB is part of a safety function, place the motor enable directly under the Failsafe signal (F-Output) of the safety program, not under standard outputs. The standard FB can request motion, but the F-Output must be TRUE for the drive enable to be wired up.
- Retain behaviour. In the technology object, set the axis to "retain" the actual position. Otherwise, after a CPU restart the position reference is lost and the first move should always start with MC_Home before the cycle runs.
- CPU load. Each MC_MoveAbsolute in flight creates an internal axis job. Running the state machine in OB1 at 10 ms and ten motors in parallel is well within an S7-1516's capability; the same load on an S7-1214C is too high and you should move the cycle to a slower task (OB30 with 50 ms).
FAQ
Why does my motor function block run only once and then stop?
Most likely the Execute input is a one-shot pulse, or the Continuous input is FALSE. Wire the FB's EN input to a TRUE source for the full duration, keep Execute driven by the internal state machine, and set the operator tag Continuous = TRUE in the instance DB or HMI.
Should I use EN or Execute to start a Siemens motion FB?
EN is a level-sensitive call enable and must be TRUE for the FB to be processed at all. Execute is a rising-edge trigger that starts a new motion job. For a continuously running motor cycle, keep EN permanently TRUE and use a state machine inside the FB to generate Execute on the next MC block.
My motor reaches the top position but never reaches the down position. What should I check first?
Verify that TargetBot has the correct sign and unit (LU) for your axis. Open the technology object's online diagnostics and check the positioning tolerance, the drive's PROFIdrive status word bit 14 (PositioningComplete) and the actual velocity during the down stroke. If the position oscillates around the target, the tolerance is too tight relative to the mechanical backlash.
Can I use the LAxisControl library instead of writing the state machine myself?
Yes. The Siemens example 109749348 ships a ready-made LAxisControl block that includes jog, homing and absolute moves with a continuous-loop option. Install the support package that matches your TIA Portal version, drag the blocks into your project, configure the AXIS_REF and select Continuous = ON on the HMI faceplate.
How do I keep the FB state across CPU restart?
Declare the state variable and any required flags as STAT inside the FB so they are stored in the instance DB. Enable retentivity on the instance DB under the FB's Properties > Retain if you want the cycle counter and last state to survive a power cycle. Always re-home the axis after restart before resuming the cycle.