Building a Station Shift Register on Siemens S7-1500 with SCL

David Krause11 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

Overview: The Station Shift Register Problem

On a discrete assembly line, a single Vehicle Identification Number (VIN) or production token must travel with the physical carrier through every station. Each station reads the identifier from the previous station, performs its operation, and hands the data to the next station on a single trigger pulse (typically a photo-eye, presence sensor, or RFID read complete flag). The PLC implementation of that mechanism is a shift register stored in a data block: one trigger edge advances every entry by one slot, the newest station entry is written, and the oldest entry leaves the line.

The same primitive is used for:

  • Tracking workpieces through 10, 20, or 100 stations
  • Buffering serialized part numbers between conveyor segments
  • Storing inspection results per station in a rolling history window
  • Sequencing recipe parameters for batch processing

For a general reference on shift register hardware primitives and IO expansion, see the Texas Instruments application brief Designing with Shift Registers (SCEA117). The PLC implementation, however, uses indexed arrays in a data block rather than cascaded flip-flops.

Prerequisites: Hardware and Software

Build the example on a Siemens SIMATIC S7-1500 with the firmware and software shown in the table below. The same code compiles and runs on S7-1200 (firmware 4.0 or later) with minor tag changes.

Item Specification Notes
CPU SIMATIC S7-1516-3 PN/DP (6ES7516-3AN02-0AB0) Any S7-1500 with FW 2.5+ works
Optional CPU SIMATIC S7-1214C DC/DC/DC (6ES7214-1AG40-0XB0) Use FW 4.2+ for full SCL support
Trigger input %I0.0 (24 V digital, PNP) Photo-eye or part-presence sensor
Engineering SIMATIC TIA Portal V17 Update 5 (or V18) SCL compiler must be present
Firmware S7-1500 CPU FW 2.9.7 or later Required for optimized block access
I/O ET 200SP digital input module (e.g. 6ES7131-6BF01-0BA0) Used for station trigger

Reference the S7-1500 Automation System Manual (entry ID 59191792) and the S7-1200 Programmable Controller Manual (entry ID 109751591) for hardware configuration, addressing, and data block rules.

Architecture Options: Ring Buffer vs Linear Shift

Two patterns are used in practice. The selection depends on whether the line uses FIFO semantics (oldest part leaves the line) or whether each station must hold a fixed, addressable position (1..N).

Property Ring Buffer (FIFO) Linear Shift
Memory model Two index pointers, array size = N Array size = N, every index written on each trigger
Per-trigger cost One write, two index updates, O(1) One memmove-style copy, O(N)
Station mapping Indirect: Station k = array[(head+k) mod N] Direct: Station k = array[k]
Part exit Oldest element at head pointer Last element array[N-1] leaves
Reset behavior Pointers wrap; data overwritten in place Entire array must be cleared
Best for Long lines, hot path, large arrays Small N (< 32), direct station tagging

For the 23-station VIN line referenced in the source, both patterns work. The ring buffer is preferred because (a) station count may grow, (b) only one write per trigger keeps the OB1 cycle time low, and (c) the head/tail pointer pair is easy to visualize on HMI.

Declaring the Station Array in the Data Block

Create a global DB named DB_LineTracking with Optimized block access (the default for S7-1500). The optimized layout is required for symbolic addressing of array slices. Tag the line as non-retain if the buffer is initialized on cold start, or retain if the line must survive a power cycle.

// DB_LineTracking (Global DB, optimized, retentive for VIN data)
{
  StationCount : Int := 23;          // number of stations
  StartIndex   : Int := 1;           // head pointer (oldest)
  EndIndex     : Int := 0;           // tail pointer (newest)
  EntryIdentity: DInt := 0;          // incoming VIN/identity value
  ExitIdentity : DInt := 0;          // outgoing VIN/identity value
  ObjectTrain  : Array[1..32] of DInt; // pre-allocated buffer
  SystemReset  : Bool;
  NewObject    : Bool;
}

Always declare the array larger than the current station count (32 in the example). This lets you re-parameterize the line by changing StationCount without re-declaring the data block. Use Array[*] only when the upper bound is genuinely unknown at compile time; TIA Portal V17 limits Array[*] to certain standard library types.

SCL Implementation: Ring Buffer with Edge Detection

The ring buffer is the most efficient pattern. Two pointers, StartIndex (oldest) and EndIndex (newest), move through the array on every trigger. The newest part is written at EndIndex, the oldest part is read at StartIndex. Both pointers wrap at StationCount.

// FB_LineShift (SCL, S7-1500)
// Inputs:  ixTrigger (Bool) - station advance pulse
//          ixEntry   (DInt) - VIN/identity from upstream
//          ixReset   (Bool) - full buffer reset
// Outputs: qxExit    (DInt) - VIN/identity leaving line
//          qxStation : Array[1..32] of DInt - per-station view

IF #ixReset THEN
  #StartIndex := 1;
  #EndIndex   := 0;
  FOR #i := 1 TO 32 DO
    #ObjectTrain[#i] := 0;
  END_FOR;
  RETURN;
END_IF;

IF #ixTrigger AND NOT #trigLast THEN   // rising edge detection
  // advance head pointer (oldest)
  IF #StartIndex >= #StationCount THEN
    #StartIndex := 1;
  ELSE
    #StartIndex := #StartIndex + 1;
  END_IF;

  // advance tail pointer (newest)
  IF #EndIndex >= #StationCount THEN
    #EndIndex := 1;
  ELSE
    #EndIndex := #EndIndex + 1;
  END_IF;

  // write new entry at tail, read outgoing at head
  #ObjectTrain[#EndIndex] := #ixEntry;
  #qxExit := #ObjectTrain[#StartIndex];
END_IF;

#trigLast := #ixTrigger;

Edge detection (ixTrigger AND NOT trigLast) is essential. A sustained-high sensor will otherwise advance the buffer hundreds of times per second. The pattern is documented in the S7-1500 Motion Control Function Manual (entry ID 67585224) under "latch and edge" primitives and in the TIA Portal help for the R_TRIG system block.

SCL Implementation: Linear Block Shift

When the HMI must show Station 1, Station 2, ... Station N with fixed array indices (no pointer math), use a linear shift. The cost is one full-array copy per trigger; for N = 23 with DInt entries, that is 184 bytes per shift, well within the 100 µs OB1 budget on an S7-1516.

// FB_LineLinear (SCL)
IF #ixReset THEN
  FOR #i := 1 TO 32 DO #ObjectTrain[#i] := 0; END_FOR;
  #StartIndex := 1;
  RETURN;
END_IF;

IF #ixTrigger AND NOT #trigLast THEN
  // shift right by one slot: station N-1 receives station N-2, etc.
  FOR #i := #StationCount DOWNTO 2 DO
    #ObjectTrain[#i] := #ObjectTrain[#i-1];
  END_FOR;
  // write new entry at station 1 (line entry)
  #ObjectTrain[1] := #ixEntry;
  // exit is the last station
  #qxExit := #ObjectTrain[#StationCount];
END_IF;

#trigLast := #ixTrigger;

The DOWNTO direction is critical. Iterating upward would clobber the source values before they are read. The pattern mirrors the memmove call in C: copy from high index to low index when the destination is to the right of the source.

Ladder Logic Alternative Using FILL_BLK and BLKMOV

Engineers who prefer ladder can implement the linear shift with the standard library blocks. The ladder version is verbose but easy to commission with watch tables.

  1. On a rising edge of %I0.0, call BLKMOV to copy ObjectTrain[1..22] into ObjectTrain[2..23]. Use the variant for an array slice: source P#DB_LineTracking.ObjectTrain[1] BYTE 22*4, destination P#DB_LineTracking.ObjectTrain[2] BYTE 22*4.
  2. Write the new entry to ObjectTrain[1] with a single MOVE_DINT.
  3. Copy ObjectTrain[StationCount] to the exit word qxExit with MOVE_DINT.
  4. Place the rising-edge detection in a separate network with R_TRIG (system block, S7-1500) or a manual FP instruction (S7-300/400).

For the ring buffer variant in ladder, two index words, two compare-and-add rungs, and two unconditional moves replace the BLKMOV. The ring buffer is the only practical ladder option once N exceeds ~32 elements because the BLKMOV slice becomes a long, error-prone ANY pointer literal.

Edge Detection and Trigger Conditioning

Mechanical and inductive sensors produce contact bounce. A 5 ms debounce is mandatory before the shift logic. Three common implementations:

Method Block Resolution Notes
IEC timer TON with PT = 5 ms 5 ms OB1 must run < 5 ms; S7-1500 default is 1 ms
System input filter DI module parameter 0.1 to 20 ms Hardware filter, frees PLC scan time
IEC counter CTU with N = 2 2 × OB1 Cheapest in memory, but jittery

Configure the digital input module's input filter to at least 3 ms in the device configuration. See the S7-1200 Programmable Controller Manual (entry ID 109751591), section "Wiring and Interconnection of Digital Inputs". For S7-1500 ET 200SP modules, the filter value is set in the device view under Properties > Inputs > Input filter; refer to the ET 200SP Digital Input Module Manual (entry ID 55652616).

Commissioning and Verification

  1. Compile the project (Ctrl+B) and download to the CPU. Acknowledge any stop caused by the recompile.
  2. Open the SCL block online and add it to a watch table. Force ixReset = TRUE for one cycle; verify StartIndex = 1, EndIndex = 0, and that all ObjectTrain entries read 0.
  3. Set ixEntry = 17 (a constant VIN surrogate) and pulse ixTrigger once. Verify EndIndex = 1, ObjectTrain[1] = 17, and qxExit = 0.
  4. Pulse ixTrigger a total of StationCount times with a unique VIN for each pulse. On the final pulse, verify that qxExit equals the first VIN written and that EndIndex = StationCount.
  5. Continue pulsing and verify that the ring buffer wraps: after pulse StationCount + 1, StartIndex = 2 and EndIndex = 1 (wrapping has occurred).
  6. Record the OB1 cycle time before and after the shift logic. The shift itself should add < 50 µs on an S7-1516.

For HMI visualization, expose the entire ObjectTrain array as a tag and bind it to a recipe view or a tabular control in WinCC Professional. See the WinCC Professional V17 Manual (entry ID 109773506) for tag binding under "S7-1500 Array Tags".

Common Pitfalls and Diagnostics

Symptom Root Cause Fix
Buffer advances many times per trigger No edge detection; sustained sensor input Add R_TRIG or manual FP rung
Buffer advances erratically Sensor contact bounce < OB1 cycle Set DI filter to 3 ms minimum
Buffer skips a station Trigger pulse shorter than one OB1 cycle Use hardware input filter; or latch with R_TRIG from DI interrupt OB40
Compiler error: "Array index out of range" Index variable declared as UInt and compared to Int constant Use Int for index in S7-1500; use DInt for Array[*]
Data is overwritten on power cycle DB configured non-retentive Set Retain attribute on the array tag
Two triggers lost in 5 ms window OB1 cycle time > 1 ms and sensor not latched Reduce OB1 cycle, or wire sensor to a hardware interrupt OB
Linear shift overwrites newest data FOR loop direction is UPTO, not DOWNTO Reverse loop direction; copy high-to-low
WinCC shows zeros for all stations Optimized DB access blocked by absolute addressing Bind to symbolic tag name, not absolute address
Safety note: A shift register that controls downstream actuators (e.g. torque controllers, welders) must be implemented as a safety-related shift using the F-CPU's safety data blocks when the application falls under ISO 13849 PL d or higher. The standard shift register above is for tracking only; it is not a safety function.

Extending the Pattern

For lines with non-uniform station spacing, store a parallel array of station type tags and validate the identity before allowing the shift. For lines with multiple parallel lanes, declare one ObjectTrain per lane and use the same index math. For lines with a recipe payload (more than one DInt per station), promote the array element to a UDT:

TYPE UDT_Part :
  STRUCT
    VIN       : DInt;
    Variant   : Int;
    Timestamp : DInt;          // milliseconds since line start
    Quality   : Byte;          // 0=good, 1=rework, 2=scrap
  END_STRUCT;
END_TYPE

// then in DB: ObjectTrain : Array[1..32] of UDT_Part;

UDT-based entries are still shifted by the same pointer math; the per-element size becomes 12 bytes (96 bits) on an S7-1500, which still costs under 30 µs per shift for the entire array of 32 elements.

Field-Proven Caveats

  • Always bound the array index with IF #EndIndex > 32 THEN #EndIndex := 1; END_IF;. A stuck trigger or index variable declared as DInt can run past the array end and trigger an S7-1500 STOP with diagnostic buffer entry OB1 / SF / area length error.
  • Do not put the trigger in a high-priority OB (OB35, OB40) unless every other consumer of the buffer also lives in that OB. Mixed-priority access causes data inconsistency because the lower-priority OB can be interrupted mid-write.
  • Use a single global instance of the FB and a single DB; multiple instances with shared tags will desynchronize the index pointers.
  • When migrating from S7-300/400, the BLKMOV ANY pointer uses byte counts, not element counts. DInt is 4 bytes, so 22 elements = 88 bytes.

FAQ

How do I shift a byte array in TIA Portal SCL on S7-1500?

Declare an Array[1..N] of Byte in a global optimized DB, then on a rising edge of the trigger call a FOR i := N DOWNTO 2 DO Array[i] := Array[i-1]; END_FOR; loop, and write the new value to Array[1]. The DOWNTO direction is mandatory to avoid overwriting source data.

What is the difference between a ring buffer and a linear shift register on S7-1500?

A linear shift register copies all N entries on every trigger (O(N) cost) but uses fixed station indices 1..N, which simplifies HMI binding. A ring buffer uses two index pointers and only one write per trigger (O(1) cost) but requires pointer math to map Station k to the array index. Pick ring buffer for N > 32 or hot-path performance; pick linear for small N with direct HMI addressing.

How do I prevent the shift register from advancing multiple times on a single sensor pulse?

Use rising-edge detection. In SCL: IF #ixTrigger AND NOT #trigLast THEN ... END_IF; #trigLast := #ixTrigger;. In ladder, use the R_TRIG system block. Also set the digital input hardware filter to 3 ms or higher to debounce mechanical sensors.

Can I use Array[*] (variant arrays) for the shift register in TIA Portal V17?

Yes for read-only access, but Variant arrays (Array[*]) cannot be used as the operand of indexed assignment in standard SCL blocks until TIA Portal V18 with the SCL 4.0 compiler. For runtime-configurable sizes on V17, declare the array with a fixed maximum (e.g. Array[1..128]) and parameterize the actual count with an Int tag.

How do I bind the shift register array to WinCC Professional on S7-1500?

Expose the global DB as an HMI-accessible data block, mark the array tag as accessible from HMI (default for optimized DBs in V17), and in WinCC bind the array directly to a table view or a recipe control. Ensure the connection uses S7-1500 symbolic addressing; absolute-area pointer binding does not work with optimized blocks.

Back to blog