Siemens SCL Programming: S7-300/400/1200/1500 Tutorial Guide
1. Overview
Structured Control Language (SCL) is Siemens' high-level, PASCAL-derived text language for S7 programmable logic controllers. It conforms to the IEC 61131-3 standard (ST - Structured Text) and is supported on the S7-300, S7-400, S7-1200, S7-1500, WinAC, and ET 200SP CPU families. SCL ships as a standalone option package with STEP 7 V5.x and as a fully integrated editor in TIA Portal starting with V11.
Use SCL when ladder logic becomes too dense, when you need complex math, data handling, loops, or string processing. SCL is compiled to MC7 (S7-300/400) or to the SCL runtime in TIA Portal - it does not interpret at run time, so execution is comparable to STL in performance on the same CPU.
2. Prerequisites
- STEP 7 V5.5 SPx (for S7-300/400) with the SCL optional package installed, or TIA Portal V13 or later (V15.1, V16, V17, V18, V19, V20, or V21 for current CPUs).
- A Siemens license for the SCL compiler. On STEP 7 V5.x the MLFB is 6ES7 811-1CC04-0YA5 (SCL V5.3 for S7-300/400). On TIA Portal, SCL is included in the STEP 7 Professional license.
- Familiarity with PLC basics: tags, I/O mapping, OB/FB/FC/DB organization, and the S7 data model.
- A programming device with Windows 10/11 (TIA Portal V16+) or Windows 7/10 (STEP 7 V5.5).
3. SCL Project Structure and Block Types
SCL can be written inside the following block types. All blocks are stored in the S7 program under Program blocks:
| Block type | File extension | Use |
|---|---|---|
| OB (Organization Block) | .db / .ob | Cyclic, startup, time-of-day, hardware interrupt, error OB |
| FC (Function) | .fc | Stateless subroutine with a return value |
| FB (Function Block) | .fb | Stateful block with instance DB |
| DB (Data Block) | .db | Data storage - SCL edits code-style DBs |
| UDT (User-Defined Type) | .udt | Reusable type/structure declaration |
4. Data Types and Declarations
SCL uses the same elementary and complex data types defined in IEC 61131-3. All tags must be declared in the block's Declaration section, with the syntax:
Name : Type [:= InitialValue];
4.1 Elementary Types
| Type | Size | Range |
|---|---|---|
| BOOL | 1 bit | FALSE / TRUE (0, 1) |
| SINT | 8 bit | -128 to 127 |
| INT | 16 bit | -32 768 to 32 767 |
| DINT | 32 bit | -2 147 483 648 to 2 147 483 647 |
| LINT | 64 bit | -9.22e18 to 9.22e18 (S7-1500) |
| USINT | 8 bit | 0 to 255 |
| UINT | 16 bit | 0 to 65 535 |
| UDINT | 32 bit | 0 to 4 294 967 295 |
| REAL | 32 bit | IEEE 754 single precision |
| LREAL | 64 bit | IEEE 754 double precision (S7-1500) |
| TIME | 32 bit | T#-24d20h31m23s648ms to T#24d20h31m23s647ms |
| DATE | 16 bit | D#1990-01-01 to D#2168-12-31 |
| TOD | 32 bit | TOD#00:00:00.000 to TOD#23:59:59.999 |
| DT / DTL | 64 bit | Date and time, BCD or structure (DTL is S7-1500) |
| STRING[n] | n+2 byte | Up to 254 characters (n = 0 to 254) |
| WSTRING[n] | n*2+4 byte | Unicode, S7-1500 only |
| CHAR / WCHAR | 1 / 2 byte | Single character |
4.2 Complex Types
TYPE MotorData :
STRUCT
Speed : REAL := 0.0;
Current : REAL := 0.0;
RunHours : UDINT := 0;
State : BOOL := FALSE;
END_STRUCT;
END_TYPE
Arrays use a comma-separated range. Multi-dimensional arrays are supported up to six dimensions on S7-1500, three on S7-300/400.
VAR
Conveyors : ARRAY[1..10] OF BOOL;
Recipe : ARRAY[1..5, 1..3] OF REAL;
END_VAR
4.3 Declaration Sections
| Section | Storage | Use |
|---|---|---|
| VAR INPUT | Block input | Parameters passed in by reference (IN) |
| VAR_OUTPUT | Block output | Returned values (OUT) |
| VAR_IN_OUT | In/out parameter | Caller passes a tag; block can read and write |
| VAR_TEMP | Local stack | Temporary tags; reset every call |
| VAR STATIC | Instance DB (FB only) | Retained between calls |
| VAR CONSTANT | Compile-time | Read-only named constant |
| VAR_GLOBAL | Global DB (OB1 / DB only) | Shared across blocks |
5. Operators
| Class | Operators | Precedence |
|---|---|---|
| Parentheses | ( ) | 1 (highest) |
| Function call | FCName( ) | 2 |
| Power | ** | 3 |
| Unary | -, NOT | 4 |
| Multiplicative | *, /, MOD, DIV | 5 |
| Additive | +, - | 6 |
| Comparison | <, <=, >, >=, =, <> | 7 |
| Equality | =, <> | 8 |
| Boolean AND | AND, & | 9 |
| Boolean XOR | XOR | 10 |
| Boolean OR | OR | 11 (lowest) |
The integer division operator DIV returns the integer quotient, MOD returns the remainder. Mixing REAL and INT in an expression is allowed; the integer is implicitly cast to REAL. Use explicit conversion functions REAL_TO_INT, INT_TO_REAL, WORD_TO_INT, DWORD_TO_REAL, etc., when crossing type families.
6. Control Structures
6.1 IF...THEN...ELSIF...ELSE...END_IF
IF iMotorCurrent > 50.0 THEN
bOverload := TRUE;
iAlarmCode := 16#0001;
ELSIF iMotorCurrent > 40.0 THEN
bWarning := TRUE;
iAlarmCode := 16#0002;
ELSE
bOverload := FALSE;
bWarning := FALSE;
iAlarmCode := 0;
END_IF;
6.2 CASE...OF
CASE iState OF
0: sStateText := 'IDLE';
1: sStateText := 'STARTING';
2: sStateText := 'RUNNING';
10..19:
sStateText := 'FAULT';
iSubState := iState - 10;
ELSE
sStateText := 'UNKNOWN';
END_CASE;
6.3 Loops
// FOR with fixed bounds (compiler unrolls where possible)
FOR i := 1 TO 100 BY 2 DO
Sum := Sum + i;
END_FOR;
// WHILE - condition evaluated first
WHILE bRunning AND NOT bError DO
iCounter := iCounter + 1;
IF iCounter > 10000 THEN
EXIT; // leave the loop early
END_IF;
END_WHILE;
// REPEAT - body executed at least once
REPEAT
rAverage := (rMin + rMax) / 2.0;
IF rAverage * rAverage > rTarget THEN
rMax := rAverage;
ELSE
rMin := rAverage;
END_IF;
UNTIL ABS(rMax - rMin) < 0.001
END_REPEAT;
FOR ranges can overflow the temp area. Use EXIT to break out and reduce nesting. On S7-1500 the local stack is virtually unlimited.7. Functions and Function Blocks
7.1 FC with Return Value
FUNCTION FC_Limit : REAL
VAR_INPUT
rValue : REAL;
rLow : REAL;
rHigh : REAL;
END_VAR
BEGIN
IF rValue < rLow THEN FC_Limit := rLow; RETURN; END_IF;
IF rValue > rHigh THEN FC_Limit := rHigh; RETURN; END_IF;
FC_Limit := rValue;
END_FUNCTION
7.2 FB with Instance Data
FUNCTION_BLOCK FB_PID
VAR_INPUT
rSetpoint : REAL;
rProcess : REAL;
bEnable : BOOL;
END_VAR
VAR_OUTPUT
rOutput : REAL;
bActive : BOOL;
END_VAR
VAR
rIntegral : REAL; // persistent
rLastError : REAL;
Kp : REAL := 1.0;
Ki : REAL := 0.1;
Kd : REAL := 0.0;
END_VAR
BEGIN
IF bEnable THEN
rError := rSetpoint - rProcess;
rIntegral := rIntegral + rError * CycleTime;
rOutput := Kp * rError + Ki * rIntegral + Kd * (rError - rLastError);
rLastError := rError;
bActive := TRUE;
ELSE
rOutput := 0.0;
bActive := FALSE;
END_IF;
END_FUNCTION_BLOCK
Calling the FB in OB1 with a multi-instance DB:
VAR
TempCtrl : FB_PID;
END_VAR
TempCtrl(rSetpoint := 80.0, rProcess := aiTemp, bEnable := TRUE,
rOutput => aoHeater, bActive => bHeaterOn);
7.3 Multi-Instance and Parameter Passing
On S7-1200/1500, parameters can be passed by value (INPUT) or by reference (IN_OUT, OUTPUT). Use := for default values in declarations, and the named-parameter style (param := value, param2 => out) to avoid order mistakes.
8. Working with Strings and Arrays
8.1 String Functions
VAR
sSrc : STRING[80] := 'Conveyor 3 fault';
sFind : STRING[6] := 'fault';
iPos : INT;
END_VAR
iPos := FIND(IN1 := sSrc, IN2 := sFind); // returns 11 or 0
IF iPos > 0 THEN
sSrc := INSERT(sSrc, '_ACK', LEN(sSrc) - iPos - LEN(sFind) + 1);
END_IF;
8.2 ARRAY Index Validation
FOR i := 1 TO 32 DO
IF aInputs[i] THEN // standard indexing
iCount := iCount + 1;
END_IF;
END_FOR;
On S7-1200/1500, ARRAY index checks are runtime-enforced; an out-of-range access produces SF LED + diagnostic buffer entry "Area length error". Wrap accesses in a IF (i >= LOWER_BOUND(a) AND i <= UPPER_BOUND(a)) block when the index comes from a field device.
9. SCL Editor Workflow in TIA Portal
- Open the project in TIA Portal V16+ and select a CPU in the project tree.
- Expand Program blocks and double-click Add new block.
- Pick the block type (FB/FC/OB) and set language to SCL.
- Type declarations in the upper declaration table; write code in the lower editor.
- Press F7 to compile. Errors appear in the Inspector under Compile / Syntax check.
- Drag the block into OB1 (or a higher-level FB) to instantiate it. Wire I/O via the LAD/FBD or SCL interface.
- Download to the CPU and use Online & diagnostics -> Monitor / modify to step through SCL.
10. Comparison: SCL vs. LAD/FBD/STL
| Feature | SCL | LAD | FBD | STL (S7-300/400) |
|---|---|---|---|---|
| High-level constructs (IF, FOR, WHILE) | Yes | Limited | Limited | Yes |
| Code density | High | Low | Low | High |
| String/array operations | Native | Manual | Manual | Manual |
| Standardization | IEC 61131-3 ST | IEC 61131-3 LD | IEC 61131-3 FBD | Siemens-specific |
| Reusability | Functions, libraries | FC/FB | FC/FB | FC/FB |
| Performance | Compiled, near-native | Interpreted in S7-300/400, compiled S7-1500 | Same as LAD | Fastest, direct MC7 |
| Learning curve | PASCAL background | Electrical schematic | Logic-gate | Assembly-like |
| Debug visibility | Single-step, breakpoints | Network monitor | Network monitor | Single-step, accumulator view |
On S7-1500, LAD and FBD are compiled to native code by the TIA Portal compiler, so the runtime gap with SCL is small. On S7-300/400, LAD/FBD is interpreted while SCL is compiled - SCL can be 2-10x faster for complex math.
11. Library and Reuse Patterns
Build a master SCL library once and reuse it across machines:
- Global libraries in TIA Portal: File > New Library > Global Library. Add typed FBs and UDTs. Use versioned library updates to roll out bug fixes.
- Know-how protection: right-click the block, select Properties > General > Protection. Apply a password so that source code in the SCL editor is hidden during online operations.
-
Source-file import/export: export blocks as
.scltext. This enables git-based version control and side-by-side diffs.
12. SCL for Data Handling and Recipes
Recipes are a common SCL use case. The example stores 20 parameters per recipe, with 8 recipes in a DB:
TYPE RecipeRow :
STRUCT
Name : STRING[16];
Setpoint : REAL;
Tolerance: REAL;
RampTime : TIME;
Enabled : BOOL;
END_STRUCT;
END_TYPE
DATA_BLOCK DB_Recipes
STRUCT
Active : INT := 0;
List : ARRAY[1..8] OF RecipeRow;
END_STRUCT
END_DATA_BLOCK
FUNCTION FC_LoadRecipe : BOOL
VAR_INPUT
iNumber : INT;
END_VAR
BEGIN
IF iNumber < 1 OR iNumber > 8 THEN
FC_LoadRecipe := FALSE;
RETURN;
END_IF;
DB_Recipes.Active := iNumber;
rSetpoint := DB_Recipes.List[iNumber].Setpoint;
tRamp := DB_Recipes.List[iNumber].RampTime;
FC_LoadRecipe := TRUE;
END_FUNCTION
13. Diagnostics and Error Handling
Use the EN/ENO mechanism (ladder-style) only when calling SCL from LAD. Inside SCL, prefer explicit error variables:
IF iNumber < LOWER_BOUND(DB_Recipes.List, 1)
OR iNumber > UPPER_BOUND(DB_Recipes.List, 1) THEN
bError := TRUE;
iStatus := 16#8001; // custom code
RETURN;
END_IF;
For CPU-side error reporting, raise a system function:
IF bFatalFault THEN
RALRM(OB_NUM := 82, OL := FALSE, F_ID := 16#0001); // generate diagnostic interrupt
END_IF;
14. Best Practices
- One block, one function. Keep FCs short (< 200 lines). If larger, split into helpers.
-
Avoid magic numbers. Use
VAR CONSTANTor DB constants for setpoints, limits, and timing. -
Bound every loop. Use
UPPER_BOUND/LOWER_BOUNDmacros - never write hard-coded loop limits that differ from the array size. - Use named parameters at call sites. Easier to read and unaffected by interface re-ordering.
-
Initialize outputs. Set every
VAR_OUTPUTat the start of the block to a known value, so the caller never sees undefined data if the block returns early. -
Keep
VAR_IN_OUTminimal. It blocks the optimizer and prevents read-only access. - Version your libraries. Tag with SemVer in the library comment and use TIA Portal's library update path.
- Compile clean, every time. Warnings become errors after firmware upgrades. Treat them seriously.
15. Verification and Commissioning
Before downloading an SCL block to a running machine:
- Compile with all warnings enabled (Project > Properties > Compile).
- Cross-check interface changes in Compile / Cross-reference - confirm no consumer was missed.
- Use the SCL PLCSIM instance: load the project into S7-PLCSIM V16+ and exercise the FC/FB with the Watch table.
- Use breakpoints in the SCL editor (right margin - red dot). Single-step with F8, watch live tag values. Breakpoints require a SCL-compatible CPU firmware (S7-1500 since V1.5; S7-1200 since V4.0; S7-300/400 with breakpoints disabled by default - activate via Online > Breakpoint properties).
- For S7-1500, run the Inspector > Compile messages tab and confirm zero warnings.
16. Common Errors and Fixes
| Symptom | Cause | Fix |
|---|---|---|
| Compiler: "Identifier already declared" | Duplicate tag name in same scope | Rename, check for accidental section duplication |
| Compiler: "Type mismatch" | INT assigned to REAL without cast | Use INT_TO_REAL() / REAL_TO_INT()
|
| CPU SF LED: "Area length error" | ARRAY index out of range | Validate index with LOWER_BOUND/UPPER_BOUND |
| CPU SF LED: "Division by zero" | Divisor is 0 | Guard with IF divisor <> 0 |
| Block is yellow in compiled program | No SCL license | Install license via Automation License Manager |
| Source download fails | CPU firmware older than library's target firmware | Update CPU firmware, or use compatibility mode |
| Online monitor: variables show "invalid" | Instance DB not downloaded, or watch table points to wrong area | Download blocks including all referenced DBs |
17. Performance Tips
- Use
DINT/REALinstead ofINTon S7-1500 - the SCL runtime prefers 32-bit native operations. - Replace division with multiplication by reciprocal when a constant divisor is used heavily.
- Avoid
VAR_IN_OUTfor read-only parameters - useVAR_INPUT; the compiler can copy the value into a register and skip a writeback. - On S7-1500, declare large temporary arrays as
VAR_TEMPonly in FBs that get a new instance DB; otherwise they consume the L-stack of the calling OB.
18. SCL Reference Documentation
Authoritative Siemens documentation for SCL:
- Siemens Online Help "S7-300/400: Structured Control Language (SCL) for S7-300/S7-400 Programming" - SCL manual (PDF)
- TIA Portal Online Help "Creating SCL programs / Basics of SCL" - TIA Portal SCL (V21)
- STEP 7 Professional / TIA Portal installation manual - lists the SCL optional package and license order numbers.
- IEC 61131-3:2013 - international standard defining the ST language, SCL implements.
19. Suggested Learning Path
- Read the first 4 chapters of the SCL PDF (overview, language description, data types, expressions).
- Build the IF / CASE / FOR examples in PLCSIM.
- Port a small LAD project to SCL - convert one FC at a time and benchmark.
- Write a UDT for a motor and an FB that exposes only named parameters.
- Package the FBs into a global library and import them into a second project.
20. FAQ
Is SCL the same as IEC 61131-3 Structured Text?
SCL is Siemens' implementation of the IEC 61131-3 ST language. Most ST constructs work identically, but Siemens adds extensions such as EN/ENO behavior, direct access to I/Q/M operands, and block-aware parameter syntax (INPUT/IN_OUT/OUTPUT/RETURN). 100% ST code from another vendor will usually compile after minor edits to address formats.
Which Siemens CPUs support SCL?
S7-300 (CPU 313 and above), S7-400, WinAC, S7-1200 (firmware 4.0+), S7-1500 (all firmware versions), and ET 200SP CPUs. S7-200 does not support SCL. The S7-1500 also supports the SCL "extended" subset with LREAL, LINT, and WSTRING.
Do I need a separate license to use SCL in TIA Portal?
Yes. SCL is part of the STEP 7 Professional license package. With STEP 7 Basic, the SCL blocks are still editable but cannot be compiled to a runnable program. Use the Automation License Manager to install the license on the engineering station and transfer it to the CPU's SIMATIC memory card if required.
Can I mix SCL and LAD in the same block?
No - each block is a single language. But you can call an SCL FC from a LAD network and vice versa. A common pattern is LAD for I/O mapping and safety logic, SCL for math, data, and recipe handling.
Where can I find SCL sample projects?
Open the TIA Portal "Add new block" dialog, click the Examples tab - it ships with simple SCL FCs and FBs. For S7-300/400, the "SCL V4" PDF linked above contains ready-to-paste example code for IF, CASE, FOR, WHILE, and STRING functions.