Indirect Addressing for Valve IO in S7-1200/1500 TIA Portal

David Krause13 min read
S7-1200SiemensTutorial / How-to
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

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.

Before reading further, validate one assumption: do you actually want physical IO reassignment, or do you want logical tag reassignment while the wiring stays fixed? Most field engineers discover that fixed wiring with a configurable FB is dramatically easier to commission, fault-find, and support than a true bit-packing engine. The article presents both approaches, but recommends the fixed-wiring path.

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
The 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

  1. Iterate through ValveType[1..10].
  2. 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.
  3. Maintain two running counters: bitDO (output index 1..20) and bitDI (input index 1..20).
  4. 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
The packer is non-deterministic across CPU restart if the operator changes the valve type array while the CPU is in RUN. Always gate the packer behind 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.

  1. Right-click the HMI tags folder, create HMI_TankType_1..HMI_TankType_10 of type Int.
  2. Map each to the corresponding PLC tag DB_ValveConfig.ValveType[i].
  3. Build the faceplate with a dropdown containing values 1 = Type A and 2 = Type B.
  4. Add status indicators bound to DB_ValveInstances.Valve[i].qStatusWord with 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

  1. 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.
  2. Wire the SM 1221 inputs identically: 2 inputs per tank.
  3. Compile and download the SCL blocks. Verify that DB_ValveConfig is marked retentive in the project tree.
  4. Open the HMI project, set the tank types, and download. Confirm the ConfigValid tag transitions TRUE.
  5. 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 using DB_ValveInstances.Valve[i].qDO_Out1 and qDO_Out2.
  6. Connect a physical valve manifold to terminals 0/1, force DB_Cmd.Extend[1] to TRUE, and verify the cylinder extends. Force Retract, verify retract.
  7. Repeat for each tank. Record the input feedback voltage on a commissioning sheet.
  8. 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 overflow in FC_PackIO.
  • Hot-swap of valve type: When the operator changes a Type A to Type B while the cylinder is extended, the qDO_Out2 is 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.0 cannot 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_BB holds 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.

Back to blog