WinCC V16 Pro VB Script: Edge Detection and Date Format

David Krause20 min read
SiemensTutorial / How-toWinCC
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

Problem Overview

A WinCC Professional V16 VB script bound to a 250 ms scheduled trigger reads a digital HMI tag on every cycle. If the tag remains TRUE for longer than 250 ms - which is the typical case for a manual push-button press that lasts 200 ms to 2 s - the same TRUE value is read on 2 to 8 consecutive ticks. A naive implementation such as

If SmartTags("InputTag1") = True Then
    SmartTags("PulseCounter") = SmartTags("PulseCounter") + 1
End If

therefore increments the counter on every 250 ms slice in which the input is high, producing a count of 4 to 8 per single physical actuation. The standard remediation is rising-edge detection: latch the previous state of the input in a separate Boolean tag and increment the counter only on the FALSE to TRUE transition. The same problem appears when the trigger is the AND of two inputs; the latch must follow the combined signal, not a single input, otherwise the detector falls out of step with the condition it is meant to monitor.

A separate but related requirement is the regional format of date and time. WinCC Runtime Professional reads its short date and short time formats from the Windows operating system locale of the runtime PC. A US-English default yields MM/dd/yyyy and a 12-hour clock; switching the locale at the OS level, or formatting the strings inside the VB script, produces dd/MM/yyyy and 24-hour output.

Symptoms and Root Cause Mapping

Observed Symptom Likely Root Cause Fix Class
Counter increments by N (where N = press duration / 250 ms) for a single button press No edge detection; direct read of input Add state-latch edge detector
Counter increments once but does not increment again on the next press Latch written before the IF, so the comparison always sees prev = curr Move latch assignment to last line
Counter never increments even though the input is observed TRUE in the debugger Internal memory tag is a PLC tag, which is overwritten on every scan Create the latch as an internal HMI tag (PLC connection: none)
Counter increments while both inputs are held Edge latch follows a single input, not the AND result Latch the combined Boolean
Date displays as 07/03/2025 but the day is the 3rd of July Windows regional format set to English (United States) Change OS locale or use VB override
Time displays as 1:30:00 PM OS short-time format set to h:mm:ss tt Change OS short-time to HH:mm:ss

Prerequisites and Environment

Before applying the code blocks below, confirm the following engineering environment.

  • Engineering station: TIA Portal V16 (Update 7 or later recommended; Update 7 corresponds to WinCC Professional V16.0.7).
  • Runtime target: WinCC Runtime Professional V16 on a PC station. Supported host operating systems include Windows 10 IoT Enterprise LTSC 2019 and Windows Server 2019 / 2022.
  • Project structure: the HMI device has been added to the TIA project and the HMI tag table is accessible from the project tree.
  • Authorisation: the user has at least Engineering rights on the HMI project to edit scripts and triggers.
  • Project text language set to English (United States) or another locale - the project language affects the VBA / VBS editor but not the runtime display of date and time.

Tags Required

Tag Name Type Connection Length / Range Purpose
InputTag1 Bool PLC tag (e.g. %DB5.DBX0.0) or local HMI bit 1 bit Digital input to be counted
InputTag2 Bool PLC tag or local HMI bit 1 bit Second digital input for AND condition (optional)
EdgeMemory Bool Internal (no PLC connection) 1 bit State latch for rising-edge detector
PulseCounter DInt Internal -2 147 483 648 to 2 147 483 647 Accumulated count
DisplayDate String Internal 10 chars (Unicode-8) Output of formatted date string
DisplayTime String Internal 8 chars Output of formatted time string
FirstRun Bool Internal, persistent 1 bit One-shot initialisation flag for the latch
Tags with a PLC connection update on every PLC scan. The edge latch must be an HMI internal tag, otherwise the PLC will overwrite the latched value on every cycle and the detector becomes a no-op.

Trigger Configuration

The 250 ms scheduler is configured in the script properties dialog under the Triggers tab:

  1. Open the HMI script in the TIA Portal editor.
  2. Switch to the Triggers tab.
  3. Click Add, choose Scheduled.
  4. Set cycle time to 250 ms. The minimum cycle on a PC runtime is 100 ms; on a Comfort panel the minimum is 1 s and the 250 ms cycle is not available.
  5. Click OK to apply.

Once compiled and downloaded, WinCC Runtime Professional invokes the script on the rising edge of each 250 ms tick regardless of whether the input tag changed. The script is the master of its own cadence; the input value is a passive source. 250 ms is well above the typical 1 to 5 ms execution time of a 10-line script and gives 200 to 250 ms of pulse-counting response latency - acceptable for a manually pressed push-button.

Edge Detection Theory

A digital signal can be in two states (FALSE, TRUE) and can transition between them. The two transitions are conventionally called rising edge (FALSE to TRUE) and falling edge (TRUE to FALSE). Counting events on a digital input means counting rising edges, not counting TRUE states; an event has a beginning (the rising edge) and an end (the falling edge) and only the beginning is what we want to count.

In a cyclic script the only way to detect a rising edge is to remember the state at the previous cycle. The latched value is called the previous or history variable. The detection rule is:

rising_edge := (current = TRUE) AND (previous = FALSE)

The same pattern translates directly to a combined signal. If two inputs must be high together, define combined = input1 AND input2 and apply the same rule to combined and previous_combined. The two-input case is mathematically a single-input case on a derived signal.

Forgetting to update the previous variable is the single most common error and produces a counter that never increments; updating the previous variable before the comparison is the second most common error and produces a counter that increments only on the first press and never again.

Single-Input Rising Edge Code

The minimal robust block for WinCC V16 Pro with a 250 ms trigger is:

' WinCC V16 Pro - single-input rising-edge pulse counter
' Trigger: 250 ms scheduled task
Dim bCurrent
Dim bPrevious
Dim nCount

bCurrent  = SmartTags("InputTag1")
bPrevious = SmartTags("EdgeMemory")
nCount    = SmartTags("PulseCounter")

If (bCurrent = True) And (bPrevious = False) Then
    SmartTags("PulseCounter") = nCount + 1
End If

SmartTags("EdgeMemory") = bCurrent

Line-by-line analysis:

  1. Lines 4 to 6 read the three relevant tags into local VBScript variables. SmartTags(...) is the WinCC object that reads or writes the value of an HMI tag; the result of a read is a Variant of the appropriate type. Local variables keep the comparison consistent even if the underlying connection updates mid-script.
  2. Line 8 is the rising-edge test. Explicit = True and = False comparisons are used because VBScript treats 0 as False, non-zero as True, and an HMI tag whose PLC connection is in the initial-value or substitute-value state can return a non-boolean Variant whose implicit truthy test still passes.
  3. Line 9 increments the counter. The new value is written back to the HMI tag immediately, not to the local variable nCount, because subsequent cycles will re-read the tag fresh.
  4. Line 12 is the latch update. It must be the last statement of the script. If it is moved to the top, the comparison on the next cycle reads previous = current and the IF never fires again.

Counter reset to zero is a one-liner that can be added to the same script (gated on a tag from a reset button) or invoked from a separate event-driven script:

If SmartTags("ResetRequest") = True Then
    SmartTags("PulseCounter")   = 0
    SmartTags("EdgeMemory")     = False
    SmartTags("ResetRequest")   = False
End If
Resetting the counter does not require resetting the edge latch, but doing so prevents a false edge from being registered on the very next cycle if the input is already TRUE at the moment of reset.

Dual-Input AND Edge Counter

When two inputs must be TRUE simultaneously to qualify as a valid pulse, the combined Boolean is computed and the rising-edge detector is applied to the combined signal. Field implementations that fail to latch the combined signal will miscount on the second and subsequent presses, because the detector drifts out of step with the AND condition it is meant to monitor. The clean single-block implementation is:

' WinCC V16 Pro - dual-input rising-edge counter (AND condition)
Dim bIn1, bIn2
Dim bCombined
Dim bPrevious
Dim nCount

bIn1       = SmartTags("InputTag1")
bIn2       = SmartTags("InputTag2")
bCombined  = (bIn1 = True) And (bIn2 = True)
bPrevious  = SmartTags("EdgeMemory")
nCount     = SmartTags("PulseCounter")

If (bCombined = True) And (bPrevious = False) Then
    SmartTags("PulseCounter") = nCount + 1
End If

SmartTags("EdgeMemory") = bCombined

Three rules apply:

  1. Always latch the combined signal, never a single input. Latching InputTag1 alone while testing the AND result is the most common cause of count-1-only behaviour in the field.
  2. The latch is the last statement. If it is moved to the top of the script, the comparison on the next cycle reads previous = current and the IF never fires again.
  3. If the inputs can be released and re-asserted within the same 250 ms tick, the basic detector may miss the second event; use the state-machine variant below.

State-Machine Counter for Sustained Overlap

When both inputs can be held high for arbitrarily long and a new edge must be detected only after the overlap ends, a two-state machine is more explicit than a single Boolean latch:

' Two-state edge detector for sustained AND overlap
Dim bIn1, bIn2
Dim bCombined, bLatched
Dim nCount

bIn1      = SmartTags("InputTag1")
bIn2      = SmartTags("InputTag2")
bCombined = (bIn1 = True) And (bIn2 = True)
bLatched  = SmartTags("EdgeMemory")
nCount    = SmartTags("PulseCounter")

' State: ARMED -> count edge -> LATCHED
If (bCombined = True) And (bLatched = False) Then
    SmartTags("PulseCounter") = nCount + 1
    SmartTags("EdgeMemory")   = True
End If

' State: LATCHED -> release when both inputs drop -> ARMED
If (bCombined = False) And (bLatched = True) Then
    SmartTags("EdgeMemory")   = False
End If

The counter increments exactly once per overlap window, regardless of how long the overlap is held. The state machine is preferred over an edge detector on the falling edge of the AND result because it avoids the need for a one-tick delay between release and re-arm.

ARMED EdgeMemory = FALSE LATCHED EdgeMemory = TRUE bCombined = TRUE Counter = Counter + 1 bCombined = FALSE

Counter Persistence and Bounds

Internal HMI tags default to volatile: they lose their value when the runtime is stopped. For an industrial counter that must survive a power cycle of the panel PC, open the HMI tag PulseCounter in the tag table and set the Persistance property to Persistent. WinCC Runtime Professional saves the tag value to <Project>\<Runtime>\Persistence\<TagName>.pvc on shutdown and reloads it on start. For an upper bound that prevents a 32-bit overflow on a long-running system:

If SmartTags("PulseCounter") >= 9999 Then
    SmartTags("PulseCounter") = 0
End If

The cap is set to 9999 to match a typical four-digit 7-segment display; for a 6-digit display, raise it to 999999. Internal DInt tags can hold up to 2 147 483 647, which is reached in roughly 24 days at 1000 counts per second.

A persistent counter combined with a non-persistent edge latch can disagree on the first cycle after a start-up. The simplest fix is a one-shot initialisation that seeds the latch from the current input state:

If SmartTags("FirstRun") = False Then
    SmartTags("EdgeMemory") = SmartTags("InputTag1")
    SmartTags("FirstRun")   = True
End If

Date Format Configuration: MM/dd/yyyy to dd/MM/yyyy

WinCC Runtime Professional uses the Windows operating system regional settings for the short date and short time formats. The project itself does not override these; only the runtime PC's locale does. A North American Windows install therefore defaults to M/d/yyyy, which displays as 7/3/2025 for the 3rd of July, easily confused with the 7th of March.

Method 1 - Windows Regional Settings (project-wide fix)

  1. On the runtime PC, open Settings, then Time and Language, then Region (Windows 10/11) or Control Panel, then Region (Windows Server / LTSC builds).
  2. Click Additional date, time, and regional settings or Additional settings.
  3. On the Formats tab, change Short date from M/d/yyyy to dd/MM/yyyy.
  4. Click Apply. The change is previewed in the example field.
  5. Click OK to close.
  6. Close and re-launch WinCC Runtime Professional - the locale is read at process start, not on every tag update.
On a multi-user runtime PC, set the format in Welcome screen and system accounts from the Administrative tab so it is applied to all users, not only the currently signed-in user. A locale applied to the user account but not to the system account reverts to MM/dd/yyyy whenever WinCC Runtime is launched as a service.

Method 2 - VB Script Override (per-tag)

When only a single I/O field should show a non-default format, the script can build the string explicitly using Day, Month and Year:

' Custom date string dd/MM/yyyy, independent of OS locale
Dim sDate
sDate = Right("0" & Day(Now), 2)   & "/" & _
        Right("0" & Month(Now), 2) & "/" & _
        Year(Now)
SmartTags("DisplayDate") = sDate

The Right("0" & n, 2) trick pads single-digit days and months to two characters; Day(Now) returns 1 to 31, Month(Now) returns 1 to 12, and Year(Now) returns a four-digit year. For the ISO 8601 form yyyy-MM-dd used in many log files:

SmartTags("DisplayDate") = Year(Now) & "-" & _
    Right("0" & Month(Now), 2) & "-" & _
    Right("0" & Day(Now), 2)

Time Format Configuration: 12-Hour to 24-Hour

The 12-hour clock with AM/PM suffix is a Windows locale setting in the same way the date is. The two practical methods are identical in structure.

Method 1 - Windows Regional Settings

  1. Open Region, then Additional settings as above.
  2. Change Short time from h:mm:ss tt to HH:mm:ss. Capital H specifies 24-hour; lower-case h specifies 12-hour. The tt suffix is the AM/PM marker.
  3. Apply and re-launch WinCC Runtime.

Method 2 - VB Script Override

' 24-hour time string HH:mm:ss
Dim sTime
sTime = Right("0" & Hour(Now), 2)   & ":" & _
        Right("0" & Minute(Now), 2) & ":" & _
        Right("0" & Second(Now), 2)
SmartTags("DisplayTime") = sTime

Note that Hour(Now) in VBScript always returns a value in the range 0 to 23, regardless of the OS locale. The 12-hour format is purely a display setting applied to the formatted string by the OS; the underlying Date value is a Double whose day fraction follows the 24-hour convention. Therefore Right("0" & Hour(Now), 2) produces a correct 24-hour display on any Windows locale, even before the regional setting has been changed.

Combined Date and Time Output

A common requirement is a single timestamp string of the form dd/MM/yyyy HH:mm:ss for use in audit logs or message text:

SmartTags("Timestamp") = Right("0" & Day(Now), 2) & "/" & _
    Right("0" & Month(Now), 2) & "/" & _
    Year(Now) & " " & _
    Right("0" & Hour(Now), 2) & ":" & _
    Right("0" & Minute(Now), 2) & ":" & _
    Right("0" & Second(Now), 2)

Parameter Passing and Sub-Routines: ByRef vs ByVal

WinCC VB scripts can call sub-routines defined either in the same project or in a global module. The parameter direction is configured per parameter, and the runtime behaviour follows the standard VBScript rule: ByVal passes a copy of the value, ByRef passes a reference to the original storage. When a sub-routine is declared with ByRef for an HMI tag parameter, modifications inside the sub-routine propagate back to the HMI tag system; with ByVal, modifications are lost on return. The official WinCC V16 to V20 documentation describes this behaviour in the System functions and scripts - WinCC readme.

' Sub-routine with ByRef output parameter
Sub IncrementCounter(ByRef nCount)
    nCount = nCount + 1
End Sub

' Caller
Dim nC
nC = SmartTags("PulseCounter")
IncrementCounter nC
SmartTags("PulseCounter") = nC

If the same sub-routine is declared ByVal nCount, the increment is lost on return and the HMI tag value is unchanged. The edge detector above does not use sub-routines and so is not affected by this distinction; it is included here because it is the most common parameter-direction mistake when a maintenance engineer refactors a working script into a library of helpers.

Performance and Alternative Implementations

The 250 ms script completes in well under 5 ms on a PC runtime and consumes under 2 % of one CPU core on a modern Intel Core i5/i7. The cycle time is therefore dictated by the desired pulse-counting response latency, not by the runtime cost of the script. For applications that need to count inputs faster than 250 ms - for example, encoder pulses at 1 kHz - the VB script is the wrong tool; the count must be done in the PLC (using a high-speed counter or a technology object) and read into WinCC as a periodically refreshed value.

Method Best Use Case Limitations
PLC high-speed counter (technology object) Encoder pulse counting, frequency measurement, metering Requires a CPU with high-speed counter inputs; tied to specific HW
C script (ANSI C) in WinCC Computationally heavy logic, bit-manipulation, large loops Less common skill in the maintenance team; longer development
VB script with state-latch edge detector (this article) Manual push-button pulse counting at human-press speeds (up to 4 Hz) Cycle time is limited by the scheduler (100 ms on PC, 1 s on Comfort)
WinCC system function InvertBit or arithmetic on tag Simple increment on tag change No edge detection on its own; needs a separate latch tag and a tag-change trigger

For the WinCC V16 Pro use case of a single HMI push-button counted at human-press speeds, the VB script pattern documented above is the correct tool. Move the count into the PLC only when the input frequency exceeds 10 Hz or when the count must remain coherent across an HMI restart without relying on persistent tags.

Validation and Best Practices

Manual Validation Procedure

  1. Compile and download the project to the runtime PC or simulator (RT Professional).
  2. Start the runtime and open an I/O field bound to PulseCounter.
  3. Press the input button once for approximately 200 ms.
  4. Verify that the counter increments by exactly 1.
  5. Hold the button for 2 s; verify the counter increments by exactly 1 (not 8).
  6. Release the button for at least 1 s; press again; verify the counter increments again by 1.
  7. Stop the runtime and re-start it; verify the counter retains its value (Persistance = Persistent).
  8. Open the date and time I/O fields and verify the format matches dd/MM/yyyy and HH:mm:ss.

Code-Review Checklist

  • The latch assignment is the last executable line of the script.
  • The latch tag is an HMI internal tag with no PLC connection.
  • The trigger is a 250 ms scheduled task, not a tag-change trigger on the input.
  • Explicit = True / = False comparisons are used in the IF condition.
  • Local variables are used for repeated SmartTags reads inside the same cycle.
  • The counter tag has Persistance = Persistent if it must survive a power cycle.
  • The counter has an explicit upper bound to prevent overflow.
  • Date and time formatting uses OS-level locale changes for the project; script formatting is reserved for tags that intentionally deviate (e.g. ISO 8601 log entries).

Troubleshooting Matrix

Symptom Most Likely Cause Diagnostic Step Corrective Action
Counter increments by N per press, N > 1 No edge detection; direct read of input Inspect the script body for the IF Add the state-latch edge detector pattern
Counter never increments Latch assignment before the IF, so the comparison always sees prev = curr Confirm the latch write is the last statement Re-order the script so the latch is the last line
Counter increments only on the first press EdgeMemory is a PLC tag and is overwritten each cycle Open the tag table, look at the connection column Recreate the tag as an HMI internal tag with no PLC connection
Counter fires on the falling edge too Detection rule is wrong; Not prev used by mistake Inspect the IF condition Use the exact form curr = True And prev = False
Counter drifts upward by 1 on every runtime restart Persistent counter and an initial-TRUE input Observe EdgeMemory and the input on start-up Initialise the latch in the script at first run, or wire the input through a normally-open contact
Counter goes to 0 on every restart Tag persistence not set Check the Persistance column of the tag Set Persistance = Persistent
Date still MM/dd/yyyy after Windows change WinCC Runtime not restarted Task Manager: confirm the runtime process restarted Close and re-launch the runtime
Date correct for the signed-in user but wrong for the system account Locale applied to current user only Region, then Administrative, then Welcome screen and system accounts Copy settings to the system account and the welcome screen
Time still shows AM/PM after Windows change Short-time format still h:mm:ss tt Region, then Additional settings, then Short time Set to HH:mm:ss
Type mismatch on first script cycle Tag does not exist or has wrong data type Check the HMI tag table for the tag spelling Create the tag or correct the spelling
Script does not run at all Trigger not configured, or runtime project not compiled and downloaded Open the script in the runtime debugger Configure the 250 ms trigger, recompile, redownload
Counter increments asynchronously with the press Trigger is tag-change based and the tag is updated faster than the cycle Inspect the Triggers tab of the script Use the scheduled 250 ms trigger instead of a tag-change trigger
Counter overshoots at the exact moment of button release Input has mechanical bounce, latch captures one of the bounces Observe the input with a trace Add a de-bounce timer in the PLC or use a hysteresis filter on the input
Sub-routine returns but the HMI tag is unchanged Parameter declared ByVal instead of ByRef Inspect the Sub signature in the script editor Change the parameter direction to ByRef

FAQ

Why does my counter increment more than once for a single button press in WinCC V16?

Because a 250 ms script reads the input on every cycle and the input remains TRUE for longer than one cycle. Add a rising-edge detector: read the input into a local variable, read a separate internal Boolean tag that holds the previous state, increment the counter only when the current value is TRUE and the previous was FALSE, and write the current value into the latch as the last line of the script.

How do I detect a rising edge on the AND of two HMI tags in WinCC V16 Pro?

Compute a local Boolean bCombined = (InputTag1 = True) And (InputTag2 = True), then apply the same rising-edge rule to bCombined and the previous latched value. Latch the combined result, not a single input, otherwise the detector drifts out of step with the condition it is supposed to monitor.

How do I change the date from MM/dd/yyyy to dd/MM/yyyy in WinCC Runtime Professional?

Open the Windows regional settings on the runtime PC, choose Additional settings, set Short date to dd/MM/yyyy, apply, and re-launch the runtime. For a per-tag override, format the date inside the VB script with Day(Now), Month(Now), and Year(Now).

How do I switch the clock from 12-hour to 24-hour format in WinCC V16?

Set the Windows short-time format to HH:mm:ss (capital H) and restart runtime, or build the string in the script with Right("0" & Hour(Now), 2) & ":" & Right("0" & Minute(Now), 2) & ":" & Right("0" & Second(Now), 2). Hour(Now) always returns 0 to 23, so the 24-hour value is correct regardless of the display format.

Does a 250 ms VB script in WinCC Runtime Professional add noticeable CPU load?

No. A script that reads a handful of Boolean tags, performs two comparisons, and writes two tag values typically completes in well under 5 ms on a PC runtime. CPU overhead is under 2 % on a modern Core i5/i7, so the 250 ms cycle is dictated by the desired pulse-counting response latency, not by the runtime cost of the script itself.

Back to blog