Resolving WinCC C Script IF Conditions for Drive Speed Control
WinCC C scripts that evaluate multiple IF conditions against a single control tag frequently exhibit two failure modes: the obvious comparison-operator mistake (= versus ==) and a less obvious WinCC runtime script-cache anomaly that produces identical symptoms. This reference walks through both root causes, provides a verified multi-state speed-control implementation, and documents the cache workaround so commissioning engineers do not chase a non-existent logic bug on a live site.
The Drive Speed Control Problem
A demonstration HMI panel must drive a variable-frequency drive (VFD) from three discrete operating states emitted by the PLC:
- Stopped / unknown (default): speed setpoint = 0
- Manual run: speed setpoint = 1000 (fixed engineering unit, e.g. 0.1 Hz resolution)
-
Auto run: speed setpoint =
var1(computed upstream by simulation maths)
The PLC exposes a single BYTE tag control with encoded state values 3 (manual run) and 7 (auto run). Any value other than 3 or 7 must default the speed setpoint to zero so a stale panel never commands a speed the drive should not see. A naive script implementing this in WinCC Global C scripts often only updates one branch at runtime, leaving the panel stuck on a single value regardless of PLC state changes. The cause is almost never a logic error in the C code itself; it is one of two platform-level pitfalls documented below.
WinCC Global Script C Environment
WinCC Runtime exposes a deterministic C interpreter (the "Global Script" C editor) used to evaluate scheduled actions, picture events, and tag-triggered logic. The interpreter is not full ISO C; it is a Microsoft C subset linked to the WinCC Tag Manager through the WinCC API. Key constraints:
- API access to process tags is synchronous via
GetTagXxx()/SetTagXxx()functions documented in the WinCC Information System under Working with WinCC > ANSI-C for Creating Functions and Actions (Siemens WinCC V7.5 SP2 manual collection). - Each script is compiled at edit time, stored in a project DLL, and reloaded on Runtime start. The Runtime does not transparently detect source edits while running.
- No preemptive garbage collection; tag handles are resolved by name at every
GetTagcall, which is why script-cache corruption can manifest as "the value never updates" rather than a thrown error.
Prerequisites and Tag Configuration
Before authoring the script, confirm the following:
- WinCC V7.5 SP2 or later (or TIA Portal V17+ with WinCC Professional). Older V7.0 projects exhibit the script-cache behaviour more aggressively.
- PLC connection active: the HMI must show green in WinCC Explorer > Tag Management for the target connection (S7PLUS, S7-1200/1500, OPC, MPI/DP).
-
Tag types declared correctly:
controlmust be a BYTE (8-bit unsigned),speeda WORD (16-bit unsigned), andvar1a WORD or INT depending on engineering-unit range. - Trigger configured: in the C-Action editor set the trigger to "Tag Trigger > control" with standard cycle of 1 s, or use a 250 ms standard cycle if the drive is in a closed-loop demo.
-
Picture-level variable scope: declare
b,speed,var1in the picture's variable block (or as global actions) so the compiler can resolve them.
| Tag | Direction | PLC Type | WinCC Type | Length (bytes) | Trigger / Use |
|---|---|---|---|---|---|
| control | Read (PLC → HMI) | BYTE (e.g. DB1.DBX0.0) | Unsigned 8-bit | 1 | Trigger of C-Action |
| speed | Write (HMI → PLC) | WORD (e.g. DB1.DBW2) | Unsigned 16-bit | 2 | Setpoint to VFD |
| var1 | Read | WORD / INT | Unsigned / Signed 16-bit | 2 | Computed speed in AUTO |
Root Cause Analysis: Two Distinct Failure Modes
Symptom: only one branch of the IF ladder ever takes effect, even when the PLC control value changes and the HMI tag is updating correctly (verified via Tag Diagnosis). Two root causes explain this 95% of the time.
Root Cause A: Assignment Instead of Comparison
C's if (x = 3) assigns 3 to x and evaluates the result as a boolean. With control being a BYTE that the compiler treats as a 16-bit int, the assignment expression yields 3, which is truthy, so the body executes and the subsequent else if never runs. This is the classic "first branch always wins" bug. Correct syntax requires the equality operator:
if (b == 3) { ... } else if (b == 7) { ... } else { ... }
Root Cause B: WinCC Runtime Script-Cache Anomaly
WinCC V7.x Runtime maintains a compiled-image cache of the project's C actions. When you edit a global C action in the editor, save, and re-trigger, the Runtime sometimes holds the previously compiled bytecode for the affected trigger if (a) the WinCC Explorer window is left open in a way that retains the old compiled image, or (b) the Runtime was started from a "stale" PDL that itself references an older script snapshot. The PDL (Picture Description Language) file embeds a hash of its referenced scripts; if the hash mismatch is masked (e.g. due to a copy/paste round-trip via Notepad), the Runtime will appear to execute the new code, but the actual call into the C runtime still resolves to the old image. The visible symptom is identical to the assignment bug: one branch works, the others do not.
Step-by-Step: IF / ELSE IF / ELSE Implementation
The following is a verified working snippet for the project, using correct comparison, declared types, and a default branch that always executes on every cycle.
- Open the WinCC Explorer and navigate to Global Script > C-Actions.
- Right-click > New > Project Function (or a scheduled action). Name it
act_DriveSpeedControl. - Set the trigger: Standard cycle 1 s, or attach to Tag Trigger > control for event-driven execution.
- Paste the body. Note the semicolons, the
==comparison, and the use ofSetTagWord(notSetTag):
// Trigger: standard cycle 1 s, or on change of "control"
// State encoding: 3 = manual run, 7 = auto run, other = stop
BYTE b;
WORD fixedSpeed = 1000; // manual run setpoint, 0.1 Hz units
WORD autoSpeed; // populated by upstream maths block
b = GetTagByte("control");
autoSpeed = GetTagWord("var1");
if (b == 3) {
SetTagWord("speed", fixedSpeed);
}
else if (b == 7) {
SetTagWord("speed", autoSpeed);
}
else {
SetTagWord("speed", 0);
}
- Compile via the toolbar "Compile" button. Resolve any "tag not found" errors by checking the spelling in Tag Management and the connection assignment.
- Right-click the project function and select Assign to Trigger. Choose the standard cycle (1 s is typical for a demo) or a tag trigger on
controlwith a 500 ms standard cycle minimum to avoid starving the dispatcher. - Run the project. Use the on-screen Tag Diagnosis (WinCC Explorer > Tools > Tag Diagnosis) to confirm
controltoggles 0 → 3 → 7 andspeedfollows the rules above.
Alternative Implementation: SWITCH / CASE
For three or more mutually exclusive states, a switch statement expresses intent more clearly and generates fewer comparisons in the WinCC interpreter's pre-computed jump table. The C-Action becomes:
switch (GetTagByte("control")) {
case 3: SetTagWord("speed", 1000); break;
case 7: SetTagWord("speed", GetTagWord("var1")); break;
default: SetTagWord("speed", 0); // stop / unknown / fault
}
Why this is preferable to a long else if chain:
- The default branch is explicit and self-documenting.
- Adding a new state requires a single new
case, not a refactor of the entireifchain. - Performance scales O(1) with a dense state space (the C compiler emits a jump table), versus O(n) for an
else ifchain.
switch requires an integral control expression. GetTagByte is suitable; GetTagFloat and string tags are not. Wrap float comparisons with an integer casting helper (e.g. multiply by 10 and truncate) if the state is encoded as a floating value.Data Type Mapping and API Reference
WinCC C-script tag accessors are strictly typed. Choosing the wrong accessor causes silent truncation or a runtime exception. The table below covers the eight most common accessors in this code path. Source: Siemens WinCC V7.5 SP2: ANSI-C function reference.
| Function | PLC Source Type | Returns | Common Trap |
|---|---|---|---|
GetTagByte(tag) |
BYTE, SINT (range-limited) | BYTE (8-bit unsigned) | Reading a signed INT as BYTE loses sign above 127. |
GetTagWord(tag) |
WORD, INT, BYTE pair | WORD (16-bit unsigned) | Negative INT becomes a large unsigned number. |
GetTagSByte(tag) |
SINT | Signed 8-bit | Not available on all V7.x builds — check project properties. |
GetTagShort(tag) |
INT | Signed 16-bit | Mis-name: "Short" here is the C "short", not "short integer" PLC type. |
GetTagDWord(tag) |
DWORD, DINT, REAL (lossy) | 32-bit unsigned | REAL read as DWORD gives the IEEE-754 bit pattern, not the value. |
GetTagFloat(tag) |
REAL | float (32-bit IEEE-754) | Direct switch on float is not permitted. |
SetTagByte(tag, val) |
BYTE | BOOL return | Truncates values > 255 without warning. |
SetTagWord(tag, val) |
WORD | BOOL return | Truncates values > 65535 without warning. |
Return-value handling: all SetTag calls return a BOOL indicating write success. For safety-critical setpoints (drive speed, valve commands), wrap the call:
if (!SetTagWord("speed", 0)) {
// log via internal WinCC tag or trigger an alarm
SetTagByte("diag.speedWriteFail", 1);
}
The WinCC Runtime Script-Cache Anomaly: Workaround Procedure
If Root Cause A has been ruled out (your comparison is ==, the compiler produced no warnings, and a unit test of the same logic in isolation passes), the Runtime is almost certainly serving stale bytecode. The field-proven fix sequence is:
- Stop the WinCC Runtime: right-click the WinCC taskbar icon > Stop WinCC Runtime. Wait for the explorer to fully close.
- Close WinCC Explorer if it is still open. Any open PDL editor holding an in-memory script snapshot keeps the cache hot.
-
Delete the compiled project DLLs: navigate to the project directory (e.g.
C:\Siemens\WinCC\ProjectName\<server>\) and remove any file matching*.dl_,*.dllin thelibrarysubfolder, plus thePDLcachefolder. The Runtime will recompile from source on next start. Reference: Siemens FAQ: "Why are my changes in C actions not active?" - Run WinCC Reset: Start > Siemens Automation > WinCC > Reset WinCC Runtime. This terminates any orphaned CCWrite.exe and CCAlgRt.exe processes that can hold script handles.
- Reboot on a live plant where you cannot verify no other HMI client is connected. The user's experience was that a Runtime stop/start alone was not enough on Windows Server 2012 R2 hosts with UAC elevated; a full OS reboot was required.
- Restart WinCC Runtime and verify the failing branch now updates.
Verification and Runtime Test Procedure
After implementing the action and applying the cache workaround, validate with the following structured test. Each step uses a known PLC state and a measured HMI tag value, recorded in a commissioning log.
| Step | PLC: Set control to |
PLC: var1 loaded with |
Expected speed value |
Pass/Fail |
|---|---|---|---|---|
| 1 | 0 (stopped) | n/a | 0 | |
| 2 | 1 (undefined state) | any | 0 (default branch) | |
| 3 | 3 (manual) | n/a | 1000 | |
| 4 | 5 (undefined) | any | 0 (default branch) | |
| 5 | 7 (auto) | 500 | 500 | |
| 6 | 7 (auto) | 2000 | 2000 | |
| 7 | 7 (auto) → 0 (transition) | 2000 | 0 within 1 cycle |
Transition test (step 7) is the single most useful diagnostic. If speed remains 2000 when the PLC state transitions to 0, the default branch is not being reached. Either (a) the else clause is missing, (b) the C script is not being triggered on every cycle, or (c) the cache is stale and the Runtime is still running the previous version.
Best Practices for WinCC C Script Logic
-
Always include a
defaultbranch inswitchstatements, and anelseinifchains, for any state-mapped control logic. A drive left at its last commanded speed during a PLC restart is a known safety hazard. -
Use
==for equality,=for assignment. Enable the WinCC C editor's compiler warning level to maximum (Project > Properties > C-Editor) so accidental assignments produce a warning. -
Declare tag types explicitly. Do not rely on implicit integer promotion; declare
BYTE b;notint b;so the comparison is bit-exact. -
Trigger on tag change, not on standard cycle, for setpoint logic. Standard cycles waste dispatcher time and may lag PLC state by up to 1 s. Tag triggers on
controlfire within WinCC's typical 250 ms standard cycle. - External edit then paste. The WinCC editor has a known copy-paste-cache bug; editing in Notepad++ and pasting fresh text avoids the stale-image class of issue described above.
- Wrap
SetTagcalls with return-value checks for any safety-relevant output, and route failures to an alarm tag. - Test transitions, not just steady states. A 0→3→7→0 sequence catches both the assignment bug and the cache bug. Steady-state tests mask both.
- Document the state encoding in the script header. Three months from now, "3 = manual, 7 = auto" will not be obvious to the next commissioning engineer.
Why does my WinCC C script IF/ELSE only execute the first branch even when I wrote ==?
The C syntax is correct, but the WinCC Runtime is executing a cached compiled image of the previous script version. Close the WinCC Explorer, stop the Runtime, run WinCC Reset from the Start menu, delete the project DLLs in library\ and the PDLcache folder, then restart. If the issue persists, reboot the HMI station. This is a documented WinCC V7.x behaviour when a PDL is copy-pasted or partially re-imported.
What is the difference between SetTag and SetTagWord in WinCC C scripts?
SetTag is a generic dispatcher that routes based on the destination tag's declared type; SetTagWord writes a 16-bit unsigned value directly. For a 16-bit WORD speed setpoint, SetTagWord is faster and avoids an implicit type-check branch. Use SetTagDWord for 32-bit destinations, SetTagFloat for REAL, and SetTagByte for BYTE/BOOL. Mismatched calls silently truncate.
Can I use a switch statement on a string tag in WinCC C scripts?
No. switch requires an integral control expression, so it only works on BYTE, WORD, SHORT, or DWORD tag reads. For string tags, use an if / else if chain with strcmp(), or encode the state as an integer in the PLC to keep the C script compact and fast.
How do I trigger a C action only on tag change rather than on a fixed cycle?
In the C-Action properties, set the trigger to Tag Trigger rather than Standard cycle, then add the source tag (e.g. control) to the trigger list. The Runtime will fire the action whenever the tag's value changes. Keep a 250 ms standard cycle active as a safety net so a missed trigger does not leave the drive with a stale setpoint indefinitely.
What WinCC version introduced the cleanest C-script recompile on edit?
WinCC V7.4 SP1 and later substantially reduced the script-cache anomaly by invalidating compiled images on save. WinCC V7.0 and V7.3 require a full Runtime restart after every C-script edit. For new projects, target V7.5 SP2 or WinCC Professional in TIA Portal V17+ to avoid the cache issue entirely. Always confirm the WinCC build with Help > About > Installation Details before commissioning.