s7-SCL-scripts/s7-1500/demo-advance-scl-contol.md

16 KiB

README — S7-1500 SCL Sequencer (AUTO / INCR) with Pause vs Stop, Stop Categories, Skip/Disable, Safety-Locked Steps

This project shows a Siemens S7-1500 step sequencer in SCL with:

  • AUTO mode (advance automatically)
  • INCR mode (advance only on operator NEXT pulse)
  • Pause vs Stop
  • Stop categories (immediate / end-of-step / end-of-cycle / no-stop)
  • Per-step UDT configuration (timeout + operator text)
  • Skip/disable steps (optional steps can be disabled)
  • Prevent disabling safety steps (PLC forces critical steps enabled)

You can paste the TYPES into "PLC data types" and the FB code into an SCL FB.


  1. Concepts / How it works

  1. Step machine (FSM)
  • Variable "Step" defines the state: Step = 0 -> IDLE Step = 1..10 -> execute step logic
  • Each step has:
    • Actions (outputs to command)
    • Done condition (when it is allowed to advance)
    • Timeout (if not done within time -> Error)
  1. Modes We use enum E_Mode:
  • MODE_STOP = 0 (not running, safe outputs)
  • MODE_AUTO = 1 (auto advance)
  • MODE_INCR = 2 (manual advance with NEXT)

Mode selection priority:

  • If Enable=FALSE OR Cmd_Stop=TRUE OR ErrorActive=TRUE => MODE_STOP
  • Else if Mode_Auto=TRUE => MODE_AUTO
  • Else if Mode_Incr=TRUE => MODE_INCR
  • Else MODE_STOP
  1. Pause vs Stop
  • STOP:
    • forces safe outputs (FALSE)
    • typically sets Step=0 (IDLE)
  • PAUSE:
    • freezes step progression and timer
    • keeps outputs frozen at last values (freeze outputs)

Pause is implemented by:

  • PauseLatched toggled by Cmd_Pause edge
  • when pause becomes TRUE, capture current outputs into Frozen_* variables
  • while paused, outputs are forced to Frozen_* values
  1. Stop categories (StopReq + StopCat) Enum E_StopCat:
  • STOPCAT_IMMEDIATE: stop instantly (Step=0 + safe outputs)
  • STOPCAT_END_OF_STEP: finish current step then stop
  • STOPCAT_END_OF_CYCLE: continue until the configured end-of-cycle step completes, then stop
  • STOPCAT_NO_STOP: info only (never stops)

Stop requests are latched:

  • STOPCAT_IMMEDIATE / END_OF_STEP / END_OF_CYCLE -> latched in PLC
  • STOPCAT_NO_STOP -> not latched (only displayed as info)
  1. Per-step configuration via UDT array Cfg[1..10] Each step stores:
  • Text: shown to operator
  • Timeout: step timeout
  • IsEndOfCycle: marks the “cycle end” step
  • ReqEnabled: HMI requested enable
  • Enabled: PLC effective enable used by sequencer
  • SafetyLocked: if TRUE, cannot be disabled
  • NextStep: optional jump target (0 means normal next)

The PLC NEVER trusts ReqEnabled directly. PLC computes Enabled every scan:

  • if SafetyLocked => Enabled := TRUE (and optionally ReqEnabled forced TRUE too)
  • else Enabled := ReqEnabled
  1. Skip/Disable steps Instead of Step := Step + 1, we compute "next enabled step":
  • If operator disables a step, sequence jumps over it
  • A helper FC_NextEnabledStep finds the next enabled step
  • In AUTO mode we wrap 10->1, in INCR mode we can end at 0

Optional routing:

  • If Cfg[Step].NextStep <> 0 then jump to that step (if enabled).

  1. PLC Data Types (copy into "PLC data types")

TYPE E_Mode : USINT ( MODE_STOP := 0, MODE_AUTO := 1, MODE_INCR := 2 ); END_TYPE

TYPE E_StopCat : USINT ( STOPCAT_NONE := 0, STOPCAT_IMMEDIATE := 1, STOPCAT_END_OF_STEP := 2, STOPCAT_END_OF_CYCLE := 3, STOPCAT_NO_STOP := 4 ); END_TYPE

TYPE UDT_StepCfg : STRUCT Text : STRING[80]; // Operator text for HMI Timeout : TIME; // Per-step timeout IsEndOfCycle : BOOL; // Marks end-of-cycle step (e.g., step 10)

ReqEnabled   : BOOL;        // From HMI/DB (operator request)
Enabled      : BOOL;        // PLC effective enable (used by sequencer)
SafetyLocked : BOOL;        // If TRUE, PLC forces Enabled := TRUE always

NextStep     : INT;         // 0 = normal next enabled step, else jump to 1..10

END_STRUCT END_TYPE


  1. Helper FC: Next enabled step (skip disabled steps)

FUNCTION FC_NextEnabledStep : INT { S7_Optimized_Access := 'TRUE' } VAR_INPUT CurrentStep : INT; MaxStep : INT; // 10 Wrap : BOOL; // TRUE in AUTO (10->1), FALSE in INCR (end->0) Cfg : ARRAY[1..10] OF UDT_StepCfg; END_VAR VAR s : INT; i : INT; END_VAR BEGIN s := CurrentStep;

FOR i := 1 TO MaxStep DO
    s := s + 1;

    IF s > MaxStep THEN
        IF Wrap THEN
            s := 1;
        ELSE
            FC_NextEnabledStep := 0;
            RETURN;
        END_IF;
    END_IF;

    IF Cfg[s].Enabled THEN
        FC_NextEnabledStep := s;
        RETURN;
    END_IF;
END_FOR;

FC_NextEnabledStep := 0; // none enabled

END_FUNCTION


  1. Main FB: Sequencer (AUTO/INCR + Pause/Stop + Stop categories)

FUNCTION_BLOCK FB_Seq10_Adv { S7_Optimized_Access := 'TRUE' }

VAR_INPUT Enable : BOOL; Mode_Auto : BOOL; Mode_Incr : BOOL;

Cmd_Start    : BOOL;
Cmd_Stop     : BOOL;      // hard stop (safe outputs)
Cmd_Pause    : BOOL;      // toggle pause (freeze)
Cmd_ResetErr : BOOL;
Cmd_Next     : BOOL;      // NEXT pulse in increment mode

StopReq      : BOOL;      // stop request flag
StopCat      : E_StopCat; // stop request category

// Example process inputs (replace with real ones)
In_Ready       : BOOL;
In_ClampClosed : BOOL;
In_ProcessDone : BOOL;
In_EjectDone   : BOOL;

// Step config from DB/HMI/recipe
Cfg : ARRAY[1..10] OF UDT_StepCfg;

END_VAR

VAR_OUTPUT Step : INT; // 0..10 Running : BOOL; Paused : BOOL;

ErrorActive  : BOOL;
ErrorCode    : WORD;

HMI_StepText : STRING[80];
HMI_StopInfo : STRING[80];

// Example outputs (replace)
Out_ClampClose : BOOL;
Out_ProcessOn  : BOOL;
Out_Eject      : BOOL;

END_VAR

VAR Mode : E_Mode;

rStart : R_TRIG;
rNext  : R_TRIG;
rReset : R_TRIG;
rPause : R_TRIG;

StepPrev  : INT;
StepEntry : BOOL;

tStep : TON;

AllowAdvance : BOOL;

StopLatched    : BOOL;
StopLatchedCat : E_StopCat;

PauseLatched     : BOOL;
Frozen_ClampClose: BOOL;
Frozen_ProcessOn : BOOL;
Frozen_Eject     : BOOL;

Cmd_ClampClose : BOOL;
Cmd_ProcessOn  : BOOL;
Cmd_Eject      : BOOL;

i   : INT;
nxt : INT;

END_VAR

BEGIN // --------- edges ---------- rStart(CLK := Cmd_Start); rNext(CLK := Cmd_Next); rReset(CLK := Cmd_ResetErr); rPause(CLK := Cmd_Pause);

// --------- sanitize step config (prevent disabling safety steps) ----------
// IMPORTANT: HMI should write ONLY ReqEnabled, not Enabled or SafetyLocked.
FOR i := 1 TO 10 DO
    IF Cfg[i].SafetyLocked THEN
        Cfg[i].Enabled := TRUE;
        Cfg[i].ReqEnabled := TRUE; // reflect back to HMI (optional)
    ELSE
        Cfg[i].Enabled := Cfg[i].ReqEnabled;
    END_IF;
END_FOR;

// --------- reset error ----------
IF rReset.Q THEN
    ErrorActive := FALSE;
    ErrorCode := WORD#0;
    Step := 0;
    StopLatched := FALSE;
    StopLatchedCat := STOPCAT_NONE;
    PauseLatched := FALSE;
END_IF;

// --------- mode selection ----------
IF (NOT Enable) OR Cmd_Stop OR ErrorActive THEN
    Mode := MODE_STOP;
ELSIF Mode_Auto THEN
    Mode := MODE_AUTO;
ELSIF Mode_Incr THEN
    Mode := MODE_INCR;
ELSE
    Mode := MODE_STOP;
END_IF;

// --------- start ----------
IF rStart.Q AND (NOT ErrorActive) THEN
    IF Step = 0 THEN
        // start at first enabled step
        IF Cfg[1].Enabled THEN
            Step := 1;
        ELSE
            Step := FC_NextEnabledStep(CurrentStep := 0, MaxStep := 10, Wrap := TRUE, Cfg := Cfg);
        END_IF;
    END_IF;
END_IF;

// --------- latch stop request (category-aware) ----------
IF StopReq THEN
    CASE StopCat OF
        STOPCAT_IMMEDIATE, STOPCAT_END_OF_STEP, STOPCAT_END_OF_CYCLE:
            StopLatched := TRUE;
            StopLatchedCat := StopCat;
        STOPCAT_NO_STOP:
            // info only; do not latch stop
        ELSE
            // none
    END_CASE;
END_IF;

// --------- pause toggle ----------
IF rPause.Q THEN
    PauseLatched := NOT PauseLatched;

    // capture outputs when entering pause
    IF PauseLatched THEN
        Frozen_ClampClose := Out_ClampClose;
        Frozen_ProcessOn  := Out_ProcessOn;
        Frozen_Eject      := Out_Eject;
    END_IF;
END_IF;

Paused := PauseLatched;

// --------- running ----------
Running := (Mode <> MODE_STOP) AND (Step > 0) AND (NOT ErrorActive) AND (NOT Paused);

// --------- step entry ----------
StepEntry := (Step <> StepPrev);
StepPrev := Step;

// --------- HMI step text ----------
IF (Step >= 1) AND (Step <= 10) THEN
    HMI_StepText := Cfg[Step].Text;
ELSE
    HMI_StepText := 'IDLE';
END_IF;

// --------- stop info ----------
HMI_StopInfo := '';
IF StopReq AND (StopCat = STOPCAT_NO_STOP) THEN
    HMI_StopInfo := 'INFO: stop request is NO_STOP (alarm/info only)';
ELSIF StopLatched THEN
    CASE StopLatchedCat OF
        STOPCAT_IMMEDIATE:    HMI_StopInfo := 'Stop requested: IMMEDIATE';
        STOPCAT_END_OF_STEP:  HMI_StopInfo := 'Stop requested: END_OF_STEP';
        STOPCAT_END_OF_CYCLE: HMI_StopInfo := 'Stop requested: END_OF_CYCLE';
    ELSE
        HMI_StopInfo := 'Stop requested';
    END_CASE;
END_IF;

// --------- if current step disabled -> jump immediately ----------
IF (Step >= 1) AND (Step <= 10) THEN
    IF NOT Cfg[Step].Enabled THEN
        Step := FC_NextEnabledStep(CurrentStep := Step-1, MaxStep := 10, Wrap := (Mode = MODE_AUTO), Cfg := Cfg);
    END_IF;
END_IF;

// --------- default commanded outputs ----------
Cmd_ClampClose := FALSE;
Cmd_ProcessOn  := FALSE;
Cmd_Eject      := FALSE;

AllowAdvance := FALSE;

// --------- step timer (does not run while paused) ----------
IF StepEntry THEN
    tStep(IN := FALSE, PT := Cfg[Step].Timeout);
END_IF;

IF (Step >= 1) AND (Step <= 10) THEN
    tStep(IN := Running, PT := Cfg[Step].Timeout);
ELSE
    tStep(IN := FALSE, PT := T#0S);
END_IF;

// --------- step logic (demo steps, replace with your real logic) ----------
CASE Step OF
    0:
        ;

    1: // WAIT READY
        IF In_Ready THEN AllowAdvance := TRUE; END_IF;

    2: // CLOSE CLAMP
        Cmd_ClampClose := TRUE;
        IF In_ClampClosed THEN AllowAdvance := TRUE; END_IF;

    3: // PROCESS
        Cmd_ProcessOn := TRUE;
        IF In_ProcessDone THEN AllowAdvance := TRUE; END_IF;

    4: // EJECT
        Cmd_Eject := TRUE;
        IF In_EjectDone THEN AllowAdvance := TRUE; END_IF;

    5,6,7,8,9:
        // demo pass-through steps
        AllowAdvance := TRUE;

    10: // END OF CYCLE
        AllowAdvance := TRUE;

    ELSE
        ErrorActive := TRUE;
        ErrorCode := WORD#16#9001; // invalid step
END_CASE;

// --------- timeout -> error ----------
IF tStep.Q AND Running THEN
    ErrorActive := TRUE;
    ErrorCode := WORD#16#8000 + WORD_TO_WORD(INT_TO_WORD(Step));
    Mode := MODE_STOP;
END_IF;

// --------- stop category enforcement: immediate stop ----------
IF StopLatched AND (StopLatchedCat = STOPCAT_IMMEDIATE) THEN
    Step := 0;
    StopLatched := FALSE;
    StopLatchedCat := STOPCAT_NONE;
    PauseLatched := FALSE;
END_IF;

// --------- step advance ----------
IF (NOT ErrorActive) AND (NOT Paused) THEN

    // Evaluate "advance permission" depending on mode
    IF (Mode = MODE_AUTO AND AllowAdvance)
       OR (Mode = MODE_INCR AND AllowAdvance AND rNext.Q) THEN

        // Handle stop-after conditions
        IF StopLatched AND (StopLatchedCat = STOPCAT_END_OF_STEP) THEN
            Step := 0;
            StopLatched := FALSE;
            StopLatchedCat := STOPCAT_NONE;

        ELSIF StopLatched AND (StopLatchedCat = STOPCAT_END_OF_CYCLE) AND Cfg[Step].IsEndOfCycle THEN
            Step := 0;
            StopLatched := FALSE;
            StopLatchedCat := STOPCAT_NONE;

        ELSE
            // Compute next step:
            // 1) optional jump if NextStep configured
            IF (Step >= 1) AND (Step <= 10) AND (Cfg[Step].NextStep <> 0) THEN
                nxt := Cfg[Step].NextStep;

                IF (nxt >= 1) AND (nxt <= 10) AND Cfg[nxt].Enabled THEN
                    Step := nxt;
                ELSE
                    ErrorActive := TRUE;
                    ErrorCode := WORD#16#9100; // invalid NextStep config
                END_IF;

            ELSE
                // 2) normal: next enabled step
                nxt := FC_NextEnabledStep(
                    CurrentStep := Step,
                    MaxStep     := 10,
                    Wrap        := (Mode = MODE_AUTO),
                    Cfg         := Cfg
                );
                Step := nxt;
            END_IF;

        END_IF;
    END_IF;
END_IF;

// --------- output policy: STOP vs PAUSE vs NORMAL ----------
IF (Mode = MODE_STOP) OR (Step = 0) OR ErrorActive THEN
    Out_ClampClose := FALSE;
    Out_ProcessOn  := FALSE;
    Out_Eject      := FALSE;

ELSIF Paused THEN
    Out_ClampClose := Frozen_ClampClose;
    Out_ProcessOn  := Frozen_ProcessOn;
    Out_Eject      := Frozen_Eject;

ELSE
    Out_ClampClose := Cmd_ClampClose;
    Out_ProcessOn  := Cmd_ProcessOn;
    Out_Eject      := Cmd_Eject;
END_IF;

END_FUNCTION_BLOCK


  1. Recommended usage in OB1

  • Create an instance DB for FB_Seq10_Adv.
  • Fill Cfg[1..10] with texts/timeouts and safety locks.

Typical values:

  • Step 1: Text='Wait Ready', Timeout=T#10S, SafetyLocked=TRUE (if required)
  • Step 2: Text='Close Clamp', Timeout=T#5S, SafetyLocked=TRUE
  • Step 10: Text='Cycle End', Timeout=T#2S, IsEndOfCycle=TRUE

Operator can only toggle:

  • Cfg[i].ReqEnabled for optional steps (SafetyLocked=FALSE)

Run modes:

  • AUTO: Mode_Auto=TRUE
  • INCR: Mode_Incr=TRUE and use Cmd_Next pulses

Stop:

  • Cmd_Stop -> immediate safe stop
  • StopReq+StopCat:
    • END_OF_CYCLE for "stop after part"
    • END_OF_STEP for "stop after step"
    • IMMEDIATE for urgent stop
    • NO_STOP for warnings/info

Pause:

  • Cmd_Pause toggles pause/resume (outputs frozen + timer frozen)

  1. What to customize next (common in production)

  1. Split outputs into "Freeze on pause" and "Force safe on pause" groups.
  2. Add per-step "StopAllowed" (some steps must never stop mid-step).
  3. Add alarm integration with FIFO + timestamps (your previous alarm project).
  4. Add step entry actions (run once on step change):
    • reset internal flags
    • start motion commands
    • log step start time
  5. Add interlocks:
    • if safety chain open -> force STOPCAT_IMMEDIATE

END OF README