Structures and arrays used to be second-class citizens in Beremiz’ debugger. You could watch a REAL,
but not the REAL sitting in the third element of an array of structures. That limitation is gone:
the whole instance tree is now browsable, down to the last leaf, and every leaf can be traced, plotted
and forced on its own.
Browsing the instance tree
The Instance Variables panel is no longer a flat list. Any variable whose type has internals can be unfolded, recursively and without limit on depth:
- structures — each element with its own type, including inline
ARRAY [..] OF ..members - named array types and inline array declarations, single or multi-dimensional
- function blocks and programs, as before, but now mixed freely with the above
Enumerated, subrange and directly derived types are correctly recognised as leaves, so they no longer pretend to be expandable.
Large arrays stay usable
An ARRAY [0..99999] would be useless as a tree of a hundred thousand items. Arrays are shown through
a sliding window of 100 elements, with ◀ and ▶ navigation items at the window edges, and a dialog
to jump straight to an arbitrary index. The window position is tracked per instance, so two arrays
expanded at the same time keep their own offsets.
Getting to the variable you want
A search box filters the panel’s top-level variables, with optional case sensitivity and whole-word matching. Selection is multiple: pick a handful of leaves, hit the debug button, and all of them land in the Debug panel at once. Double-click adds them as graphs instead.
Paths that mean something
Variables added to the Debug panel carry a full IEC path, and the path syntax now covers everything the tree can show:
PROGRAM0.MOTORS[1].SPEED
PROGRAM0.GRID[2][3]
PROGRAM0.SETTINGS.LIMITS.HIGH
Names, subscripts and nested structures mix freely. On the IDE side these paths are resolved against
the type description matiec emits alongside the generated C, walking members and computing array
strides to end up with the flat index the runtime debugger understands. Along the way we fixed
monitoring of a plain array element reporting “Unsupported type to debug __ARRAY_OF_TYPE_N“, and
named array types reaching the debugger’s code generator as opaque aliases rather than as arrays.
What changed underneath
Every leaf carries its own flags
Until now, only top-level variables were wrapped in the __IEC_*_t {flags, value} pair that the
debugger uses. Struct fields and array elements were plain C, which means debug, force and retain
flags existed per variable, never per element. matiec now wraps simple fields and elements too.
The generated code changes shape accordingly — reaching a leaf goes through a .value at each wrapped
level, and the force check reads the leaf’s own flags:
/* s.a := 5 */
__SET_VAR(data__->,S,.A,5) /* before */
__SET_VAR(data__->,S.value.A,,5) /* after */
The practical consequence is the interesting part: forcing a single array element or structure
field now works, and VAR RETAIN on a structure propagates to all of its leaves instead of being
lost somewhere in the middle.
POUS.h became a set of macros
Rather than generating several parallel descriptions of the program that could drift apart, matiec now
emits POUS.h entirely in terms of __DECLARE_* macros. The same header is re-included several times
with different macro definitions, each pass producing one view of the same tree:
| pass | what it produces |
|---|---|
| declaration | the C types and variables themselves |
| flat count | the number of debuggable leaves per type |
| recurse | static inline walkers visiting every node of a type |
| python | the description the IDE reads to resolve paths |
Because they are all derived from one source, the flat index the IDE computes and the one the runtime
resolves cannot disagree. GLOBALS.h got the same treatment, and now carries the configuration name so
that the rest of the toolchain can tell configuration globals from resource-scoped ones.
A single tree walk in the runtime
The generated __recurse() functions gave plc_debug.c one traversal to build everything on:
resolving a flat index to a pointer and a type, collecting the retain list by reading the flags
config_init__() sets, and scanning the whole tree for the IDE. Complex nodes are reported to the
callback with their element count, so a walker can skip an entire subtree it is not interested in, or
descend into it — which is how a RETAIN flag set on a structure is propagated to every leaf below it.
That same walk turned out to be the foundation of another feature — see the next post.
Also fixed along the way
- display of enumerated and derived types in the instances panel
- parsing of initial values for nested structures
- variable monitoring not restarting after a PLC stop
- a button to remove all variables from the Debug panel at once
- SFC states are now part of the instance tree, so they are debuggable like anything else