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:
- PLC side – S7-315-2DP holds eight REAL tags in a data block (e.g. DB100, bytes 0-63).
- Communication layer – PROFIBUS master CP 5612 in the PC, PROFINET or PROFIBUS connection configured in the TIA device configuration of the HMI.
- 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 |
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
- Open Scripts → C scripts → Global actions.
- Add a new action, name it
CalcMotion_Trigger, paste the body from the project function (or simply callCalcMotion();). - Right-click the action → Properties → Trigger. Add a Tag trigger on
HMI_X_Rawwith 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. - Compile. The action will appear in Runtime diagnostics (Task Manager / Tools → Runtime diagnostics) under C scripts.
Step 5 – Bind the Calculated Tags to the Screen
- Open the screen MotionView.
- 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 toCalc_Y_Screen. - 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 matchBAR_MAX_PX. - Add four IO field objects for the calculated values (
Calc_Dist_From_Origin,Calc_Path_Length,Calc_Velocity, optionalCalc_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
- TIA Portal V20 – C scripting (RT Professional)
- WinCC Information System Scripting (VBS, ANSI-C, VBA) – PDF
- Siemens Support FAQ – WinCC Advanced / Comfort F(x) trend and C reference
- SIMATIC S7-300 CPU 315-2DP manual
- SIMATIC NET – PROFIBUS CP 5612/CP 5622 driver documentation
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.