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.
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:
- 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.
-
INT range. The internal
CVis a 16-bitINT, so the count is bounded by -32768..32767. Modern machines with 32-bit position values exceed this range. - 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
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 |
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:
-
Standard step fits the range:
(iHigh - sCount) >= iStep. The counter increments by the full step. -
Remaining range is smaller than the step: the count is forced to
iHigh. The next edge ofiUpis ignored because the comparator is no longer satisfied. -
DINT overflow: with
iHigh = 2147483647andiStep = 1the addition can theoretically overflow. The pre-check(iHigh - sCount) >= iStepprevents the addition from being executed whensCountis 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
-
Compile the FB. The SCL compiler reports line numbers for any syntax error; STL reports
*** Error in line NN ***. - Download the blocks (FB100, DB100, OB1) to the CPU or to S7 PLCSIM. Use PLC → Download or Ctrl+L.
- 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.
-
Force inputs from a VAT table (PLC → Monitor/Modify → VAT). With
iHigh=10,iLow=0,iStep=3, setiUp=TRUE. Confirm sCount reaches 3, 6, 9, and 10 (saturation). -
Down test: set
iDn=TRUEand watch the count decrement by 3 down to 0. -
Edge test: hold
iUp=TRUEfor 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. -
Reset/preset test: pulse
iReset; sCount must snap toiLow. PulseiLoadwithiPreset=7; sCount must snap to 7. -
Error test: set
iStep=0.oError.%X0must be TRUE,oValidmust 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
sUpEdgeis 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
sCountand 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.