Structured Text (ST) is a powerful, non-graphical programming language defined as part of the IEC 61131-3 family of PLC programming languages.
Other IEC programming languages include graphical languages such as Ladder Diagram (LD), Function Block Diagram (FBD) and Sequential Function Chart (SFC). Each language has particular strengths, and one of the major advantages of the IEC programming environment is that the programmer does not have to use one language for every part of an application.
Ladder Diagram, for example, represents Boolean logic as a series of contacts and coils arranged into individual rungs. Contacts may be connected in series or parallel to describe the logical conditions required to operate an output. This representation is particularly easy to understand when dealing with digital inputs, interlocks, permissives and outputs.
More complex operations can be performed using function blocks. These may perform timing, counting, mathematics, comparisons, data conversion, communications and many other operations.
Structured Text takes a different approach.
Rather than drawing the required logic graphically, the programmer describes the required operation using statements and expressions. The language is strongly influenced by Pascal and has been deliberately designed to be readable.
Correctly written Structured Text can be remarkably close to a plain-English description of the required control operation. This makes ST particularly useful for calculations, data processing, decision making and algorithms which would otherwise require large and complicated graphical diagrams.
The important words here are correctly written.
Like any programming language, Structured Text can be written clearly or badly. A long page of obscure variable names, unexplained constants and deeply nested conditions may compile perfectly but be extremely difficult for another engineer to understand.
Good Structured Text should describe the operation of the machine or process, not merely instruct the PLC processor what to do.
Early PLC programming was heavily based around physical memory locations or registers. The programmer needed to know not only what information was required but also where that information was stored.
Modern IEC programming is much more heavily oriented towards named variables, commonly referred to as Tags.
Instead of writing logic around an arbitrary register number, a variable can be given a meaningful name such as:
TankLevel TankLevelHigh PumpRunning PumpStartRequest DischargePressure MotorCurrent FlowSetpoint
The name immediately provides information about the purpose of the variable.
Where a variable is associated with physical hardware, it can be mapped to the appropriate physical I/O location. Internal variables normally do not need a physical I/O address at all.
This separates two important concepts:
What the information means
from
Where the information is stored or connected.
This is a significant improvement in program readability and maintainability.
For example, the program should be concerned that TankLevelHigh is TRUE. The fact that this information originates from a particular digital input is primarily an I/O configuration issue.
If the physical input is subsequently changed, the program logic should ideally remain unchanged.
Structured Text could be used to write an entire PLC application, just as an application could theoretically be written entirely in Ladder Diagram.
That does not necessarily make either approach desirable.
The objective should not be to demonstrate that a particular programming language can perform every task. The objective is to produce a control program which is reliable, understandable, testable and maintainable.
My preference is to start by structuring a sequential machine or process using Sequential Function Chart (SFC), and then "fill in the gaps" using small modules written in Structured Text, Ladder Diagram or Function Block Diagram as appropriate.
For example:
SFC describes where the machine or process is in its operating sequence.
Ladder Diagram is particularly convenient for Boolean logic, permissives, interlocks and simple digital control.
Structured Text is particularly convenient for mathematics, data manipulation, calculations and decision-making algorithms.
Function Block Diagram can be very effective where the operation is naturally represented as a flow of information between defined functions.
There is no engineering prize for writing an entire application in one language.
The correct language is the language which most clearly describes the required operation.
If it works correctly, can be understood by the people who have to maintain it, and consistently produces the required result, then it is an appropriate implementation.
Structured Text has a defined syntax which must be followed, but the basic rules are relatively simple.
A Structured Text program consists of a series of statements.
Most statements are terminated with a semicolon:
;
One of the most common statements is an assignment.
The assignment operator is:
:=
This should be read as:
"becomes equal to"
or
"is assigned the value of."
For example:
MotorSpeedReference := RequiredSpeed;
This means that the value contained in RequiredSpeed is assigned to MotorSpeedReference.
This is deliberately different from a mathematical equals sign.
Consider:
Counter := Counter + 1;
Mathematically this makes no sense because a number cannot equal itself plus one.
As a programming instruction it makes perfect sense:
Take the present value of Counter, add one to it, and store the result back into Counter.
The right-hand side of an assignment can contain an expression.
For example:
FlowError := FlowSetpoint - ActualFlow;
or:
TankVolume := TankArea * TankLevel;
or:
MotorPower := MotorVoltage * MotorCurrent;
Expressions can contain variables, constants, mathematical operators and functions.
Parentheses should be used whenever they make the intended order of calculation clearer.
For example:
AveragePressure := (Pressure1 + Pressure2 + Pressure3) / 3.0;
Even where the compiler's normal operator precedence would produce the same answer, parentheses can make the intention much easier for the next engineer to understand.
Structured Text is equally capable of processing Boolean information.
For example:
PumpPermissive := TankLevelOK AND SuctionPressureOK AND NOT EmergencyStop;
or:
AlarmActive := HighTemperature OR HighPressure OR MotorOverload;
The common Boolean operators include:
AND OR NOT XOR
Again, the objective should be readability.
A meaningful expression such as:
PumpStartAllowed := SystemReady AND PumpAvailable AND NOT PumpFault;
is effectively self-documenting.
Not every statement directly produces an output.
Conditional statements determine which section of program is to be executed depending upon the current conditions.
The most common example is the IF statement.
IF TankLevel < LowLevelSetpoint THEN PumpStartRequest := TRUE; END_IF;
An alternative action can be added using ELSE:
IF TankLevel < LowLevelSetpoint THEN PumpStartRequest := TRUE; ELSE PumpStartRequest := FALSE; END_IF;
Several conditions can be tested using ELSIF:
IF TankLevel >= HighHighLevel THEN LevelStatus := 3; ELSIF TankLevel >= HighLevel THEN LevelStatus := 2; ELSIF TankLevel >= LowLevel THEN LevelStatus := 1; ELSE LevelStatus := 0; END_IF;
Notice that the structure itself helps describe the decision being made.
Indentation is not required to determine the logical operation of the Structured Text program, but it is extremely important for human readability.
Where a variable can have a number of defined states, a CASE statement can often be clearer than a long chain of IF and ELSIF statements.
For example:
CASE OperatingMode OF 0: MotorRun := FALSE; 1: MotorRun := ManualRunCommand; 2: MotorRun := AutomaticRunCommand; ELSE MotorRun := FALSE; END_CASE;
This immediately shows that the action depends upon the value of OperatingMode.
CASE statements are particularly useful for operating modes, status codes, machine states and other clearly enumerated conditions.
However, they should not be used to recreate a complicated sequential machine structure which would be more clearly represented using SFC.
Structured Text provides looping structures including:
FOR WHILE REPEAT
A FOR loop is useful where the same operation must be performed a known number of times.
For example:
FOR Index := 1 TO 10 DO Total := Total + Values[Index]; END_FOR;
Loops can be extremely useful for processing arrays, tables and blocks of data.
They must also be used with some care in a PLC.
Unlike a conventional computer program which may simply take longer to complete a large operation, PLC logic normally operates as part of a continuously repeating scan. A poorly designed loop can therefore increase scan time or, in the case of an incorrectly constructed WHILE or REPEAT loop, potentially prevent the processor from completing the normal scan.
The exit condition of any loop must therefore be completely understood and guaranteed.
Comments are an essential part of a maintainable program, but comments should explain why something is being done rather than simply repeating the code.
For example, a comment such as:
(* Prevent restart until discharge pressure has fallen *)
adds useful engineering information.
A comment such as:
(* Set PumpRun to TRUE *) PumpRun := TRUE;
adds very little.
Meaningful variable names and clearly structured code should explain most of what the program is doing.
Comments should explain the engineering reasoning which may not otherwise be obvious.
Structured Text can call predefined IEC functions and function blocks.
There is an important distinction between the two.
A function performs an operation using its supplied input values and returns a result. It does not normally need to remember its previous operating state.
Examples include mathematical, comparison, selection and conversion functions.
Conceptually:
Result := Function(Input);
A function block, on the other hand, can contain internal state or memory. Timers, counters and similar operations are common examples.
Because a function block can retain information about its previous execution, an individual instance of the function block must normally be declared.
This distinction becomes important when constructing reusable modules.
Two instances of the same type of function block use the same algorithm but maintain their own independent operating data.
This is similar to having two identical physical timers: they operate in the same manner, but each timer maintains its own timing value and output state.
As a program becomes larger, it should be divided into small logical modules rather than allowed to develop into one large block of code.
Horner Cscape IEC provides callable subroutine modules which can be written in Structured Text and called from other logic modules.
A subroutine should perform one clearly defined task.
Examples might include:
CalculateTankVolume ScalePressureInput CalculatePumpEfficiency ProcessAlarmLimits UpdateProductionTotals
The name of the subroutine should describe what it does.
Where practical, the operation of a subroutine should be understandable without having to examine unrelated sections of the program.
Inputs should provide the information required by the routine and outputs should return the resulting information.
Local or private variables should be used for intermediate calculations which have no meaning elsewhere in the application.
This reduces unnecessary interaction between unrelated parts of the program and makes each section easier to test independently.
One important point with Cscape subroutine modules is that their private and local variables have allocated storage. They should therefore not automatically be thought of as temporary values that disappear simply because the routine has completed.
The scope and lifetime of every variable should be understood when the program is designed.
A program will often contain values which are fixed by the design.
For example:
MaximumPressure MinimumSpeed NumberOfPumps MaximumTankVolume
Where a number has a defined engineering meaning, it is generally preferable to give that value a meaningful name rather than repeatedly entering the literal number throughout the program.
For example, compare:
IF Pressure > 850.0 THEN
with:
IF Pressure > MaximumOperatingPressure THEN
The second example immediately tells the reader why the comparison is being made.
It also means that if the design value changes, the defined value can be changed in one known location rather than searching through the complete application looking for every occurrence of 850.0.
Unexplained literal values buried inside code are often referred to as magic numbers, and they should generally be avoided.
Horner Cscape IEC provides a Defines facility for assigning constant values, including predefined Library Defines.
Every variable has a data type.
Common examples include:
BOOL INT DINT UINT REAL
The data type determines what information can be stored in the variable and how the processor interprets that information.
A Boolean variable represents only two logical states:
TRUE FALSE
Integer types represent whole numbers.
REAL variables represent floating-point numbers and are appropriate where fractional values are required.
The choice of data type should be deliberate.
For example, a motor running indication is naturally Boolean:
MotorRunning : BOOL
A production counter may be an integer:
ProductionCount : DINT
A measured pressure may be represented as a real number:
DischargePressure : REAL
Using an appropriate data type makes the intention of the variable clearer and reduces the possibility of unexpected conversions, overflow or loss of precision.
Variables should be given the smallest appropriate scope.
A variable required only within one routine should normally be local to that routine.
A variable which genuinely needs to be accessed by several sections of the application may need to be global.
A retained variable is used where its value must survive a power cycle or restart, subject to the capabilities and configuration of the controller.
Retention should be intentional.
Not every variable should be retained simply because retention is available.
For example, retaining a production total may be essential.
Retaining an intermediate calculation or an operating command may be undesirable and could result in unexpected behaviour following a power interruption.
The question should always be:
What state should this variable have when the controller starts?
This is part of the control-system design, not merely a programming decision.
One of the most important concepts for programmers coming from conventional computer programming is that PLC Structured Text normally executes repeatedly.
The controller performs its logic as part of a continuous scan.
A main-loop Structured Text module is therefore not normally executed once from beginning to end and then finished permanently.
It is executed again on the next scan.
And again on the scan after that.
This has an important consequence.
Consider:
IF StartButton THEN StartCounter := StartCounter + 1; END_IF;
This does not mean "increment the counter once when the Start button is pressed."
It means:
Increment the counter on every PLC scan for which StartButton is TRUE.
If the button remains pressed for half a second, this statement may execute many times.
If the requirement is to increment only once when the signal changes from FALSE to TRUE, an edge detection method is required.
Understanding the difference between a state and an event is fundamental to good PLC programming.
Program execution order also matters.
If one module calculates a value and another module uses that value, their execution sequence must be understood.
For example:
Module A: FlowError := FlowSetpoint - ActualFlow; Module B: HighFlowError := FlowError > MaximumFlowError;
If Module B executes before Module A, it may be working with the value of FlowError calculated during the previous scan rather than the present scan.
This may or may not matter.
The important point is that it must be intentional.
A well-structured application should have an obvious and documented execution flow rather than relying upon accidental program ordering.
Structured Text becomes particularly powerful when combined with Sequential Function Chart.
SFC describes the overall state and sequence of the machine.
Structured Text can then perform the calculations and decisions required within individual states.
For example, an SFC might contain the steps:
IDLE | PREPARE | START_PUMP | RUN | STOP | IDLE
The SFC makes the overall operating sequence immediately visible.
The RUN step may then contain a small Structured Text action which calculates a required pump speed:
FlowError := FlowSetpoint - ActualFlow; SpeedReference := BaseSpeed + (FlowError * FlowGain);
The calculation belongs inside the appropriate operating context rather than being buried somewhere inside one enormous continuously executing program.
Similarly, an SFC transition can be based upon a clear Boolean condition such as:
PumpRunning AND PressureEstablished
This division of responsibility is important.
SFC describes where the process is.
Structured Text describes what calculation or decision is required.
Ladder can describe straightforward Boolean interlocks and permissives.
The result is generally much easier to understand than attempting to force all three concepts into a single programming representation.
A calculation should not merely work when all of its inputs are correct.
It must also have defined behaviour when they are not.
Consider:
Efficiency := OutputPower / InputPower;
What happens if InputPower is zero?
A more robust implementation would first validate the input:
IF InputPower > 0.0 THEN Efficiency := OutputPower / InputPower; ELSE Efficiency := 0.0; END_IF;
Similarly, an analogue input should not automatically be assumed to contain a valid engineering value.
A good design asks:
Is the input within its valid range?
Has the sensor failed?
Is communications data valid?
Could a divisor become zero?
Could a calculation exceed the range of its data type?
What happens during initialisation?
What happens following power restoration?
What happens if a value has never previously been calculated?
What should happen if the requested condition is physically impossible?
The purpose is not to add unnecessary defensive code everywhere.
The purpose is to define abnormal behaviour before the abnormal condition occurs.
My recommendations for Structured Text programming are:
Define all physical inputs and outputs as meaningful variables.
Where applicable, map the variable to the required physical I/O location and assign the correct data type.
Use meaningful variable names.
DischargePressure conveys considerably more information than R1234.
Use meaningful routine and module names.
The program structure should provide an overview of the application before the individual code is examined.
Use the appropriate variable scope.
Do not make every variable global merely because it is convenient.
Retain variables only where retention is actually required.
Avoid unexplained constants or "magic numbers".
Define engineering constants using meaningful names.
Keep modules small.
Each module should perform one clearly defined task.
Avoid excessive nesting.
If an IF statement contains another IF, which contains another IF, which contains another IF, it is worth reconsidering the program structure.
Use comments to explain engineering intent rather than translating the code into English.
Always consider the PLC scan.
Ask what happens when the same code executes again on the next scan.
Understand execution order.
Know when values are calculated and when they are subsequently used.
Define startup behaviour.
Every important state and retained value should have an intentional behaviour following startup or power restoration.
Consider abnormal conditions during the design.
Do not wait until commissioning to discover what happens when an input fails or a calculated value goes outside its expected range.
Use the IEC language which most clearly expresses each part of the problem.
There is no advantage in making a simple Boolean interlock difficult to understand merely because the rest of the application happens to use Structured Text.
Be consistent.
Naming conventions, indentation, capitalisation, comments and program organisation should remain consistent throughout the project.
I prefer to limit an individual module to approximately two screens of code wherever practical.
This is not a compiler limitation and it does not make the PLC execute the program faster.
It is purely a maintainability rule.
If I can see most or all of the module without continually scrolling through pages of code, I can understand its operation much more quickly.
If a module becomes excessively large, this is often an indication that it is performing more than one logical task and should be divided.
Small modules also simplify commissioning.
Instead of asking:
"Why doesn't the machine work?"
the engineer can ask:
"Is the SFC in the correct state?"
then:
"Is the required module receiving the correct inputs?"
then:
"Is that module producing the correct outputs?"
The problem is progressively reduced to a small and clearly defined area of code.
The greatest advantage of Structured Text is not that it allows a PLC to perform complicated mathematics.
PLC processors have been capable of mathematics for a very long time.
Its real advantage is that it allows an engineer to express an algorithm in a concise and readable form.
A well-written Structured Text routine can often be read almost as a description of the engineering requirement:
PressureError := PressureSetpoint - ActualPressure; IF PressureError > MaximumAllowedError THEN IncreasePumpSpeed := TRUE; ELSE IncreasePumpSpeed := FALSE; END_IF;
The closer the program structure follows the engineering specification, the easier the program is to understand, test and maintain.
This leads back to the most important principle of all:
Do not start programming by asking how to make the PLC do something.
First define exactly what the machine or process is required to do, including what it must do when conditions are not normal.
Structure that requirement into states, inputs, outputs, conditions and actions.
Then choose the IEC programming language which expresses each part most clearly.
When approached in this way, Structured Text is not a replacement for Ladder Diagram, Function Block Diagram or Sequential Function Chart.
It is another engineering tool, and a particularly powerful one when it is used for the part of the problem that it describes best.