Troubleshooting Multiple Object Animation in WinCC V7 Global

David Krause14 min read
SiemensTroubleshootingWinCC
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

Troubleshooting Multiple Object Animation in WinCC V7 Global Script C

Symptom: an operator presses two pushbuttons (or the PLC toggles two output bits in the same OB1 scan) and only one animated object moves on the WinCC picture. The conveyor scrolls but the piston is frozen, or the piston drives forward but the workpiece on the conveyor does not translate. Each individual animation runs correctly when triggered in isolation. The combined motion does not.

This failure mode is not a PLC bug, not a tag-prefix mistake, and not a Graphics Designer fault. It is a side-effect of how WinCC V7 schedules ANSI-C global actions across its two internal script queues. This article reconstructs the failure, isolates the root cause, and provides drop-in ANSI-C and VBS replacements that animate any number of objects in lockstep.

Problem Details

Reported Behavior

A WinCC V7 project drives a screen animation through three independent global C actions:

  • ConveyorAction — triggered on change of bit DC_bt1 (PLC tag Q124.0). Loops six rectangles (hcn1 through hcn6) leftward across the picture at 1 pixel per iteration until the rectangles exit at the left edge of the conveyor body BT1, at which point the rectangles reappear on the right edge.
  • PistonAction — triggered on change of bit DC_capSP (PLC tag Q124.1). Drives the piston rod CanDaySPBT1 from its rest position toward x = 1017 at 1 pixel per iteration.
  • Embryo-plastic action — driven by output bit Q124.2. Inherits the same code skeleton as the two above.

Source Code (Failing Pattern)

/* ConveyorAction - WINCC:GLOBAL_ACTION tag-triggered on DC_bt1 */
#include "apdefap.h"
int gscAction( void )
{
    float xb, xbb;
    float xa1, xa2, xa3, xa4, xa5, xa6;

    xb  = GetLeft("HeThongChinh.Pdl","BT1");
    xbb = GetWidth("HeThongChinh.Pdl","BT1") + xb;

    while ( GetTagBit("DC_bt1") == 1 )
    {
        xa1 = GetLeft("HeThongChinh.Pdl","hcn1");
        if ( xa1 >= xb ) xa1 = xa1 - 1;
        else            xa1 = xbb - GetWidth("HeThongChinh.Pdl","hcn1");
        SetLeft("HeThongChinh.Pdl","hcn1", xa1);

        xa2 = GetLeft("HeThongChinh.Pdl","hcn2");
        if ( xa2 >= xb ) xa2 = xa2 - 1;
        else            xa2 = xbb - GetWidth("HeThongChinh.Pdl","hcn1");
        SetLeft("HeThongChinh.Pdl","hcn2", xa2);

        /* ... hcn3..hcn6 identical pattern ... */
    }
    return 0;
}
/* PistonAction - WINCC:GLOBAL_ACTION tag-triggered on DC_capSP */
#include "apdefap.h"
int gscAction( void )
{
    float mr2, mr2s, mr1;

    mr2s = 1017;
    mr2  = GetLeft("HeThongChinh.Pdl","CanDaySPBT1");
    mr1  = GetLeft("HeThongChinh.Pdl","BT1") + GetWidth("HeThongChinh.Pdl","BT1");

    while ( GetTagBit("DC_capSP") == 1 || ( mr2 < mr2s ) )
    {
        if ( mr1 <= mr2 ) mr2 = mr2 - 1;
        else              mr2 = mr2s;
        SetLeft("HeThongChinh.Pdl","CanDaySPBT1", mr2);
    }
    return 0;
}

When each tag is flipped individually with S7-PLCSIM, the corresponding action animates smoothly. When two tags change in the same OB1 cycle — Q124.0 and Q124.1 both rising, for example — only one of the two animations runs. The user reports "the conveyor moves, the piston freezes" or "the piston moves, the conveyor freezes," and the loser is whichever action was registered first in the WinCC scheduler.

Expected Behavior

Both the conveyor rectangles and the piston rod should animate simultaneously, with frame-to-frame position deltas in sync with the operator's view of the process. This is the standard behavior of WinCC V7 when configured correctly.

Root Cause

WinCC V7 Global Script Scheduler Topology

WinCC V7 exposes two independent runtime queues for ANSI-C global actions, as documented in the official scripting manual. From the Siemens support portal, "Scripting for WinCC and WinCC Professional":

In global scripting, there are two queues for ANSI-C: one queue for cyclical triggers (periodically, e.g. "1 second"), and one queue for tag triggers.

The relevant characteristics of these queues for the reported bug:

Queue Trigger Source Service Cycle Concurrency Model
Cyclical queue Periodic timer (250 ms, 500 ms, 1 s, custom) Fixed, deterministic Single-threaded cooperative; each action runs to completion before the next is dispatched
Tag-trigger queue Tag value change (rising, falling, both, any) Best-effort, processed by the Tag-Logging / Runtime scheduler Same as above; tag-change events are queued and dispatched one at a time per tag-trigger action

Both queues are strictly single-threaded. An action that is currently running blocks the queue. If a second tag changes while the first action is inside its while() loop, the second action's trigger event is buffered but cannot fire until the first action returns.

The Real Culprit: Blocking while() Loops Inside Tag-Triggered Actions

The source code embeds an infinite while ( GetTagBit(...) == 1 ) loop inside each tag-triggered global action. This pattern is the actual cause of the apparent "only one thing moves" symptom. The mechanics are:

  1. PLC toggles DC_bt1 from 0 to 1 in OB1 cycle N.
  2. WinCC Runtime detects the tag change, schedules ConveyorAction in the tag-trigger queue.
  3. The dispatcher starts ConveyorAction, which enters the while() loop. The loop never returns as long as DC_bt1 == 1, and the dispatcher cannot preempt it.
  4. PLC toggles DC_capSP from 0 to 1 in OB1 cycle N+1 (or the operator presses a second button).
  5. WinCC Runtime buffers the tag-change event for PistonAction.
  6. PLC toggles DC_bt1 back to 0 in OB1 cycle N+k. The while() condition fails; the action returns 0; the queue dispatches PistonAction; PistonAction enters its own while() loop and runs to completion.

From the operator's perspective, only the conveyor (or only the piston, depending on which while() exits first) is visible moving at any given moment. The two animations never overlap in time.

A Secondary Defect: Read-Modify-Write Inefficiency

Inside the conveyor loop, each iteration performs six GetLeft() calls, six arithmetic branches, and six SetLeft() calls. With C-function call overhead, tag-pre-prefix resolution, and the Graphics Designer property setter, a single iteration can take 5 to 20 ms per object on a typical WinCC station. With six objects, a single screen refresh cycle takes 30 to 120 ms — long enough that the visible motion stutters even when running alone.

Hidden Bug in the Piston Code

Inside the piston loop, the wrap-around reference is hard-coded:

mr2 = mr2s;   /* jump back to 1017 when mr1 <= mr2 */

This resets the rod to the far-right extreme whenever the conveyor reference crosses it, producing a visible snap rather than a continuous extension. Acceptable in some animations, but the loop semantics should be while ( GetTagBit(...) == 1 ) only — not while ( tag == 1 || (mr2 < mr2s) ). The compound condition causes the piston to keep sliding right even after the operator releases the button, which is rarely the desired physical behavior.

Solution

The fix is structural, not cosmetic. Three changes are required:

  1. Move all animations out of tag-triggered global actions.
  2. Drive them from a single cyclical global action (recommended 100 to 250 ms period).
  3. Consolidate per-frame work into one read-calculate-write pass.

Optionally replace ANSI-C with VBS for visibly smoother motion. Per the Siemens scripting manual, VBS carries lower per-call overhead for Graphics Designer property setters than ANSI-C, because the VB interpreter marshals the Get/Set calls through COM without crossing the C-runtime function-call boundary that ANSI-C requires.

Pattern A — Single ANSI-C Cyclical Action Driving All Objects

Create a global C function that holds the per-frame motion logic for every object. Create a single cyclical global action that calls it every 250 ms. The trigger for the action is periodic, not a tag.

/* Project function: Anim_Frame — called from cyclical action */
#include "apdefap.h"
void Anim_Frame( void )
{
    /* Read phase — read tags and current positions ONCE */
    int    bConveyor = GetTagBit("DC_bt1");
    int    bPiston   = GetTagBit("DC_capSP");

    float  convLeft  = GetLeft("HeThongChinh.Pdl","BT1");
    float  convWidth = GetWidth("HeThongChinh.Pdl","BT1");
    float  convRight = convLeft + convWidth;

    float  hcnW      = GetWidth("HeThongChinh.Pdl","hcn1");
    float  hcnX[6];
    int    i;
    char   name[16];

    for ( i = 0; i < 6; i++ )
    {
        sprintf( name, "hcn%d", i + 1 );
        hcnX[i] = GetLeft("HeThongChinh.Pdl", name);
    }

    float  rodX      = GetLeft("HeThongChinh.Pdl","CanDaySPBT1");
    float  rodTarget = 1017.0f;

    /* Calculate phase */
    if ( bConveyor == 1 )
    {
        for ( i = 0; i < 6; i++ )
        {
            hcnX[i] -= 2.0f;                       /* 2 px per cycle, smoother */
            if ( hcnX[i] < convLeft )
                hcnX[i] = convRight - hcnW;
        }
    }

    if ( bPiston == 1 )
    {
        rodX += 4.0f;                             /* 4 px per cycle */
        if ( rodX > rodTarget ) rodX = convRight; /* retract to start */
    }

    /* Write phase — set positions ONCE */
    for ( i = 0; i < 6; i++ )
    {
        sprintf( name, "hcn%d", i + 1 );
        SetLeft("HeThongChinh.Pdl", name, hcnX[i]);
    }
    SetLeft("HeThongChinh.Pdl","CanDaySPBT1", rodX);
}
/* Global action: ConveyorAndPiston — trigger = 250 ms cyclic */
#include "apdefap.h"
int gscAction( void )
{
    Anim_Frame();
    return 0;
}

Register the global action with trigger Standard cycle / 250 ms instead of Tag trigger. Both animations are now evaluated from the same frame tick. They cannot drift relative to each other because they share the read-calculate-write boundary.

Pattern B — VBS Cyclical Action (Lower Latency Per Frame)

For HMI stations that show visible juddering under ANSI-C, convert the per-frame logic to VBS and register the action with the same 250 ms cyclic trigger:

' Global action: ConveyorAndPiston_VBS — trigger = 250 ms cyclic
Sub ConveyorAndPiston_VBS()

    Dim bConv, bPist, convL, convR, hcnW, rodT, rodX
    Dim hcnX(5), i, objName

    bConv = HMIRuntime.Tags("DC_bt1").Read
    bPist = HMIRuntime.Tags("DC_capSP").Read

    convL = HMIRuntime.Screens("HeThongChinh").ScreenItems("BT1").Left
    convR = convL + HMIRuntime.Screens("HeThongChinh").ScreenItems("BT1").Width
    hcnW  = HMIRuntime.Screens("HeThongChinh").ScreenItems("hcn1").Width
    rodT  = 1017
    rodX  = HMIRuntime.Screens("HeThongChinh").ScreenItems("CanDaySPBT1").Left

    For i = 0 To 5
        objName = "hcn" & (i + 1)
        hcnX(i) = HMIRuntime.Screens("HeThongChinh").ScreenItems(objName).Left
    Next

    If bConv = True Then
        For i = 0 To 5
            hcnX(i) = hcnX(i) - 2
            If hcnX(i) < convL Then hcnX(i) = convR - hcnW
        Next
    End If

    If bPist = True Then
        rodX = rodX + 4
        If rodX > rodT Then rodX = convR
    End If

    For i = 0 To 5
        objName = "hcn" & (i + 1)
        HMIRuntime.Screens("HeThongChinh").ScreenItems(objName).Left = hcnX(i)
    Next

    HMIRuntime.Screens("HeThongChinh").ScreenItems("CanDaySPBT1").Left = rodX

End Sub

Pattern C — Function Library Called From Cyclical Action (Recommended for WinCC V7.0 SP3 and Later)

For larger projects, factor the animation into a separate function in the Global Script project module:

  1. In the WinCC Explorer tree, right-click Global Script → Project Functions, choose New → Function, name it Anim_Frame.
  2. Paste the function body from Pattern A above.
  3. Create a new Global Action with trigger Standard cycle 250 ms, body Anim_Frame();.

This keeps the dispatch logic trivial and the animation logic reusable from other actions (e.g., a "Stop All" button that calls Anim_Reset()).

Verification

After applying Pattern A, B, or C, confirm the fix with the following checks.

Step-by-Step Verification

  1. Compile the global action. In the editor, choose File → Compile. Any syntax error in the C or VBS body halts the action and WinCC Runtime logs a Script error: line N entry in WinCC_SysLog_*.LOG. Confirm the log contains no error lines for the new action.
  2. Activate WinCC Runtime. Open the project, switch to runtime, navigate to HeThongChinh.Pdl.
  3. Toggle one tag. In S7-PLCSIM, force Q124.0 = 1. The six rectangles should scroll left at 2 pixels per 250 ms tick (~8 px/s), wrapping around at the conveyor's right edge.
  4. Toggle the second tag while the first is still set. Force Q124.1 = 1 without resetting Q124.0. The piston rod should extend at 4 px per tick (~16 px/s) while the conveyor continues to scroll.
  5. Reset both tags. Force both bits to 0. Both animations should freeze at their current positions on the next 250 ms tick.
  6. Toggle in reverse order. Force Q124.1 first, then Q124.0 while Q124.1 is still set. Both animations should still run in lockstep.

Diagnostic Output to Confirm Single-Tick Execution

For field debugging, add a transient tag to write the action's execution count:

/* inside Anim_Frame, top of function */
long n = GetTagDWord("Anim_Frame_Counter") + 1;
SetTagDWord("Anim_Frame_Counter", n);

Watch this tag in the tag monitor. It should increment at exactly 4 per second when the cyclical trigger is 250 ms. If it increments at the tag-change rate instead, the trigger is still misconfigured as a tag trigger.

Advanced Tuning

Frame-Rate Selection

Cycle Visual Effect CPU Load (WinCC station, 100 objects) Recommended Use
100 ms Smooth, near-60 Hz High — 10 dispatches/s Short, fast motions (jog, eject)
250 ms Smooth for most HMIs Moderate — 4 dispatches/s Conveyor, piston, dial (default)
500 ms Visible stepping Low — 2 dispatches/s Slow drift gauges, fill levels
1000 ms Discrete jumps Minimal Background indicators only

Velocity as a Tag, Not a Constant

Hard-coding 2 px/tick inside the function forces recompilation whenever the line speed changes. Expose velocity as a tag:

float vConv = GetTagFloat("V_Conv_PxPerTick");
/* ... */
hcnX[i] -= vConv;

The PLC writes V_Conv_PxPerTick from a runtime scaling of the actual line speed. Operators can change it from a faceplate without recompiling the script.

Handling Picture Window Navigation

Global actions run regardless of which picture is open. If HeThongChinh.Pdl is in a picture window that is closed, the SetLeft() calls return -1 and log a warning. Guard against this with:

if ( GetLeft("HeThongChinh.Pdl","BT1") < 0 ) return;  /* picture not loaded */

Or query GetOpenPictureName() from inside the action and only update when the target picture is foregrounded.

WinCC Unified Migration Note

If the project is later migrated to WinCC Unified (TIA Portal V17+), the C-API used here is not available. Use the JavaScript-based runtime scripting instead. The global-module structure is similar: project-side Global modules hold reusable functions, Global functions live inside them, and scheduled tasks register them as triggers. The conceptual mapping is direct:

WinCC V7 ANSI-C WinCC Unified JavaScript
GetLeft() HMIRuntime.Screens(...).ScreenItems(...).Left
SetTagBit() HMIRuntime.Tags(...).Write
Global action (cyclic) Scheduled task (cyclic trigger)
Project function Global module / global function

Troubleshooting Matrix

Symptom Likely Cause Fix
Only one object moves when two tags toggle Each object in a separate tag-triggered action with blocking while() Consolidate into one cyclical action
Animation stutters visibly ANSI-C function-call overhead; too many Get/Set per cycle Switch to VBS or raise cycle to 250 ms
Action never fires Tag trigger filter is "rising" but bit toggles both edges Set trigger to "change" or fix PLC edge logic
Pictures flicker Cyclical action writes properties every cycle even when tag = 0 Wrap Set calls in if (bConveyor == 1) guard
Runtime reports Script error: line N Bad object name, bad tag prefix, or wrong PDL reference Cross-check in Graphics Designer and tag management
CPU at 100 % on the WinCC station Cyclical trigger set to < 100 ms with many objects Raise cycle to 250 ms or offload to VBS
Animation continues after tag reset Compound while condition with position check Use while (tag == 1) only

Common Pitfalls

Never use while() inside a tag-triggered global action. The action cannot be preempted; the queue is blocked for the entire loop duration. Use a periodic trigger and read the tag inside.
Trigger type and cycle rate are global project properties. Editing the trigger of an active action requires a Runtime restart. Plan the cycle before activating.
VBS is significantly faster than ANSI-C for screen property manipulation on most WinCC V7 stations, not slower. The COM marshalling path through the VBS interpreter is shorter than the C runtime → property-getter → COM hop that ANSI-C requires for every GetLeft / SetLeft pair.
Object names in C are case-sensitive. hcn1HCN1. The field report uses lowercase; match exactly.

Safety and Operational Considerations

These patterns drive visual feedback only. They do not replace hard-wired safety circuits, E-Stops, or PLC-side interlocks. The WinCC station may lose power, lose Runtime activation, or have its global scripts disabled for debugging — in any of these cases, the screen freeze is acceptable. Real motion control must remain inside the PLC's fail-safe logic, not in a graphics script.

References Used in This Article

All technical claims about queue architecture and ANSI-C / VBS performance characteristics are drawn from the official Siemens scripting documentation:

FAQ

Why does only one object move when I trigger two animations at the same time in WinCC V7 Global Script C?

WinCC V7 runs ANSI-C global actions in a single-threaded scheduler with separate queues for cyclic and tag-triggered actions. If each animation is in its own tag-triggered action with a blocking while() loop, the second action cannot start until the first returns. Consolidate all animations into one cyclic global action (e.g., 250 ms) that reads all relevant tags, computes all new positions, and writes them in a single pass.

Is VBS really smoother than ANSI-C for WinCC screen animations?

For Graphics Designer property updates, yes. The VBS interpreter's COM marshalling path is shorter than the ANSI-C function-call → property-getter → COM path required for every GetLeft / SetLeft. For screen-object animation with 250 ms cycles, VBS typically shows less frame-to-frame jitter than ANSI-C.

What trigger type should I use for a continuously animated screen object in WinCC V7?

Use a standard cyclical trigger (typically 250 ms for conveyor / piston animations) on a single global action, and read the enabling tag inside the action body. Do not use a tag trigger for continuously animated motion; tag triggers are best for one-shot events such as state transitions.

How many objects can a single 250 ms WinCC V7 global action animate?

On a typical WinCC station (Intel i5 / i7, 8 GB RAM, WinCC V7.4 SP1), one 250 ms action can comfortably update 100 to 200 screen-object properties per frame. Beyond that, raise the cycle to 500 ms, split the work across two actions on alternating cycles, or reduce the number of objects.

Does this approach carry over to WinCC Unified in TIA Portal?

The conceptual pattern does, but the APIs differ. ANSI-C functions like GetLeft and SetLeft are replaced by JavaScript access through HMIRuntime.Screens(...).ScreenItems(...). Cyclical triggers become scheduled tasks. Project functions become global modules. See the TIA Portal runtime scripting documentation for the full mapping.

Back to blog