Document scope: Siemens SIMATIC WinCC 7.5, 7.6, 8.0 (TIA Portal WinCC Professional) and WinCC Unified V17–V20. Topic: phantom operator messages on operator-driven setpoint changes across 100+ variables.
1. Problem Overview
A typical WinCC implementation captures operator-driven setpoint changes as follows:
- Operator types a new value into an I/O-Field bound to an external setpoint tag (e.g.
SP_Motor07). - A VBS scheduled action (commonly 250 ms or 1 s) reads the live external tag and compares it to a persistent internal tag holding the previous value.
- If the values differ, the action sets a single bit inside a 16-bit unsigned word (
MessageBitsWord). - Each bit in that word is wired to a discrete WinCC message whose process value blocks 2 and 3 are filled with the old and new value at the moment the bit transitions to 1.
The architecture scales to 114 variables by using one bit per variable, but introduces four race conditions that produce phantom messages. This article documents the five root causes and four engineered fixes, with verification steps, parameter tables, and migration guidance to WinCC Unified.
2. Root Cause Analysis
Five mechanisms produce the reported symptoms. The first three account for roughly 90% of field incidents on WinCC V7.5/7.6/8.0 and on TIA Portal WinCC Professional runtime.
2.1 Tag Acquisition Cycle Aliasing
WinCC polls external tags at a configured acquisition cycle (default 1 s; commonly 500 ms or 250 ms on production HMIs). The internal "old value" tag is read by a scheduled VBS action at the same cadence. Because the two reads occur inside the same VBS cycle but the HMI write and the PLC echo are asynchronous, the script can see a momentary non-difference even though the operator typed a new value, or vice versa.
Sequence that creates a phantom alarm:
| Step | Internal OldValue | Live Setpoint | Delta | Bit 2 set? | Message fired? |
|---|---|---|---|---|---|
| t0 (operator presses Enter on 1500) | 1450 | 1450 | 0 | 0 | no |
| t0+250 ms (HMI write completes) | 1450 | 1500 | 500 | 0 | no |
| t0+250 ms (script fires) | 1450 | 1500 | 500 | 1 | yes |
| t0+500 ms (PLC echo arrives) | 1500 | 1500 | 0 | 0 | no |
| t0+1 s (next cycle, momentary PLC drop) | 1500 | 1450 | -50 | 1 | yes (phantom) |
2.2 16-bit Bit Word Race Condition
When VBS performs a read-modify-write of a shared 16-bit word to set a single bit, every other bit in the same word must hold its state. If two scheduled actions interleave — action A reads the word, action B reads the word, A writes with bit X set, B writes with bit Y set — the second write clobbers the first. From the operator's perspective, a message fires for a variable that did not change, because the bit that was set corresponds to a different tag than the one the operator modified.
The classic mistake in C-style VBS:
' WRONG: read-modify-write of a shared word
Dim w, bits
Set w = HMIRuntime.Tags("MessageBits")
W.Read
bits = CLng(W.Value)
bits = bits Or &H0004 ' set bit 2
W.Value = bits
W.Write ' clobbers whatever else set bit 5
2.3 Process Value Block Formatting Truncation
Process value blocks 2 and 3 default to a 16-bit signed integer format (%d or %s with 16-bit width). If the setpoint tag is a 32-bit DINT or a 32-bit REAL, the high word of the value is truncated when the message is formatted. A real change from 32760 to -32760 displays as 32760 → 32760 (because the high word of both values is identical in the low-word-only view), and a real change from 1.5 to 1.5000001 displays as 1 → 1. Always size the process value block to the full native data type width.
2.4 HMI Tag Type vs. PLC Type Mismatch
If the WinCC external tag is configured as signed 16-bit and the PLC sends a REAL, the tag wraps on overflow. A change from 32767 to -32768 registers as a real change, but the displayed values in the message process value blocks can be identical because the conversion is lossy. Match the WinCC tag type to the PLC tag's actual data type, including width and signedness.
2.5 Trigger Bit Not Cleared Between Cycles
A bit-triggered WinCC message fires once per rising edge. If the operator changes the setpoint twice within one acquisition cycle, or if the script that sets the bit does not reset it before the next acquisition, the message fires on the second cycle with stale data, or fires twice for the same change. The standard pattern is:
- Detect change → set the bit
- Hold for one full acquisition cycle
- Clear the bit from the same script or from the PLC
Step 3 is frequently missing. Without it, the bit remains high and a second cycle interprets the same value as a new edge.
3. Diagnostic Flowchart
Use this decision tree to identify which cause is active in your project. The output drives which of the four solutions to apply.
4. Solution 1 — Built-in Operator Message on the I/O-Field
The cleanest single-variable fix is to activate the Operator Message property on the I/O-Field itself. WinCC then:
- Detects the operator-driven value change natively in the graphics engine
- Generates message number
12508141automatically - Populates process value block 2 with the previous value
- Populates process value block 3 with the new value
- Logs the operator's username and a millisecond time stamp
- Avoids the bit-trigger and VBS overhead entirely
Configuration steps:
- Open the HMI screen in the WinCC Graphics Designer (V7.x) or TIA Portal HMI editor (V15+).
- Select the I/O-Field linked to the setpoint tag (e.g.
SP_Motor07). - In the Properties pane, expand
Events > Output(orProperties > Operationfor TIA Portal). - Enable
Operator Message(TIA Portal label:Operator action). - Set the message class to
Operator message — value change. - Confirm that process value blocks 2 and 3 map to the old and new value.
- Compile and download to runtime.
Operation on the I/O field under Properties > Events. The trigger is configured as a Tag trigger with optional deadband. See the Siemens TIA Portal V20 help: Basics of alarm logging (WinCC Unified).5. Solution 2 — Robust Edge-Detection VBScript for 114 Variables
For installations where 114 individual I/O-Field operator messages would clutter the message log, replace the bit-trigger scheme with a single scheduled VBS action that performs strict edge detection per variable. The script must:
- Read the previous value from a persistent internal tag (DWord array, indexed by variable ID)
- Read the new value from the live external tag
- Compare them as their native data type (REAL, DINT, BOOL)
- Only emit a message if the value truly changed, with a configurable deadband
- Update the persistent history atomically after the message is fired
- Use the WinCC alarm API (
HMIRuntime.Alarmin V7, or alarm trigger in Unified) for logging, not a bit word
Sketch for a single variable — scale to 114 by looping an array. The 114 setpoint tags are assumed to be S7_PLC_DB1504_Sp1 through S7_PLC_DB1504_Sp114, all of type REAL:
' WinCC V7.x scheduled action, 250 ms cycle
' Tags required:
' SetpointHistory[0..227] - internal DWord array, holds 114 REALs as 2 DWords each
' SetpointIndex[0..113] - internal string array, holds variable names
Const VAR_COUNT = 114
Const DEADBAND = 0.01 ' ignore changes smaller than this
Dim prevArr, i, newVal, prevVal, hi, lo, nameTag, liveTag, prevTag
Set prevArr = HMIRuntime.Tags("SetpointHistory")
prevArr.Read
For i = 0 To VAR_COUNT - 1
' Resolve variable name from string array
Set nameTag = HMIRuntime.Tags("SetpointIndex")
nameTag.Read
Dim varName : varName = CStr(nameTag.Value(i))
' Read live value from external tag
Set liveTag = HMIRuntime.Tags(varName)
liveTag.Read
newVal = CDbl(liveTag.Value)
' Decode previous REAL from 2 DWords (big-endian, S7 REAL layout)
hi = CLng(prevArr.Value(i * 2))
lo = CLng(prevArr.Value(i * 2 + 1))
prevVal = CDbl(MergeToReal(hi, lo))
' Edge detection with deadband
If Abs(newVal - prevVal) > DEADBAND Then
' Emit message with old/new in process value blocks
HMIRuntime.Trace "SP change: " & varName & " " & prevVal & " -> " & newVal
' Update persistent history atomically
prevArr.Value(i * 2) = CLng(GetHiWord(newVal))
prevArr.Value(i * 2 + 1) = CLng(GetLoWord(newVal))
End If
Next
prevArr.Write ' single atomic write covers all 114 variables
' Helper: combine 2 DWords (hi, lo) into IEEE-754 REAL
Function MergeToReal(ByVal hi As Long, ByVal lo As Long) As Single
Dim bytes(0 To 3) As Byte
bytes(0) = (hi And &HFF)
bytes(1) = ((hi \ 256) And &HFF)
bytes(2) = (lo And &HFF)
bytes(3) = ((lo \ 256) And &HFF)
' Use CopyMemory or BitConverter equivalent for performance
MergeToReal = CSng(CDbl(BitConverter_ToDouble(bytes)))
End Function
The GetHiWord, GetLoWord, and BitConverter_ToDouble helpers are stored in a project-wide VBS module. For 114 variables at a 250 ms cycle, the action performs 456 tag reads per second — well within WinCC V7.5's capacity on a typical engineering station.
6. Solution 3 — Bit Word Hardening (PLC Ownership)
If the project architecture mandates the 16-bit message word (for example to integrate with a third-party alarm subsystem that polls that word), harden the bit-setting logic with three rules:
-
PLC ownership of the word. The PLC has exclusive ownership of
MessageBitsWord. VBS only reads the word; it never writes it. This eliminates the VBS-side read-modify-write race. - PLC-side edge detection. A one-shot in the PLC detects the rising edge of a setpoint-change flag, sets the corresponding message bit for exactly one PLC cycle, then clears it. A standard S7 pattern:\li>
// SCL for PLC-side one-shot (TIA Portal S7-1500)
IF ("SP_Motor07") <> "SP_Motor07_old" THEN
"MessageBits".%X2 := TRUE; // set bit 2
"SP_Motor07_old" := "SP_Motor07"; // latched
END_IF;
// Bit auto-clear in OB1 cycle
IF "MessageBits".%X2 AND NOT "PlcCycleAck" THEN
"MessageBits".%X2 := FALSE;
END_IF;
-
WinCC trigger type. Configure the WinCC message trigger as
Tag biton a dedicated BOOL tag per variable, notWord bit. A dedicated BOOL eliminates the read-modify-write race entirely on the WinCC side.
7. Solution 4 — Migrate to WinCC Unified Alarm Logging
In WinCC Unified V17 and later (V20 current), alarm logging uses a server-side persistent log on the Unified runtime, not the legacy WinCC message database. The model fixes the reported issues by design:
- The alarm source is a
Triggerdefined per tag with hysteresis and deadband - The alarm is fired by the runtime's tag model, not by a VBS action
- Process value blocks 1–10 are populated from the same tag read that triggered the alarm — no skew between detection and logging
- The configured trigger can require a minimum delta (deadband) before raising the alarm, eliminating the acquisition-aliasing problem
- The message bit is a per-tag trigger, not a shared word — eliminating the race condition
The Unified trigger model is documented in the TIA Portal V20 help: Basics of alarm logging (WinCC Unified). Reference configuration for a value-change trigger:
| Property | Value |
|---|---|
| Trigger mode | Value change |
| Trigger tag | SP_Motor07 (REAL) |
| Deadband | 0.01 |
| Min dwell time | 500 ms |
| PV block 1 | Operator name (auto) |
| PV block 2 | Old value (auto from trigger) |
| PV block 3 | New value (auto from trigger) |
| Logging destination | SQLite or SQL alarm log |
8. Configuration Reference Tables
8.1 Process Value Block Usage
| Block | Field | Source |
|---|---|---|
| 1 | Operator name | WinCC internal — automatic |
| 2 | Old value (before change) | Tag value at t-1 |
| 3 | New value (after change) | Tag value at t0 |
| 4 | Setpoint index / variable name | User-defined |
| 5 | Engineering unit | User-defined |
| 6–10 | Reserved for extended logging | Optional |
8.2 Recommended Acquisition and Trigger Settings
| Setting | WinCC V7.x | WinCC Unified V20 |
|---|---|---|
| Tag acquisition cycle | 500 ms (min) | 100 ms (default), decimation configurable |
| Message trigger type | Tag bit / Word bit | Trigger with deadband |
| Operator message ID | 12508141 | Configurable per tag |
| PV block 2 type | Match tag data type | Match tag data type |
| PV block 3 type | Match tag data type | Match tag data type |
| Logging destination | SQL alarm log | SQLite / SQL alarm log |
| Minimum hold time | 1 acquisition cycle | Hysteresis configurable |
8.3 Error Pattern and Resolution Matrix
| Symptom | Likely Cause | Fix |
|---|---|---|
| Old == New in message | Acquisition aliasing | Increase cycle to 1 s, add deadband |
| Message fires without operator action | Bit not cleared | PLC one-shot or VBS reset |
| New of N+1 ≠ Old of N | Internal tag not updated atomically | Single VBS cycle, atomic history write |
| New of N == Old of N but bit set | Read-modify-write torn write | Use individual BOOL tags, not shared word |
| Float values truncated | PV block sized as integer | Resize PV block to 32-bit / REAL |
| Log entries 0 ms apart | Script runs more than once per change | Guard with debounce timer |
| Wrong variable in message | Torn read-modify-write of shared word | PLC ownership of the bit word |
9. Verification Procedure
Run this checklist after applying any of the four solutions:
- Open WinCC Runtime, navigate to the screen containing the 114 setpoint I/O-Fields.
- Force a single setpoint change on one variable, e.g. motor 7 from 1450 to 1500.
- Confirm in the message log: exactly one message appears, old value = 1450, new value = 1500, operator name matches the logged-in user, time stamp matches the operator action within 1 acquisition cycle.
- Force a second change to the same setpoint with the same value (1450 to 1450). Confirm no message is generated.
- Force two changes within 250 ms. Confirm either zero or one message appears, never two.
- Force a change on a different variable, e.g. motor 12. Confirm the message log shows only motor 12, not motor 7.
- In WinCC tag management, perform a manual write to a setpoint tag from the value editor. Confirm a message is generated with the user "SYSTEM" (or no message, per project policy).
- Export the alarm log to CSV and verify the
OldValuecolumn matches the previous loggedNewValueof the prior entry for the same variable. - Run the project for 24 hours and verify the message count equals the number of real operator actions within ±1%.
10. Performance Considerations for 114 Variables
A single VBS scheduled action at 250 ms that loops 114 variables performs roughly 456 tag reads per second. WinCC V7.5 sustains this on a typical engineering station, but on a Comfort Panel you should:
- Increase the cycle to 500 ms or 1 s
- Group the 114 variables into 4–6 arrays of 19–20 elements, then loop the arrays
- Move edge detection to the PLC and only trigger the message bit from the PLC
For WinCC Unified, the runtime's internal alarm engine handles this load natively. No custom VBS is required for 114 variables. Benchmarks on a Unified PC Runtime V20 (Intel i5, 8 GB RAM) confirm that 1000+ value-change triggers per second is sustainable without observable UI lag.
11. Migration Path from WinCC V7 to Unified
If you are on WinCC 7.5 / 7.6 / 8.0 and want to leave the bit-word scheme behind:
- Export the existing message configuration as a CSV from the WinCC Alarm Logging editor (
File > Export > Alarm Log CSV). - In TIA Portal, open the Unified HMI project that replaces the panel.
- Use the
Import alarmswizard to import the CSV into the Unified alarm configuration. - Convert each
Word bittrigger into aTag triggerwith deadband (default 0.01 for REAL setpoints). - Convert each VBS scheduled action that loops the 114 variables into a single
OnChangeevent on the setpoint tag array, or into per-tag triggers. - Resize all process value blocks to match the source tag's data type (32-bit for REAL, 32-bit for DINT, 16-bit for INT).
- Validate against the verification checklist in Section 9.
- Run the legacy and Unified configurations in parallel for 48 hours and compare the alarm log counts.
Why does my WinCC message log show old value = new value when the setpoint never changed?
This is tag acquisition aliasing. The HMI reads the live tag and the internal "old value" tag at different instants inside the VBS cycle, and the bit trigger fires from a stale comparison. Increase the acquisition cycle to 1 s, add a deadband of 0.01 to the comparison, or migrate to the I/O-Field Operator Message property (message number 12508141) which performs the comparison inside the graphics engine and is not subject to VBS-cycle skew.
Which WinCC message number is the built-in operator message for value changes?
Message number 12508141. Process value block 2 carries the previous value and block 3 carries the new value. The message is generated automatically when the I/O-Field Operator Message property is enabled, with the operator name and time stamp populated by the runtime.
Can I activate operator messages on 114 I/O-Fields without cluttering the log?
Yes. Group the 114 setpoint tags by area (e.g. mixer 1–20, conveyor 1–20, pump 1–20, extruder 1–20, dryer 1–20, packaging 1–14) and assign each group to a different message class. The Operator message — value change class can be filtered or colored in the alarm view so high-frequency entries are de-emphasized. Alternatively, use Solution 2 (edge-detect VBS) or Solution 4 (WinCC Unified triggers) to consolidate the 114 messages into a parameterized single message class.
Should the 16-bit message word be set from the PLC or from VBS?
From the PLC. The PLC has exclusive ownership of the bit word, writes it atomically inside one OB1 cycle, and clears it in the next cycle. A VBS read-modify-write can race with other VBS actions and produce phantom messages on neighboring bits, which appears as messages on variables the operator did not change. The pattern is: PLC detects rising edge of a per-variable change flag, sets the corresponding bit in MessageBitsWord, the WinCC message fires, and the PLC clears the bit on the next cycle.
How do I stop a message from firing twice for the same operator action?
Use a PLC one-shot: set the bit when the value changes, hold it for one full acquisition cycle (typically 500 ms), then clear it. In WinCC Unified, configure the trigger with a hysteresis value larger than the smallest expected PLC-side noise (e.g. 0.01 for a REAL setpoint) and a minimum dwell time of 500 ms — the runtime will not generate a second message until the value has been outside the deadband for the dwell time. For VBS-based detection, guard the emit step with a per-variable debounce timestamp stored in the persistent history array.