1. Problem Statement: Raw Format Codes Inside PLC Strings
A common field observation with the Siemens Program_Alarm instruction on S7-1200 and S7-1500 controllers is that the alarm message visible on the HMI shows fully resolved numeric values, but the same alarm text read out of a string variable inside the user program still contains the unprocessed format placeholders. A typical HMI display looks like:
Maximal height buffer pit 1 reached, Set: 12345 mm. Measured: 12348 mm.
while the value retrieved in the PLC from the same msg input or copied into a tag is:
Maximal height buffer pit 1 reached, Set: @1%5.0f@ mm. Measured: @2%5.0f@ mm.
This is not a bug. Program_Alarm stores the format string and the eight associated values as separate alarm payloads and hands the substitution work to the alarm subscriber (WinCC Unified, Comfort Panel, Web Server, HMI Tag logging). To produce a fully resolved text inside the PLC, the application code must perform the substitution itself. This article documents the underlying mechanism, the format-string grammar, and a complete SCL implementation strategy.
%5.0f, two associated values of type REAL, originating alarm text "Maximale hoogte buffer put 1 bereikt, Ingesteld: @1%5.0f@ mm. Gemeten: @2%5.0f@ mm."
2. Program_Alarm Instruction Anatomy
The Program_Alarm instruction is a system block that lives under Instructions → Basic Instructions → Alarm. It was introduced in TIA Portal V15.1 for the S7-1500 family and is available for S7-1200 from firmware V4.4 / TIA V16 onward. A complete call signature is shown below.
| Parameter | Direction | Data Type | Description |
|---|---|---|---|
sig |
Input | BOOL |
Trigger condition (rising edge fires the alarm) |
id |
Input | DWORD |
User-defined alarm ID for selective acknowledgement or filtering |
msg |
Input |
WSTRING / STRING
|
Alarm text containing @n%fmt@ placeholders |
sd_1 ... sd_8 |
Input | Any elementary type | Up to eight associated values (BOOL, INT, DINT, REAL, LREAL, STRING, WSTRING, DTL, etc.) |
ENO |
Output | BOOL |
Set if the block executes without internal error |
The block sends one notification to the PLC alarm buffer per rising edge of sig. The WinCC / HMI runtime, the integrated Web Server, the SysLog library and any OPC UA PubSub client receiving the alarm all perform the same final step: substitute each @n%fmt@ placeholder using sd_n. The PLC user program, however, only sees msg as a constant string literal; sd_1 through sd_8 are individual tag values that the user already owns.
3. The @n%fmt@ Format String Grammar
The placeholder grammar accepted by Program_Alarm (and the closely related Program_Alarm_W, Program_Alarm_S variants) follows the C99 printf conventions with an extra pair of @ wrappers so that the alarm text remains self-contained:
@<index>%[flags][width][.precision]specifier@
| Element | Allowed Values | Meaning |
|---|---|---|
<index> |
1 - 8 | Pointer to sd_1 ... sd_8
|
flags |
-, +, , 0, #
|
Left-justify, sign prefix, leading zero, alternate form |
width |
Decimal integer, may use *
|
Minimum field width (pads with spaces or zeros) |
.precision |
Decimal integer | Digits after the decimal point for floats, max chars for strings |
specifier |
d, u, x, X, f, e, E, g, G, b, s, t
|
Type of the associated value |
Commonly used patterns:
-
@1%5.0f@– five-character wideREALwith zero decimals (typical level / pressure read-out) -
@2%6.2f@– six-character wideREALwith two decimals (temperature, flow) -
@1%08x@– eight-digit hex with zero padding (status word) -
@3%-.20s@– left-justified string, up to twenty characters (recipe name) -
@4%t@– DTL date/time stamp (TIA V18+)
@ wrappers are mandatory. A stray %5.0f without the surrounding @ characters is interpreted as literal text and copied to the HMI without substitution.4. Why the HMI Resolves the Text but the PLC Does Not
Inside the PLC, msg is a constant WSTRING located in the load memory of the program block. The associated values sd_1 ... sd_8 are ordinary tags that the user has already linked to the block call. The alarm manager (component of the S7-1500 firmware or, on S7-1200, of the runtime from firmware V4.4) builds the resolved text only when an alarm subscriber requests it. The PLC never stores the resolved string; it stores only the format string and the raw values.
The HMI runtime is the typical subscriber. When the panel receives the alarm notification, it pulls msg, the associated values, and the id from the PLC, runs the substitution locally and renders the final string. Web Server, WinCC Unified GraphQL, SysLog and OPC UA A&C do the same. Because the PLC user program cannot subscribe to its own alarms, the only reliable way to obtain the resolved text inside the PLC is to format it yourself with SCL string operations.
5. Solution 1: Manual String Composition in SCL
The most direct path is to compose the resolved text from the same source values that are fed into sd_1 ... sd_8. For the source case ("Set: @1%5.0f@ mm. Measured: @2%5.0f@ mm.") the SCL block below produces an identical resolved string inside a WSTRING tag.
FUNCTION "AlarmString_BufferPit1" : Void
VAR_IN_OUT
rSetpoint : REAL; // bound to Program_Alarm.sd_1
rMeasured : REAL; // bound to Program_Alarm.sd_2
sResolved : WSTRING; // destination, e.g. for data log
END_VAR
VAR_TEMP
sSet : WSTRING; // formatted setpoint
sMea : WSTRING; // formatted measured value
END_VAR
BEGIN
// --- 1. Format each REAL like %5.0f would ---
sSet := REAL_TO_WSTRING(rSetpoint);
sMea := REAL_TO_WSTRING(rMeasured);
// --- 2. Compose the complete alarm text ---
sResolved := '';
sResolved := CONCAT(sResolved,
WSTRING#'Maximal height buffer pit 1 reached, Set: ');
sResolved := CONCAT(sResolved, sSet);
sResolved := CONCAT(sResolved, WSTRING#' mm. Measured: ');
sResolved := CONCAT(sResolved, sMea);
sResolved := CONCAT(sResolved, WSTRING#' mm.');
END_FUNCTION
Notes on the code:
-
REAL_TO_WSTRINGis available on S7-1500 from firmware V2.0 and on S7-1200 from V4.4. On older firmware, useREAL_TO_STRINGand convert withSSTRING_TO_WSTRING. - The result string uses the default decimal point (dot). To switch to a comma for European locales, replace the dot character after the conversion (see Section 8).
-
CONCATallocates a freshWSTRINGon each call. For eight substitutions per alarm, build the string with a singleCONCATchain rather than repeated assignments to minimise reallocations.
6. Solution 2: Reusable Function Block With Formatted Output
Realistic installations use dozens of alarm texts. A reusable FB that mirrors the printf-style specifier reduces the maintenance cost to a single source of truth and keeps the resolved text in lock-step with the HMI rendering.
FUNCTION_BLOCK "Fmt_Real5_0"
VAR_INPUT
rValue : REAL; // input value
END_VAR
VAR_OUTPUT
sOut : WSTRING; // formatted result, padded to width 5
END_VAR
VAR CONSTANT
cPad : WSTRING := ' '; // 5 spaces
cNeg : WSTRING := '-';
END_VAR
VAR_TEMP
sRaw : WSTRING;
iLen : INT;
i : INT;
iSign : INT; // 0 or 1
END_VAR
BEGIN
// Handle sign
IF rValue < 0.0 THEN
iSign := 1;
rValue := -rValue;
ELSE
iSign := 0;
END_IF;
// Convert absolute value
sRaw := REAL_TO_WSTRING(rValue);
// Strip decimal portion (precision = 0)
iLen := LEN(sRaw);
FOR i := 1 TO iLen DO
IF MID(sRaw, i, 1) = WSTRING#'.' THEN
sRaw := LEFT(sRaw, i - 1);
EXIT;
END_IF;
END_FOR;
// Pad left with spaces to total width 5
iLen := LEN(sRaw);
IF iLen + iSign < 5 THEN
sOut := RIGHT(cPad, 5 - iLen - iSign);
ELSE
sOut := '';
END_IF;
IF iSign > 0 THEN
sOut := CONCAT(sOut, cNeg);
END_IF;
sOut := CONCAT(sOut, sRaw);
END_FUNCTION_BLOCK
Pair the FB with a second block that assembles the full text:
FUNCTION_BLOCK "AlarmBufferPit1_FB"
VAR
fbFmt1 : "Fmt_Real5_0";
fbFmt2 : "Fmt_Real5_0";
sSet : WSTRING;
sMea : WSTRING;
END_VAR
VAR_IN_OUT
rSet : REAL;
rMea : REAL;
sOut : WSTRING;
END_VAR
BEGIN
fbFmt1(rValue := rSet, sOut => sSet);
fbFmt2(rValue := rMea, sOut => sMea);
sOut := CONCAT(WSTRING#'Maximal height buffer pit 1 reached, Set: ',
sSet);
sOut := CONCAT(sOut, WSTRING#' mm. Measured: ');
sOut := CONCAT(sOut, sMea);
sOut := CONCAT(sOut, WSTRING#' mm.');
END_FUNCTION_BLOCK
Call the FB in the same OB / FC that drives Program_Alarm. Pass the same tags rSet and rMea to both sd_1 / sd_2 inputs and to the FB. The HMI text and the PLC-resident string are now generated from the same source values and cannot drift apart.
7. S7-1200 vs S7-1500 Capability Matrix
| Feature | S7-1500 | S7-1200 |
|---|---|---|
First TIA version with Program_Alarm
|
V15.1 | V16 (firmware V4.4) |
| Max. associated values | 8 | 8 |
| Specifiers supported | Full set incl. %t DTL |
Subset: d u x f e s b
|
| WSTRING message | Yes | Yes (firmware V4.4) |
REAL_TO_WSTRING |
V2.0+ | V4.4+ |
| Local alarm buffer | Yes, 1024 entries | Yes, 64 entries |
| Web Server subscriber | Yes | Yes |
| WinCC Comfort / Unified subscriber | Yes | Yes |
| SysLog / OPC UA A&C subscriber | Yes (V18+) | Yes (V18+) |
Program_Alarm instruction is not present. Use the legacy WRMSG / WR_USMSG instructions instead, which support a smaller set of format specifiers and cannot be re-rendered by the PLC user program.8. Common Pitfalls, Format Specifier Errors and Edge Cases
| Symptom | Root Cause | Fix |
|---|---|---|
Text on HMI shows @1%5.0f@ literally |
Placeholder not enclosed in @
|
Rewrite as @1%5.0f@
|
Text on HMI shows ? instead of value |
Specifier does not match the data type (e.g. %d for REAL) |
Use %f for REAL, %d for INT/DINT, %s for STRING |
| PLC string and HMI string differ by one character | Locale-specific decimal separator (. vs ,) |
Apply REPLACE on the PLC output to switch . → , if required |
| Truncated leading characters on HMI |
width too small for the value at full precision |
Increase width or reduce precision
|
| Alarm fires but is not visible on the panel | Tag subscription filter does not include the alarm ID | Add the alarm number to the HMI alarm log filter |
| ENO = FALSE on first scan |
sig is TRUE on cold restart and the alarm buffer is full |
Initialise sig = FALSE on startup; check buffer size |
| Resolved string uses scientific notation | Value magnitude outside default range | Use explicit %e / %g and a wider field |
| Step 7 import shows obsolete format | Migrated from WinCC flexible legacy alarm text | Run "Update block" in TIA, re-enter placeholder manually |
9. Verification Procedure
-
Online watch table. Open the data block or instance DB in Online & Diagnostics → Monitor / Modify. Force
rSetpoint = 12345andrMeasured = 12348and observesResolved. The expected output is"Maximal height buffer pit 1 reached, Set: 12345 mm. Measured: 12348 mm.". -
Trace recording. Add the
sResolvedtag to a Trace configuration together withsig. Trigger the alarm and verify that the text is generated on the same scan as the rising edge. - Buffer consistency. Open Online → Alarm display → Show alarm buffer in TIA. The latest alarm entry must show the identical resolved text.
- HMI cross-check. Acknowledge the alarm on the Comfort Panel. The HMI log row must show the same values character-by-character.
-
Edge values. Force
rSetpoint = -1.0andrSetpoint = 99999to verify that the field-width logic pads and clips correctly. Confirm that@1%5.0f@with the value12345shows five characters with no decimals. -
Loss of power. Power-cycle the PLC, confirm that the formatted tag initialises to an empty string and that the first
Program_Alarmcycle produces the correct resolved text without any pre-buffer left over from the previous run.
WSTRING consumes approximately 0.05 ms on a CPU 1515-2 PN. Avoid calling the FB inside fast OB1 loops when the alarm condition cannot change; gate the FB with the same sig rising edge used by Program_Alarm.10. Best-Practice Notes
- Use WSTRING rather than
STRINGwhenever the alarm text is also displayed on WinCC Unified panels with multi-byte fonts. MixingSTRINGfor PLC logic andWSTRINGfor HMI forces a conversion and can cause the placeholders to be visible during a brief window on cold start. - Centralise all format strings in a single data block of type
ARRAY[1..n] OF WSTRING. Both theProgram_Alarm.msginput and the FB composition logic read the same constant. This guarantees that the resolved text on the PLC never drifts from the HMI message. - For long-term data logging, prefer the SysLog library (TIA V18+) rather than logging the resolved string into a CSV. The SysLog entry stores the format string and the eight associated values separately, allowing post-processing tools to re-render the text in any locale.
- Avoid manually string-formatting the value if you only need it for the HMI. Use the Alarm logging features of the panel; the PLC user program should be reserved for scenarios such as sending the resolved text to a second controller, a printer, or an SMS gateway.
- Document the maximum possible value of every associated value next to the placeholder in the data block comment. The runtime does not warn if the value exceeds the field width.
11. Frequently Asked Questions
Why does the HMI show the resolved value but the PLC string still contains @1%5.0f@?
The Program_Alarm block stores the format string and the eight associated values separately. The substitution is performed by the alarm subscriber (HMI runtime, Web Server, OPC UA A&C). The PLC user program never receives the resolved text; it only owns the format string and the raw values, and must format them itself if a resolved string is needed inside the PLC.
Which TIA Portal version and firmware are required for Program_Alarm with eight associated values?
Program_Alarm with up to eight associated values is available on S7-1500 from TIA Portal V15.1, and on S7-1200 from TIA Portal V16 / firmware V4.4. TIA V17 adds the %t DTL specifier and improved width handling; V18 introduces SysLog / OPC UA A&C integration.
Can I read the resolved text from the PLC alarm buffer with a system function?
There is no system instruction that returns the resolved text from the PLC alarm buffer. The Get_Alarm / Get_AlarmState instructions return the alarm metadata (ID, state, timestamp) but not the rendered string. The only robust path is to format the string yourself using SCL, exactly as documented in Sections 5 and 6 of this article.
How do I reproduce the %5.0f behaviour exactly in SCL?
Convert the value with REAL_TO_WSTRING, strip everything from the decimal point onward (precision = 0), prepend a minus sign if negative, and pad with leading spaces until the total length equals the field width. The reusable FB Fmt_Real5_0 shown in Section 6 implements this behaviour byte-for-byte equivalent to the HMI renderer.
My alarm text contains both ASCII text and placeholders. Will the HMI show the placeholders if I forget the closing @?
Yes. A placeholder that is not properly closed is treated as literal text and copied unchanged to the HMI. For example, the string 'Set: @1%5.0f mm' (missing trailing @) renders on the panel as the literal sequence 'Set: @1%5.0f mm'. Always validate the format string in TIA's alarm text editor, which underlines malformed placeholders in red from V16 onward.
Does using REAL_TO_WSTRING in SCL affect the cycle time of OB1?
A single REAL_TO_WSTRING call followed by four CONCAT operations typically consumes 30-80 µs on a CPU 1515-2 PN. Gate the FB with the rising edge of sig so the conversion only runs once per alarm, not on every scan. For very high-frequency alarms consider migrating to the SysLog library which renders the text in the WinCC Unified runtime, off-loading the PLC entirely.