Implementing 32-Bit Counters on Siemens S7-300 CPUs (S7-315-2 DP)
The default CTU, CTD, and SFB2 CTUD blocks shipped in the STEP 7 Standard Library expose only a 16-bit current value (WORD) and a 16-bit preset value. On an S7-315-2 DP, those limits force you into either counter cascading or a user-defined function block that promotes the count variable to a 32-bit (DINT) or even a 64-bit (two-DWORD) representation. This article walks through the three production-ready methods, the exact SCL/ST/LAD code, the memory budget for 80 simultaneous counters, and the CP343-1 / WinCC integration path required to publish the high-range totals to a server.
1. Problem Definition and Constraints
The original requirement is to deploy ~80 independent counters that each must hold a value higher than 65,535, with the data later forwarded over Industrial Ethernet through a CP343-1 to a server. The key constraints derived from the platform are:
- Counter range: 0 to 65,535 (WORD) is insufficient; target range is 0 to 2,147,483,647 (DINT) or 0 to 9,999,999,999+ (two DWORDs).
- CPU type: S7-300 family. S7-300 does not natively support 64-bit signed integers (LINT) in the same way as S7-1500; high-range counters must be split into two DWORDs.
- Counter quantity: 80 instances must coexist within the work memory and the user-load memory of the 315-2 DP.
- Communication: Each counter value must be exposed to a CP343-1 for transmission to a WinCC station or external OPC server.
- Cycle time: Input pulses may arrive faster than the OB1 cycle; an interrupt-driven or HSC-style approach is required for high-frequency counts.
2. S7-300 Counter Architecture and Data Type Limits
STEP 7 V5.x exposes three distinct counter mechanisms on an S7-300. Each has a different size, range, and storage cost.
| Mechanism | Block | Data type of CV | Range | Storage |
|---|---|---|---|---|
| IEC counter (system memory) | SFB 0 CTU / SFB 1 CTD / SFB 2 CTUD | WORD | 0 to 65,535 | DB instance, 28–40 bytes per instance |
| S7 system counter (IEC) | Z0–Z255 (S7 counters) | BCD-coded WORD | 0 to 999 | 256 × 2 bytes in system memory |
| User counter (no block) | Custom FC/FB with DINT | DINT (32-bit signed) | -2,147,483,648 to 2,147,483,647 | 4 bytes per counter (DWORD/DINT) |
| User counter (extended) | Custom FB with two DWORDs | DWORD + DWORD | 0 to 18,446,744,073,709,551,615 | 8 bytes per counter |
The S7 system counters (Z0–Z255) are limited to 0–999 BCD and are wholly inadequate for the requirement. The IEC SFB2 CTUD block uses a WORD for its CV output, which the original poster correctly identified as the limiting factor. The official block description is found in the SIMATIC S7-300 System and Standard Functions reference manual on the Siemens Industry Online Support document portal (search for "S7-300 System and Standard Functions").
3. Method 1 — SFB2 CTUD Cascade (16-bit Native, 32-bit Composite)
The classic S7-300 pattern is to instantiate two SFB2 blocks per counter and let the overflow output of the low word feed the count-up input of the high word. This keeps the code 100% inside the standard library and uses no SCL add-on, which is useful for older 315-2 DP firmware revisions that do not have the SCL compiler available.
3.1 Cascade logic
SFB2 exposes the following I/O:
- CU (BOOL): count up — the physical pulse input.
- CD (BOOL): count down.
- R (BOOL): reset to zero.
- PV (INT/WORD): preset value.
- Q (BOOL): status, TRUE when CV ≥ PV.
- CV (WORD): current value 0–65535.
To detect an overflow of the low word you cannot simply watch CV in OB1 because SFB2 does not emit a carry bit. The clean implementation routes the next CU pulse through a small FC that increments the high word and resets the low word:
// FC 100 "Counter32_Cascade"
// Input: iCU - count-up pulse
// iRst - reset
// InOut: ioLo - low word (WORD) of SFB2_1.CV
// ioHi - high word (DINT) manually maintained
// Output: qCV - composite 32-bit value
IF iRst THEN
"DB_Cnt".iHi := 0;
"DB_Cnt".iLo := 0; // reset low-word SFB2 outside this FC
qCV := 0;
RETURN;
END_IF;
IF iCU THEN
IF "DB_Cnt".iLo = 65535 THEN
"DB_Cnt".iLo := 0;
"DB_Cnt".iHi := "DB_Cnt".iHi + 1;
ELSE
"DB_Cnt".iLo := "DB_Cnt".iLo + 1;
END_IF;
END_IF;
qCV := SHL(IN := WORD_TO_DINT("DB_Cnt".iHi), N := 16) OR WORD_TO_DINT("DB_Cnt".iLo);
3.2 Memory cost of cascade
For 80 counters you need 80 × 2 = 160 SFB2 instances. Each SFB2 instance DB consumes ~36 bytes of load memory plus 36 bytes of work memory on the 315-2 DP. Total: ~5,760 bytes work memory, well within the 128 KB–256 KB work memory available on a 315-2 DP. The drawback is scan-time cost: two SFB2 calls per counter plus a custom FC = 160 SFB calls + 80 FC calls per OB1 pass.
4. Method 2 — Custom FB with DINT (Recommended, Up to 2,147,483,647)
4.1 FB100 — High-range up/down counter
FUNCTION_BLOCK FB 100
TITLE = 'DINT up/down counter 0..2,147,483,647'
VERSION : '1.0'
VAR_INPUT
iCU : BOOL; // count up pulse
iCD : BOOL; // count down pulse
iRst : BOOL; // synchronous reset to 0
iSet : BOOL; // synchronous load of iPreset
iPreset : DINT; // value to load when iSet = TRUE
END_VAR
VAR_OUTPUT
qCV : DINT; // current value
qOver : BOOL; // TRUE when at +2,147,483,647
qUnder : BOOL; // TRUE when at 0
END_VAR
VAR
sCV : DINT; // static retention
END_VAR
BEGIN
IF iRst THEN
sCV := 0;
ELSIF iSet THEN
sCV := iPreset;
ELSIF iCU AND NOT iCD THEN
IF sCV < 2147483647 THEN
sCV := sCV + 1;
END_IF;
ELSIF iCD AND NOT iCU THEN
IF sCV > 0 THEN
sCV := sCV - 1;
END_IF;
END_IF;
qCV := sCV;
qOver := (sCV = 2147483647);
qUnder := (sCV = 0);
END_FUNCTION_BLOCK
4.2 Ladder equivalent (single-rung ADD_DI)
For engineers who prefer LAD, the same logic reduces to a single ADD_DI rung with the count and reset in a shared instance DB:
|
|---[ iCU ]---[ ADD_DI ]---|
| EN ENO |
| IN1 = DB1.DBD0 |
| IN2 = L#1 |
| OUT = DB1.DBD0 |
|
|---[ iRst ]---[ MOVE ]-----|
| EN ENO |
| IN = L#0 |
| OUT = DB1.DBD0 |
DB1.DBD0 is the absolute address of the FB100 instance DB (e.g. DB100.DBD0 if you use DB100 as the instance). The L#1 literal is the 32-bit constant 1. The same DB byte can be referenced by the HMI as a DINT tag.
4.3 Instance DB and memory cost
Each FB100 instance DB consumes 22 bytes of work memory and load memory (header + the single static DINT + interface area). For 80 counters:
- Work memory: 80 × 22 = 1,760 bytes
- Load memory (MMC): 80 × 22 = 1,760 bytes
Plus the source code (FB100): ~250 bytes load memory. Total footprint < 2.1 KB — trivial for any 315-2 DP variant. The original poster's worry about running out of DBs is unfounded: a 315-2 DP allows up to 1,023 DBs in the address range 1–16,000 depending on firmware. See the S7-300 CPU 31xC and CPU 31x Technical Data manual on the Siemens support portal.
5. Method 3 — Dual-DWORD 64-bit Manual Counter
5.1 Carry logic
// Increment a 64-bit count stored as two DWORDs
// hi = upper 32 bits, lo = lower 32 bits
IF lo = 16#FFFF_FFFF THEN
lo := 0;
hi := hi + 1;
ELSE
lo := lo + 1;
END_IF;
The full unsigned 64-bit range is 0 to 18,446,744,073,709,551,615 (1.84 × 10¹⁹). For display purposes, convert to a decimal string with the SCL VAL_STRING or write a manual division routine. Note that STEP 7 V5.x has no native 64-bit integer type (LINT was introduced in S7-1500 / TIA Portal). All arithmetic is performed on DWORDs with explicit carry.
5.2 Memory cost of dual-DWORD approach
6. Memory Planning for 80 Counters on the CPU 315-2 DP
The 315-2 DP family ships with the following resources (values vary by MLFB; consult the device manual for the exact variant):
| Resource | 6ES7315-2AF03 / -2AF04 | 6ES7315-2AH14 | 6ES7315-2EH14 |
|---|---|---|---|
| Work memory (code + data) | 128 KB | 256 KB | 256 KB |
| Load memory (MMC) | up to 8 MB | up to 8 MB | up to 8 MB |
| Bit memory | 2,048 bytes (M0.0–M2047.7) | 2,048 bytes | 2,048 bytes |
| S7 timers / counters | 256 / 256 | 256 / 256 | 256 / 256 |
| DBs (1–16,000) | up to 1,023 | up to 1,023 | up to 1,023 |
| FBs / FCs | 2,048 / 2,048 | 2,048 / 2,048 | 2,048 / 2,048 |
For 80 DINT counters (Method 2) the total footprint is roughly 2 KB of work memory, well under 1 % of available memory. The original poster's concern about running out of "DW" is most likely a misunderstanding: 80 × 4 bytes = 320 bytes, far less than the bit-memory area alone.
7. SCL Source Code (Full FB100 Implementation)
The SCL version below compiles under the SCL add-on package (part of STEP 7 Professional) and is the cleanest implementation for the DINT case. It also includes saturation and edge detection to avoid double-counting on slow inputs.
FUNCTION_BLOCK FB 100
TITLE = 'High-range up/down counter, DINT, edge-detected'
VERSION : '1.1'
VAR_INPUT
iCU : BOOL; // count up edge
iCD : BOOL; // count down edge
iRst : BOOL; // sync reset
iSet : BOOL; // sync preset
iPreset : DINT;
iEnable : BOOL; // 1 = counting active
END_VAR
VAR_OUTPUT
qCV : DINT;
qOver : BOOL;
qUnder : BOOL;
qRun : BOOL; // pulses since last reset (32-bit overflow flag)
END_VAR
VAR
sCV : DINT; // retained across STOP/RUN if MMC retentive
sCU_old : BOOL;
sCD_old : BOOL;
END_VAR
VAR_TEMP
tCU_re : BOOL;
tCD_re : BOOL;
END_VAR
BEGIN
// rising-edge detection on count inputs
tCU_re := iCU AND NOT sCU_old;
tCD_re := iCD AND NOT sCD_old;
sCU_old := iCU;
sCD_old := iCD;
IF NOT iEnable THEN
qCV := sCV;
qOver := (sCV = 2147483647);
qUnder := (sCV = 0);
qRun := FALSE;
RETURN;
END_IF;
IF iRst THEN
sCV := 0;
ELSIF iSet THEN
sCV := iPreset;
ELSIF tCU_re AND NOT tCD_re THEN
IF sCV < 2147483647 THEN
sCV := sCV + 1;
END_IF;
ELSIF tCD_re AND NOT tCU_re THEN
IF sCV > 0 THEN
sCV := sCV - 1;
END_IF;
END_IF;
qCV := sCV;
qOver := (sCV = 2147483647);
qUnder := (sCV = 0);
qRun := iEnable AND NOT iRst;
END_FUNCTION_BLOCK
7.1 Generating 80 instance DBs
In STEP 7, right-click FB100 in the program blocks tree and choose Insert Instance DB 80 times. The instance DBs are created as DB100 through DB179 (or any free range). The block is called from OB1 (or a cyclic OB35 at, say, 100 ms for slow counters):
// OB1 — call 80 counter FBs
FOR i := 1 TO 80 DO
// index selects input flag word M (e.g. MW100 + 2*(i-1))
// and instance DB (e.g. "Cnt_DB".Instance[i])
END_FOR;
A more compact pattern uses an ARRAY of FB100 instances inside a single shared DB (available since STEP 7 V5.4 + SCL):
DATA_BLOCK DB 200
STRUCT
Counter : ARRAY[1..80] OF FB 100; // multi-instance
END_STRUCT
BEGIN
END_DATA_BLOCK
8. Ladder Logic Implementation
For pure-LAD projects (no SCL), the pattern below implements a single DINT counter. The hardware input pulse comes in on I0.0; the reset on I0.1. The count is stored in DB100.DBD0 (the FB100 instance) and exposed to the HMI.
Network 1: count up
I0.0 DB100.DBX 8.0 DB100.DBD 0 L#1
|--| |--+---ADD_DI---------------( )--|
| | EN IN1:=DB100.DBD0 ENO |
| | IN2:=L#1 |
| | OUT:=DB100.DBD0 |
Network 2: reset
I0.1 DB100.DBD 0 L#0
|--| |---( MOVE )---|
| EN IN:=L#0 |
| OUT:=DB100.DBD0 |
Repeat the two networks 80 times in OB1 — once per instance DB — or wrap them in a multi-instance call. For very large counts, the 32-bit ceiling at 2,147,483,647 is detected with a simple compare:
Network 3: overflow flag
DB100.DBD 0 2147483647
|--| DBD0 >= DBD 2147483647 --( S M 100.0 )--|
| (overflow flag) |
9. CP343-1 Ethernet and WinCC Tag Mapping
The original poster mentioned a CP343-1 to forward counts to a server. The CP343-1 (e.g. 6GK7343-1EX30-0XE0) supports S7-communication, PROFINET IO, and open TCP/UDP. For high-counter publishing to WinCC, use the S7-communication path.
9.1 Hardware configuration
- In HW Config, drag the CP343-1 from the catalog onto the DIN rail at slot 4 of the S7-300 station.
- Set the IP address (e.g. 192.168.0.10), subnet mask (255.255.255.0), and connect it to an Ethernet subnet object.
- Set the CP343-1 operating mode to "S7 communication" or "S7 communication + open communication" depending on whether the server uses S7 or raw TCP.
- Configure the connection in NetPro as an S7 connection to the WinCC station (PG/PC partner) and download.
9.2 Tag exposure to WinCC
Each counter value lives in an instance DB (e.g. DB100.DBD0 for counter 1, DB101.DBD0 for counter 2, etc.). In WinCC Explorer:
- Open Tag Management → SIMATIC S7 PROTOCOL SUITE → TCP/IP.
- Insert a new connection. Driver = TCP/IP, IP = 192.168.0.10, rack = 0, slot = 2 (the CPU 315-2 DP). For some firmware revisions slot 2 is the CPU; consult the Siemens support portal manual for the exact slot.
- Add tags:
Counter_01= DB100,DBD0, datatype DINT32;Counter_02= DB101,DBD0; … throughCounter_80= DB179,DBD0. - Bind a numeric I/O field to each tag; set format string to
+999999999to display the full DINT range with sign.
9.3 Publishing to a non-WinCC server
If the destination is a generic OPC server or a custom TCP application, use the CP343-1 open communication services (AG_SEND / AG_RECV) to send a raw byte buffer containing 80 × 4 bytes = 320 bytes of count data on each cycle. Configure the CP in HW Config under Properties → Open Communication with a free connection resource and reserve a length field that matches 320.
10. Commissioning and Verification Procedure
- Build the project. Compile FB100 and all 80 instance DBs. Use the STEP 7 menu PLC → Compile and Download Objects.
- Download HW Config. Stop the CPU, download the hardware configuration, restart in RUN.
- Monitor DBs online. Open DB100 in Monitor/Modify and force I0.0 = TRUE once. Verify DB100.DBD0 increments by 1.
- Edge-detection check. Hold I0.0 high for 5 seconds. The counter must increment by exactly 1, not by the number of OB1 scans during that interval.
-
Saturation test. Set DB100.DBD0 to 2,147,483,646 in the VAT table, pulse I0.0 twice, confirm the value is clamped at 2,147,483,647 and that
qOver= TRUE. - Reset test. Pulse I0.1 (reset). DB100.DBD0 must be 0 within one OB1 cycle.
- Retention test. Place the CPU in STOP, then back to RUN without a power cycle. Counts must be retained (requires the instance DB and the FB static variable to be marked Non-retentive = No in the DB properties or in the FB variable declaration).
- Cold-restart test. Power-cycle the rack. Counts must be retained only if the DB is on the MMC and marked retentive; otherwise they will zero out.
-
CP343-1 communication test. From WinCC, force
Counter_01to a known value and verify the I/O field updates within the configured acquisition cycle (default 1 s). - Long-run test. Run a continuous 1 Hz pulse for 24 hours. Confirm the value is 86,400 ± 2, no missed or double counts.
11. Troubleshooting Matrix
| Symptom | Likely cause | Fix |
|---|---|---|
| Counter advances by more than 1 per pulse | No edge detection; OB1 scans multiple times while input is high | Use the rising-edge detection in FB100 (variables sCU_old / sCD_old) or a dedicated hardware interrupt OB40 |
| Counter wraps to negative after 32,767 | Storage is INT, not DINT | Re-declare variable as DINT and use ADD_DI, not ADD_I |
| Counter resets on STOP→RUN | DB is marked non-retentive or the static is non-retentive | Open DB properties → uncheck Non-retentive and ensure the FB static DINT is declared without the NON_RETAIN attribute |
| WinCC shows 0 for all counters | Wrong rack/slot in the WinCC connection | CPU 315-2 DP is rack 0, slot 2 in the S7-protocol suite; verify with the SIMATIC S7-300 Module Data manual on the Siemens support portal |
| CP343-1 connection times out | Connection resource not configured in NetPro | Open NetPro, insert an S7 connection to the WinCC partner, download the connection table to the CP |
| Counts are correct in VAT but wrong on HMI | WinCC tag points to a different DB byte or DBD offset | Verify the tag address matches the FB instance DBD offset; many FB100 instances place the DINT at offset 8 (header) — adjust WinCC tag area pointer accordingly |
| CPU goes to STOP with SF on | OB121 not loaded and a tag is missing | Load OB121 (empty OK) and OB122 into the S7 program; the CPU will then go to RUN with the missing tag replaced by 0 |
| Pulses lost at high frequency | OB1 cycle longer than pulse period | Route the pulse to a digital input module with hardware interrupt capability and use OB40 to call FB100; on S7-300, the SM321 DI 16×24 VDC supports hardware interrupts |
| Counts overflow at 65,535 instead of 2,147,483,647 | Compiler picked ADD_I / MOVE for a WORD variable | Explicitly cast to DINT (e.g. DINT_TO_DWORD) and use ADD_DI |
12. Field-Proven Caveats
- Retentivity on the 315-2 DP is DB-level. The CPU itself has no built-in retentive flags. Configure the instance DB with Retain = Yes in the DB properties; otherwise the count zero-resets on every cold restart.
- Edge detection in OB35. If you call the counter FB from a cyclic interrupt OB (e.g. OB35 at 100 ms), the rising-edge detection works correctly only if the input pulse is captured by a faster mechanism (hardware interrupt OB40) and stored in a memory flag before OB35 reads it.
- CP343-1 connection limits. The CP supports a maximum of 16 S7 connections simultaneously. With 80 counters going to one WinCC server, one S7 connection is sufficient because all tags share the same connection.
- STEP 7 version compatibility. FB100 with multi-instances compiles in STEP 7 V5.4 SP3 or later. Earlier versions require a single instance DB per counter.
- Load memory on the MMC. Each instance DB adds ~22 bytes; 80 instance DBs ≈ 1.8 KB. Plan for at least a 512 KB MMC even on a small project — the 8 KB MMC that ships with some starter kits is too small once you add the system data and the CP343-1 GSD file.
- S7-300 → S7-1500 migration. When the project is later ported to a S7-1500 / TIA Portal, replace the manual DINT counter with the native IEC_Counter (CTU_DINT) function block, which supports LINT and 64-bit counting out of the box.
FAQ
What is the maximum count I can hold in a DINT on an S7-315-2 DP?
A DINT is a 32-bit signed integer. The maximum positive value is 2,147,483,647 and the minimum is -2,147,483,648. If you need a higher unsigned range, use the dual-DWORD method described in Section 5, which reaches 18,446,744,073,709,551,615.
Can I cascade two SFB2 blocks to get a 32-bit count without writing custom code?
No. SFB2 CTUD exposes only a 16-bit CV output and no carry bit. You must use one of the three methods in this article: the explicit SFB2 cascade (Section 3), a custom DINT FB (Section 4), or a dual-DWORD FB (Section 5). The cascade method is the only one that uses no SCL.
How much load memory does each counter use on the MMC?
A DINT-based FB100 instance DB consumes roughly 22 bytes of work and load memory. For 80 counters the total is approximately 1.8 KB plus ~250 bytes for the FB100 source. A 512 KB MMC has more than enough room.
Why does my counter reset to zero on every STOP→RUN transition?
By default S7-300 instance DBs are non-retentive on the 315-2 DP. Open the DB properties in STEP 7 and uncheck Non-retentive, or add OB121 / OB122 to the project so the CPU re-initialises correctly. The static DINT inside FB100 must also be declared without the NON_RETAIN attribute.
How do I publish the 80 counts to a server that does not speak S7-communication?
Use the open-communication services of the CP343-1 (AG_SEND / AG_RECV). Pack the 80 DINT values into a 320-byte buffer using the BLKMOV / SFC20 block-move function, and send the buffer on a configurable trigger (e.g. every second or on a tag change).
My input pulses are faster than the OB1 scan. How do I avoid losing counts?
Use a digital input module with hardware-interrupt capability (SM321 with interrupt enable) and route the pulse to OB40. In OB40 call FB100 directly — the OB40 priority is higher than OB1 and runs to completion before OB1 resumes, so no pulses are lost regardless of OB1 cycle time.