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 of MAIN. 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:

Strukts and GVL Softwarebus

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 stReadand 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.

Softwarebus Pattern

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.


  1. 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. ↩︎

  2. In reality, safety-relevant actuators have a separate switch-off in the safety programme and would naturally switch off anyway. ↩︎