Indirect addressing: one rung instead of forty
Indexed access, the loop that replaces copied logic, and the out of range fault that takes the controller down.
Short answer
Indirect addressing uses the value of one tag as the index into an array of others, so the same rung can act on any element depending on a pointer. It replaces forty near identical rungs with one rung and a loop. The danger is an index outside the array, which on most controllers is a major fault that stops the processor, so every index must be range checked before it is used.
Every plant has a program with forty near identical rungs in it, one per station, differing only in a number.
They were copied, and then one of them was edited to fix something, and now thirty nine of them behave one way and one behaves another, and nothing on the screen says which.
Indirect addressing is how that program should have been written.
The idea
Rather than writing the address into the rung, put the address in a tag and let the rung read it.
Station[i].Ready where i is an integer. Change i and the same rung acts on a different station.
Combined with a loop, one rung covers every station, and there is exactly one place to fix a bug.
How it looks on different platforms
Modern tag based controllers. Array indexing with an integer: Station[i], with structures inside so each element carries a whole station's data.
Older file based controllers. Indirect addressing with a pointer: N7:[N10:0], which reads word N10:0 and uses its value as the element number in file N7.
The second reads badly and does the same job. Programs full of it are why indirect addressing has a reputation for being unreadable, which belongs to the syntax rather than the technique.
The fault that stops the line
This is the paragraph that matters.
An index outside the array bounds is, on most controllers, a major fault. Not a clamp, not a warning: the processor faults and the machine stops.
It is also the easiest bug to write. An index incremented in a loop with the wrong terminating condition. An index set from an HMI entry nobody validated. An index left at zero when the array is one based, or at the array length when it is zero based.
Check every index before use, on every path that sets it. A limit instruction, or an explicit compare, immediately before the indexed access. It costs one rung and it is the difference between a faulted controller and a handled condition.
Range check where the index is used, not only where it is set. There is usually more than one place it is set, and the one added next year will not have the check.
Loops and scan time
A loop executes entirely within one scan. A loop over a thousand element array does a thousand iterations before the scan can end.
For a few dozen elements, invisible. For thousands, it extends the scan measurably, and for a loop whose bound is a variable, it can extend it unpredictably, which is worse.
The pattern that avoids it: process a fixed few elements per scan, keeping the index in a retentive tag. The whole array is covered every n scans, and the scan time is constant. On anything where the data is not urgent, this is the better shape.
Arrays of structures
Where this becomes genuinely powerful rather than merely compact.
Define a structure holding everything one station needs, its state, its timers, its counters, its faults. Make an array of forty. Now Station[i] is a whole station, and one routine handles any of them.
Adding a forty first station is changing a number. In the copied rung version, it is copying forty rungs and editing every address in them, which is where the drift came from in the first place.
When not to
Two honest exceptions.
- Three stations that are genuinely different from each other. Forcing them into an array creates a structure full of fields that only apply to one, and reads worse than three explicit routines.
- Safety related logic, where the extra indirection makes review harder and reviewability outranks compactness.
Common questions
- What happens if an array index goes out of range?
- On most controllers, a major fault that stops the processor. It is not a warning or a clamped value. Every index used indirectly must be checked against the array bounds in logic before it is used, on every path that can set it.
- Is indirect addressing slower?
- Marginally, per access, because the address is computed at runtime rather than resolved at compile time. Against forty copied rungs it is dramatically faster overall, because thirty nine of them are not being solved. The exception is a tight loop over a large array inside one scan, which can extend scan time noticeably.
- Should I use a loop or unrolled rungs?
- A loop, in almost every case, because copied logic drifts. The classic failure is forty rungs where one was edited and thirty nine were not, and nothing indicates which. Where a loop genuinely costs too much scan time, process a few elements per scan rather than unrolling.
Keep reading
- Safety
SIL or PL: which one does your machine need?
Two standards, two scales, and one machine. Which one applies, how they map to each other, and why the answer is usually ISO 13849.
- Safety
Categories B, 1, 2, 3 and 4, in plain terms
Five architectures, what a single fault does to each, and the practical wiring that goes with them.
- Safety
Safety relay or safety PLC: how to decide
One is a wiring decision, the other is a programming one. The count of safety functions, not the size of the machine, is what settles it.