Problem Description
A WinCC Runtime (WinCC RT, WinCC RT Professional, or PCS 7 OS) project uses a button event (typically OnLButtonDown or OnClick) to start a motor. The motor logic in the AS (PLC) only acknowledges a rising edge (0 → 1) on the start tag. A single Visual Basic Script on the button must therefore write 0 first, then 1, so that any residual 1 from a previous latched state is cleared and a fresh positive edge is generated.
The script as written never reaches the PLC: only the value 0 is delivered; the trailing Tag.Write 1 is silently lost. Symptoms include:
- Clicking Start does not start the motor.
- Clicking Stop followed by Start works.
- Manually pressing Start only works after a previous successful stop has cleared the bit.
- Inserting a
MsgBoxbetween the two writes "magically" makes the script work. - Splitting the two writes across OnLButtonDown and OnLButtonUp (mouse press / release) also works, but is unreliable when the user releases the button outside the control.
The defect is in the runtime write semantics, not in the ladder/CFC logic.
Root Cause Analysis
Three independent mechanisms conspire to drop the second write. All three must be addressed for a robust fix.
1. Asynchronous Tag.Write
The VBS method HMIRuntime.Tags("Tag").Write queues the value into the WinCC data manager; it does not block until the value has been transmitted to the AS. The next line of script executes immediately. When two writes are issued back-to-back against the same tag within microseconds, the second write overwrites the first in the queue before the data manager has had a chance to dispatch the first value to the PLC.
Consequence: only the last assignment in the script survives — in this case Tag.Value = 1 is written, but the prior forced write of 0 is lost, so no edge is generated if the previous value was already 1.
2. PLC Cycle Time and Read-Back Race
When the script does a Tag.Read immediately after Tag.Write 0, the readback returns the value held in the local WinCC tag image, not the value currently latched in the AS. With a typical PCS 7 / S7-300 OB1 cycle of 100 ms, the value 0 may not yet be confirmed in the WinCC image by the time the second write fires, so the conditional If Tag.Value = 0 Then Tag.Write 1 branch is never entered.
3. WinCC Data Manager Cycle
The data manager flushes pending tag updates on its own cycle (default 1000 ms / 1 s for standard projects, configurable). Two rapid-fire writes inside one script tick collapse into a single delta event in the tag log because the second write cancels the first before transmission. Increasing the script-trigger cycle does not solve this; only a synchronous write primitive can.
Tag.Write calls of the same tag to produce a deterministic edge in the PLC. Use the Wait variants of the C-function API, or use the global VBS Action approach with a debounced toggle tag.Affected Products and Versions
| Product | Component | Status |
|---|---|---|
| SIMATIC WinCC V7.4 / V7.5 / V7.5 SP1 / V7.5 SP2 | Graphics Designer VBS | Affected (asynchronous Tag.Write) |
| SIMATIC WinCC RT Professional V13 / V14 / V15 / V15.1 / V16 / V17 | HMI Tags / VBScripts | Affected (asynchronous Tag.Write) |
| SIMATIC PCS 7 V8.1 / V8.2 / V9.0 / V9.1 | OS Runtime VBS | Affected (asynchronous Tag.Write, OB1 cycle interaction) |
| WinCC Comfort / WinCC Advanced (Comfort Panels, TP/MTP) | VBS on panels | Affected (same model) |
| WinCC Unified (V16+) | JavaScript runtime | Different model — uses await HMIRuntime.Tags.SysFct.SetTagValueWait()
|
Siemens documents the asynchronous write behavior in FAQ entry 18943009 on the Siemens Industry Online Support portal — see Why are values not written to / read from the PLC by means of WinCC scripts?
Workaround 1 — Two-Event Split (OnLButtonDown / OnLButtonUp)
The simplest workaround is to split the two writes across mouse press and mouse release events. The human interaction time between down and up (typically 80–300 ms) gives the data manager enough time to flush the first write.
' Button event: OnLButtonDown (Mouse Action)
Sub OnLButtonDown(ByVal Item, ByVal Flags, ByVal x, ByVal y)
HMIRuntime.Tags("MOTOR_CMD").Write 0
End Sub
' Button event: OnLButtonUp (Mouse Action)
Sub OnLButtonUp(ByVal Item, ByVal Flags, ByVal x, ByVal y)
HMIRuntime.Tags("MOTOR_CMD").Write 1
End Sub
Drawback: If the user drags the cursor off the control before release (common in operator HMI environments), OnLButtonUp does not fire, and the motor command stays at 0, latched off. This is unsafe for a motor-start HMI.
Workaround 2 — MsgBox Delay
A MsgBox between the two writes blocks the script long enough for the data manager to transmit. Engineers sometimes use this as a diagnostic step, not a production solution:
Sub OnLButtonDown(ByVal Item, ByVal Flags, ByVal x, ByVal y)
Dim Tag, OK
Set Tag = HMIRuntime.Tags("CFC_variable")
Tag.Value = 0
Tag.Write, 1 ' force flush, no read-back
Tag.Read
If Tag.Value = 0 Then
OK = MsgBox("Start motor?", 308, "Confirm")
If OK = 6 Then Tag.Write 1
End If
End Sub
Drawback: A modal MsgBox is unacceptable in a process HMI: it blocks operator acknowledgement, cannot be localized through the WinCC text library, violates GAMP 5 / 21 CFR Part 11 audit trail requirements, and confuses the operator because the focus leaves the control.
Workaround 3 — Global VBS Action with Toggle Tag
This pattern uses a separate boolean edge-request tag (named iSTRT in the field report) that triggers a global action. The action reads the current motor state and flips it through a debounced state machine.
Tag configuration:
| Tag | Type | Direction | Purpose |
|---|---|---|---|
| MOTOR_CMD | Bool | HMI → AS | Actual start/stop command to PLC |
| iSTRT | Word (or Bool) | Internal | Edge-request debounced by the global action |
| iSTRT (trigger) | — | — | Scheduled trigger; standard cycle ≥ 500 ms |
Button configuration (no VBS required on the button):
- Button Start: Mouse → Mouse Action → Direct connection → set
iSTRT= 1 - Button Stop: Mouse → Mouse Action → Direct connection → set
MOTOR_CMD= 0
Global VBS Action (trigger: iSTRT, cycle 500 ms):
Option Explicit
Function action
Dim motor, i
Set motor = HMIRuntime.Tags("MOTOR_CMD")
motor.Read
Set i = HMIRuntime.Tags("iSTRT")
i.Read
If i.Value <> 0 Then
If motor.Value = 1 Then
' First pass: clear the bit
motor.Value = 0
motor.Write, 1 ' force immediate write
i.Value = i.Value + 1 ' request the rising edge on the next cycle
i.Write
If i.Value > 2 Then
i.Value = 0 ' safety clamp
i.Write
End If
Else
' Second pass: rising edge delivered
motor.Value = 1
motor.Write
i.Value = 0
i.Write
End If
End If
End Function
The 500 ms cycle guarantees the data manager has flushed 0 before 1 is queued. Increase the cycle to 1000 ms if the AS scan is sluggish, but never drop below 500 ms.
Drawback: Adds a global scheduler, two extra tags, and a 500 ms minimum latency between press and motor start. Not acceptable for fast jog commands.
Recommended Solution — Synchronous C Functions (SetTagXXXWait)
The clean, deterministic fix is to replace the asynchronous VBS Tag.Write with the synchronous C-function equivalent from the WinCC API. These functions block the calling thread until the value has been confirmed by the data manager (and, with the Wait suffix, until the next AS cycle has latched the value).
Siemens exposes the following synchronous primitives for Bool tags:
| Function | Header | Return | Behavior |
|---|---|---|---|
SetTagBitWait(tag, value) |
apdefap.h | BOOL | Writes a Boolean tag and blocks until acknowledged |
SetTagByteWait(tag, value) |
apdefap.h | BOOL | 8-bit signed |
SetTagWordWait(tag, value) |
apdefap.h | BOOL | 16-bit signed |
SetTagDWordWait(tag, value) |
apdefap.h | BOOL | 32-bit signed |
SetTagFloatWait(tag, value) |
apdefap.h | BOOL | 32-bit IEEE-754 |
SetTagRawWait(tag, pValue, len) |
apdefap.h | BOOL | Raw byte buffer |
To use these in a button event, expose them through a project-wide C function or call them directly from a global action written in ANSI-C (WinCC V7.x) or a C# wrapper in WinCC Unified (V16+). For a VBS-only project, create a small C function in the project, then call it from VBS using HMIRuntime.SysFct....
Implementation: C function in the WinCC project
Open Graphics Designer → C scripts → project functions. Add a new function:
// Project function: PulseMotorCmd(LPCTSTR lpszTagName)
// Writes 0, waits for acknowledgement, then writes 1.
#include "apdefap.h"
void PulseMotorCmd(LPCTSTR lpszTagName)
{
// Step 1: clear the bit and wait for the data manager to flush it
SetTagBitWait(lpszTagName, 0);
// Step 2: small grace period (AS scan-time dependent, default 50 ms)
Sleep(50);
// Step 3: deliver the rising edge
SetTagBitWait(lpszTagName, 1);
}
Reference the C-function documentation at WinCC V7.5 - Scripting: VBS, ANSI-C, VBA for the full API and the apdefap.h header definition. The SetTagBitWait semantics are formally described in the WinCC Information System under Runtime API > C-Scripting > Tag functions > Synchronous tag access.
Calling the C function from a VBS button
Sub OnLButtonDown(ByVal Item, ByVal Flags, ByVal x, ByVal y)
HMIRuntime.SysFct.PulseMotorCmd "MOTOR_CMD"
End Sub
This is the production-grade fix: single button event, deterministic edge, no MsgBox, no global scheduler, no reliance on human timing.
Alternative: Pure-VBS Pulse Using a Hidden Trigger Tag
When C functions are not available (e.g., WinCC RT Professional on TIA Portal with restricted scripting), the following pure-VBS pattern produces a clean 0→1 pulse by writing the trigger and letting a scheduled global action perform the actual edge. This is the most portable workaround.
' Global action triggered on iSTRT, cycle 250 ms
Sub PulseTrigger()
Dim cmd, req, ack
Set cmd = HMIRuntime.Tags("MOTOR_CMD")
Set req = HMIRuntime.Tags("iSTRT")
Set ack = HMIRuntime.Tags("iSTRT_ACK")
cmd.Read
req.Read
ack.Read
If req.Value = 1 And ack.Value = 0 Then
If cmd.Value = 0 Then
' We are already low; deliver the edge
cmd.Write 1, 1
ack.Write 1
Else
' Force low first; next cycle raises the edge
cmd.Write 0, 1
End If
End If
End Sub
The ack tag is reset from the AS on receipt of the rising edge, releasing the latching so the operator can re-trigger.
Step-by-Step Commissioning Procedure
- Open the WinCC project in Graphics Designer (or TIA Portal → HMI for RT Professional).
- Create the output tag
MOTOR_CMDof type Bool, mapped to the AS address (e.g.,DB100.DBX0.0). Update cycle = 100 ms. - If using the C-function route, add the project function
PulseMotorCmdshown above. Compile. - Open the Start button properties → Events → Mouse → Mouse Action → Direct connection to call
HMIRuntime.SysFct.PulseMotorCmd "MOTOR_CMD", or a small VBS that calls the function. - Open the Stop button → Direct connection → set
MOTOR_CMD= 0. - In the PLC (STEP 7 / TIA Portal), implement the motor block as a positive-edge evaluation on
MOTOR_CMD, e.g., usingFPon a Bool, or a TON/TOF pulse generator on the rising edge. - Activate the Runtime. Click Start. The motor should start on every press, even if it was latched off due to a previous AS error.
Verification Checklist
| # | Check | Expected result |
|---|---|---|
| 1 | Click Start with motor stopped | Motor runs within one AS cycle (≤ 100 ms) |
| 2 | Click Start again while motor runs | Motor continues to run (idempotent press) |
| 3 | Click Start after AS-side fault and reset | Motor starts on the first press |
| 4 | Click Stop | Motor stops; tag MOTOR_CMD reads 0 |
| 5 | Drag the cursor off the Start button before release | Motor still starts (only OnLButtonDown is used) |
| 6 | Watch MOTOR_CMD in the tag tracing tool (WinCC TagMonitor / TIA Trace) |
Observe a clean 0 → 1 transition, not a 0 → 1 → 0 (i.e. no glitch) |
| 7 | Open WinCC Channel Diagnosis (apdiag) and inspect update / error counters | No WriteQueue overflow or Tag not found entries |
Troubleshooting Matrix
| Symptom | Likely cause | Fix |
|---|---|---|
| Only the value 0 reaches the PLC; 1 is never seen | Asynchronous Tag.Write collapses the two writes | Use SetTagBitWait C function |
Script works with MsgBox, fails without |
Modal delay lets the data manager flush 0 | Replace MsgBox with synchronous write |
| Script works on OnLButtonDown+OnLButtonUp, fails on OnLButtonDown alone | Human interaction time masked the bug | Use single-event synchronous write |
| Global action with 500 ms cycle sometimes drops the edge | AS cycle > trigger cycle | Increase cycle to 1000 ms; verify with TagMonitor |
| Tag readback always returns 1 regardless of script | Stale local image due to read-cache | Force Tag.Read with refresh; or use GetTagBitWait in C |
| Error Tag not found in Channel Diagnosis | Tag prefix / structure mismatch | Verify tag name spelling and project structure namespace |
| Works in RT, fails when RT Professional on TIA Portal | API surface differences | Use HMIRuntime.Tags.SysFct.SetTagValueWait in V16+ |
| Motor runs but no longer stops | Direct connection overwriting the VBS-driven reset | Use VBS for both Start and Stop, or move reset to a dedicated event |
Why the PLC-Side Cycle Matters
Even with synchronous C functions, the AS scan time determines the minimum separation between the 0 and the 1 writes. With a 100 ms OB1 cycle, a 50 ms Sleep() inside the C function is sufficient. With a 1000 ms CFC cycle (typical PCS 7), increase the grace period to at least 1100 ms, or — preferred — let the AS-side logic confirm the 0 before the HMI sends the 1. The most robust pattern is a small AS-side handshake:
// AS code (SCL, FB "MotorHmi")
IF HMI_Start_Pulse THEN
IF NOT Motor_Running THEN
Motor_Running := TRUE;
END_IF;
HMI_Start_Pulse := FALSE; // acknowledge and reset
END_IF;
The HMI then sets HMI_Start_Pulse for one cycle and the AS clears it; the rising edge is generated entirely on the AS side, eliminating the HMI-side race entirely.
Standards and Documentation References
Siemens documents the asynchronous write behavior in FAQ 18943009 — Why are values not written to / read from the PLC by means of WinCC scripts? — and the synchronous API in the WinCC Information System under ANSI-C for Creating Functions and Actions > Synchronous Tag Access. The full WinCC V7.5 SP2 scripting manual is available at SIMATIC HMI WinCC V7.5 - Scripting (VBS, ANSI-C, VBA). For TIA Portal WinCC Unified V17/V18, the equivalent is documented under JavaScript runtime API > HMIRuntime.Tags.SysFct.
Why does my WinCC VBS button send 0 to the PLC but never 1 when both writes are in the same script?
The WinCC data manager processes Tag.Write asynchronously. Two writes to the same tag within microseconds collapse into a single delta; the second value cancels the first before transmission. Use a synchronous C function such as SetTagBitWait with a short delay, or split the writes across a global action with a 500–1000 ms cycle.
Why does adding a MsgBox between the two writes make the script work?
The modal MsgBox blocks the VBS thread long enough for the data manager to flush the first write of 0 to the PLC. Once the user clicks OK, the second write of 1 sees a clean state. A MsgBox is a diagnostic clue, not a production solution; it blocks operator focus and cannot be integrated with text library translation.
What is the difference between Tag.Write and SetTagBitWait in WinCC?
Tag.Write (VBS / HMIRuntime.Tags(...).Write) is asynchronous — it queues the value and returns immediately. SetTagBitWait (ANSI-C, declared in apdefap.h) is synchronous — it blocks the calling thread until the WinCC data manager confirms the write succeeded. Use the latter whenever two writes to the same tag must both reach the PLC.
Can I use the OnLButtonUp event instead of OnLButtonDown to write 1?
Yes, and this is the classic WinCC 7 pattern. The drawback is that OnLButtonUp does not fire if the operator drags the cursor off the control before releasing the mouse, which silently latches the motor off. For a safety-relevant Start, prefer a single-event synchronous solution such as SetTagBitWait or the global-action edge generator.
How do I implement a 0-then-1 pulse in WinCC Unified (TIA Portal V16+)?
Use the JavaScript equivalent of the synchronous C functions: await HMIRuntime.Tags.SysFct.SetTagValueWait('MOTOR_CMD', 0), then a short awaitable sleep, then await HMIRuntime.Tags.SysFct.SetTagValueWait('MOTOR_CMD', 1). WinCC Unified's JavaScript runtime supports async/await, so the pattern is cleaner than in VBS.
Does the WinCC data manager cycle (default 1 s) affect the write?
Yes. Two rapid-fire Tag.Write calls inside a single script tick frequently collapse into one update event because the data manager has not yet flushed the first delta to the channel DLL / AS. Increasing the data manager cycle does not fix this — only a synchronous write primitive (SetTagBitWait or SetTagValueWait) or a multi-cycle global action will.
What is the safest AS-side pattern for handling a pulse command from WinCC?
Implement a positive-edge evaluation in the AS using a one-shot flip-flop (S/R with auto-reset, or a TON with PT = 1 cycle) on the rising edge of the HMI bit. The HMI should set the bit for one cycle, the AS evaluates the edge and sets the actual motor run bit, then the HMI clears the bit. This isolates the HMI from any race condition and is auditable.