WinCC C Script: Reading PLC Tags and Driving XY Animation

David Krause12 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

Overview

WinCC Professional (TIA Portal) exposes a full ANSI-C scripting interface that runs inside Runtime. It is the only WinCC edition that supports user-written C functions: WinCC Advanced and Comfort ship with VBScript only and cannot host compiled C code. This makes Professional the correct runtime for a project that needs to read 8 real-valued tags from a SIMATIC S7-315-2DP over PROFIBUS (CP 5612 / CP 5622), perform derived calculations (distance, area, slope, rate of change), and drive both an XY motion object and six height-bars on the same screen.

The architecture has three layers:

  1. PLC side – S7-315-2DP holds eight REAL tags in a data block (e.g. DB100, bytes 0-63).
  2. Communication layer – PROFIBUS master CP 5612 in the PC, PROFINET or PROFIBUS connection configured in the TIA device configuration of the HMI.
  3. HMI side – WinCC Professional RT project with a C action scheduled by a trigger, an internal tag pool for the computed values, and a screen with Graphic view with animation and Bar objects.

Prerequisites

Component Required Version / Part Notes
TIA Portal V14 SP1 or higher (V15, V15.1, V16, V17, V18, V19, V20 supported) Project must be migrated when moving between major versions
WinCC Professional RT Matching the TIA version (e.g. V14 SP1 RT for V14 SP1 engineering) Only Professional supports C. Advanced / Comfort do not.
PLC SIMATIC S7-300 CPU 315-2DP (6ES7315-2AG10 or later) DP port for PROFIBUS, second port optional for PROFINET
PC communication card CP 5612 (PCI) or CP 5622 (PCIe) – Siemens part number 6GK1561-2AA00 / 6GK1562-2AA00 Drives the PROFIBUS DP master; SIMATIC NET driver must be installed
PROFIBUS 12 Mbps standard, DP master/slave Bus terminating resistors on both ends, shielded twisted pair
Tags on PLC REAL (32-bit float), DB100 starting at byte 0 Eight tags = 32 bytes total; align to even addresses to avoid byte-swap artefacts
Edition check before you start: In the TIA project tree, right-click the HMI device → Properties → Runtime → Active runtime. If it shows WinCC Advanced RT or Comfort RT, the C editor under Scripts → C scripts is greyed out. You must switch to WinCC Professional RT (or add a second HMI device with the Professional RT licence).

WinCC Professional C Scripting Architecture

The C interface inside WinCC Runtime Professional is documented in the TIA Portal V20 C scripting reference (RT Professional) and the cross-version WinCC Information System Scripting manual (PDF). The runtime distinguishes between three C entry points:

  • Project functions – compiled once, called from actions, screens, or other C functions. Use these for reusable math.
  • Global actions – scheduled C programs triggered by tag change, time cycle, or window event. This is where you read PLC tags and push results into internal HMI tags.
  • Screen functions / events – local C code attached to screen Open, Click, Value change, etc. Best for one-shot initialisation (origin offset, scaling).

All C functions receive and return BOOL and use the WinCC API: GetTagFloat(), SetTagFloat(), GetTagWord(), SetTagWord(), GetTagBit(), etc. The link table generated at compile time resolves the symbolic tag name (e.g. "HMI_Tag_X") to the process image address.

Step 1 – Create the HMI Tag Mirror and Internal Calculation Tags

Open HMI tags in the TIA project tree and create the eight external tags pointing at the PLC. Use the connection editor to point each tag to the S7-300 connection over PROFIBUS. Example table for a study with axes X, Y and six auxiliary heights Z1..Z6:

HMI tag PLC address Type Length Use
HMI_X_Raw DB100.DBD0 REAL 4 Object 1 X position (mm)
HMI_Y_Raw DB100.DBD4 REAL 4 Object 1 Y position (mm)
HMI_Z1_Raw DB100.DBD8 REAL 4 Height bar 1 (mm)
HMI_Z2_Raw DB100.DBD12 REAL 4 Height bar 2 (mm)
HMI_Z3_Raw DB100.DBD16 REAL 4 Height bar 3 (mm)
HMI_Z4_Raw DB100.DBD20 REAL 4 Height bar 4 (mm)
HMI_Z5_Raw DB100.DBD24 REAL 4 Height bar 5 (mm)
HMI_Z6_Raw DB100.DBD28 REAL 4 Height bar 6 (mm)

Then add internal HMI tags that the screen animations will read. Internal tags are not bounded to the PLC, so the C script writes them freely.

Internal tag Type Driven by
Calc_X_Screen INT C script – pixel X for object 1
Calc_Y_Screen INT C script – pixel Y for object 1 (origin bottom-left)
Calc_Dist_From_Origin REAL C script – euclidean distance
Calc_Path_Length REAL C script – integral of motion
Calc_Velocity REAL C script – mm/s derivative
Calc_Z1..Z6_Bar INT C script – bar height in pixels

Step 2 – Map (0,0) to the Bottom-Left of the XY Motion Object

WinCC object coordinates in a Graphic view use the top-left corner of the screen as (0,0) and increase right and down. To place the engineering origin at the bottom-left of a defined work area, you scale and offset the raw values coming from the PLC.

Define screen parameters as constants in the C script (or in a project function that returns the values):

  • SCREEN_X_MIN = 0, SCREEN_X_MAX = 800 (pixels of the motion rectangle)
  • SCREEN_Y_MIN = 0, SCREEN_Y_MAX = 480
  • PLANT_X_MIN, PLANT_X_MAX, PLANT_Y_MIN, PLANT_Y_MAX – engineering units in mm from the PLC

Linear interpolation for X, with Y inverted so that increasing PLC Y moves up on screen:

Calc_X_Screen = (HMI_X_Raw - PLANT_X_MIN) * (SCREEN_X_MAX - SCREEN_X_MIN) / (PLANT_X_MAX - PLANT_X_MIN) + SCREEN_X_MIN;
Calc_Y_Screen = (PLANT_Y_MAX - HMI_Y_Raw) * (SCREEN_Y_MAX - SCREEN_Y_MIN) / (PLANT_Y_MAX - PLANT_Y_MIN) + SCREEN_Y_MIN;

Step 3 – Write the C Project Function (Math)

Under Project tree → Scripts → C scripts → Project functions, create CalcMotion:

#include "apdefap.h"

void CalcMotion(void)
{
    float xRaw, yRaw, z1, z2, z3, z4, z5, z6;
    static float xPrev = 0.0f, yPrev = 0.0f;
    static float pathLength = 0.0f;
    static DWORD lastTick = 0;
    float dx, dy, dt, v;
    int sx, sy;
    int b1, b2, b3, b4, b5, b6;

    /* Plant range in mm, screen range in pixels */
    const float PLANT_X_MIN = 0.0f,   PLANT_X_MAX = 1000.0f;
    const float PLANT_Y_MIN = 0.0f,   PLANT_Y_MAX = 800.0f;
    const int   SCR_X_MIN   = 50,     SCR_X_MAX   = 750;
    const int   SCR_Y_MIN   = 30,     SCR_Y_MAX   = 430;
    const int   BAR_MAX_PX  = 250;
    const float Z_MIN       = 0.0f,   Z_MAX       = 500.0f;

    /* Read PLC tags */
    xRaw = GetTagFloat("HMI_X_Raw");
    yRaw = GetTagFloat("HMI_Y_Raw");
    z1   = GetTagFloat("HMI_Z1_Raw");
    z2   = GetTagFloat("HMI_Z2_Raw");
    z3   = GetTagFloat("HMI_Z3_Raw");
    z4   = GetTagFloat("HMI_Z4_Raw");
    z5   = GetTagFloat("HMI_Z5_Raw");
    z6   = GetTagFloat("HMI_Z6_Raw");

    /* Engineering-to-screen mapping (origin bottom-left) */
    sx = (int)((xRaw - PLANT_X_MIN) * (SCR_X_MAX - SCR_X_MIN) /
               (PLANT_X_MAX - PLANT_X_MIN) + SCR_X_MIN);
    sy = (int)((PLANT_Y_MAX - yRaw) * (SCR_Y_MAX - SCR_Y_MIN) /
               (PLANT_Y_MAX - PLANT_Y_MIN) + SCR_Y_MIN);

    SetTagWord("Calc_X_Screen", (WORD)sx);
    SetTagWord("Calc_Y_Screen", (WORD)sy);

    /* Bar scaling 0..Z_MAX -> 0..BAR_MAX_PX */
    b1 = (int)((z1 - Z_MIN) * BAR_MAX_PX / (Z_MAX - Z_MIN));
    b2 = (int)((z2 - Z_MIN) * BAR_MAX_PX / (Z_MAX - Z_MIN));
    b3 = (int)((z3 - Z_MIN) * BAR_MAX_PX / (Z_MAX - Z_MIN));
    b4 = (int)((z4 - Z_MIN) * BAR_MAX_PX / (Z_MAX - Z_MIN));
    b5 = (int)((z5 - Z_MIN) * BAR_MAX_PX / (Z_MAX - Z_MIN));
    b6 = (int)((z6 - Z_MIN) * BAR_MAX_PX / (Z_MAX - Z_MIN));

    SetTagWord("Calc_Z1_Bar", (WORD)b1);
    SetTagWord("Calc_Z2_Bar", (WORD)b2);
    SetTagWord("Calc_Z3_Bar", (WORD)b3);
    SetTagWord("Calc_Z4_Bar", (WORD)b4);
    SetTagWord("Calc_Z5_Bar", (WORD)b5);
    SetTagWord("Calc_Z6_Bar", (WORD)b6);

    /* Distance from origin (mm) */
    SetTagFloat("Calc_Dist_From_Origin",
                (float)sqrt(xRaw * xRaw + yRaw * yRaw));

    /* Path length + velocity */
    dx = xRaw - xPrev;
    dy = yRaw - yPrev;
    pathLength += (float)sqrt(dx * dx + dy * dy);
    xPrev = xRaw;
    yPrev = yRaw;
    SetTagFloat("Calc_Path_Length", pathLength);

    if (lastTick != 0) {
        dt = (GetTickCount() - lastTick) / 1000.0f;
        if (dt > 0.01f) {
            v = (float)sqrt(dx * dx + dy * dy) / dt;
            SetTagFloat("Calc_Velocity", v);
        }
    }
    lastTick = GetTickCount();
}

Save and compile. The function is a pure C entry point and uses the apdefap.h macro definitions that WinCC injects at build time. The static variables xPrev, yPrev, pathLength and lastTick are preserved between calls because the runtime keeps the function instance alive in memory.

Step 4 – Schedule the Function as a Global Action

  1. Open Scripts → C scripts → Global actions.
  2. Add a new action, name it CalcMotion_Trigger, paste the body from the project function (or simply call CalcMotion();).
  3. Right-click the action → Properties → Trigger. Add a Tag trigger on HMI_X_Raw with a tolerance of 0.5 mm, and a Time trigger of 100 ms as a fallback so the velocity stays alive even when X is constant.
  4. Compile. The action will appear in Runtime diagnostics (Task Manager / Tools → Runtime diagnostics) under C scripts.
Trigger tuning: 100 ms is a good starting point for human-readable animation. Going below 50 ms consumes significant CPU on a WinCC RT PC and may starve PROFIBUS I/O. The WinCC Information System recommends not exceeding the configured acquisition cycle of the external tags; otherwise the script reads stale values.

Step 5 – Bind the Calculated Tags to the Screen

  1. Open the screen MotionView.
  2. Drop a Graphic view object. Inside it, place a small circle (the moving object). Open its Properties → Position X, click the lamp icon and bind to Calc_X_Screen. Bind Position Y to Calc_Y_Screen.
  3. Place six Bar objects below the work area. For each one bind Process value → Bar height to Calc_Z1_Bar … Calc_Z6_Bar. Set Maximum to 250 to match BAR_MAX_PX.
  4. Add four IO field objects for the calculated values (Calc_Dist_From_Origin, Calc_Path_Length, Calc_Velocity, optional Calc_Angle). Format with one decimal place.

Because the binding is to internal tags written by the C action, the screen updates automatically when the action runs – no VBS or property script is required on the screen itself.

Verification

Check How Expected result
PROFIBUS link WinCC RT diagnostics → Connections, green status Connected, no diagnostic buffer entries
Tag values arrive Online → HMI tags, observe HMI_X_Raw Number changes when PLC moves the axis
C action fires Online → Scripts → Global actions, status LED + last-run timestamp Timestamp updates every 100 ms
Origin (0,0) Force PLC X=0, Y=0, observe object Object sits at the bottom-left of the motion rectangle
Origin (max,max) Force PLC X=1000, Y=800 Object sits at top-right of the rectangle
Bar scaling Force Z1=0 and Z1=500 Bar height 0 px → 250 px
Velocity sign Move axis at constant speed, observe Calc_Velocity Steady value in mm/s matching PLC speed
Load Task Manager during animation RT process < 25 % CPU on a quad-core i5

Troubleshooting Matrix

Symptom Likely cause Resolution
"C scripts" branch is missing in the project tree WinCC Advanced or Comfort RT selected Change HMI device to a WinCC Professional RT variant; recompile the project
Compiler error "undefined reference to GetTagFloat" Function was added outside the project function editor or the function was declared static in a wrong scope Recreate the function in Scripts → C scripts → Project functions so the WinCC build adds the runtime stubs
Tag values stay at 0 Connection to PLC not active, or wrong slot in the connection editor Open Connections, run Connection diagnostics; verify slot 2 of the 315-2DP is bound to the HMI connection
Object jumps, never animates smoothly Trigger tolerance too coarse or external tag acquisition cycle too slow Set Acquisition cycle on the external tags to 100 ms; lower trigger tolerance to 0.1 mm
C script compiles but is not executed Global action is not linked to a trigger Open the action properties and confirm at least one trigger (tag or time) is set
PROFIBUS diagnostic buffer shows "station failure" CP 5612 driver version mismatch or missing PG/PC interface assignment Set PG/PC interface to CP5612.PROFIBUS.1 in Control Panel → Set PG/PC Interface; install the matching SIMATIC NET version
Object visible at top-left instead of bottom-left Y mapping not inverted Apply SCR_Y_MAX - yRaw * factor formula from Step 2
Calc_Velocity spikes at startup Static lastTick initialised to 0, dt becomes huge Guard with if (lastTick != 0) as shown in the code, or seed lastTick = GetTickCount() on first run
Numeric IO field shows ### for very large value Output format too narrow Set Output format → decimal places = 1, increase field width
Trend does not move with object User attached a F(x) trend (VBS-only) to a C-driven value Bind the trend to the internal tag, or use a VB global action for the trend (C actions cannot directly drive F(x) trend interfaces in WinCC Professional)

Performance and Field-Proven Caveats

The PROFIBUS update rate of the 315-2DP on a CP 5612 is limited to the bus cycle – typically 1–10 ms per slave in a small installation. Reading eight REAL tags in one cycle is cheap; do not place the eight reads in eight separate scheduled actions, because each one locks the PROFIBUS driver for a round trip. A single global action that reads all eight then writes all derived values is the recommended pattern and is what the code above does.

When the HMI screen is opened in WinCC Runtime, the Screen Open event can host a one-shot C function that pre-seeds the static xPrev / yPrev variables to the current PLC position, avoiding a path-length spike the first time the user opens the screen. Use GetTagFloatWait() in that one-shot path so the initial values are guaranteed to be valid before the global action starts running.

For long-running sessions, the pathLength and lastTick values grow in 32-bit float precision. After several hours the float mantissa resolution degrades. A field-proven fix is to reset the integrator at every PLC program cycle using a rising-edge flag from the PLC, or to store the running total as a 64-bit double by promoting the variable to double in the C function and using SetTagDouble() on a 64-bit HMI tag.

Standards and Cross-References

Can I use C scripts in WinCC Advanced or Comfort?

No. C scripting is available only in WinCC Professional RT. WinCC Advanced and Comfort support VBScript and the built-in F(x) trend control, but not user-defined C functions. Switch the HMI device to a WinCC Professional RT variant before opening Scripts → C scripts.

How do I place the (0,0) origin at the bottom-left of the motion object?

WinCC uses a top-left screen origin with Y increasing downward. To flip Y, compute sy = (PLANT_Y_MAX - yRaw) * (SCR_Y_MAX - SCR_Y_MIN) / (PLANT_Y_MAX - PLANT_Y_MIN) + SCR_Y_MIN. Bind Position X to Calc_X_Screen and Position Y to Calc_Y_Screen on the motion object inside the Graphic view.

Why does my calculated value flicker between 0 and the new value?

Usually the trigger fires before the PROFIBUS driver has delivered the new value, or the external tag acquisition cycle is slower than the trigger cycle. Set the Acquisition cycle of the external tags to match the trigger (e.g. 100 ms) and use a tag trigger with a tolerance of 0.1 mm so the action only runs when a meaningful change occurred.

Which PROFIBUS card should I use, CP 5612 or CP 5622?

CP 5612 is the PCI version (6GK1561-2AA00), CP 5622 is the PCIe version (6GK1562-2AA00). Both run the same SIMATIC NET driver and support 12 Mbps PROFIBUS DP. Pick CP 5622 on modern PCs because most current motherboards no longer carry legacy PCI slots.

How often should the C global action be triggered?

For a human-readable motion study, 100 ms is a good default. Faster than 50 ms increases CPU load without visible improvement on a typical HMI panel, while slower than 250 ms makes the motion look stuttered. Tie the action to a 100 ms time trigger and a tag trigger on the X axis with a small tolerance.

Back to blog