Implementing 32-Bit Counters on Siemens S7-300 CPUs (S7-315-2 DP)

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

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.

Target platform. SIMATIC S7-300, CPU 315-2 DP (MLFB 6ES7315-2AF03-0AB0, 6ES7315-2AF04-0AB0, 6ES7315-2AH14-0AB0, or 6ES7315-2EH14-0AB0), STEP 7 V5.5 / V5.6, CP343-1 (6GK7343-1EX30-0XE0 or later), and SCL add-on package. All Siemens documentation referenced is available from the Siemens Industry Online Support portal under product family SIMATIC S7-300.

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").

Why SFB2 alone is not enough. SFB2 CV is a 16-bit WORD. You cannot simply concatenate two SFB2 blocks and expect a 32-bit result. The two blocks must be cascaded with explicit carry logic, or you must abandon SFB2 and write a custom FB that stores the count in a DINT (32-bit) tag directly.

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.

MMC vs. RAM load memory. All S7-300 CPUs require a Micro Memory Card (MMC) for retentive and non-retentive load memory. The 315-2 DP does not have integrated load memory; the MMC is the only storage. Pick an MMC of at least 512 KB to keep room for the project plus recipe data.

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

  1. In HW Config, drag the CP343-1 from the catalog onto the DIN rail at slot 4 of the S7-300 station.
  2. Set the IP address (e.g. 192.168.0.10), subnet mask (255.255.255.0), and connect it to an Ethernet subnet object.
  3. Set the CP343-1 operating mode to "S7 communication" or "S7 communication + open communication" depending on whether the server uses S7 or raw TCP.
  4. 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:

  1. Open Tag Management → SIMATIC S7 PROTOCOL SUITE → TCP/IP.
  2. 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.
  3. Add tags: Counter_01 = DB100,DBD0, datatype DINT32; Counter_02 = DB101,DBD0; … through Counter_80 = DB179,DBD0.
  4. Bind a numeric I/O field to each tag; set format string to +999999999 to 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

  1. Build the project. Compile FB100 and all 80 instance DBs. Use the STEP 7 menu PLC → Compile and Download Objects.
  2. Download HW Config. Stop the CPU, download the hardware configuration, restart in RUN.
  3. Monitor DBs online. Open DB100 in Monitor/Modify and force I0.0 = TRUE once. Verify DB100.DBD0 increments by 1.
  4. 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.
  5. 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.
  6. Reset test. Pulse I0.1 (reset). DB100.DBD0 must be 0 within one OB1 cycle.
  7. 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).
  8. 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.
  9. CP343-1 communication test. From WinCC, force Counter_01 to a known value and verify the I/O field updates within the configured acquisition cycle (default 1 s).
  10. 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.
Standards reference. IEC 61131-3 defines the standard counter function blocks (CTU, CTD, CTUD) with INT, DINT, and LINT (where supported) preset/current types. S7-300 only supports the INT variant of SFB2 natively; the DINT and LINT variants are user-implemented. Verify against IEC 61131-3:2013 section 6.5.2 (standard function blocks).

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.

Back to blog