Overview: SCL as the Siemens Implementation of IEC 61131-3 Structured Text
SCL (Structured Control Language) is Siemens' high-level textual programming language for the S7-1200, S7-1500, S7-300, and S7-400 controller families, edited inside the TIA Portal and (for legacy targets) STEP 7 V5.x. SCL is the Siemens implementation of the Structured Text (ST) language defined in IEC 61131-3. Because the IEC standard is the common ancestor, SCL is syntactically closer to Pascal than to C, and a C-to-SCL port is best approached as a C → Pascal → SCL two-step translation rather than as a direct C → SCL rewrite.
There is no Siemens-blessed automated C-to-SCL converter. Conversion is a manual, code-review-driven exercise that relies on a working knowledge of both languages and a precise understanding of what the C code is supposed to do. This reference documents every language feature you will encounter in embedded C, the SCL equivalent, and the limits where the port becomes impractical.
Prerequisites
- Working knowledge of C (data types, pointers, structs, control flow) and of PLC programming under TIA Portal.
- Access to the target S7-1200 or S7-1500 firmware and a TIA Portal project at the matching STEP 7 version. STEP 7 V14 or later is required for recursive SCL functions on the S7-1200/S7-1500.
- The Siemens SCL programming and programming manual for the target CPU, plus the STEP 7 Professional V18 (or current) documentation set.
- The source C file, the C header describing structs/unions, and a clear functional specification. Without a spec, the port will diverge silently from the intent.
IEC 61131-3 Foundation and Pascal Bridge
Both SCL and Pascal descend from Wirth's block-structured design, while C uses curly-brace expression statements. Because the keyword sets overlap heavily (BEGIN/END, IF/THEN/ELSE, CASE, FOR/WHILE/REPEAT, FUNCTION/FUNCTION_BLOCK), automated C → Pascal converters produce code that an engineer can hand-clean into SCL in a fraction of the time required for a direct C → SCL port. Several freely available C-to-Pascal translators exist; their output is a starting point, not finished SCL.
The IEC 61131-3 third edition (2013) — which Siemens implements in current SCL — adds features that did not exist in earlier SCL, in particular recursive POUs with a bounded nesting depth. This is the single most important SCL feature for C ports, because the most common C patterns (tree walks, parsers, divide-and-conquer algorithms) rely on recursion.
Data Type Mapping: C to SCL
The PLC uses 8-bit, 16-bit, 32-bit, and 64-bit (S7-1500) elementary types that map cleanly to C, with the SCL names shown in the table below. All types are signed unless prefixed with UNSIGNED (or the older WORD/DWORD/LWORD for bit-string interpretation).
| C type | SCL equivalent | Size (bits) | Notes |
|---|---|---|---|
char, signed char
|
SINT |
8 | Range -128..127 |
unsigned char |
USINT |
8 | 0..255 |
short, int16_t
|
INT |
16 | -32 768..32 767 |
unsigned short, uint16_t
|
UINT |
16 | 0..65 535 |
int, long, int32_t
|
DINT |
32 | -2 147 483 648..2 147 483 647 |
unsigned int, uint32_t
|
UDINT |
32 | 0..4 294 967 295 |
long long, int64_t
|
LINT (S7-1500) |
64 | S7-1200 with V4.4+ also supports LINT |
unsigned long long |
ULINT (S7-1500) |
64 | — |
float |
REAL |
32 | IEEE-754 single precision |
double |
LREAL |
64 | IEEE-754 double precision; not identical to double on every x86 toolchain |
bool / _Bool
|
BOOL |
1 | 0/1 only, no implicit integer |
char[] string |
STRING[n] |
8*(n+1) | 2-byte header; not NUL-terminated in the C sense |
struct |
STRUCT…END_STRUCT
|
sum | Field alignment is byte-packed, not C-natural |
union |
AT overlay on a STRUCT
|
max | See "Unions and AT" below |
enum |
INT constant block |
16/32 | No native ENUM in classic SCL; use named constants |
STRING is a Pascal-style length-prefixed string, not NUL-terminated. Any C code that depends on strlen, strcpy, or implicit NUL termination must be rewritten using SCL string functions (LEFT, RIGHT, MID, CONCAT, FIND, DELETE, INSERT, REPLACE) and explicit length handling.Control Flow Translation
C's keyword-based control flow maps almost line-for-line to SCL. The only real difference is that Pascal-family languages use semicolons as statement separators rather than as statement terminators, and block delimiters are BEGIN/END rather than {/}.
| C construct | SCL equivalent |
|---|---|
if (cond) { ... } else { ... } |
IF cond THEN ... ELSE ... END_IF; |
switch (x) { case 1: ...; break; } |
CASE x OF 1: ...; ELSE ... END_CASE; (no fall-through) |
for (i=0; i |
FOR i := 0 TO n-1 DO ... END_FOR; |
while (cond) { ... } |
WHILE cond DO ... END_WHILE; |
do { ... } while (cond); |
REPEAT ... UNTIL cond END_REPEAT; (note inverted condition) |
break; |
EXIT; |
continue; |
CONTINUE; |
goto label; |
GOTO label; with label: target |
return value; |
RETURN value; (or RETURN; for void) |
CASE in SCL has no implicit fall-through: every label must end with an explicit terminator. C code that depends on fall-through must be refactored.
Functions, Function Blocks, and the Missing Function Pointer
Procedures and functions translate directly:
- FC (Function): stateless, returns one typed value, has no instance DB. Equivalent to a C function returning a value.
- FB (Function Block): stateful, owns an Instance DB that retains static variables between calls. Equivalent to a C++ class with member data, or to a C function operating on a passed-in context struct.
-
Void C function → SCL FC that returns
VOIDand usesVAR_IN_OUTfor parameters it must modify.
Three C features do not survive the port cleanly:
-
Function pointers. There is no SCL equivalent. Replace with a
CASEon an integer selector, or implement the strategy pattern through FB instantiation and an array of FB instances, each implementing a common interface (i.e., the same set of method FCs with the same input/output parameter list). -
Variadic functions (
printf-style). Not supported. Use fixed-arity FCs or build a string withCONCATand explicit type conversions. - setjmp / longjmp. Not supported. Use a tagged-state machine in SCL instead of exception-style control flow.
Pointers, PEEK, POKE, and the ANY Pointer
SCL supports typed pointers declared with POINTER TO <type> and used through ^ dereference. The PLC pointer is not the C pointer: it can be assigned, passed to FBs, and dereferenced, but pointer arithmetic (p++, *(p+1)) is not allowed at the SCL language level.
Three workarounds cover almost every case:
- Index through an array instead of pointer arithmetic.
-
Disassemble an ANY pointer into a base address and length, then step through with explicit offsets using
PEEK(read) andPOKE(write) over aBYTEview.PEEKandPOKEare bit-oriented, not byte-oriented in the C sense; cast throughATto aBYTEarray to get a byte stream. -
Variant / VARIANT in S7-1500 (STEP 7 V14+) for type-generic blocks analogous to
void *.
PEEK/POKE, PIW, PQW) bypasses PLC I/O consistency. Use it only when the C code accesses shared memory regions (DBs) and never for cyclic I/O update during the same OB1 cycle.Unions and the AT Construct
SCL has no UNION keyword. Use the AT overlay to declare a second view of an existing variable at the same memory location:
VAR
raw : STRUCT
b0 : BYTE;
b1 : BYTE;
b2 : BYTE;
b3 : BYTE;
END_STRUCT;
asWord AT raw : WORD; // overlays the 4-byte struct as one 16-bit value
asDWord AT raw : DWORD; // overlays as one 32-bit value
END_VAR
Limitations: the overlaid view must be at an aligned offset for its declared type on S7-1500 (strict), and is more permissive on S7-1200. Read the CPU-specific SCL manual before assuming byte-packed overlay works the way it does in C.
Recursion in SCL — What Changed in STEP 7 V14
Classic SCL (pre-STEP 7 V14) forbade recursive POU calls, because PLCs traditionally disallow stack-heap interaction that could collide with deterministic cycle times. STEP 7 V14 relaxed this for the S7-1200 and S7-1500 families. A recursive FC or FB is allowed, but the call chain is bounded by the CPU's maximum nesting depth (a project property) and the recursion will fault with a nesting-stack overflow if exceeded.
Practical rules for porting recursive C:
- Convert the C function to an SCL FC with the same signature.
- Add a depth counter as an
VAR_IN_OUTparameter; increment on entry, decrement on exit, andRETURNthe base case at a fixed maximum. - Configure the CPU's nesting-depth setting in the device properties high enough to cover the realistic worst case of the input data set.
- Verify with a stress-test that drives the recursion to its documented limit and confirms a clean
RETURNrather than an SF LED.
Step-by-Step Conversion Procedure
- Catalogue the C source. List every function, struct, union, global variable, pointer, and recursion in the C file. Anything you miss here will silently break the port.
-
Translate data types using the mapping table above. Convert
enumto aCONSTblock in a global DB. -
Translate control flow keyword by keyword; replace
switch/casewithCASEand refactor any fall-through. -
Replace C functions with SCL FCs; replace functions with static state with FBs and an Instance DB. Refactor any function-pointer call site into a
CASEdispatch or a strategy-block instance array. -
Replace pointer arithmetic with array indexing where possible. Use
AToverlays for unions. UsePEEK/POKEthrough aBYTEview for any remaining absolute-address patterns. -
Replace C strings with
STRING[n]and the SCL string built-ins; add explicit length handling at every boundary. -
Refactor
printf/sprintfintoCONCATchains with explicit type conversions (SINT_TO_STRING,INT_TO_STRING,REAL_TO_STRING, etc.). - Port recursion only if the target CPU runs STEP 7 V14 or later; set the nesting depth in device properties; add a depth-limit guard.
- Compile, download, and watch. Use the TIA Portal SCL editor's online watch tables to step through the program and compare against the C reference output for the same inputs.
Verification and Field-Proven Caveats
-
Bit-width trap: C
intis 32-bit on almost every modern toolchain; SCLINTis 16-bit. Code that usesintfor indices and assumes 32-bit range will overflow in SCL if you used the obviousINTmapping. UseDINTfor any index or counter that can exceed 32 767. -
Signed-vs-unsigned trap: Mixing
UINTandINTin SCL expression evaluation can promote to a 32-bit signed result. Cast explicitly to avoid silent sign-extension bugs that are easy to miss because the PLC will compile and run. - Cycle-time blow-up: Loops that were cheap on a PC can dominate an S7-1500 OB1 cycle. Add an upper-bound watchdog to any loop translated from C, or move it to a time-driven OB (e.g., OB35 at 100 ms).
-
Floating-point determinism:
LREALresults can differ from x86doublein the last bit. Do not assert exact equality; assert absolute or relative tolerance. - Watch table parity test: Build an HMI watch table or a test FB that drives both the C reference (offline) and the SCL block (online) with the same input vectors, and compare side-by-side for every test case in the original C unit tests.
Troubleshooting Matrix
| Symptom | Likely C-side cause | SCL fix |
|---|---|---|
| SF LED + "Nesting depth exceeded" diagnostic buffer entry | Recursive C function translated without depth limit | Add a depth counter; raise CPU nesting depth property; both |
| Compile error: "Function pointer not allowed" | C function pointer, callback, or qsort comparator |
Replace with CASE dispatch or strategy-FB array |
| Compile error: "UNION not supported" | C union type |
Use AT overlay on a STRUCT of the same byte length |
| Wrong result, low byte correct high byte garbage | C struct with implicit padding |
Pad explicitly in the SCL STRUCT; do not assume C-natural alignment |
| Compile error: variadic parameter not allowed |
printf, scanf, custom variadic |
Replace with fixed-arity FC or CONCAT with explicit conversions |
| String length off by 1 or 2 | C strlen / NUL handling |
Use SCL string built-ins; remember the 2-byte S7 string header is not a NUL |
| Index out of range at runtime | Pointer arithmetic mapped as p+n
|
Replace with array indexing; do not pass addresses |
| Result flips sign on large values | Unsigned/signed mix | Cast to DINT / UDINT explicitly; do not rely on implicit promotion |
FAQ
Is there an automated C-to-SCL converter from Siemens?
No. Siemens does not publish a C-to-SCL converter. The practical industry approach is C → Pascal via a third-party translator, then hand-clean the Pascal into SCL inside TIA Portal.
Does SCL support recursive function calls?
Yes, on S7-1200 and S7-1500 controllers running STEP 7 V14 or later. Recursion is bounded by the CPU's configured maximum nesting depth; a depth-limit guard parameter is strongly recommended to avoid a stack-overflow diagnostic-buffer entry.
Can SCL use function pointers like C?
No. SCL has no function-pointer type. Replace C function pointers with a CASE-on-selector dispatch, or instantiate one FB per strategy and store the instance reference in an array that is indexed at runtime.
How do I translate a C union into SCL?
Use the AT overlay. Declare a STRUCT with the same total byte length, then declare a second variable AT that STRUCT with the alternative type. SCL has no UNIONS keyword.
Why does my SCL result differ from the C reference for floating-point values?
LREAL is IEEE-754 double precision but rounding and instruction order differ from the x86 toolchain that produced the C reference. Do not compare with =; compare with an absolute or relative tolerance band suited to the algorithm.