Table of Contents

Global Data and Communication

As introduced in Application Structure, a Zeugwerk Application keeps all of its data outside of logic code, in the global variable list ZGlobal:

{attribute 'qualified_only'}
VAR_GLOBAL
  Com : ZCom;
END_VAR
VAR_GLOBAL PERSISTENT
  Data : ZData;
END_VAR

Com and Data are the only two members of ZGlobal, but the split between them is deliberate: Com is the volatile, runtime communication surface toward the outside world (HMI, ADS, ...); Data is the persisted configuration and process data that has to survive a reboot.

Com: communication structs

ZCom groups the application-level communication objects together with one entry per unit:

TYPE ZCom :
STRUCT
  // module context
  App : ZApplication.ApplicationComPublish;
  Alarming : ZApplication.AlarmingCom;
  Messages : ZApplication.MessagingCom;

  // unit context
  Unit : ZComUnit;
END_STRUCT
END_TYPE

ZComUnit in turn has one member per unit instance, using that unit's own Com type:

TYPE ZComUnit :
STRUCT
  MyUnit1 : MyUnit1Com;
END_STRUCT
END_TYPE

Every unit therefore gets its own communication struct at a predictable location (ZGlobal.Com.Unit.<UnitName>), while application-wide concerns such as Alarming or the application state itself are exposed once, at ZGlobal.Com.App/ZGlobal.Com.Alarming/ZGlobal.Com.Messages.

The Publish/Subscribe protocol

Most communication structs in the framework, whether they belong to a unit or the application itself, follow the same Publish/Subscribe convention: Publish is written by the PLC and read by the external application, Subscribe is written by the external application and read by the PLC. This keeps the direction of every single field unambiguous, which matters once multiple programs (PLC, HMI, a logging/SCADA service, ...) access the same memory concurrently.

MessagingCom, used to stream log/HMI messages out of the PLC, is a good concrete example of this convention, since it also has to deal with a ring buffer:

TYPE MessagingCom :
STRUCT
  Publish : MessagingComPublish;     //< contains all messages and the index to the next buffer position, where the PLC will write to.
  Subscribe : MessagingComSubscribe; //< contains an index to the oldest entry in the message buffer. After reading the buffer this value should be updated.
END_STRUCT
END_TYPE

The receiver has to listen for changes of Publish.IndexOfNext. Whenever it changes:

  1. All messages from Subscribe.IndexOfFirst to Publish.IndexOfNext - 1 should be read from the buffer (dealing with the array wrap-around, since the buffer size is a fixed library parameter).
  2. To indicate to the PLC that all messages have been read, Subscribe.IndexOfFirst should be set to the value of Publish.IndexOfNext.

The same idea (a Publish struct the PLC owns, a Subscribe struct the external application owns) reappears in every unit's own Com struct, for example to request a state transition (Subscribe) or to report the current state (Publish).

Data: persistent data structs

ZData is the persistent counterpart to ZCom. Where ZCom is organized around who is talking to whom, ZData is organized around what kind of data is stored, with one substructure per concern:

TYPE ZData :
STRUCT
  Config : ZDataConfig;
  Machine : ZDataMachine;
  Calibration : ZDataCalibration;
END_STRUCT
END_TYPE

Just like ZComUnit, ZDataConfig/ZDataMachine/ZDataCalibration each have one member per unit instance:

TYPE ZDataConfig :
STRUCT
  MyUnit1 : MyUnit1DataConfig;   // e.g. { Simulated : BOOL }
END_STRUCT
END_TYPE
  • Config: settings that describe how a unit should behave (e.g. whether it runs in simulation), usually set once during commissioning.
  • Machine: physical/process parameters of the concrete machine (e.g. speeds, positions), tunable without a PLC download.
  • Calibration: values determined by a calibration procedure (e.g. offsets), typically written by a dedicated calibration sequence rather than by hand.

Each unit only ever sees its own slice of these structs: it receives ZGlobal.Data.Config.MyUnit1 etc. by reference in its constructor (see Application Structure), so a unit's implementation never needs to know about ZGlobal directly.

Data shared across units

The three structs above are always per unit instance. Larger applications, however, often also need data that is inherently shared across several units, for example a recipe that is executed by many stations, or the state of a workpiece as it moves from unit to unit. In that case, add further top-level members to ZData that are not split per unit:

TYPE ZData :
STRUCT
  Config : ZDataConfig;
  Machine : ZDataMachine;
  Calibration : ZDataCalibration;

  Recipe : ZDataRecipe;                                       // one recipe, used by several units
  Tool : ARRAY[0..(ParameterList.DataToolCount - 1)] OF ZDataTool; // per-workpiece tracking data, read/written by several units
END_STRUCT
END_TYPE

Such shared data is passed by reference to every unit that needs it, in addition to its own Config/Machine/Calibration slice, for example MyUnit1 : MyUnit1Unit(..., recipedata := ZGlobal.Data.Recipe, tooldata := DataTool). See Multi-Unit Applications for when this pattern is worth introducing.