Softwarebus - Part 1
As the complexity of machine controls increases, so do the connections between the submodules of the software. This contradicts the idea of loose coupling of modules, which is a quality criterion for comprehensible and maintainable software. The software bus pattern helps to reduce coupling and scales structurally very well.
The code examples are based on Beckhoff’s TwinCAT 3 and use ST as language. However, the concepts are transferable to all platforms and languages.
The initial situation
Since I started PLC programming ten years ago, I have repeatedly stumbled across a pattern that I could never really get to grips with: Networks with countless Boolean links, at the end of which was an assignment to an enable or start signal. Often these networks exceeded the width of my screen by far, had several parallel branches with special conditions, and were thus unparalleled in terms of confusion. These networks were still decipherable in LD or FBD, but in IL or ST I quickly reached the limit where I could still understand the code.
With each module that the machine grew, these networks also grew. In order to bring structure back into the chaos, intermediate results began to be formed and these in turn were included in the release networks. At first glance, this provided clarity, but it created a new hurdle. In the global release network, one could now see which intermediate flags were responsible for dropping the release and then had to jump to the place where they were formed. There they were possibly interlinked one more time, which meant another jump. Once you now understood this path, you could go back to the top level and follow the next path. By the time you reached the last branch, you had usually already forgotten the beginning.
So this kind of programme structuring is neither clear nor loosely coupled.
Initialise - Modify - Process
A first improvement on the local level was a pattern in which the enable signals were initialised with TRUE and the control signals with FALSE at the beginning of the block.
// Initialize
bReleaseMotorOn := TRUE;
bSwitchMotorOn := TRUE;
// Modify
IF NOT bSafetyOk THEN bReleaseMotorOn := FALSE; END_IF
IF NOT bCircuitBreakerOk THEN bReleaseMotorOn := FALSE; END_IF
IF NOT bLimitSwitchFree THEN bReleaseMotorOn := FALSE; END_IF
IF stOpMode.bAutomatic AND NOT bSensorConveyorFree THEN
bReleaseMotorOn := FALSE;
END_IF
IF stOpMode.bAutomatic AND bSensorConveyorFree THEN
bSwitchMotorOn := TRUE;
END_IF
IF stOpMode.bManual AND bJogManualPositive THEN
bSwitchMotorOn := TRUE;
END_IF
// Process
outCoilMotor := bSwitchMotorOn AND bReleaseMotorOn;
This scheme makes the code easier to understand because it is closer to human language. It expresses the programmer’s reasoning step by step:
If the safety circuit is not closed, then the motor must not be able to be switched on.
translates into:
IF NOT bSafetyOk THEN bReleaseMotorOn := FALSE; END_IF
Condition after condition can be checked this way and the code reads almost like prose.1
Besides being understandable, another advantage is that these lines do longer survive. By this I mean everyday life in a stressful situation during commissioning or troubleshooting, where a section of code is quickly commented out or bypassed. If, on the other hand, one programs in a granular way, there is less chance that these lines will disappear by mistake in the course of troubleshooting.
A third advantage is that you can elegantly avoid the pitfall of multiple output assignments. Only the intermediate variable that is assigned to the output at the end of the block is modified.
In addition to the advantages mentioned, however, there are also disadvantages to this scheme:
- It can only be used sensibly within one module. Seen in the context of the complete program, this would mean that all intermediate variables had to be initialised at the beginning of
MAIN
and all outputs had to be set at the end ofMAIN
. This is not practical. - If one wants to find the reason why the release of the motor is not present during a troubleshooting, this sometimes requires many jumps to the usage points of
bReleaseMotorOn
, all of which have to be searched.
We will look at how to overcome these disadvantages in the following section.
The software bus
Wikipedia](https://en.wikipedia.org/wiki/Software_bus) defines the software bus as a
software architecture model where a shared communication channel facilitates connections and communication between software modules.
In order to create this communication channel, we take up the idea of initialise - modify - process and examine it using a first simple example.
For initialisation, we create three structs and a global variable list:
Structures:
TYPE ST_SoftwarebusRelease
STRUCT
bMotorOn : BOOL;
bValveOn : BOOL;
bMachineOn : BOOL;
END_STRUCT
END_TYPE
TYPE ST_SoftwarebusAction
STRUCT
bMotorOn : BOOL;
bValveOn : BOOL;
bMachineOn : BOOL;
END_STRUCT
END_TYPE
TYPE ST_Softwarebus
STRUCT
stRelease : ST_SoftwarebusRelease;
stAction : ST_SoftwarebusAction;
END_STRUCT
END_TYPE
Global variable list GVL_Softwarebus
:
VAR_GLOBAL
stRead : ST_Softwarebus;
stWrite : ST_Softwarebus;
END_VAR
Now the initialisation takes place at the beginning of MAIN
:
MEMSET(ADR(GVL_Softwarebus.stWrite.stRelease), 1, SIZEOF(GVL_Softwarebus.stWrite.stRelease));
MEMSET(ADR(GVL_Softwarebus.stWrite.stAction, 0, SIZEOF(GVL_Softwarebus.stWrite.stAction));
As in the previous section, the enable signals are initialised with TRUE and the control signals with FALSE.
In any module, these signals can now be modified:
IF NOT bSafetyOk THEN
GVL_Softwarebus.stWrite.stRelease.bMotorOn := FALSE;
GVL_Softwarebus.stWrite.stRelease.bValveOn := FALSE;
GVL_Softwarebus.stWrite.stRelease.bMachineOn := FALSE;
END_IF
Tacitly, I added an intermediate stage: ST_Softwarebus
, which consists of the components stRead
and stWrite
. What is this all about?
With the help of this software bus we keep the advantages of the first approach and in addition we want to be able to use it at any point in the program without having to take care that the (output) assignments are only made at the end of the program. This can be achieved by a little trick: by decoupling two cycles of the PLC from one another. The signals that are written in the first cycle are only processed in the following cycle. This is achieved by adding the following code at the end of `MAIN':
MEMCPY(ADR(GVL_Softwarebus.stRead), ADR(GVL_Softwarebus.stWrite), SIZEOF(GVL_Softwarebus.stWrite));
Wrapping up: we have created two structures, stWrite
, which is always written and stRead
, which is always read and contains the status of stWrite
of the previous cycle. This scheme must be maintained! For example, the following code would never cause the motor to switch off:
outCoilMotor := GVL_Softwarebus.stRead.stAction.bMotorOn AND GVL_Softwarebus.stRead.stRelease.bMotorOn;
IF NOT bSafetyOk THEN
GVL_Softwarebus.stRead.stRelease.stMotorOn := FALSE;
END_IF
At the end of the cycle, stRead
is overwritten with stWrite
and the motor would not switch off after a violation of the safety circuit.2.
A software bus thus consists of two identical structures, one responsible for write access and the other for read access. At the beginning of MAIN
the initialisation of the write structure takes place, at the end of MAIN
this is copied to the read structure. In PLC cycle n the software bus is written, in the following cycle n+1 this information is available for reading.
Data exchange between modules is thus possible without the modules knowing each other. This leads to a weaker coupled software. In addition, the system status can be read out at a central point.
One disadvantage remains: Cross-reference information of this software bus are now scattered all over the program and in case of an error, finding out at which point it is written to the bus can be time-consuming. Therefore, in part 2 of this article I will describe how the software bus can be supplemented by a diagnostic function.
Writing code as understandable as prose is the focus of the “Clean Code” philosophy. Special emphasis is placed on self-explanatory code through sensibly chosen variable names. Robert C. Martin’s Clean Code is a highly recommended introduction. ↩︎
In reality, safety-relevant actuators have a separate switch-off in the safety programme and would naturally switch off anyway. ↩︎