WinCC C-Script Bit Toggle: Set and Reset Bits in Tag Words

David Krause11 min read
HMI ProgrammingSiemensTutorial / How-to
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

WinCC C-Script Bit Toggle: Setting and Resetting Individual Bits in a Tag Word

When a single boolean state must be controlled inside a packed 16-bit or 32-bit HMI tag, the cleanest path in WinCC V7.x and WinCC (TIA Portal) is a short C/VBS function that reads the word, applies a bit mask, and writes the word back. This reference documents the three production-grade approaches: the Dynamic Wizard "Set/Reset Bit in Tag", a hand-written ANSI-C script using GetTagWordWait/SetTagWordWait, and the equivalent VBScript path. It also covers the failure modes (race conditions, signed words, picture change, read-back propagation) that catch engineers on commissioning day.

Scope: WinCC V7.4 SP3 and later, plus WinCC Professional / WinCC Advanced in TIA Portal V15.1 and later. The logic is identical for BYTE, WORD, and DWORD tags; only the helper and the mask width change.

1. Why a Whole Word Instead of a Discrete Tag?

PLCs pack boolean states into words to conserve addressing space. A S7-1500 data block with 32 valve feedbacks is one WORD of 16 bits or two WORDs of 16 bits rather than 32 separate BOOL tags. On the HMI side the engineer has three options:

  1. Unpack into discrete tags at the PLC using bit-debounce FCs. Clean but doubles the tag count.
  2. Connect the IO field directly to the word and use a style for bit-level editing. Limited; WinCC IO fields are whole-word.
  3. Manipulate the bit in the HMI tag with a script triggered by a button event. This is the standard HMI-only solution when the PLC program is locked or you do not own it.

Option 3 is the focus of this article. The Dynamic Wizards shipped with WinCC generate the same C-script code automatically; the discussion below explains what that generated code does so you can extend, debug, or port it to VBS.

2. Bit Math Recap for WinCC Tags

A WORD is an unsigned 16-bit integer (range 0-65535). Bit n (counting from LSB = bit 0) has the value 1 << n. Two operators do all the work:

Operation Operator Example (bit 4) Result if word = 0x00F0
Test a bit & (AND) w & 0x0010 0x0010 (true / non-zero)
Set a bit | (OR) w | 0x0010 0x00F0 (no change, already set)
Clear a bit & ~ (AND NOT) w & ~0x0010 0x00E0
Toggle a bit ^ (XOR) w ^ 0x0010 0x00E0 (was set, now clear)

Hex masks for every bit in a 16-bit word are listed below; copy them into a #define block at the top of the script.

Bit Hex Mask Decimal Bit Hex Mask Decimal
0 0x0001 1 8 0x0100 256
1 0x0002 2 9 0x0200 512
2 0x0004 4 10 0x0400 1024
3 0x0008 8 11 0x0800 2048
4 0x0010 16 12 0x1000 4096
5 0x0020 32 13 0x2000 8192
6 0x0040 64 14 0x4000 16384
7 0x0080 128 15 0x8000 32768

Mnemonic shortcut: bit n mask = 1 << n. The hex notation above is convenient for hand-editing an existing mask; the shift form is preferable in generated code.

3. Method A - Dynamic Wizard "Set/Reset Bit in Tag"

WinCC ships two picture-level wizards that emit ANSI-C functions you call from a button click event:

  • Set/Reset a bit in a tag - toggles a single named bit in one word.
  • Set/Reset bits in a tag - sets or clears up to 16 bits in one transaction.

Both are available from Graphics Designer → right-click object → Dynamic Wizard → System → Tags. The wizard generates a global C function such as SetBitInTag(char* szTagName, int nBit) and inserts the call into the object's mouse-event script.

Generated source (typical output):

// Wizard-generated - DO NOT edit the function signature
BOOL SetBitInTag(char* szTag, int nBit)
{
    DWORD dwValue = 0;
    DWORD dwMask  = (DWORD)(1 << nBit);

    if (GetTagDWord(szTag, &dwValue) == FALSE)
        return FALSE;

    dwValue |= dwMask;          // SET bit
    // dwValue &= ~dwMask;     // CLEAR bit (uncomment as needed)
    return SetTagDWord(szTag, dwValue);
}

The wizard's value is that it generates a syntactically correct template, including the critical Get... result check, but the engineer still decides whether the action is set, clear, or toggle. Always rename the wizard output; the default name SetBitInTag collides between pictures if you copy-paste wizards.

4. Method B - Manual ANSI-C Script

When the wizard does not match (multi-bit atomic operations, conditional set based on other tags, picture-change persistence), write the C script by hand. Use the wait variants so the script blocks until the tag write completes; this avoids the race condition where the operator clicks twice before the first write commits.

4.1 Set one bit (bit 4 of TAG_1)

// Set bit 4 in tag TAG_1
#include "apdefap.h"
void OnClick(char* lpszPictureName, char* lpszObjectName, char* lpszPropertyName, UINT nFlags, int x, int y)
{
    WORD wValue = GetTagWordWait(lpszPictureName, "TAG_1");
    wValue |= 0x0010;                                  // set bit 4
    SetTagWordWait(lpszPictureName, "TAG_1", wValue);
}

4.2 Clear one bit (bit 4 of TAG_1)

void OnClick(char* lpszPictureName, char* lpszObjectName, char* lpszPropertyName, UINT nFlags, int x, int y)
{
    WORD wValue = GetTagWordWait(lpszPictureName, "TAG_1");
    wValue &= (WORD)~0x0010;                           // clear bit 4
    SetTagWordWait(lpszPictureName, "TAG_1", wValue);
}

4.3 Toggle (XOR) one bit

void OnClick(char* lpszPictureName, char* lpszObjectName, char* lpszPropertyName, UINT nFlags, int x, int y)
{
    WORD wValue = GetTagWordWait(lpszPictureName, "TAG_1");
    wValue ^= 0x0010;                                  // flip bit 4
    SetTagWordWait(lpszPictureName, "TAG_1", wValue);
}

4.4 Atomic set + clear of multiple bits

To force bits 0 and 4 HIGH and bits 1, 2 LOW in a single read-modify-write cycle:

WORD wValue = GetTagWordWait(lpszPictureName, "CMD_WORD");
wValue |=  (WORD)(0x0011);    // bits 0 + 4
wValue &= (WORD)(~0x0006);   // clear bits 1 + 2
SetTagWordWait(lpszPictureName, "CMD_WORD", wValue);

The advantage of a single GetTagWordWait/SetTagWordWait pair is that no other script can race between the read and the write. If your button event runs multiple actions on the same tag, keep them inside one function or accept the inherent race.

4.5 Conditionally set a bit (interlock)

WORD wValue = GetTagWordWait(lpszPictureName, "MODE_WORD");
if (wValue & 0x0001)                  // if bit 0 (Auto) is set
    wValue |= 0x0010;                 // also enable bit 4 (Run)
else
    wValue &= (WORD)~0x0010;          // else force Run off
SetTagWordWait(lpszPictureName, "MODE_WORD", wValue);

This pattern - read once, branch, single write - is the canonical way to implement an HMI-side interlock without an FB on the PLC.

5. Method C - VBScript Equivalent (WinCC Professional / TIA Portal)

In TIA Portal WinCC the scripting language is VBScript. The same logic becomes:

' Toggle bit 4 of HMI tag "Tag_1"
Sub OnClick(ByVal Item)
    Dim vValue
    vValue = SmartTags("Tag_1").Value
    vValue = vValue Or &H10          ' set bit 4
    ' vValue = vValue And Not &H10   ' clear bit 4
    ' vValue = vValue Xor &H10       ' toggle bit 4
    SmartTags("Tag_1").Value = vValue
End Sub

VBScript integers are 16-bit inside SmartTags(...).Value when the tag is a Word, so the same masks apply. For DWord tags use &H10000 style literals or build the mask with 2^n and the CLng conversion.

6. Function Reference - C-Script Tag Access

Function Return Behavior Use When
GetTagWord(szTag, pwValue) BOOL Asynchronous read, returns immediately. Animation or scheduled action; result not yet guaranteed.
GetTagWordWait(szTag) WORD Synchronous read, blocks until value available. Button-event scripts; value must be current.
SetTagWord(szTag, wValue) BOOL Asynchronous write. High-frequency updates, e.g. bar graph.
SetTagWordWait(szTag, wValue) BOOL Synchronous write, blocks until acknowledged. Command scripts where the next line depends on the write.
GetTagBit(szTag, dwBit) BOOL Reads a single bit from a tag. Status display only.
SetTagBit(szTag, dwBit, bValue) BOOL Writes a single bit (WinCC auto-handles bit-packed tags). Single-bit toggle without manual mask.

Hidden gem: WinCC exposes SetTagBit / GetTagBit for tags of type BYTE, WORD, and DWORD. If the tag address points to a bit-offset within a word (e.g. DB1.DBW2.4), the runtime resolves it and you do not need a mask. This is the cleanest solution when the PLC side allows per-bit tag addressing.

7. Performance and Scheduler Considerations

Every GetTagWordWait/SetTagWordWait pair costs one full request/response cycle on the configured channel (typically 50-200 ms on S7-MPI/TCP, <10 ms on S7-PLCSIM or named connections). Engineering guidance from the WinCC Performance Manual:

  • Batch all changes to a single tag into one script invocation. Five sequential clicks that each read-modify-write the same word cost five cycles; one consolidated script costs one.
  • Avoid C-scripts in scheduled actions (1 Hz, 250 ms, 100 ms) that only set/clear bits. Convert the trigger to a PLC-side logic and present the resulting bit to the HMI; the HMI is for visualization, not for cycling state.
  • Use SetTagRaw/GetTagRaw only for cross-process tags. Standard SetTag* calls already cache on the data manager side and outperform raw access for user-defined tags.
  • Compile the C project after every global change. Uncompiled C functions throw Runtime error: Function not found in RT and the operator sees a frozen button.

Reference: WinCC V7.5 SP2 - Working with WinCC - C Scripting (Siemens Support entry 109748145).

8. Common Failure Modes and Diagnostics

Symptom Likely Root Cause Fix
Click has no effect on PLC tag. Tag is configured as read-only, or no write authorization for current user. Open Tag Management; check Update column for the tag and the user level for the button.
Value inverts immediately after being set. PLC OB1 is overwriting the word every scan with derived logic. Use a separate handshake bit; never toggle a derived status word.
Bit toggles, then two seconds later reverts. Operator clicked twice; second click is a toggle, not a redundant set. Use explicit set/clear, not XOR, for command buttons.
Compile error "undefined symbol SetTagWordWait". Project header file apdefap.h not included, or new project missing the global C project. Add #include "apdefap.h" and confirm C-Editor → Options → Generate Global C Project is checked.
Bit 15 toggle unexpectedly affects the value sign. Tag configured as SWORD (signed) and operator sees -1 / +1 wrapping. Change tag type to WORD in Tag Management; signed masking with ~0x8000 is undefined behavior.
WinCC Runtime hangs for 5 s on click. Connection to PLC lost; SetTagWordWait blocks until timeout. Check Channel Diagnosis applet; set Wait time for tag I/O per project settings.
Bit manipulation works in RT but not on the engineering station. RT and ES use different project directories; ES never compiled the C project. Compile the global C project on the ES, then copy *.fct files to RT's \<project>\library.

9. Verification Procedure After Deployment

  1. Open WinCC Explorer → Tools → Channel Diagnosis; confirm the tag TAG_1 has a green connection status before testing.
  2. Open Tag Simulator and force TAG_1 to 0x0000. Click the toggle button; verify 0x0010 appears in the simulator.
  3. Click again; verify the value returns to 0x0000. If it goes to 0x0020, you used set twice instead of toggle - swap the OR for XOR.
  4. Force TAG_1 to 0xFFFF from the simulator. Click the clear button. The tag should read 0xFFEF. If it reads 0xFFEE, the mask is shifted by one - recheck the bit number.
  5. Open the C project in the C-Editor, press Build, then re-run RT. A clean rebuild eliminates stale function references after refactoring wizard-generated names.
  6. On the PLC side (STEP 7 / TIA Portal), monitor the word online. Confirm the bit change is visible in the PLC tag within one cycle of the configured update time (default 1 s for WinCC tags).

10. Related Patterns and When to Use Them

10.1 Indirect bit addressing

When the bit number is dynamic (e.g. operator selects bit 0-15 from a drop-down), pass n as a parameter:

void SetBitInTag(char* szTag, int nBit)
{
    WORD wValue = GetTagWordWait(NULL, szTag);
    wValue |= (WORD)(1 << nBit);
    SetTagWordWait(NULL, szTag, wValue);
}

Guard against nBit < 0 or nBit > 15; an out-of-range shift produces undefined behavior in C.

10.2 Tag-prefixed names in multi-picture projects

Pass lpszPictureName to the helper so tag-name resolution looks in the current picture's prefix space first:

WORD wValue = GetTagWordWait(lpszPictureName, "TAG_1");

Without the prefix, SetTagWordWait("TAG_1", ...) relies on the global tag scope and can collide with a similarly named picture tag in a popup.

10.3 Bit-toggling in faceplates

WinCC faceplate instances expose internal tags via the .intern namespace. The script lives in the faceplate's events and operates on Faceplate.Properties.X rather than GetTagWordWait:

Dim vMask : vMask = 2 ^ InternalTag("BitIndex").Value
Properties("StatusWord").Value = Properties("StatusWord").Value Or vMask

This keeps the toggle encapsulated and re-usable across many faceplate instances.

11. Documentation References

12. FAQ

What is the easiest way to toggle a single bit in a WinCC tag word?

Open Graphics Designer, right-click the button, choose Dynamic Wizard → System → Tags → Set/Reset a bit in a tag, then select the tag and bit number. The wizard generates a C function you assign to the click event; for a true flip, change the OR operator inside the generated function to XOR (^).

Why use SetTagWordWait instead of SetTagWord in a button script?

SetTagWord returns immediately and the next script line may execute before the write reaches the PLC, causing race conditions on rapid double-clicks. SetTagWordWait blocks until the runtime acknowledges the write, so a second click always sees the updated word.

How do I clear multiple bits in one C-script action?

Read the word once with GetTagWordWait, then AND it with the inverse of a combined mask. Example for clearing bits 1 and 2: wValue &= (WORD)~0x0006;. Combine the OR for bits you want set and the AND-NOT for bits you want clear in one read-modify-write cycle.

Does the same code work in TIA Portal WinCC Professional?

Yes, but the language is VBScript instead of ANSI-C. Replace GetTagWordWait with SmartTags("TagName").Value and SetTagWordWait with the same property assignment. The mask values (0x0001, 0x0010, etc.) are identical because VBScript uses 16-bit Word and 32-bit DWord the same way.

Can I use SetTagBit with a bit offset instead of a mask?

Yes. If the tag is addressed with a bit suffix such as DB1.DBW2.4, calling SetTagBit(szTag, 0, TRUE) sets bit 4 directly and the runtime performs the read-modify-write internally. This is the cleanest method when your PLC tag configuration already exposes individual bits.

Back to blog