Indirect Addressing for Valve IO in S7-1200/1500 TIA Portal
1. Problem Overview
A standard machine design often defines a fixed number of tank stations, each equipped with a single valve manifold. In a flexible skid, however, the type of valve on every tank can vary. Two common variants are:
- Valve Type A (double-solenoid, 5/3 or 5/2 bistable): 2 digital inputs + 2 digital outputs per tank.
- Valve Type B (single-solenoid, 5/2 monostable): 2 digital inputs + 1 digital output per tank.
For a 10-tank skid, the worst-case IO count is fixed at 20 DI and 20 DO. The engineering question is not "how many bytes do I need" but "how do I let the HMI operator assign valve types per tank and have the PLC program route the correct bits to the correct physical outputs without rewriting logic every time the configuration changes?"
This is the indirect addressing problem. The operator selects Tank 3 = Type B and Tank 7 = Type A on the HMI; the PLC program must place the corresponding coil bits onto the correct output byte and the correct input byte in a deterministic way. The technique is supported on S7-1200/1500 through indexed array access in SCL, indirect DB accesses, and standard faceplate libraries.
2. Prerequisites
- SIMATIC S7-1200 (CPU 1214C DC/DC/DC or higher) or S7-1500 (CPU 1511-1 PN or higher). Indirect array access is supported on every S7-1200/1500 CPU.
- TIA Portal V16 or later (V18 recommended for the faceplate library). Earlier versions support the same SCL but ship with older WinCC faceplate toolchains.
- STEP 7 Professional / SCL compiler license.
- WinCC Comfort/Advanced/Unified for the HMI faceplate.
- Working knowledge of SCL programming basics and S7-1500 system manual terminology.
| Item | Version / Model | Purpose |
|---|---|---|
| CPU | S7-1214C DC/DC/DC, FW 4.5+ | Logic execution |
| CPU (alt.) | S7-1511-1 PN, FW 2.9+ | Larger program/work memory |
| DI module | SM 1221 DI 16x24VDC | Up to 32 isolated inputs |
| DO module | SM 1222 DQ 16x24VDC | Up to 32 transistor outputs |
| HMI | TP700 Comfort or MTP700 Unified | Operator selection screen |
| TIA Portal | V18 Update 2 | Engineering suite |
3. Architecture Comparison: Two Strategies
There are two ways to solve the dynamic IO assignment requirement. The choice has long-term consequences for commissioning, troubleshooting, and operator training.
| Criterion | Strategy A: Fixed 2-DO per Tank | Strategy B: Dynamic Packing with Counters |
|---|---|---|
| Wiring | Fixed: 2 DO wired per tank regardless of valve type | Fixed: 2 DO wired per tank, only first DO is used for Type B |
| Program | FB per tank with iValveType IN parameter |
Counter-based packing engine in OB1 |
| Commissioning | Direct: tank 1 is always Q0.0/Q0.1 | Indirect: tank 1 may be Q0.0, Q0.1, or Q0.2 depending on neighbors |
| Fault finding | Cross-reference from the HMI tag to the output is one click | Operator must mentally re-pack the bit map |
| Flexibility | Operator can swap A/B without PLC changes | Operator can swap A/B; FB has to be re-instanced for new tank count |
| Risk during HMI comms loss | Low; only tag update is lost | Medium-high; packing must be deterministic at restart |
| Code size | Small and parallel | Larger, single-threaded |
| Recommended | Yes | Only when physical DO count is hard-limited |
Strategy A is the Siemens-recommended pattern for a reason: it lets the valve FB do the work and keeps the HMI selection purely a configuration value. Strategy B is shown in detail because it is the answer when the customer hard-limits the number of physical outputs.
4. Strategy A: Fixed 2-DO Allocation Per Tank
Allocate 2 DO and 2 DI per tank in a contiguous block. Wire them physically once. The FB "knows" the valve type via an input parameter and ignores the second DO if the valve is Type B.
4.1 Data Block for Tank Configuration
Create a global DB with optimized access that stores one integer per tank. The integer codes the valve type and is written from the HMI.
DATA_BLOCK "DB_ValveConfig"
{ S7_Optimized_Access := 'TRUE' ; S7_Setpoint := 'FALSE' }
VERSION : 0.1
STRUCT
ValveType : ARRAY[1..10] OF INT; // 1 = Type A, 2 = Type B
ConfigValid : BOOL; // Set after HMI download
LastChange : DATE_AND_TIME; // For audit trail
END_STRUCT;
RETAIN // Remanent
ValveType RETAIN;
ConfigValid RETAIN;
END_RETAIN;
BEGIN
END_DATA_BLOCK
RETAIN attribute is critical. Without it, a CPU STOP-to-RUN transition after power loss would reset every tank to default and force the operator to reconfigure. Siemens documents the retentive behavior in the S7-1200 system manual, section on memory areas.4.2 Function Block for Valve Control
The FB encapsulates a single tank. It accepts the valve type as IN, and exposes the coil commands as IN_OUT. The OB1 just calls the FB ten times in a loop or ten explicit instances.
FUNCTION_BLOCK "FB_ValveCtrl"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
VAR_INPUT
iValveType : INT; // 1 = A, 2 = B
iCmdExtend : BOOL; // Open / extend cylinder
iCmdRetract : BOOL; // Retract / close cylinder
iDI_FB : BOOL; // Feedback extended
iDI_BB : BOOL; // Feedback retracted
iEnable : BOOL; // Master enable
END_VAR
VAR_OUTPUT
qDO_Out1 : BOOL; // Coil A (extend)
qDO_Out2 : BOOL; // Coil B (retract) - used only for Type A
qFault : BOOL; // Feedback mismatch
qStatusWord : WORD;
END_VAR
VAR
sState : INT;
tMon : TIME; // Monitoring timer
END_VAR
BEGIN
// Default: clear outputs
qDO_Out1 := FALSE;
qDO_Out2 := FALSE;
qFault := FALSE;
qStatusWord := 0;
IF NOT iEnable THEN
sState := 0;
RETURN;
END_IF;
// Coil A always responds to iCmdExtend
qDO_Out1 := iCmdExtend;
// Coil B only used for Type A
IF iValveType = 1 THEN
qDO_Out2 := iCmdRetract;
END_IF;
// Feedback supervision: if extended and retracted simultaneously > 1s -> fault
IF iDI_FB AND iDI_BB THEN
tMon := tMon + T#1s;
IF tMon > T#2s THEN
qFault := TRUE;
END_IF;
ELSE
tMon := T#0s;
END_IF;
// Status word for HMI
qStatusWord.0 := qDO_Out1;
qStatusWord.1 := qDO_Out2;
qStatusWord.2 := iDI_FB;
qStatusWord.3 := iDI_BB;
qStatusWord.4 := qFault;
END_FUNCTION_BLOCK
4.3 OB1 Wiring (Loop Invocation)
For ten tanks with identical structure, an explicit loop is the cleanest path. Place the FB instances in a static array in another DB and call them in a FOR loop.
DATA_BLOCK "DB_ValveInstances"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
STRUCT
Valve : ARRAY[1..10] OF "FB_ValveCtrl";
END_STRUCT;
BEGIN
END_DATA_BLOCK
// OB1 cyclic - simplified
"FC_InputsToShadows"(); // First block: pack physical I into shadow array
FOR i := 1 TO 10 DO
"DB_ValveInstances".Valve[i](
iValveType := "DB_ValveConfig".ValveType[i],
iCmdExtend := "DB_Cmd".Extend[i],
iCmdRetract := "DB_Cmd".Retract[i],
iDI_FB := "DI_Shadow".Tank[i].FB,
iDI_BB := "DI_Shadow".Tank[i].BB,
iEnable := "DB_System".RunEnable
);
END_FOR;
"FC_ShadowsToOutputs"(); // Last block: write qDO_Out1/qDO_Out2 to physical Q
5. Strategy B: Dynamic Packing with Counters
When the customer hard-limits the number of physical outputs (e.g., a legacy 16-DO SM 022 module and no budget to add another), you pack the bits dynamically. Each call to the FB consumes the next two output bits; the FB does not know its absolute address; the OB1 keeps a running counter and uses indirect DB access to route the coil bit to the correct output.
5.1 The Packing Algorithm
- Iterate through
ValveType[1..10]. - For each Type A tank, consume 2 DO slots and 2 DI slots. For each Type B tank, consume 1 DO slot and 2 DI slots.
- Maintain two running counters:
bitDO(output index 1..20) andbitDI(input index 1..20). - After packing, the FB call uses the resulting offset to select the correct physical IO via PEEK/POKE on the process image, or via the input shadow DB.
5.2 SCL Implementation of the Packer
FUNCTION "FC_PackIO" : VOID
VAR_TEMP
i : INT;
bitDI : INT; // Next free DI slot, 1..20
bitDO : INT; // Next free DO slot, 1..20
slot : INT;
END_VAR
BEGIN
bitDI := 1;
bitDO := 1;
FOR i := 1 TO 10 DO
"DB_PackMap".TankIndex[i] := i;
"DB_PackMap".DI_Offset[i] := bitDI;
"DB_PackMap".DO_Offset[i] := bitDO;
IF "DB_ValveConfig".ValveType[i] = 1 THEN
// Type A: 2 DI + 2 DO consumed
"DB_PackMap".DO_Count[i] := 2;
bitDO := bitDO + 2;
ELSE
// Type B: 2 DI + 1 DO consumed
"DB_PackMap".DO_Count[i] := 1;
bitDO := bitDO + 1;
END_IF;
bitDI := bitDI + 2;
END_FOR;
"DB_PackMap".TotalDOUsed := bitDO - 1;
"DB_PackMap".TotalDIUsed := bitDI - 1;
END_FUNCTION
5.3 Indirect Bit Access in the FB
The FB receives an offset, not an absolute address. Inside the FB, the offset is used with a slice access on a shadow DB.
FUNCTION_BLOCK "FB_DynValve"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
VAR_INPUT
iDO_Offset : INT; // 1-based index into shadow DO array
iDI_Offset : INT; // 1-based index into shadow DI array
iValveType : INT;
iCmdOpen : BOOL;
iCmdClose : BOOL;
END_VAR
VAR
bCoilA : BOOL;
bCoilB : BOOL;
END_VAR
BEGIN
bCoilA := iCmdOpen;
bCoilB := FALSE;
IF iValveType = 1 THEN
bCoilB := iCmdClose;
END_IF;
// Write into shadow DO array
"DB_DOShadow".Coil[iDO_Offset] := bCoilA;
IF iValveType = 1 THEN
"DB_DOShadow".Coil[iDO_Offset + 1] := bCoilB;
END_IF;
END_FUNCTION_BLOCK
5.4 OB1 Sequence for the Dynamic Approach
"FC_PackIO"(); // Recompute offsets
FOR i := 1 TO 10 DO
"DB_DynInstances".Valve[i](
iDO_Offset := "DB_PackMap".DO_Offset[i],
iDI_Offset := "DB_PackMap".DI_Offset[i],
iValveType := "DB_ValveConfig".ValveType[i],
iCmdOpen := "DB_Cmd".Open[i],
iCmdClose := "DB_Cmd".Close[i]
);
END_FOR;
"FC_DOShadowToPhysical"(); // Copy DB_DOShadow.Coil[1..20] to %Q0.0..%Q2.3
ConfigValid and force a one-time re-pack on rising edge of ConfigValid. The S7-1500 system manual describes the relevant startup OB behavior in section "Program execution".6. HMI Faceplate Integration
The HMI does not see the packing. From the HMI's perspective, there are ten tanks, each with a Type dropdown and a state indicator. The HMI tags point at DB_ValveConfig.ValveType[1..10] and DB_ValveInstances.Valve[i].qStatusWord.
6.1 WinCC Comfort Faceplate Snippet
Create one faceplate, instantiate it ten times. Bind the tank index to the instance number, and bind the type to the corresponding array element.
- Right-click the HMI tags folder, create
HMI_TankType_1..HMI_TankType_10of typeInt. - Map each to the corresponding PLC tag
DB_ValveConfig.ValveType[i]. - Build the faceplate with a dropdown containing values
1 = Type Aand2 = Type B. - Add status indicators bound to
DB_ValveInstances.Valve[i].qStatusWordwith bit mask property.
For WinCC Unified, the same pattern is implemented with a UserControl and a typed interface. Refer to the WinCC Unified faceplate engineering manual.
7. Step-by-Step Commissioning Procedure
- Wire the SM 1222 outputs: terminal 0 = Tank 1 coil A, terminal 1 = Tank 1 coil B, terminal 2 = Tank 2 coil A, terminal 3 = Tank 2 coil B, and so on. For Type B valves, only the first DO is used; leave the second unconnected but wired to the field terminal strip.
- Wire the SM 1221 inputs identically: 2 inputs per tank.
- Compile and download the SCL blocks. Verify that
DB_ValveConfigis marked retentive in the project tree. - Open the HMI project, set the tank types, and download. Confirm the
ConfigValidtag transitions TRUE. - On the PLC, open a watch table with
DB_ValveConfig.ValveType[1..10]. Force each tank to Type A and Type B and verify that the corresponding output bits toggle correctly usingDB_ValveInstances.Valve[i].qDO_Out1andqDO_Out2. - Connect a physical valve manifold to terminals 0/1, force
DB_Cmd.Extend[1]to TRUE, and verify the cylinder extends. ForceRetract, verify retract. - Repeat for each tank. Record the input feedback voltage on a commissioning sheet.
- Save the HMI recipe with the configured valve types and back it up to the project archive.
8. Verification and Diagnostics
| Check | Tool | Expected Result |
|---|---|---|
| All tanks respond to forced commands | Watch table + force | Output toggles within 1 OB1 cycle (~10 ms) |
| Feedback bits update from the field | Online & diagnostic view |
iDI_FB and iDI_BB follow physical inputs |
| Type B ignores coil B | Watch table | Forcing Type B, qDO_Out2 stays FALSE even if iCmdRetract is TRUE |
| Retentive behavior across power cycle | Power off / on |
DB_ValveConfig.ValveType retains last values |
| Cycle time | Online & diagnostics → cycle time | OB1 cycle < 20 ms for 10 tanks |
| Fault supervision | Force both feedbacks TRUE |
qFault rises after 2 s |
For deeper diagnosis, enable the S7-1500 trace on the status word array. The trace will show the exact sequence of state transitions when an operator changes the valve type at runtime.
9. Edge Cases and Field Caveats
-
Asymmetric consumption: If the operator mixes tank types so that the running counter wraps past the available DO count, the packer must clamp. Add
IF bitDO > 20 THEN ... raise overflowinFC_PackIO. -
Hot-swap of valve type: When the operator changes a Type A to Type B while the cylinder is extended, the
qDO_Out2is forced to FALSE on the next scan. The cylinder may drift if the valve has no spring return; add a fault if feedback is lost. - Communication loss to HMI: If the HMI connection drops, the PLC keeps the last commanded state. Document this in the operator manual so that valve reconfiguration is performed only at standstill.
- S7-1200 vs S7-1500 performance: S7-1200 with FW 4.5 supports the same SCL but executes loops roughly 2-3x slower than an S7-1511. With 10 tanks and a packer, the OB1 cycle stays under 10 ms on both; however, scaling to 50+ tanks may require the 1500.
-
Optimized access pitfalls: When using
S7_Optimized_Access := 'TRUE', slice accesses on tags require symbolic addressing. HMI tags must use symbolic names; absolute IO addresses like%I0.0cannot be sliced in optimized blocks. Refer to the TIA Portal programming guideline. -
Field wiring mistake: A common commissioning error is swapping the two DI feedback wires. The FB does not detect this; symptom is "valve never reaches end position". Add a cross-check that
iDI_FB XOR iDI_BBholds TRUE within 3 s of a command. - Recipe vs configuration: Always treat valve type as a recipe value, not a tag the operator can change ad hoc. Lock the dropdown behind a password level.
10. Frequently Asked Questions
Can I use indirect addressing on S7-300 or S7-400?
Yes. S7-300/400 support indirect DB access via the OPN DI + pointer method or via the ANY pointer. However, the SCL syntax for indexed array access is the same and is generally cleaner; the dynamic pattern in this article applies to S7-300/400 as well, with the caveat that the array index must be a local or static INT.
How many tanks can an S7-1200 realistically handle with this pattern?
An S7-1214C with 50 tanks and the packer pattern executes the loop in roughly 5-8 ms of OB1 time. Practical limit is dictated by the number of physical IO modules and the cycle time budget, not by the array. For 100+ tanks, switch to an S7-1516 and consider precomputing the pack map in a startup OB rather than in OB1.
Should I store the valve type as INT, BOOL, or WORD in the configuration DB?
Use INT. With BOOL, an HMI dropdown of more than two options forces a separate tag per tank. With WORD, bit mask encoding makes HMI scripting unnecessarily complex. INT also leaves room for Type C / Type D in the future without a data model change.
What happens if the operator reconfigures valve type while a tank is in motion?
The next scan of the packer reassigns the output offset. If the new offset points to a different physical output, the cylinder receives a different coil signal than expected. Always stop the machine before allowing reconfiguration, and add a runtime interlock that forces ConfigValid := FALSE while any tank is non-idle.
Can I use the same faceplate on multiple HMI panels?
Yes. Export the faceplate to a library in TIA Portal and re-import it in the second HMI project. The tag interface is symbolic, so the faceplate automatically binds to the same DB names as long as the project name space is consistent. For Unified Comfort Panel and Unified PC Runtime, use the global faceplate library described in the WinCC Unified manual.