S7-300 Custom Up/Down Counter with Variable Step in STEP 7

David Krause16 min read
S7-300SiemensTutorial / 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

1. Overview

Siemens S7-300/400 CPUs expose the IEC 61131-3 CTUD (Count Up/Down) functionality through System Function Block SFB 2 (CTUD) in the System Function Blocks library. SFB 2 increments by +1 on a rising edge at CU and decrements by -1 on a rising edge at CD, which is sufficient for the classic IEC counter use case. It does not, however, allow the engineer to configure the step width: the count always changes by exactly 1 per qualified edge.

When a machine requires a user-defined increment (for example "add 5 to the count on every up-pulse") or when the count variable must survive a scan cycle so that it can be read by an HMI or transferred to another block, the engineer must build a custom Function Block (FB) backed by an Instance Data Block (DB). The FB holds the count value in a STAT variable, qualifies the up/down command inputs with edge flags, applies the step, and clamps the result against configurable high and low limits.

This reference describes a production-ready FB_CountUpDown for STEP 7 V5.x targeting S7-300 CPUs (also valid for S7-400 and ET 200S IM 151-7/151-8 with CPU interface). The implementation covers:

  • STL, SCL and LAD/FBD source code variants.
  • Edge detection with debounce semantics.
  • Limit clamping and DINT/INT rollover protection.
  • Status outputs for HMI, alarms, and axis interlocks.
  • Commissioning checks via VAT and the S7 PLCSIM simulator.
CPU firmware baseline: The code assumes STEP 7 V5.5 SP2 or later and S7-300 CPU 31x firmware V2.x or higher. For S7-300 CPUs with firmware V1.x the DINT range and indirect addressing behaviour differ; see the S7-300 CPU 31x/31xC Manual for your specific order number (e.g. 6ES7315-2AH14-0AB0) before deployment.

2. Why SFB 2 Is Not Sufficient for a Variable-Step Counter

SFB 2 declares a fixed PV (preset) and reports a 1-bit difference. The relevant I/O footprint is documented in chapter 26.6 of SIMATIC S7-300/400 Standard Functions (Reference Manual):

Parameter Declaration Type Description
CU INPUT BOOL Count up – each rising edge adds 1
CD INPUT BOOL Count down – each rising edge subtracts 1
R INPUT BOOL Reset – sets CV to 0
PV INPUT INT Preset value for output Q
CV OUTPUT INT Current count (range -32768..32767)
QU OUTPUT BOOL Upper limit reached (CV >= PV)
QD OUTPUT BOOL Lower limit reached (CV <= 0)

Three limitations make SFB 2 inadequate for variable-step applications:

  1. Fixed step width. SFB 2 always adds or subtracts 1. There is no input that lets the caller pass a step of 2, 5, 0.5, or any other value.
  2. INT range. The internal CV is a 16-bit INT, so the count is bounded by -32768..32767. Modern machines with 32-bit position values exceed this range.
  3. No user-defined low limit. The lower bound is hard-coded to 0; an application that needs to count within, for example, 200..800 cannot use SFB 2 without external arithmetic that has to mirror the internal state.

The recommended path is a custom FB. The FB carries the count in its STAT section (so the value persists between cycles and can be observed through the instance DB), accepts the step and limits as inputs, and exposes clean STAT/OUT values to the HMI.

3. Prerequisites

Before building the counter, verify that the following hardware and software is in place:

  • STEP 7 V5.5 SP2 (or later V5.x) installed on the engineering station. The SCL optional package is required for the SCL variant.
  • PLC: S7-300 CPU 31x with firmware V2.x or higher. For DINT counters, all 31x CPUs (CPU 312 to CPU 319) support DINT; CPU 312 IFM (6ES7312-5AC00) only supports INT counters and must use the INT variant.
  • Project structure: a S7 program with a S7-300 station, an OB1 (or OB35 for cyclic tasks) and at least one FB. The FB must be created in the S7 Program / Blocks folder.
  • Online interface: MPI, PROFIBUS, or PROFINET to the target CPU for commissioning. The S7 PLCSIM simulator (part of STEP 7) can be used for dry-run tests.
  • I/O wiring: two digital inputs wired to the operator's up/down push-buttons (or to discrete sensors). The wiring follows the S7-300 Wiring and Installation Manual.

4. Block Interface Design

Open the S7 Program, right-click Blocks and select Insert New Object → Function Block. Name the FB FB_CountUpDown and assign a number in the user range (e.g. FB100). Define the interface as follows:

Section Name Type Initial / Comment
INPUT iUp BOOL Up command (level or pulse, edge-detected)
INPUT iDn BOOL Down command (level or pulse, edge-detected)
INPUT iHigh DINT Upper limit, inclusive
INPUT iLow DINT Lower limit, inclusive
INPUT iStep DINT Step width, must be > 0
INPUT iPreset DINT Value to load on iLoad
INPUT iLoad BOOL One-shot command to copy iPreset into the count
INPUT iReset BOOL Reset to iLow (level-triggered)
INPUT iEnable BOOL Master enable; edges are ignored when FALSE
OUTPUT oCount DINT Current count (mirrors the STAT variable)
OUTPUT oHigh BOOL TRUE when count has reached iHigh
OUTPUT oLow BOOL TRUE when count has reached iLow
OUTPUT oValid BOOL TRUE if iHigh > iLow and iStep > 0
OUTPUT oError WORD Bit-coded error word (see Section 11)
STAT sCount DINT Persistent count value (lives in the instance DB)
STAT sUpEdge BOOL Edge memory for iUp
STAT sDnEdge BOOL Edge memory for iDn
STAT sInit BOOL First-run flag for initialisation
TEMP tDelta DINT Scratch variable for the saturation check

Mark sCount, sUpEdge, sDnEdge, and sInit with the attribute S7_retentive = TRUE on the FB properties so the count survives a CPU STOP/RUN transition and a power loss (provided the CPU has a retentivity configuration that covers the instance DB).

5. STL Implementation (STEP 7 V5.5)

The following STL source runs in OB1 or any cyclic OB. The two edge memories produce exactly one increment per qualified input edge regardless of how long the input is held.

FUNCTION_BLOCK FB100
TITLE  ='Up/Down Counter with Variable Step'
VERSION : '1.0'

//-------------------------------------------------
// Section: STAT initialisation on first scan
//-------------------------------------------------
      A     #sInit
      JCN   INIT
      L     L#0
      T     #sCount
      SET
      =     #sInit
INIT: NOP 0

//-------------------------------------------------
// Section: input validation
//-------------------------------------------------
      L     #iStep
      L     0                   // L 0
      >I                       // step <= 0 ?
      =     #oError.%X0         // E0: step must be > 0

      L     #iHigh
      L     #iLow
      <=D                       // iHigh <= iLow ?
      =     #oError.%X1         // E1: invalid limits

      L     #oError.%W0
      L     0
      <>I
      JC    SKIP                // abort counting on error

      SET
      =     #oValid

//-------------------------------------------------
// Section: reset and preset (priority order)
//-------------------------------------------------
      A     #iReset
      JCN   NORST
      L     #iLow
      T     #sCount
NORST:NOP 0

      A     #iLoad
      JCN   NOLOAD
      L     #iPreset
      T     #sCount
NOLOAD:NOP 0

//-------------------------------------------------
// Section: edge detection and counting
//-------------------------------------------------
      A     #iEnable
      JCN   SKIP                // master enable off

      // --- count UP ---
      A     #iUp
      AN    #sUpEdge
      JCN   NOUP
      L     #iHigh
      L     #sCount
      -D
      L     #iStep
      >=D                       // (High - sCount) >= Step ?
      JC    UPOK
      L     #iHigh              // saturate
      JU    UPWRT
UPOK: L     #sCount
      L     #iStep
      +D
UPWRT:T     #sCount
NOUP: NOP 0
      A     #iUp
      =     #sUpEdge

      // --- count DOWN ---
      A     #iDn
      AN    #sDnEdge
      JCN   NODN
      L     #sCount
      L     #iLow
      -D
      L     #iStep
      >=D                       // (sCount - Low) >= Step ?
      JC    DNOK
      L     #iLow               // saturate
      JU    DNWRT
DNOK: L     #sCount
      L     #iStep
      -D
DNWRT:T     #sCount
NODN: NOP 0
      A     #iDn
      =     #sDnEdge

SKIP: NOP 0

//-------------------------------------------------
// Section: outputs
//-------------------------------------------------
      L     #sCount
      T     #oCount

      L     #sCount
      L     #iHigh
      >=D
      =     #oHigh

      L     #sCount
      L     #iLow
      <=D
      =     #oLow

END_FUNCTION_BLOCK
Compile in STEP 7: paste the STL into the FB source view, choose File → Compile (Ctrl+B). The instance DB will be created the first time the FB is called and the compiler generates DB100 automatically. Open the generated DB, set sCount to iLow on first download if you want a deterministic cold start.

6. SCL Implementation (recommended for readability)

SCL is included with the S7-SCL optional package and is supported by all S7-300 CPUs that are programmed with STEP 7 V5.5. The SCL source is the most maintainable form of the same algorithm:

FUNCTION_BLOCK FB100
TITLE = 'Up/Down Counter with Variable Step'
VERSION : '1.0'

VAR_INPUT
  iUp      : BOOL;
  iDn      : BOOL;
  iHigh    : DINT;
  iLow     : DINT;
  iStep    : DINT;
  iPreset  : DINT;
  iLoad    : BOOL;
  iReset   : BOOL;
  iEnable  : BOOL;
END_VAR

VAR_OUTPUT
  oCount   : DINT;
  oHigh    : BOOL;
  oLow     : BOOL;
  oValid   : BOOL;
  oError   : WORD;
END_VAR

VAR
  sCount   : DINT;
  sUpEdge  : BOOL;
  sDnEdge  : BOOL;
  sInit    : BOOL;
END_VAR

VAR_TEMP
  tDelta   : DINT;
END_VAR

BEGIN

  // --- input validation ----------------------------------------
  oError  := 0;
  IF iStep <= 0 THEN
    oError.%X0 := TRUE;             // E0 step must be > 0
  END_IF;
  IF iHigh <= iLow THEN
    oError.%X1 := TRUE;             // E1 invalid limits
  END_IF;
  oValid := (oError = 0);

  IF NOT oValid THEN
    oCount := sCount;
    oHigh  := FALSE;
    oLow   := FALSE;
    RETURN;
  END_IF;

  // --- reset / preset ------------------------------------------
  IF iReset THEN
    sCount := iLow;
  END_IF;

  IF iLoad THEN
    sCount := iPreset;
  END_IF;

  // --- edge detection + step arithmetic -----------------------
  IF iEnable THEN

    // count UP
    IF iUp AND NOT sUpEdge THEN
      tDelta := iHigh - sCount;
      IF tDelta >= iStep THEN
        sCount := sCount + iStep;
      ELSE
        sCount := iHigh;             // saturate at upper limit
      END_IF;
    END_IF;
    sUpEdge := iUp;

    // count DOWN
    IF iDn AND NOT sDnEdge THEN
      tDelta := sCount - iLow;
      IF tDelta >= iStep THEN
        sCount := sCount - iStep;
      ELSIF sCount > iLow THEN
        sCount := iLow;              // saturate at lower limit
      END_IF;
    END_IF;
    sDnEdge := iDn;

  END_IF;

  // --- outputs -------------------------------------------------
  oCount := sCount;
  oHigh  := (sCount >= iHigh);
  oLow   := (sCount <= iLow);

END_FUNCTION_BLOCK

Compile with Options → Compile (Ctrl+B) or via the menu PLC → Compile and Download Objects. SCL produces more compact ladder-equivalent MC7 code, which can be confirmed by opening the compiled FB and switching the view to STL.

7. Ladder / FBD Implementation Notes

The same logic is possible in LAD and FBD but requires more network segments. Use the following network plan as a starting point:

Network Function Key elements
NW1 Validate inputs Compare iStep > 0 and iHigh > iLow; set oError
NW2 Reset ---|iReset---| MOVE iLow -> sCount
NW3 Preset ---|iLoad---| MOVE iPreset -> sCount
NW4 Edge flag up ---| iUp |---N---| sUpEdge |---(sUpEdge)
NW5 Add step (up) ---| NW4 |---| iEnable |---ADD sCount + iStep -> tDelta; compare; conditional MOVE
NW6 Edge flag down ---| iDn |---N---| sDnEdge |---(sDnEdge)
NW7 Subtract step (down) ---| NW6 |---| iEnable |---SUB sCount - iStep -> tDelta; compare; conditional MOVE
NW8 Clamp + outputs GE/LE comparators, MOVE sCount -> oCount
Caution with LAD/FBD: the conditional MOVE that saturates the value requires either a separate comparator + MOVE pair or a SEL block; LAD/FBD count the block call towards the local data stack of the OB. Reserve at least 32 bytes of local data in OB1 if the FB is also called from a different OB in the same priority class.

8. OB1 Call and Instance DB Generation

Open OB1 in the S7 program and call the FB. STEP 7 will offer to create the instance DB:

      CALL FB100 , DB100
       iUp      := I 0.0
       iDn      := I 0.1
       iHigh    := MW 20
       iLow     := MW 24
       iStep    := MW 28
       iPreset  := MW 32
       iLoad    := I 0.2
       iReset   := I 0.3
       iEnable  := I 0.4
       oCount   := MW 40
       oHigh    := M 50.0
       oLow     := M 50.1
       oValid   := M 50.2
       oError   := MW 52

On OK, STEP 7 inserts the call and creates DB100. Right-click the FB call to inspect the Instance DB; the Data View tab shows the current count, edge flags, and limit values that can be monitored online.

9. Edge Detection, Debounce, and Input Filter Strategy

The edge flag pattern (iUp AND NOT sUpEdge) guarantees one count per transition. If the push-button has mechanical bounce, a 5-10 ms input filter on the digital input module is the right solution. For S7-300 SM 321 digital input modules set the filter with the Properties → Inputs dialog in HW Config (see Working with STEP 7 for the screen flow).

Filter setting Typical use
0.5 ms Fast electronic sensors (light barriers, encoders)
3 ms General-purpose sensors with light contact bounce
15 ms Mechanical push-buttons, relay contacts

For applications that need software debounce in addition (e.g. a 50 ms reject window), wrap the input in an IEC timer or a TON with a 50 ms preset and use the timer's output as the FB input.

10. Limit Handling, Saturation, and Rollover Protection

Three saturation scenarios are relevant:

  1. Standard step fits the range: (iHigh - sCount) >= iStep. The counter increments by the full step.
  2. Remaining range is smaller than the step: the count is forced to iHigh. The next edge of iUp is ignored because the comparator is no longer satisfied.
  3. DINT overflow: with iHigh = 2147483647 and iStep = 1 the addition can theoretically overflow. The pre-check (iHigh - sCount) >= iStep prevents the addition from being executed when sCount is already at the limit, so the overflow is avoided in practice.

For INT-only CPUs (e.g. 6ES7312-5AC00), substitute DINT with INT and add an INT#-32768/INT#32767 constant check. The CPU 31x manual describes the data type ranges and arithmetic flags in detail.

11. Error Word Mapping

The 16-bit oError word is bit-coded for quick diagnostic reads from the HMI:

Bit Symbolic name Meaning Remedy
E0 (%X0) ERR_STEP iStep is zero or negative Set step to a positive DINT
E1 (%X1) ERR_LIMITS iHigh <= iLow Re-order high/low or adjust preset
E2 (%X2) ERR_RANGE Step wider than iHigh - iLow Reduce step or widen limits
E3 (%X3) ERR_BOTH Up and Down edges present in same cycle Verify wiring / debounce
E15 (%X15) ERR_CPU Internal MC7 fault (e.g. divide by zero) Reset cold restart; check retentivity

Bit E15 is set by the CPU's group error bits if the FB is called with corrupt actual parameters. Treat it as a fatal event and clear the instance DB.

12. HMI Integration (WinCC flexible / TIA Portal)

For a WinCC flexible runtime that connects to the S7-300 over MPI/ PROFIBUS/ Ethernet, expose the following tags from DB100:

HMI tag DB100 address Type Display format
Count DB100.DBD0 (sCount) DINT Signed decimal
High DB100.DBX12.0 (oHigh) BOOL Indicator
Low DB100.DBX12.1 (oLow) BOOL Indicator
Valid DB100.DBX12.2 (oValid) BOOL Indicator
Error DB100.DBW14 (oError) WORD Hex (4 digits)
Step DB100.DBD16 (iStep mirror) DINT Signed decimal

Set the acquisition cycle for the Count tag to 250 ms in WinCC flexible; faster rates increase bus load on MPI networks and rarely add value for a human-operated counter. Refer to the STEP 7 V5.5 Programming Manual for the exact DB address offset for your block version.

13. Verification Procedure

  1. Compile the FB. The SCL compiler reports line numbers for any syntax error; STL reports *** Error in line NN ***.
  2. Download the blocks (FB100, DB100, OB1) to the CPU or to S7 PLCSIM. Use PLC → Download or Ctrl+L.
  3. Online monitor OB1 and DB100. In the LAD/FBD/STL editor press Ctrl+F3 (Monitor on/off). The sCount value should remain at 0 (or at iLow if pre-loaded) until the first edge.
  4. Force inputs from a VAT table (PLC → Monitor/Modify → VAT). With iHigh=10, iLow=0, iStep=3, set iUp=TRUE. Confirm sCount reaches 3, 6, 9, and 10 (saturation).
  5. Down test: set iDn=TRUE and watch the count decrement by 3 down to 0.
  6. Edge test: hold iUp=TRUE for 5 s. The count must advance by exactly one step per cycle of OB1, not by five steps. If it advances continuously, the edge flag is missing.
  7. Reset/preset test: pulse iReset; sCount must snap to iLow. Pulse iLoad with iPreset=7; sCount must snap to 7.
  8. Error test: set iStep=0. oError.%X0 must be TRUE, oValid must be FALSE, and the count must not change.

14. Field Commissioning and Diagnostic Tips

  • When the count drifts upward by one every scan, the most common cause is that the caller has wired a level input that is held TRUE, with the edge flag missing. Re-read the code and confirm that sUpEdge is updated at the end of the network.
  • When the count never moves, check that OB1 is actually being executed (use the Module Information tool, Diagnostic Buffer). On a CPU in STOP, the FB does not run.
  • If the FB is called from OB35 (cyclic interrupt) instead of OB1, the call frequency is independent of OB1 and the count is updated at the OB35 cycle rate. Set the OB35 cycle to 100 ms or longer for a human-driven counter; faster rates saturate the input filter and add no value.
  • For multi-instance use, the FB must be declared with multi-instance capability. Open the FB properties and tick Multi-instance capable. This avoids the need for a separate instance DB per call and reduces the load on the CPU's work memory.
  • Persist the count across power loss by adding sCount and the edge flags to a retentive area in HW Config → CPU Properties → Retentive Memory. The exact size depends on the CPU order number.

15. Comparison: Built-in SFB 2 vs. Custom FB

Feature SFB 2 (CTUD) Custom FB_CountUpDown
Step width Fixed +1 / -1 Configurable (DINT input)
Count range -32768..32767 (INT) -2147483648..2147483647 (DINT)
Low limit Hard-coded to 0 User-defined
High limit Configurable (PV) Configurable (iHigh)
Edge handling Internal to SFB Explicit edge flags
Saturation None (rolls over) Clamps to iHigh/iLow
Preset/Reset R only (to 0) iLoad + iPreset, iReset to iLow
Multi-instance No (one DB per call) Yes (when marked multi-instance)
IEC 61131-3 compliance Yes (CTUD) No (vendor FB)

Use the custom FB whenever the step is anything other than 1, whenever the count must be visible on the HMI as a writable tag, or whenever the application has to handle 32-bit values. Use SFB 2 for a simple 16-bit preset/reset pattern that conforms strictly to the IEC 61131-3 CTUD definition.

16. Frequently Asked Questions

Can the counter step be changed at runtime, or does it have to be a constant?

Yes. The step is an FB input (iStep) and can be written from the HMI, another block, or a VAT. The validation logic re-checks iStep on every call, so a step of zero is rejected the cycle after it is written and the count stops advancing.

How do I migrate the count to TIA Portal without losing its current value?

Open the instance DB (DB100) in STEP 7 V5.5, read the sCount value, then re-use the same SCL source in TIA Portal V13 or later. On the first download in TIA Portal the instance DB is initialised; if you need to retain the value, set the DB attribute Retain = TRUE on the sCount variable and download without re-initialising the DB.

Why does the count continue to change after I clear iEnable?

iEnable is sampled once per call. If your OB calls the FB multiple times within the same scan (e.g. from a loop in OB1), the edge flag is updated in the first call and the second call sees sUpEdge already TRUE, so no further step is added. If the count still moves, check for additional calls in other OBs at the same priority class.

What is the maximum count frequency of this implementation?

The FB itself is limited only by the OB cycle time. In OB1 at a typical 20 ms scan, the upper bound is 50 counts/s. For faster inputs, move the call to OB35 with a 1 ms cycle (CPU 317/319 only) and use a faster input filter. For very high counts use the S7-300 high-speed counter module FM 350-1 or the integrated counters of the CPU 31xC.

Can I read sCount from another FB without going through the instance DB?

Yes, if the second FB is a multi-instance of the same parent FB it has direct access to the parent's STAT area. Otherwise read the value from DB100 (e.g. L DB100.DBD0 in STL) or expose it as an output of the calling FB and chain the call.

Back to blog