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.
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:
- Unpack into discrete tags at the PLC using bit-debounce FCs. Clean but doubles the tag count.
- Connect the IO field directly to the word and use a style for bit-level editing. Limited; WinCC IO fields are whole-word.
- 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/GetTagRawonly for cross-process tags. StandardSetTag*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 foundin 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
- Open WinCC Explorer → Tools → Channel Diagnosis; confirm the tag
TAG_1has a green connection status before testing. - Open Tag Simulator and force
TAG_1to0x0000. Click the toggle button; verify0x0010appears in the simulator. - Click again; verify the value returns to
0x0000. If it goes to0x0020, you used set twice instead of toggle - swap the OR for XOR. - Force
TAG_1to0xFFFFfrom the simulator. Click the clear button. The tag should read0xFFEF. If it reads0xFFEE, the mask is shifted by one - recheck the bit number. - 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.
- 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
- Siemens Support entry 109748145 - WinCC V7.5 SP2: Working with WinCC - C Scripting
- Siemens Support entry 109772707 - WinCC Professional / TIA Portal: VBScript Programming Reference
- Siemens Support entry 109758131 - WinCC V7.5: C-Script Function Reference (GetTag*/SetTag*)
- Siemens Support entry 109753420 - WinCC V7.5: Tag Management and Bit Access
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.