Chapter 14 / 36

Types, arrays and data

Represent a machine with explicit units, bounds and data ownership.

A value needs a meaning, a range and an owner

Imagine opening a watch window containing Value1 = 250, Value2 = 250 and Value3 = 250. One means millimetres, one means tenths of a degree, and one is an alarm number. The PLC can store all three. Only the programmer knows whether adding them makes sense.

Types help you make some mistakes impossible and other mistakes visible. Names and units finish the job. ConveyorSpeed_mm_s is longer than Speed, but it prevents a quiet thousandfold error when somebody supplies metres per second. Define the engineering meaning before choosing the storage size.

For each important value, write five facts: meaning, unit, valid range, source and restart behaviour. A recipe temperature might be degrees Celsius, 20 to 65, entered by an authorised operator, copied into an active batch at start, and restored as a candidate after power returns. That description is already most of the design.

Choose types for the operations you need

Use BOOL for two-state facts and requests. Use integer types for counts and indexes. Use REAL or LREAL when fractional values matter. Use TIME for durations rather than an unexplained number of task calls. Use an enumeration for a small set of meaningful states when your environment supports it.

Do not choose the smallest type simply to save a few bytes. A signed 16-bit integer commonly used as IEC INT has a maximum of 32,767. At sixty products per minute, a lifetime count reaches that value in about 546 minutes: a little over nine hours. A display that worked during commissioning can fail in the first full shift. Select a suitable larger type and still define overflow behaviour.

Conversions are decisions. Turning a real measurement into an integer loses a fraction; the target's conversion rules determine rounding and out-of-range handling. Test the exact compiler rather than building a recipe on an assumed rounding rule. For tolerances, compare a real value with a permitted interval instead of expecting exact equality after arithmetic.

Group things that belong together

The following declarations illustrate a user-defined structure and an array. They are declarations, not a complete executable program. Your IDE may store the type in a separate data-type object.

TYPE ST_Temperature :
STRUCT
    Value_degC : REAL;
    Valid : BOOL;
    Age_ms : UDINT;
END_STRUCT
END_TYPE

VAR
    Zone : ARRAY[1..4] OF ST_Temperature;
    ZoneIndex : INT;
    Sum_degC : REAL;
    GoodCount : INT;
    Average_degC : REAL;
    AverageValid : BOOL;
END_VAR

Keeping validity beside value makes it harder to accidentally use yesterday's temperature as today's measurement. It does not automatically enforce that rule; the consuming logic must inspect Valid and, where relevant, freshness. A plain REAL cannot tell you whether the cable is disconnected.

Arrays express repetition. Four oven zones have the same kind of data, so a bounded loop can process them. The index still requires a contract. If an HMI sends zone number zero, do not assume the runtime will conveniently select zone one. Reject it before indexing. Bounds checks and runtime reactions vary; neither should be your normal validation mechanism.

Calculate with the data you actually possess

This executable-body excerpt averages available valid zone values. The result has its own validity flag. It deliberately does not label one working sensor a satisfactory substitute for all four; the application must decide whether partial coverage is enough.

Sum_degC := 0.0;
GoodCount := 0;

FOR ZoneIndex := 1 TO 4 DO
    IF Zone[ZoneIndex].Valid THEN
        Sum_degC := Sum_degC + Zone[ZoneIndex].Value_degC;
        GoodCount := GoodCount + 1;
    END_IF;
END_FOR;

AverageValid := GoodCount > 0;
IF AverageValid THEN
    Average_degC := Sum_degC / INT_TO_REAL(GoodCount);
ELSE
    Average_degC := 0.0;
END_IF;

The zero in the final branch is a storage choice, not a measured zero-degree oven. Display the invalid state prominently and prevent consumers from using that number as evidence. If all zones are required for production, add a separate CoverageComplete := GoodCount = 4; this is a process decision, not a mathematical property of the average.

Sum_degC and GoodCount reset at the start of every calculation. Forgetting that reset makes a running accumulation rather than a current average. This is a common example of correct-looking arithmetic attached to the wrong lifetime of data.

Retention is a recovery contract

Three situations need separate answers: another scan, a warm restart, and replacing or changing the application. “Retained” does not mean “survives everything.” Storage technology, runtime settings, reset commands and changes to the data layout all affect the result. CODESYS documents persistence as an explicit mechanism with declaration and placement rules. Check the target's persistent-variable rules.

Good retention candidates include calibrated offsets, approved configuration and production totals with a defined reconciliation process. A motor run request is a poor default candidate. After a power interruption, the plant may have changed while the PLC was absent: a pallet moved, an operator opened a valve, or pressure decayed. Restoring a step number cannot restore those physical facts.

Store the information needed to decide recovery, then enter a deliberate recovery state. Validate configuration version and ranges on startup. For a batch, retaining its identifier and last confirmed operation can help an operator assess the material. Automatically resuming agitation because one retained bit was true requires a much stronger machine-specific argument.

Never assume a set of separately updated retained fields forms one consistent transaction. A power loss between updates may leave a new count with an old batch identifier. Use the platform's supported persistence facilities and a documented consistency strategy appropriate to the consequence.

Protect the boundary, simplify the inside

The network decoder should convert a device's raw words into a documented structure. The HMI interface should validate proposed settings. The recipe manager should publish an approved snapshot. Interior logic can then work with understandable quantities instead of repeating byte swaps and limit checks everywhere.

This does not justify trusting all internal data blindly. Important modules should reject impossible configuration. It means each validation has an owner and a reason. Repeating the same clamp in five places can conceal which component first received a bad value.

Try it

A tank recipe contains TargetLitres, MaximumFillTime and ProductCode. The HMI currently writes those fields directly while filling. The programmer proposes retaining the entire live structure so a power interruption “continues exactly where it stopped.” Identify three distinct problems and describe a better data flow.

Work through the answer

First, the target can change mid-fill, so the operation no longer has a stable specification. Second, independent field writes can produce a temporary mixture of two recipes. Third, retained numbers do not prove the tank's actual contents after an interruption.

Use an editable candidate structure. Validate all fields together: product permitted, target within vessel and process limits, duration plausible. Accept the candidate as a versioned recipe, then copy it into a separate active-batch snapshot at an authorised start. Keep that snapshot unchanged during the operation. Retain only the recovery information justified by the process, and require reconciliation after restart before enabling further action. A clean type design has made the physical uncertainty visible rather than hiding it in memory.

Now make the decision yourself

Use the chapter’s model on a fresh question, then compare your reasoning with the worked decision.

How this becomes a program

The recipe says 120. Is that grams, millilitres, or seconds?

A filling recipe stores a target and a timeout. A valid numeric value can still mean the wrong physical quantity.

TargetMass_gFillTimeout / TIMERecipe snapshot

Your first artifact

Put units and bounds beside the value. Group related parameters. Validate an edited recipe before copying it into the active cycle snapshot.

Open the worked decision
RecipeValid := TargetMass_g >= 50.0
               AND TargetMass_g <= 500.0
               AND FillTimeout > T#0s;

Why this line belongs here

Types eliminate some mistakes; units and bounds eliminate others. This is a teaching validation range, not a universal fill limit. Active-cycle parameters need one owner so an HMI edit cannot change a running batch silently.

Change the task

A recipe index can come from the HMI. Show where you reject an index outside the configured array bounds before accessing the array.