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:
- All messages from
Subscribe.IndexOfFirsttoPublish.IndexOfNext - 1should be read from the buffer (dealing with the array wrap-around, since the buffer size is a fixed library parameter). - To indicate to the PLC that all messages have been read,
Subscribe.IndexOfFirstshould be set to the value ofPublish.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.