PLC Programming SAPLC ProgrammingSOUTH AFRICA
Menu

learn · South Africa

Structured Text: Arrays, CASE States and Validated Maths

Learn structured text with bounded arrays, complete CASE states and validated calculations, plus useful questions for South African PLC training courses.

Conceptual structured text learning workstation with a laptop, controller and enclosed conveyor
Conceptual learning illustration; not a validated circuit, program screenshot or physical test result.

Structured text is a textual PLC programming language for expressing decisions, calculations and repeated operations. Its value is not a promised reduction in rung count. Its value is that a reader can follow a well-structured calculation or state transition and explain the result from the supplied inputs and stored state.

This tutorial develops three small learning examples: a bounded array calculation, a complete three-state workflow and a validated numeric conversion. It also explains how to check an index before accessing an array and how to distinguish a valid result from a placeholder after an error.

For South African learners comparing PLC programming courses, these examples provide evidence you can ask to produce yourself. A useful assessment includes predictions, observations and boundary cases, rather than only a screenshot of code that compiles.

Place ST in the current language context

The official IEC 61131-3:2025 publication summary identifies structured text as a textual language alongside the graphical ladder diagram and function block diagram languages. It also describes sequential function chart elements for organising programs and function blocks. Use the edition relevant to your project rather than repeating an older language list as a description of every current implementation.

A language standard and a controller's supported feature set are different things. The compiler, runtime, libraries and project structure determine which declarations and instructions are available in a particular environment. Similar ST syntax does not guarantee that a native project can be copied unchanged between platforms.

ST is useful for a calculation whose mathematical structure is clearer as an expression, or for a bounded operation over several values. Ladder may be a useful representation for a maintenance team inspecting discrete logic. Compare readability and available tools for the actual task; neither language requires invented line-count claims to justify its use.

The ladder logic basics guide provides a companion route. Explain the same small Boolean decision in both representations, then compare how easy it is to verify the requirement.

Learn assignment, comparison and complete branches

Assignment uses := in the examples below. Equality comparison uses =, and inequality uses <>. A block such as IF has an explicit closing keyword. Semicolons terminate the illustrated statements; indentation helps the reader see the structure but does not replace the required syntax.

IF Request AND Available THEN
    Accepted := TRUE;
ELSE
    Accepted := FALSE;
END_IF;

Both branches assign Accepted. When the condition is false, the result is explicitly false rather than retaining an earlier true value. The shorter Boolean assignment Accepted := Request AND Available; expresses the same decision for these supplied Boolean inputs.

Now consider an IF that assigns Accepted only in its true branch. If Accepted has persistent storage and the false branch performs no assignment, its old value may remain. That may be intentional memory, but it must be part of the requirement rather than an accidental omission.

Separate a value that is recalculated each call from a value intended to persist. Names such as CurrentTotal, PreviousInput and LatchedRequest help indicate the difference, but names alone do not establish storage lifetime. Check the declaration and containing program organisation.

The scan-cycle tutorial shows why statement order matters. A later statement can read a value written earlier in the same call, while an earlier reader sees the previously stored value.

Choose data types from the values and operations

Record the expected range and meaning of each variable before choosing its type. A Boolean decision, a signed index, a count and a measured real-valued quantity are not interchangeable merely because all can be displayed as numbers.

The examples use INT for small indices and state labels, BOOL for decisions, and REAL for the illustrated numeric calculation. A real project must confirm the target's supported types, conversion rules and intermediate arithmetic behaviour.

Do not assume every mixed integer expression is rejected by every ST compiler. Some environments permit conversions that others require you to express explicitly. Equally, a successful compilation does not prove that the chosen result type has enough range for every intermediate value.

Floating-point comparisons need a requirement. A threshold comparison can be appropriate, while checking whether two independently calculated values are sufficiently close may require a justified tolerance. Do not choose a tolerance simply because a familiar decimal example produced rounding in a different language or precision.

Keep invalid data separate from numerical zero. If zero is a legitimate result, it cannot simultaneously be the only indication that a calculation failed. A validity flag or an explicit result state lets the consumer distinguish those cases.

Conceptual sensor, controller and conveyor illustration for distinguishing signals from program values
Conceptual learning illustration; not a validated circuit, program screenshot or physical test result.

Worked example: sum four values without retaining an old total

Use a four-element array with indices one through four. For this example, supply small integer values and store the total in a type with sufficient range for their sum. Reset Total before the loop so each call calculates a fresh total.

Total := 0;
FOR Index := 1 TO 4 DO
    Total := Total + Samples[Index];
END_FOR;

For Samples containing 3, 5, 7 and 9, Total progresses through 3, 8, 15 and 24. Calling the calculation again with the same samples should produce 24 again. If Total is not reset first, the second call instead finishes at 48 under the same arithmetic model.

Array index readSampleTotal after addition
133
258
3715
4924

The CODESYS FOR statement reference documents its loop condition and increment behaviour. It also warns about an end value at the counter type's upper limit. Our small index range avoids that boundary, but a larger loop still needs its own termination and range review.

The table is a checked software calculation, not a controller timing benchmark. Four iterations tell you how many iterations the model requires; they do not establish a universal execution time in microseconds.

For a useful extension, replace the samples with four negative values or a mixture of positive and negative values. Predict the sum and confirm that the chosen types preserve it. Keep the expected range small enough that the exercise is about loop behaviour, not an undocumented overflow policy.

Check an index before reading the array

A guard must prevent the array access from executing when the index is invalid. Putting a bounds comparison beside an array read in a Boolean expression does not automatically establish that protection.

The CODESYS AND_THEN documentation distinguishes its short-circuit extension from ordinary AND, whose operands it says are all evaluated. Do not assume that an expression using ordinary AND will skip an invalid array access after another condition is false.

This example uses nested decisions so the access occurs only after the bounds have been checked. Selected is an integer. Result is a placeholder zero unless ResultValid is true.

Result := 0;
ResultValid := FALSE;
IF Selected >= 1 THEN
    IF Selected <= 4 THEN
        Result := Samples[Selected];
        ResultValid := TRUE;
    END_IF;
END_IF;

With the sample array above, selections one and four produce valid results three and nine. Selections zero and five produce ResultValid false, and no array element is read. The placeholder zero must not be consumed as a valid measurement or selection.

Test an invalid selection after a valid one. ResultValid must become false again; it must not retain the success flag from the previous call. Then test a valid selection after the failure, confirming that the flag returns true and the new result is assigned.

This pattern is also relevant to recipe selection. Checking the row index prevents an out-of-range read, but it does not validate every parameter stored in that row. Treat index validity and record-content validity as separate checks.

Illustrated learning desk with a notebook and laptop for planning Boolean logic test cases
Conceptual learning illustration; not a validated circuit, program screenshot or physical test result.

Search a fixed-size array without reading beyond its end

A bounded search is a useful next exercise. Find the first zero among four supplied entries. Use Found false and FoundIndex zero initially; zero means no valid index has yet been found because the array starts at one.

Found := FALSE;
FoundIndex := 0;
FOR Index := 1 TO 4 DO
    IF NOT Found THEN
        IF Samples[Index] = 0 THEN
            Found := TRUE;
            FoundIndex := Index;
        END_IF;
    END_IF;
END_FOR;

For 8, 0, 4 and 0, the result is Found true with FoundIndex two. The later zero at index four does not replace the first result. For 8, 6, 4 and 2, Found remains false and FoundIndex remains zero.

The loop still has four iterations even if the match occurs early. Its body avoids further array reads once Found is true. That is a deliberate small-example design, not a claim that every search should use this structure.

Compare it with a WHILE expression that reads Samples[Index] before proving Index is within bounds. When no match exists, that expression can attempt an invalid read. A loop also needs a termination argument: explain how its controlling value changes and why the body cannot repeat indefinitely.

A fixed bound does not prove that a large loop fits a task's timing requirements. It provides a known amount of logical work to investigate. Measure execution in the selected environment before making a scheduling claim.

Worked example: a complete three-state workflow

The workflow has Ready = 0, Inspect = 1 and Done = 2. It controls only virtual status indicators. Request starts inspection from Ready. Accept completes inspection. Done remains until Request is observed false, then returns to Ready.

While Inspect is active, releasing Request does not cancel inspection in this contract. Accept can complete it regardless of Request. These are exercise rules, not an assumed process sequence.

Calculate NextState from the old State, then assign State once and derive both indicators from the updated value. InvalidState is true for the evaluation that receives an unknown state number; that evaluation returns to Ready without accepting a new request.

InvalidState := FALSE;
NextState := State;
CASE State OF
    0:
        IF Request THEN NextState := 1; END_IF;
    1:
        IF Accept THEN NextState := 2; END_IF;
    2:
        IF NOT Request THEN NextState := 0; END_IF;
ELSE
    NextState := 0;
    InvalidState := TRUE;
END_CASE;
State := NextState;
InspectIndicator := (State = 1);
DoneIndicator := (State = 2);

The CODESYS CASE reference describes selection of a branch by its label and an optional ELSE path. In this example, the default path and the complete output assignments are explicit design decisions.

Changing NextState does not execute another CASE branch during the same call. If Request and Accept are both true in Ready, this call enters Inspect; a later call can process Accept from Inspect. That one-transition rule makes the sequence easier to inspect.

Two illustrated learners reviewing a controller example beside a guarded training conveyor
Conceptual learning illustration; not a validated circuit, program screenshot or physical test result.

Replay the state trace, including held inputs

Initialise State Ready and both indicators false. Evaluate the following input rows once each. The expected state is the value after the call, so the indicators follow that resulting state.

RowRequestAcceptResulting state
100Ready
211Inspect
311Done
410Done
500Ready
610Inspect
700Inspect
801Done
900Ready

Row two demonstrates that both inputs true do not cause two transitions in one call. Row four demonstrates that a held request keeps Done visible. Row seven demonstrates the stated choice that releasing Request does not cancel Inspect.

Now supply State 99 with Request and Accept both true. The expected result is Ready, both indicators false and InvalidState true. On the next call the normal Ready branch applies. The diagnostic is an evaluation result, not a latched fault history.

The trace and the combinations of valid state and Boolean inputs were checked in an independent model. Reproduce them in your own implementation. If you change the cancellation or restart policy, rewrite the expected table as well as the code.

The first-out annunciator exercise is a useful next state-and-history problem. It demonstrates why state labels, input levels and newly observed events should remain distinct.

Worked example: convert a validated numeric input

Use a supplied integer Raw on a fictional zero-to-one-thousand scale. Convert it to a REAL result on a zero-to-two-hundred-and-fifty scale. This is an arithmetic exercise with no physical engineering unit assigned.

Initialise Scaled zero and ScalingValid false on every call. Only evaluate the expression after confirming Raw lies within the stated range. The fixed denominator is positive and non-zero.

Scaled := 0.0;
ScalingValid := FALSE;
IF Raw >= 0 THEN
    IF Raw <= 1000 THEN
        Scaled := INT_TO_REAL(Raw) * 250.0 / 1000.0;
        ScalingValid := TRUE;
    END_IF;
END_IF;

Raw zero produces valid zero. Raw 400 produces valid 100. Raw 1,000 produces valid 250. Raw minus one or 1,001 produces an invalid result with the placeholder zero. Consumers must check ScalingValid rather than interpreting every numeric zero as a valid converted value.

An invalid call after a valid one clears the success flag; a later valid call sets it again. This avoids a fault or validity bit becoming permanently stuck because it is assigned in only one branch.

For configurable scaling, validate the configuration before division, including the denominator and required range direction. For real-valued inputs, add the relevant finite-value and data-quality checks. Do not claim that this integer-input example handles every analogue input condition.

The scaling and resolution lesson develops the measurement context. Keep arithmetic correctness separate from sensor quality, calibration and the meaning of the raw signal.

Illustrated learner comparing program observations with a guarded conveyor training model
Conceptual learning illustration; not a validated circuit, program screenshot or physical test result.

Keep functions, instances and task execution distinct

A function call, a function-block instance and a scheduled program have different roles in a project. The exact declarations and lifecycle depend on the environment. Identify which data should be recalculated, which should persist and which task or routine calls the code.

A timer instance called conditionally is not automatically paused when its call is skipped. A stored output may remain unchanged while the implementation's time source advances. The timer call-order reference provides an explicit comparison between a skipped call and an executed false input.

Likewise, an ST routine does not imply that every input is a frozen snapshot or that the program runs at one universal fixed interval. Task scheduling and I/O update behaviour belong to the controller configuration. Read the platform documentation alongside the language syntax.

For loops over larger datasets, record both the logical bound and measured execution evidence. Splitting work over several calls introduces another requirement: what happens if the data changes while the operation is incomplete? A partial calculation may need its own input snapshot or versioning rule.

Do not infer a universal watchdog limit or instruction execution time from the language name. A bounded loop can still be too much work for a particular schedule. A timing change should follow measured evidence and the intended task requirements.

Compare vendor syntax and educational subsets carefully

When choosing a platform, compare its declarations, type conversions, library functions, instance storage and supported language extensions. Recognising IF, CASE and FOR helps with reading another environment, but does not establish complete source compatibility.

Do not assume that an object-oriented extension or a particular array feature exists only on one vendor's platform, or that all environments offer it in the same way. Check the actual compiler version and language documentation for the feature you need.

For online practice, explore the structured-text learning material. Confirm the supported educational subset before expecting a native controller project or every declaration in this tutorial to run unchanged.

The scan-cycle highlighting feature is a relevant companion for discussing evaluation order. Pair the visual observation with recorded values; highlighting alone does not verify every language construct or physical controller behaviour.

The code fragments here are teaching examples with stated types and assumptions. A native project still needs the appropriate declarations, project structure and compiler checks. The independent model checks support the algorithms and expected results, not a claim that the fragments have been compiled on every vendor platform.

Conceptual PLC learning portfolio with a process sketch, test notes and a laptop showing logic
Conceptual learning illustration; not a validated circuit, program screenshot or physical test result.

Build evidence for a structured-text course or portfolio

Save the four-value sum, bounded search, guarded lookup, nine-row state trace and scaling results together. Include the initial state and type assumptions. Show one invalid input case for each example that accepts an external value.

Add a deliberately faulty version of the total calculation without its initial reset and show why the second call produces 48 instead of 24. Add a state version that omits one output assignment and explain the stale-value risk. Restore the correct version and rerun the relevant cases.

When comparing classroom courses in Johannesburg, Pretoria, Durban, Cape Town or another South African location, ask whether students write and test their own array and state examples. Confirm the actual venue, controller family, software version and individual feedback available.

For remote learning, use the online PLC training guide to check access duration and assessment arrangements. A course description should distinguish reading syntax from producing a working, reviewed project. City names on this page do not imply local branches or scheduled classes.

Is structured text the same as programming an ordinary application?

Some control-flow ideas are familiar, but execution scheduling, stored instance state, controller libraries and I/O behaviour need separate attention. Begin with small calculations and traces rather than assuming an input event automatically calls the code once.

Can I protect an array access with an AND condition?

Only if the actual evaluation rules and expression structure provide that protection. The CODESYS source above distinguishes ordinary AND from its short-circuit extension. The nested checks in this tutorial avoid relying on that assumption.

Why does my total double on the second call?

A total intended to be recalculated may have retained its previous value. Initialise it before adding the current samples. If accumulation across calls is intended instead, define the reset policy and name the value accordingly.

Why does a CASE sequence keep an old output true?

Check whether the current branch assigns that output. In the worked workflow, both indicators are derived after the transition on every call, including the invalid-state path. That makes the output policy explicit.

What should I learn after these examples?

Choose a problem from the PLC programming examples collection and explain its state, inputs, outputs and boundary cases in ST. Preserve the expected trace when changing the representation, so the new code remains tied to the same requirement.

By PLC Programming SA · Last updated 2026-09-11