reference · South Africa
IEC Structured Text Syntax: IF, CASE and Bounded Loops
IEC Structured Text syntax for PLC learners: practise IF, CASE, FOR, WHILE and REPEAT with checked examples, clear types, bounded loops and state handling.

IEC Structured Text syntax uses assignments, expressions and control-flow statements to describe PLC logic in text. IF selects a conditional branch, CASE selects from alternatives, and FOR, WHILE and REPEAT perform repeated work. Understanding the punctuation is only the beginning: a useful program also defines types, state lifetime, valid inputs and a bounded amount of work.
This reference gives South African PLC learners worked examples for classification, display mapping and array processing. The examples change teaching data only. Their mathematical results are independently checked, but the snippets are not presented as a complete compiled project or a guarantee of identical behaviour across every vendor.
This site has a commercial connection to PLC Simulator. Its educational resources do not provide vendor engineering licences or establish native execution of arbitrary Structured Text. The illustrations are conceptual learning scenes rather than vendor screenshots or customer project evidence.
Read a statement before reading a whole routine
An assignment such as Total := Total + Value; evaluates the right-hand expression and assigns its result according to the destination's type rules. The previous Total matters if the design intends accumulation. If the design instead intends a fresh sum each invocation, initialise the working total at the correct point before processing the values.
Use := for assignment and the appropriate comparison operator for a Boolean test. A condition asks whether something is true; an assignment changes a variable. Confusing those intentions makes a program harder to review even when the editor reports a syntax error quickly.
| Construct | Purpose | Question beyond syntax |
|---|---|---|
| Assignment | Store a calculated or selected value | Is this a fresh result or persistent state? |
| IF / ELSIF / ELSE | Choose a branch from Boolean conditions | Which condition has priority? |
| CASE / OF / ELSE | Select statements for a selector value | What happens for an unexpected selector? |
| FOR | Repeat across a defined iteration range | Do the counter, bounds and body remain valid? |
| WHILE | Repeat while a precondition is true | What makes the condition become false? |
| REPEAT / UNTIL | Execute before checking completion | Is at least one execution permitted? |
Semicolons terminate statements, and closing keywords identify the end of each control structure. Indentation should reveal the nesting. A missing terminator or closing keyword may cause a later line to be flagged, so inspect the surrounding structure rather than changing the reported line blindly.
Comments should explain requirements or non-obvious choices, such as why a conflict goes to a rejected status. Repeating the statement in prose adds little. Keep units, accepted ranges and state meanings in the declarations or nearby documentation so another reader can interpret the expression.
Choose types and expression rules deliberately
Declare whether an input is a Boolean, an integer, a floating-point number, a string or a structured record. Check the source and destination ranges and the types of intermediate operations. A wide destination does not automatically prove that an earlier operation preserved its fraction or avoided overflow.
Do not describe Siemens SCL as having no implicit conversion. Siemens' STEP 7 V21 overview explicitly documents implicit conversion for supported languages including SCL. The permitted conversion depends on the types and target rules. See the Siemens data-conversion overview.
Likewise, a successful port to another editor does not prove that rounding, precision or range handling stayed the same. Test the actual values and boundaries relevant to the calculation. The PLC data conversion reference separates numeric conversion, scaling and representation.
Use parentheses to communicate grouping when an expression could be misread. For an operation with a precondition, such as division or indexed access, ensure the precondition is checked before that operation is evaluated. Do not assume every Boolean AND expression skips its later operands.
CODESYS documents AND_THEN as an extension that short-circuits evaluation, while its ordinary AND evaluates all operands. That distinction matters when a later operand dereferences a pointer or performs another operation requiring a guard. Consult the CODESYS AND_THEN reference. A separate nested validation branch can make the required order clearer when portability is important.

Worked IF example: classify a teaching score
Define a fictional whole-number score from zero through 100. ScoreValid means the input has good quality and meets that range contract. The exercise uses Band 0 for invalid input, Band 1 for scores below 50, Band 2 for scores from 50 through 79, and Band 3 for scores from 80 through 100.
These bands are arbitrary teaching categories, not an assessment policy for a qualification. The classification requires exactly one result per invocation and should not leave yesterday's valid band visible when today's input is invalid.
(* Teaching ST-style fragment; declare and validate inputs in the project. *)
IF NOT ScoreValid THEN
Band := 0;
ELSIF Score >= 80 THEN
Band := 3;
ELSIF Score >= 50 THEN
Band := 2;
ELSE
Band := 1;
END_IF;
The highest valid threshold is checked first. With Score 85, the first applicable score branch assigns Band 3; the later branch does not overwrite it. If the two threshold tests were independent IF statements assigning the same Band, the second could assign Band 2 after the first assigned Band 3.
| Score and validity | Expected Band | Boundary being checked |
|---|---|---|
| 0, valid | 1 | Lowest accepted score |
| 49, valid | 1 | Just below the first threshold |
| 50, valid | 2 | First threshold included |
| 79, valid | 2 | Just below the second threshold |
| 80, valid | 3 | Second threshold included |
| 100, valid | 3 | Highest accepted score |
| Any score, invalid status | 0 | No stale valid classification |
The input validation stage rejects scores outside the accepted range rather than assigning them a normal band. Do not set ScoreValid true merely because a numeric value was received. Quality, range and the declared whole-number representation are separate requirements to establish.
CODESYS documents that an IF chain executes at most one branch, evaluating ELSIF conditions after earlier conditions are false. This supports the priority used by the example. See the CODESYS IF statement reference.
Use CASE for an explicit display mapping
Now map the teaching Band into three Boolean display indicators: Practice, Developing and Strong. Initialise all three indicators false for each fresh display calculation. Band 1 enables Practice, Band 2 enables Developing, and Band 3 enables Strong. Band 0 and every unknown value produce an invalid indication instead.
| Band | Practice | Developing | Strong | Invalid |
|---|---|---|---|---|
| 0 | False | False | False | True |
| 1 | True | False | False | False |
| 2 | False | True | False | False |
| 3 | False | False | True | False |
| Unknown value | False | False | False | True |
The explicit defaults prevent a prior Strong indication from remaining true when a later invocation selects Practice. That result depends on this design assigning the complete display state each time. A different routine that only writes one persistent variable may intentionally preserve others, so state lifetime must remain clear.
CASE is also useful for a state selector, but changing the selector inside the chosen branch should not be assumed to execute another branch immediately. Review the runtime's CASE semantics and the surrounding statements. A sequence's progression policy is a separate requirement from how its selector is written.
Rockwell's Studio 5000 version 38.01 CASE documentation supports individual values, comma-separated alternatives and ranges written with two periods. It describes executing the first matching selector's statements. Avoid repeating a blanket claim that Studio 5000 CASE cannot use ranges. See the Rockwell CASE reference.
A default branch should handle an unexpected selector visibly according to the application contract. Silently treating every unknown value as an ordinary operating mode can hide invalid state. For the display exercise, Invalid is a useful explicit result; it does not automatically specify a machine's fault response.

Worked FOR loop: sum five declared elements
Use a fictional array with indices zero through four and values 7, minus 2, 5, 0 and 9. Start a fresh Total at zero and add each element exactly once. The expected total is 19 after five body executions.
(* Teaching ST-style fragment for the declared five-element array. *)
Total := 0;
FOR Index := 0 TO 4 DO
Total := Total + Values[Index];
END_FOR;
The upper index is four because the array has five elements starting at zero. Changing the loop to zero through five would request a sixth element. Using the number of elements directly as the final zero-based index is a common off-by-one error.
| Array index | Value | Running total after addition |
|---|---|---|
| 0 | 7 | 7 |
| 1 | -2 | 5 |
| 2 | 5 | 10 |
| 3 | 0 | 10 |
| 4 | 9 | 19 |
If the requirement changes to summing only nonnegative values, the expected result is 21, not 19. A condition inside the body can implement that different rule. Keep both examples labelled with their own requirements rather than treating either result as universally correct.
Choose an accumulator type that can represent the worst-case total, not just each individual element. The small teaching values are easy to represent, but a production array may contain many large readings. Also decide whether invalid elements are rejected, skipped with an explicit count or represented through another documented policy.
CODESYS describes FOR as checking its range before the body and using an increment of one when BY is omitted. It specifically warns about an end value equal to the counter type's upper limit, where counter overflow can prevent termination. A visibly finite bound is therefore not sufficient without checking the counter type. See the CODESYS FOR reference.
For a populated array whose count may be zero, handle the empty case before forming an upper index from count minus one. An unsigned count can underflow if the expression is formed carelessly. The array instruction and bounds guide explains how declared capacity differs from valid populated length.
WHILE and REPEAT test at different times
WHILE checks its condition before executing the body. A false initial condition permits zero body executions. REPEAT executes its body before checking the UNTIL condition, so it performs at least one body execution in the documented CODESYS form.
For a fictional loop counter starting at zero, consider a task to increment it until it reaches a target. A WHILE condition of counter less than target performs zero increments when target is zero. A REPEAT body that increments first and then checks counter greater than or equal to target performs one increment for that same target.
| Target with counter initially zero | WHILE increments | REPEAT increments in this model |
|---|---|---|
| 0 | 0 | 1 |
| 1 | 1 | 1 |
| 3 | 3 | 3 |
Neither result is a reason to call one construct inherently wrong. Choose based on whether an initial body execution is required and valid. In particular, a REPEAT loop that reads an array element before checking an empty count can access data the contract never allowed.
CODESYS documents these pre-test and post-test behaviours in its WHILE reference and REPEAT reference. The completion condition must become true for REPEAT, while the continuation condition must become false for WHILE.
For every loop, identify what changes towards termination, its valid range and the maximum expected work. A fallback iteration limit can detect a broken assumption, but it needs an explicit incomplete or rejected status. Reaching the limit should not publish a partial result as successful completion.

A loop iteration is not a separate PLC scan
A loop normally performs its repeated statements within the current invocation. Writing a hundred iterations does not request a hundred future task executions. Do not use a tight loop to wait for an external sensor, operator action or network response as though time and the rest of the application will automatically progress in the required way.
Use an explicit state and return to the scheduler when the design must wait across invocations. On a later invocation, inspect the new inputs and decide whether to advance. The scan-cycle reference connects this distinction to task execution and data updates.
For larger data work, a bounded amount per invocation can be useful when the application permits it. Define a cursor, working result, input snapshot or ownership rule, and completion status. The work budget limits how many elements are handled during one call; it is not itself a measured time guarantee.
Apply a budget of two elements to the five-element sum exercise. The first call processes 7 and minus 2, leaving a working sum of 5. The second processes 5 and 0, reaching 10. The third processes 9, reaching 19 and marking completion.
| Invocation | Elements processed | Working sum | Complete |
|---|---|---|---|
| 1 | Indices 0 and 1 | 5 | False |
| 2 | Indices 2 and 3 | 10 | False |
| 3 | Index 4 | 19 | True |
If the input array can change between calls, the result may combine different revisions. Capture a stable set of inputs or define an appropriate ownership protocol. A persistent cursor alone does not establish that the accumulated result belongs to one coherent record.
Decide what happens when a new request arrives before the current calculation finishes. Queue it, reject it, cancel explicitly or use another stated policy. Resetting the cursor while keeping the old working total can produce a plausible but incorrect result.
Calls, variables and retained state need context
A function block instance can keep data between calls, while an ordinary temporary calculation has a different lifetime. The left side of every assignment is not an instance declaration. Identify whether the statement writes a local value, an output, shared state or a member of an existing instance.
Read the result after the relevant call if the consumer needs that call's updated value. Reading before it may obtain an older value. Repeating a call can update state again; it is not automatically equivalent to reading the same result twice.
Likewise, omitting an assignment does not universally mean “set false”. For a persistent variable, leaving it untouched can preserve its previous value. For temporary or reinitialised storage, different rules apply. Use declarations and the target lifecycle documentation to establish the actual behaviour.
Keep source-language conventions scoped. Vendor extensions, parameter names and available constructs can differ by version and target. A tutorial that uses CONTINUE should identify the supporting environment rather than presenting every extension as a portable IEC feature.

Review loop faults without inventing a recovery rule
An unbounded or unexpectedly expensive loop can interfere with task timing and trigger the configured runtime response. The exact fault, controller mode and recovery procedure depend on the target and application. Do not assume that every case requires a power cycle or that a larger watchdog setting repairs the underlying logic.
When a loop fails to complete, inspect its progress variable, bounds, type overflow and conditional paths. Check whether an early branch skips the statement that should advance the cursor. A loop that terminates for typical data can still fail at an empty input, maximum counter value or unusual branch.
When a loop completes with the wrong result, inspect initialisation and whether every intended element was processed once. Repeated invocation can expose an accumulator that was never cleared. Compare the result with an independent calculation, not another copy of the same loop.
For an unexpected branch result, inspect condition order and later writers. Two separate IF statements can both execute, while an IF/ELSIF chain selects at most one branch. A later assignment outside the chain can still replace the chosen value.
A useful syntax assessment for South African learners
A practical course should ask learners to explain the requirement and predict results before running the program. For this reference, use score boundaries, an unknown display selector, a five-element sum and a zero-iteration case. Each reveals a different control-flow decision.
Ask which controller and engineering version are used for the assessment. A learner comparing training in Johannesburg, Durban or Cape Town needs to know whether the exercises are compiled in native software, checked in an educational environment or discussed on paper. Those are different forms of evidence.
Use Structured Text learning exercises for additional practice with expressions and program organisation, checking the current access terms and available exercise scope. Do not describe educational examples as native controller compilation unless that work was actually performed.
A portfolio should include the declared inputs, accepted ranges, expected outputs and observed results. A screenshot of code without its test conditions makes it difficult for an assessor to distinguish understanding from copying a snippet.
Questions about IF, CASE and PLC loops
Why use ELSIF instead of another IF?
Use ELSIF when the conditions are alternatives in one priority chain. Independent IF statements can each execute and may overwrite the same destination. The score example demonstrates the difference when more than one threshold is true.
Why does a FOR loop access one element too many?
Check the array's lower and upper indices, the populated count and the loop's inclusive endpoint. Five elements starting at zero end at four. Also check how an empty count is handled before subtracting one to form an upper index.
Can WHILE wait until a sensor changes?
Do not assume a tight loop provides the required scheduling or fresh observations. For waiting across task invocations, use an explicit state and return between checks. Confirm the data update path and timing requirements in the actual application.
Why does REPEAT run when the target is already satisfied?
Its body runs before the UNTIL check in the documented form. If zero executions must be possible, use an appropriate pre-check or another construct. The target-zero counter example makes this distinction visible.
Does compiling mean the logic is correct?
Compilation checks supported syntax and related language rules. It does not establish that the requirements, units, bounds, state lifetime or failure policy are correct. Verify expected behaviour at boundaries and through repeated invocations.

Keep the code and its expected behaviour together
Use PLC program-testing resources to organise normal cases, boundaries and deliberate counterexamples. Keep mathematical-model evidence separate from native timing and lifecycle tests.
Before accepting a Structured Text routine, account for every destination, branch and loop. State what happens when inputs are invalid, a selector is unknown, work is incomplete or the routine runs again. That turns familiar keywords into a program another person can reason about and verify.