Migrating STL Sort Algorithm to SCL on S7-1200 Controllers

David Krause14 min read
SiemensTIA PortalTutorial / How-to
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

1. Overview: Why STL Must Be Rewritten for the S7-1200

Statement List (STL) is part of the IEC 61131-3 text language family and was the dominant Siemens programming notation on the S7-300 and S7-400 generations. The S7-1200 controller family, however, has a fundamentally different firmware architecture than the S7-300/400 and historically only supported Ladder Diagram (LAD), Function Block Diagram (FBD), and Structured Control Language (SCL). The S7-1500 reintroduced limited STL support starting with TIA Portal V15.1 firmware 2.0, but STL is not available on any S7-1200 CPU firmware release. Any program originally written in STL for an S7-300/400 must therefore be translated into LAD, FBD, or SCL before it can run on an S7-1200.

SCL is the recommended target language for this kind of migration because it preserves the algorithmic clarity of the original STL, executes deterministically inside the standard OB1 cycle (or any user OB), and exposes data-block word addresses (DBW) as named tags. This article walks through the complete migration of a real STL routine that performs a conditional three-word comparison and a small arithmetic post-processing step, then verifies the result against a soft-PLC simulation.

2. Prerequisites

Item Requirement
Engineering software TIA Portal V16 or later (V17/V18 recommended for current S7-1200 firmware support)
Controller family SIMATIC S7-1200, CPU firmware 4.2 or later for full SCL feature set
Source program Validated STL source from S7-300 / S7-400 project (STEP 7 V5.x or TIA Portal V13/V14 STL block)
Hardware identifier Defined DB number (e.g., DB100) holding the input/output words used in the original code
Library knowledge Standard IEC 61131-3 operators: arithmetic, comparison, bit-shift
Always work on a copy of the original STEP 7 project. STL cannot be executed, simulated, or downloaded on an S7-1200. The translator must be done offline.

3. Decoding the Original STL Routine

The source code is a single STL network that performs two cascaded conditional subtracts, a recompute pass, an averaging step, an addition, counter settings, and a bit-reset sequence. Before any translation begins, the algorithm must be understood mathematically so the SCL version produces identical results for every possible input combination.

3.1 Word and Bit Inventory

Address Role in algorithm Suggested SCL tag
DBW 0 Control/status word 1 iCtrl1 : INT
DBW 2 Control/status word 2 iCtrl2 : INT
DBW 4 Compare value A iValA : INT
DBW 6 Compare value B iValB : INT
DBW 8 Compare value C iValC : INT
DBW 10 Reference threshold 1 iRef1 : INT
DBW 12 Reference threshold 2 iRef2 : INT
DBW 14 Computed sum iSum : INT
DBW 16 Half-sum (shift right 1) iHalf : INT
DBW 20 Offset to add iOffset : INT
DBW 24 Final result iResult : INT
M 0.3 Status flag 1 bFlag1 : BOOL
M 2.0 to 2.5 Status flags 2..7 bFlag2..bFlag7 : BOOL
C 9, C 10, C 20, C 21 IEC counters CT9, CT10, CT20, CT21

3.2 Branching Model Translated from Jump Instructions

STL uses JM (jump if negative/CC1=1), JP (jump if positive/CC1=0 and CC0=0), and JU (unconditional jump). On the S7-300/400 these instructions inspect the condition-code bits set by the immediately preceding -I or +I. The same logic can be expressed with relational operators in SCL.

STL pattern Meaning SCL equivalent
L DBW x; L DBW y; -I; JM label If (x - y) < 0 then jump IF (x - y) < 0 THEN ... END_IF;
L DBW x; L DBW y; -I; JP label If (x - y) > 0 then jump IF (x - y) > 0 THEN ... END_IF;
JU label Unconditional jump Code flows to next statement
NOP 0 Label target / no-op Comments or label constant

3.3 Reverse-Engineered Algorithm

Tracing the four JU paths and the two independent -I decision trees, the routine reduces to:

  1. Compute d1 = iValA - iValB and classify the sign.
  2. If d1 < 0, compare d1 against iRef1 and route to either M003 (sum branch) or M004 (bypass branch).
  3. If d1 > 0, compare d1 against iRef2 and again route to M003 or M004.
  4. If d1 == 0, always go to M003.
  5. At M003, perform a second classification using iValA - iValC, with the same two threshold comparisons. The logic keeps the result sum-conditional based on a second internal state.
  6. After classification, unconditionally compute iSum = iValA + iValB + iValC, iHalf = iSum SHR 1 (divide by 2), and iResult = iHalf + iOffset.
  7. Set counters C9, C10 with PV from DBW0; set counters C20, C21 with PV from DBW2; reset M0.3 and the six bits M2.0 to M2.5.
  8. Fall through to label M004 (NOP 0), which is the end-of-network marker.

Note that the original author re-uses the accumulator: the first -I leaves the difference in ACCU1, and the second subtract inside M001 and M002 therefore operates on the difference, not on a freshly loaded value. This is critical for bit-exact behavior.

4. SCL Translation Patterns

Three reusable patterns cover 90 percent of STL-to-SCL conversion on the S7-1200.

4.1 Jump-to-IF Pattern

// Original STL
L     DBW    4
L     DBW    6
-I
JM    M001
JP    M002
JU    M003

// SCL equivalent
IF (iValA - iValB) < 0 THEN
    // M001 body
ELSIF (iValA - iValB) > 0 THEN
    // M002 body
ELSE
    // M003 body
END_IF;

4.2 Cascaded-Decision Pattern

// When the second subtract inherits the ACCU1 from the first
IF d1 < 0 THEN
    d2 := d1 - iRef1;
    IF d2 < 0 THEN GOTO_SUM := FALSE; ELSE GOTO_SUM := TRUE; END_IF;
ELSIF d1 > 0 THEN
    d2 := d1 - iRef2;
    IF d2 > 0 THEN GOTO_SUM := FALSE; ELSE GOTO_SUM := TRUE; END_IF;
ELSE
    GOTO_SUM := TRUE;
END_IF;

4.3 Unconditional-Fall-Through Pattern

An unconditional JU in STL becomes a missing ELSIF/ELSE branch in SCL. The body that follows the JU in STL is the body of the ELSE in SCL.

5. Complete SCL Implementation for the S7-1200

The following code is a single SCL FB or FC block. Replace DB100 with the data block number used in the original project. All tags are declared locally for clarity but can be moved to the DB without code changes.

FUNCTION_BLOCK "FB_SortPostProc"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
   VAR
      iCtrl1   : INT;        // DBW 0
      iCtrl2   : INT;        // DBW 2
      iValA    : INT;        // DBW 4
      iValB    : INT;        // DBW 6
      iValC    : INT;        // DBW 8
      iRef1    : INT;        // DBW 10
      iRef2    : INT;        // DBW 12
      iSum     : INT;        // DBW 14
      iHalf    : INT;        // DBW 16
      iOffset  : INT;        // DBW 20
      iResult  : INT;        // DBW 24
      bFlag1   : BOOL;       // M0.3
      bFlag2   : BOOL;       // M2.0
      bFlag3   : BOOL;       // M2.1
      bFlag4   : BOOL;       // M2.2
      bFlag5   : BOOL;       // M2.3
      bFlag6   : BOOL;       // M2.4
      bFlag7   : BOOL;       // M2.5
   END_VAR
   VAR_TEMP
      d1 : INT;
      d2 : INT;
      bDoSum : BOOL;
   END_VAR

BEGIN
   // ---------- Stage 1: classify (iValA - iValB) ----------
   d1 := iValA - iValB;
   bDoSum := FALSE;

   IF d1 < 0 THEN
      // M001 body: re-use d1 as ACCU1
      d2 := d1 - iRef1;
      IF d2 < 0 THEN
         // jump to M004 (bypass)
         bDoSum := FALSE;
      ELSE
         // jump to M003 (sum)
         bDoSum := TRUE;
      END_IF;
   ELSIF d1 > 0 THEN
      // M002 body: re-use d1 as ACCU1
      d2 := d1 - iRef2;
      IF d2 > 0 THEN
         // jump to M004 (bypass)
         bDoSum := FALSE;
      ELSE
         // jump to M003 (sum)
         bDoSum := TRUE;
      END_IF;
   ELSE
      // M003 reached from JU (equal case)
      bDoSum := TRUE;
   END_IF;

   // ---------- Stage 2: second-pass classification ----------
   IF bDoSum THEN
      d1 := iValA - iValC;
      IF d1 < 0 THEN
         d2 := d1 - iRef1;
         IF d2 < 0 THEN bDoSum := FALSE; END_IF;
      ELSIF d1 > 0 THEN
         d2 := d1 - iRef2;
         IF d2 > 0 THEN bDoSum := FALSE; END_IF;
      END_IF;
   END_IF;

   // ---------- Stage 3: arithmetic post-processing ----------
   IF bDoSum THEN
      iSum    := iValA + iValB + iValC;
      iHalf   := iSum SHR 1;          // integer divide by 2
      iResult := iHalf + iOffset;
   END_IF;

   // ---------- Stage 4: counter and bit handling ----------
   // STL "S C9" with PV from ACCU1 (here DBW 0) is emulated
   // with the IEC counter instructions below.
   "CT9"(CU := TRUE,  R := FALSE, PV := iCtrl1);
   "CT10"(CU := TRUE, R := FALSE, PV := iCtrl1);
   "CT20"(CU := TRUE, R := FALSE, PV := iCtrl2);
   "CT21"(CU := TRUE, R := FALSE, PV := iCtrl2);

   bFlag1 := FALSE;
   bFlag2 := FALSE;
   bFlag3 := FALSE;
   bFlag4 := FALSE;
   bFlag5 := FALSE;
   bFlag6 := FALSE;
   bFlag7 := FALSE;

   // ---------- Label M004 (NOP 0) ----------
   // Implicit end of network; no operation required.
END_FUNCTION_BLOCK

6. Data Block Layout and Symbol Mapping

Because the original code uses absolute addresses such as DBW 4, the migrated block benefits from symbolic naming. TIA Portal's Rewire function can replace the old DBW references with new symbolic ones in a single pass.

STL address SCL tag Data type Default range
DBW 0 iCtrl1 INT -32768 to 32767
DBW 2 iCtrl2 INT -32768 to 32767
DBW 4 iValA INT -32768 to 32767
DBW 6 iValB INT -32768 to 32767
DBW 8 iValC INT -32768 to 32767
DBW 10 iRef1 INT application-specific
DBW 12 iRef2 INT application-specific
DBW 14 iSum INT computed
DBW 16 iHalf INT computed
DBW 20 iOffset INT application-specific
DBW 24 iResult INT computed
The SHR 1 instruction on a 16-bit INT performs an arithmetic/logical right shift by one bit. The sign bit is preserved only for WORD; for INT the operation is logical, so a value of -3 (0xFFFD) becomes 0x7FFE = 32766. Verify the sign-handling expectation against the original STL by stepping the code in the S7-PLCSIM simulator and comparing iHalf against the legacy control.

7. Edge Cases and Boundary Behavior

Scenario Expected result Verification
iValA == iValB, any iValC bDoSum = TRUE; iResult = (3 * iValA) / 2 + iOffset Test with iValA = 100, iValB = 100, iValC = -50
iValA < iValB and d1 < iRef1 bDoSum = FALSE; iResult unchanged Test with iValA = 10, iValB = 100, iRef1 = 200
iValA > iValB and d1 > iRef2 bDoSum = FALSE; iResult unchanged Test with iValA = 500, iValB = 100, iRef2 = 200
INT overflow in iSum Wrap-around at 32767/-32768 Test with iValA = 20000, iValB = 20000, iValC = 20000
All thresholds and values at INT_MIN/MAX Undefined intermediate; ALU flags behave as documented Run SCL overflow test suite from S7-PLCSIM
Counter PV at zero Counter remains at zero (Q = FALSE) Set iCtrl1 = 0, iCtrl2 = 0; monitor Q outputs

8. Counter and Bit-Reset Operations

The original STL sets four IEC counters from accumulator values (S C9 means "set counter with PV = ACCU1"). In SCL, the IEC counter faceplate is the cleanest representation. For an S7-1200 the IEC counters are accessible as system function blocks: CTU (count up), CTD (count down), and CTUD (count up/down).

// Background DB instance for the CTU instructions
"CT9"(CU := bRun, R := bReset, PV := iCtrl1, Q => bCT9_Q, CV => iCT9_CV);
"CT10"(CU := bRun, R := bReset, PV := iCtrl1);
"CT20"(CU := bRun, R := bReset, PV := iCtrl2);
"CT21"(CU := bRun, R := bReset, PV := iCtrl2);

If the original project used SIMATIC counters (S C9, S C10) the equivalent on the S7-1200 is the IEC CTU with an instance DB. The S7-1200 has a fixed, limited number of IEC counter resources per CPU; refer to the relevant CPU manual (for example the S7-1200 System Manual) for the exact maximum number of CTU/CTD/CTUD blocks that can be instantiated.

9. Migration Checklist

  1. Open the legacy STEP 7 V5.x project and export the STL source as a text/printable file.
  2. Create a new TIA Portal project and add an S7-1200 station with the correct CPU order number (for example 6ES7214-1AG40-0XB0).
  3. Create a global DB named SortData with INT and BOOL tags as per Section 6.
  4. Insert a new SCL block (FB1) and paste the translated source from Section 5.
  5. Compile the block. Resolve any implicit-conversion warnings (for example BOOL to INT comparisons).
  6. Call FB1 from OB1 with an instance DB.
  7. Use TIA Portal's Rewire function to map the old DBW addresses to the new symbolic tags.
  8. Download the project to the CPU and run an online watch table on the new DB.

10. Verification and Commissioning

Run the SCL block inside S7-PLCSIM and exercise the boundary conditions from Section 7. The verification table below defines the comparison method for each input class.

Test class Inputs (A, B, C, Ref1, Ref2, Offset) Expected iResult Pass criterion
All-positive, sum branch 100, 200, 300, 0, 0, 10 (100+200+300)/2 + 10 = 310 iResult == 310
Bypass branch 10, 500, 800, 0, 50, 0 Unchanged (last value) iResult == previous iResult
Negative mix -50, -50, 100, 0, 0, 5 (-50 + -50 + 100)/2 + 5 = 5 iResult == 5
INT overflow 30000, 30000, 30000, 0, 0, 0 Wrap to -4768 (0xED30) iResult == -4768 (document as platform behavior)
Counter ramp iCtrl1 = 5, pulse CU 10 times CT9.CV = 5, Q = TRUE after 5th pulse Watch table shows Q=1, CV=5
Bit reset Pre-set M2.0..M2.5 to TRUE, run OB1 All M2.x become FALSE Watch table shows FALSE for all

11. Field-Proven Caveats

  • STL is gone, not hidden. TIA Portal V16-V18 do not expose an STL editor for the S7-1200. The block must be a new SCL source. Do not attempt to paste STL text into a SCL editor; the parser will reject it.
  • Optimized vs. non-optimized DB. If the SCL block is configured with optimized access, absolute DBW addresses cannot be used inside the block. Either set the DB to non-optimized for direct compatibility, or use the fully symbolic tag interface shown in Section 5.
  • Counter resource budget. Each S7-1200 CPU has a documented maximum for IEC counter and timer blocks. The four CTU instances in this example are well within the budget for any modern CPU, but a project that re-uses this pattern in many FBs may exhaust the IEC counter pool. The remedy is to use the IEC_Timer/IEC_Counter instances in a global DB pool.
  • Shift semantics on INT. The original SRW 1 is defined for the WORD view of the accumulator. In SCL the SHR operator on an INT performs a logical shift; for negative values this differs from the original two's-complement arithmetic shift. If sign preservation is required, cast to WORD first, shift, then cast back: iHalf := WORD_TO_INT(SHR(INT_TO_WORD(iSum), 1));
  • Accumulator reuse. STL reuses ACCU1 across -I instructions without an explicit reload. The SCL translation must re-bind the previous result to a temporary variable, otherwise the second subtract will load a fresh value from the data block and break the bit-exact equivalence.
  • Watchdog time. The SCL version is on average 1.3 to 2.0 times slower than the equivalent STL on the same cycle. For a one-shot routine that runs once per OB1 this is irrelevant. For routines in fast OBs (OB35, OB82) profile the cycle in TIA Portal's Online & Diagnostics > Cycle Time view before commissioning.

12. FAQ

Can the S7-1200 execute STL at all?

No. STL is not available on any S7-1200 CPU firmware release. S7-1500 CPUs starting at firmware V2.0 with TIA Portal V15.1 or later have limited STL support. S7-1200 supports LAD, FBD, and SCL only.

What is the difference between SRW 1 and SHR 1 in SCL?

SRW 1 in STL shifts the 16-bit accumulator right by one bit as a WORD, preserving the sign bit only because the WORD type is unsigned. In SCL, the SHR operator on an INT performs a logical shift that does not preserve the sign for negative values. Cast through WORD if sign-preserving arithmetic shift is required.

How do I emulate the STL S C9 instruction in SCL?

Use the IEC CTU (count up) system function block: "CT9"(CU := bRun, R := bReset, PV := iCtrl1, Q => bCT9_Q, CV => iCT9_CV); The PV is loaded from iCtrl1 (originally DBW 0). The instance DB is generated automatically by TIA Portal when the CTU is placed.

Does the translation change the cycle time of OB1?

Yes. SCL executes the same algorithm in roughly 1.3 to 2.0 times the STL cycle, because each IF/ELSIF compiles to a sequence of load-and-compare operations rather than direct ALU flag checks. For most applications this is well under the OB1 watchdog of 150 ms (default) on the S7-1200.

Can I keep the original DBW absolute addresses inside the SCL block?

Yes, but only if the data block is set to non-optimized access. With optimized access, TIA Portal does not allow absolute DBW references inside SCL. The recommended approach is to declare symbolic tags in the block interface or in a non-optimized DB, then use the tag names in SCL.

How do I verify the migration is bit-exact?

Run the legacy STL block and the new SCL block in parallel inside S7-PLCSIM with the same input vectors, then compare iResult, iHalf, and the bit flags using a watch table. Any divergence indicates a missing accumulator reload or a sign-handling mismatch in the SHR operator.

Back to blog