Overview: Why the Name-Based Recipe Problem Exists in WinCC Flexible
WinCC Flexible (the HMI configuration suite that ships with SIMATIC Panels and WinCC Runtime, predecessor to TIA Portal's WinCC) exposes a recipe system through the panel's recipe view. The runtime library, however, only allows the operator to address recipe records by integer record number, not by a free-form alphanumeric name. Functions such as LoadDataRecord, SaveDataRecord, GetDataRecordName, and GetDataRecordNumber all return or accept a 16-bit record index, while the textual name is a parallel value the HMI holds internally.
This creates a real field problem: an upstream ERP or batch system issues a product number or batch code (e.g. "RECEPE-XCV-787") and the operator expects the matching recipe to load automatically. There is no native scripting API in WinCC Flexible that lets a VBScript or the recipe view itself call LoadDataRecord with a string argument.
The accepted solution is to push the name-to-data mapping into the PLC. The HMI keeps the canonical WinCC Flexible recipe (a numeric record on the panel), and a Siemens S7 function block on the PLC mirrors the recipe payload in large arrays indexed by the article number. The FB performs:
- Fetch (linear search the array for a matching
Active_Artikel) - Save (overwrite the existing record or write into the first empty slot)
- Status flags (
SAVED,FETCHED) that survive scan-to-scan - Automatic reset of status when the active article changes
Prerequisites
Before implementing the name-based recipe FB you need:
- SIMATIC WinCC Flexible 2008 SP5 (or WinCC Flexible 2005 HF4 and later). The recipe system is documented in the WinCC Flexible 2008 SP5 System Manual, chapter 9 ("Configuring Recipes").
- STEP 7 V5.5 + SPx or TIA Portal V13+ with the matching S7-300/S7-400 PLC support package. The reference FB below is written in STL (AWL) for the S7-300/400 family, but the logic ports cleanly to SCL in TIA Portal.
- An S7-300 or S7-400 CPU with sufficient work memory. A 15 000 REAL array consumes 60 000 bytes (15 000 × 4). For 15 000 DINT indices (the "Corr_Artikel" index array) add another 60 000 bytes. Plan for ~150 KB of work memory plus the data blocks.
- A defined tag interface between the panel recipe and the PLC. The recipe view on the HMI still reads/writes the structured recipe tags; the FB is a shadow that persists values across power cycles.
-
Acyclic recipe storage on the HMI side if persistence is required, or a DB marked as retentive (S7-300:
non-retentivein DB properties cleared; or useSET/CLRwithAR2pointers in the startup OB100 to reload).
WinCC Flexible Recipe Functions: What Each One Returns
Knowing the exact behaviour of the four native recipe functions is critical because the FB is built around their limitations. The signature set is summarised in the WinCC Flexible Runtime Recipe Functions manual:
| Function | Argument | Type | Direction | Notes |
|---|---|---|---|---|
LoadDataRecord |
Recipe number, record number | INT, INT | HMI → PLC tags | Triggers a load of the specified record into the configured recipe tags |
SaveDataRecord |
Recipe number, record number | INT, INT | PLC tags → HMI file | Persists the current recipe tag values to the panel's recipe file |
GetDataRecordName |
Recipe number, record number | INT, INT → STRING | Query | Returns the textual record name; cannot be used as a load trigger |
GetDataRecordNumber |
Recipe number, STRING | INT, STRING → INT | Query | Returns the numeric record index for a given name; can then be passed to LoadDataRecord
|
The only "by name" path in native WinCC Flexible is the two-step GetDataRecordNumber → LoadDataRecord. It works for recipes authored on the panel, but it cannot be driven from a STRING arriving from an MES/ERP at scan time, and it cannot be triggered from VBScript inside a button script on every runtime build. That gap is exactly what the PLC-side FB fills.
Data Block Layout: The Array-Based Mirror
The implementation uses two parallel DBs that together hold up to 15 000 records. The full DINT was chosen over INT so that the article number can be loaded directly from a PROFIBUS or PROFINET word/dword without type juggling.
| Symbol | Type | Length | Bytes | Role |
|---|---|---|---|---|
"Corr_Artikel".Index[1..15000] |
ARRAY[1..15000] OF DINT | 15 000 × 4 | 60 000 | Article / product number. 0 = empty slot. |
"Corr_Hoek1".Index[1..15000] |
ARRAY[1..15000] OF REAL | 15 000 × 4 | 60 000 | Angle 1 correction (degrees) |
"Corr_Hoek2".Index[1..15000] |
ARRAY[1..15000] OF REAL | 15 000 × 4 | 60 000 | Angle 2 correction (degrees) |
"Corr_Unit1".Index[1..15000] |
ARRAY[1..15000] OF REAL | 15 000 × 4 | 60 000 | Unit vector 1 |
"Corr_Unit2".Index[1..15000] |
ARRAY[1..15000] OF REAL | 15 000 × 4 | 60 000 | Unit vector 2 |
"Corr_Hoogte".Index[1..15000] |
ARRAY[1..15000] OF REAL | 15 000 × 4 | 60 000 | Height correction |
"Correcties_Open" |
STRUCT of 5 REAL | 5 × 4 | 20 | Active working set the recipe writes into |
All "Corr_*" DBs must be marked as non-retentive (i.e. snapshotted to the system memory card at every STOP→RUN if persistence is required) or backed by the S7-300/400 RecipeData mechanism, which on a 31x CPU writes to MMC at every save. The "Correcties_Open" UDT is the runtime "open recipe" that the HMI binds to its recipe view tags.
The Reference Function Block: FB1200 Walkthrough
The published FB accepts two BOOL triggers (SAVE_CORRECTIE, FETCH_CORRECTIE) and one DINT article identifier (Active_Artikel). It is invoked from OB1 (or a cyclic OB35 at 100 ms in the original installation) with a single call instance (DB1200).
Variable Declarations
FUNCTION_BLOCK FB1200
VAR_INPUT
SAVE_CORRECTIE : BOOL; // Save trigger (rising edge)
FETCH_CORRECTIE: BOOL; // Fetch trigger (rising edge)
Active_Artikel : DINT; // Article / product number to look up
END_VAR
VAR
MEM_SAVE : BOOL; // Edge memory for SAVE_CORRECTIE
MEM_FETCH : BOOL; // Edge memory for FETCH_CORRECTIE
FETCHED : BOOL; // 1 = current article has been fetched
SAVED : BOOL; // 1 = current article has been saved
PREV_ACTIVE : DINT; // Last seen article number
Index_Store : INT; // First empty slot (optional cache)
END_VAR
VAR_TEMP
SAVE_FLANK : BOOL;
FETCH_FLANK: BOOL;
i : INT;
END_VAR
Edge Detection
The first two lines compute rising edges from the BOOL triggers. Edge detection is essential because the FB is called every cycle; without SAVE_FLANK the save block would overwrite the record on every scan.
SAVE_FLANK := SAVE_CORRECTIE AND (NOT MEM_SAVE);
FETCH_FLANK := FETCH_CORRECTIE AND (NOT MEM_FETCH);
Article Change Reset
When the operator (or MES) changes the active article, both FETCHED and SAVED must be cleared. Otherwise the FB would refuse to fetch or save a new article until the panel cycled power.
IF PREV_ACTIVE <> Active_Artikel THEN
FETCHED := FALSE;
SAVED := FALSE;
PREV_ACTIVE := Active_Artikel;
END_IF;
Fetch Path
The fetch block runs only on a rising edge of FETCH_CORRECTIE and only if the current article has not already been fetched. It linearly scans the index array for a match and, when found, copies the five REAL fields into the "Correcties_Open" UDT.
IF FETCH_FLANK AND NOT FETCHED THEN
FOR i := 1 TO 15000 DO
IF Active_Artikel = "Corr_Artikel".Index[i] THEN
"Correcties_Open".Hoek1 := "Corr_Hoek1".Index[i];
"Correcties_Open".Hoek2 := "Corr_Hoek2".Index[i];
"Correcties_Open".Unit1 := "Corr_Unit1".Index[i];
"Correcties_Open".Unit2 := "Corr_Unit2".Index[i];
"Correcties_Open".Hoogte := "Corr_Hoogte".Index[i];
FETCHED := TRUE;
END_IF;
END_FOR;
END_IF;
Linear scan is acceptable for 15 000 entries because the block runs at OB1 cycle time and the article is found within milliseconds. If the article is absent, the loop still iterates the full 15 000 and FETCHED stays FALSE — the HMI can interpret this as "not found" and prompt the operator.
Save Path: Overwrite-Then-Append
The save block has two phases. Phase 1 overwrites an existing record (so editing a known article updates its slot). Phase 2 finds the first zero entry in the index array and writes a brand-new record there. SAVED is latched in both phases to prevent double-writes when both phases find a match.
IF SAVE_FLANK THEN
IF NOT SAVED THEN
// Phase 1: overwrite existing
FOR i := 1 TO 15000 DO
IF "Corr_Artikel".Index[i] = Active_Artikel AND NOT SAVED THEN
"Corr_Hoek1".Index[i] := "Correcties_Open".Hoek1;
"Corr_Hoek2".Index[i] := "Correcties_Open".Hoek2;
"Corr_Unit1".Index[i] := "Correcties_Open".Unit1;
"Corr_Unit2".Index[i] := "Correcties_Open".Unit2;
"Corr_Hoogte".Index[i] := "Correcties_Open".Hoogte;
SAVED := TRUE;
END_IF;
END_FOR;
END_IF;
IF NOT SAVED THEN
// Phase 2: append to first empty slot
FOR i := 1 TO 15000 DO
IF "Corr_Artikel".Index[i] = 0 AND NOT SAVED THEN
"Corr_Artikel".Index[i] := Active_Artikel;
"Corr_Hoek1".Index[i] := "Correcties_Open".Hoek1;
"Corr_Hoek2".Index[i] := "Correcties_Open".Hoek2;
"Corr_Unit1".Index[i] := "Correcties_Open".Unit1;
"Corr_Unit2".Index[i] := "Correcties_Open".Unit2;
"Corr_Hoogte".Index[i] := "Correcties_Open".Hoogte;
SAVED := TRUE;
END_IF;
END_FOR;
END_IF;
END_IF;
The two-phase pattern is the key piece of "ergonomic" logic that turns a primitive name-equals-number lookup into a write-on-empty-space, overwrite-when-exists primitive that an operator can use without a second thought.
Edge Memory Update
MEM_SAVE := SAVE_CORRECTIE;
MEM_FETCH := FETCH_CORRECTIE;
Performance and Scan-Time Considerations
The two linear scans are the only real cost. On an S7-315-2 PN/DP at typical 5–10 ms OB1, the worst-case 15 000-iteration loop completes in roughly 12–25 ms. If the FB is called from a 100 ms OB35 the impact on cyclic time is negligible; if it is called from OB1 directly, consider moving it to OB35 to avoid extending the cyclic interrupt.
For very large databases, replace the linear scan with a binary search: keep the index array sorted, and halve the search range on each iteration. With 15 000 entries this drops the worst case to ~14 comparisons (log2(15 000) ≈ 13.9). The complexity increase is the sort step on every insert — a one-time cost when the FB first sees the article.
Watch for two real failure modes in field installations:
-
Index wraparound. A 16-bit INT loop variable
iwith 15 000 iterations is safe, but a larger range (e.g. 50 000) would need DINT. -
DB not loaded. If "Corr_Artikel" is uninitialised, the fetch returns a false positive at index 1 (because both DBs default to 0, and a "0" article number matches). Always initialise the index DB to a sentinel value (e.g.
-1) or pre-fill it on first startup in OB100.
Wiring the HMI Side
- Open the WinCC Flexible project and create a recipe (e.g. Correcties) with five REAL elements matching the "Correcties_Open" UDT.
- Bind each element to a tag in the "Correcties_Open" DB. Recipe tags must be of the same data type as the elements (REAL in this case).
- Add a recipe view to the desired screen. The view shows record name + record number. Operators can browse and edit values.
- Configure an event on the recipe view's
OnRecordLoadedevent to set a "Fetch complete" status bit the FB reads. - Add two buttons (Fetch / Save) on the HMI. Each button sets a BOOL tag wired to
FETCH_CORRECTIE/SAVE_CORRECTIEfor exactly one scan (use the HMI'sSetBit+ delayedResetBitcycle, or a one-shot pulse via the panel's tag limit). - Bind an I/O field to
Active_Artikel. The MES/ERP writes the article number into this tag; the FB reacts to the change and clearsFETCHED/SAVED.
The recipe view itself still works in the traditional way — the FB is the overlay that survives power cycles and lets the batch system drive the choice by article number, not by a sequence of button presses.
Modernisation Path: TIA Portal and SCL
WinCC Flexible is now end-of-life. New installations should use TIA Portal V17+ with the unified WinCC (Comfort/Advanced) runtime. The TIA Portal recipe system is significantly more capable:
- Recipe elements are bound to PLC tags or DB elements with a full data-type mapping (BOOL, INT, REAL, STRING, structures).
- Recipes are stored on the panel's flash or on a network share, with
RecipeExportandRecipeImportscriptable from VBScript in the panel runtime. - Data records can be loaded by name from a user script:
HMIRuntime.UI.ReloadRecipe(RecipeName, RecordName)in TIA Portal V15.1+ Comfort panels.
The same FB logic, ported to SCL for an S7-1500, looks like this:
FUNCTION_BLOCK "FB_RecipeLookup"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
VAR_INPUT
SaveTrigger : BOOL;
FetchTrigger : BOOL;
ActiveArtikel: DINT;
END_VAR
VAR
MemSave : BOOL;
MemFetch : BOOL;
Fetched : BOOL;
Saved : BOOL;
PrevActive : DINT;
END_VAR
BEGIN
// Edge + reset logic identical to FB1200 ...
FOR #i := 1 TO 15000 DO
IF "Corr_Artikel".Index[#i] = #ActiveArtikel AND NOT #Fetched THEN
// fetch
END_IF;
END_FOR;
END_FUNCTION_BLOCK
On an S7-1516 the linear scan is ~3 ms thanks to the optimised data block access and the 1 ns bit operations. S7-1500s also support symbolic access to optimised DBs, which removes the "Corr_Hoek1".Index[i] indirection and lets the compiler pre-compute DB offsets.
Verification Steps
- Compile FB1200 in STEP 7 / TIA Portal; the cross-reference list must show all six DBs referenced from a single FB instance.
-
Download the project to the CPU. In the online view, force
Active_Artikel=1and toggleFETCH_CORRECTIEonce. Watch"Correcties_Open".Hoek1— it should snap to the value stored at"Corr_Hoek1".Index[1]. -
Edge test: set
Active_Artikel=42, forceFETCHEDhigh by hand in the VAT, then changeActive_Artikel=99. The block must clearFETCHEDautomatically. -
Save test: enter values into
"Correcties_Open", toggleSAVE_CORRECTIEonce. Re-fetch the same article. The values must round-trip. -
Append test: set
Active_Artikelto a value that is not in the index DB, set the working fields, save. The first zero entry of"Corr_Artikel".Indexmust now contain the new article number. - Power-cycle test: stop the CPU, restart. The data must persist (this requires the DBs to be retentive or stored to MMC).
- Cycle-time test: in the CPU diagnostic buffer, check the OB1 / OB35 scan time before and after enabling the FB. The delta should be < 30 ms for 15 000 records.
Troubleshooting Matrix
| Symptom | Likely Cause | Fix |
|---|---|---|
| Fetch returns zero values for every article | Index DB not initialised; all entries are 0, so a 0 article matches slot 1 | Initialise the index DB in OB100 with a non-zero sentinel (e.g. FILL_BLK with the DINT value 16#7FFFFFFF) |
| Save overwrites record 1 repeatedly | Article number 0 used as a sentinel and as a valid product | Use -1 as the empty sentinel and validate Active_Artikel > 0 before any save |
| FETCHED flag never clears after article change | OB1 not called or FB not invoked every cycle | Check the call environment; FB must run every PLC cycle |
| OB1 cycle time increases by 200 ms | Index range too large for the CPU or the FB is called in OB1 with high priority | Move call to OB35 (100 ms) or reduce range to 5 000; consider binary search |
| Data lost after power cycle | DBs not marked retentive and no MMC backup configured | In DB properties, set "Non-retentive" to "No"; or use the S7-300/400 RecipeData mechanism to write to MMC at save |
| HMI recipe view shows different values from PLC working set | Recipe tags not bound to the same DB elements the FB writes into | In WinCC Flexible tag list, bind each recipe element to the corresponding "Correcties_Open" element |
| Save appends a new record instead of overwriting | Article number type mismatch (INT vs DINT) | Ensure Active_Artikel and the index array are both DINT; mismatched types never compare equal in STEP 7 |
Frequently Asked Questions
Can WinCC Flexible load a recipe by STRING directly without a PLC-side lookup?
No. WinCC Flexible's LoadDataRecord only accepts an INT record number. The closest native workaround is the two-step GetDataRecordNumber → LoadDataRecord, but this is not scriptable from a panel button on every runtime build. The PLC-side array FB is the field-proven solution.
How much PLC memory does the 15 000-entry version consume?
Six arrays of 15 000 entries (one DINT index and five REAL fields) consume 360 000 bytes of work memory (60 000 bytes each). Plan for an S7-317 or S7-319 CPU; on an S7-314 reduce the range to 5 000 entries, which needs 120 000 bytes total.
What happens if two operators save different values for the same article within the same cycle?
The last write wins because the FB is not transactional. For multi-operator safety, add an IN_USE semaphore: set the bit on fetch, clear it on save or article change, and refuse new fetches while it is set. This is a one-line addition to the article-change reset block.
Is the same pattern valid for TIA Portal and S7-1500?
Yes. The same logic ports directly to SCL, and the S7-1500's symbolic optimised access reduces the linear scan from 25 ms to about 3 ms for 15 000 entries. TIA Portal's WinCC (Comfort/Advanced) also adds native RecipeView functions that can drive the load by name from a VBScript on the panel, so the FB is often not needed there.
How do I make the data survive a CPU power cycle on an S7-300?
Mark each "Corr_*" DB as non-retentive in the DB properties (clear the "Non-retentive" checkbox), and the values are stored to the MMC at every STOP→RUN. Alternatively, use the S7-300 RecipeData system function to copy the DB to MMC after every save cycle.