WinCC Flexible Recipe Name Lookup Using PLC Array FB

David Krause14 min read
HMI / SCADASiemensTutorial / 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

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:

  1. 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").
  2. 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.
  3. 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.
  4. 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.
  5. Acyclic recipe storage on the HMI side if persistence is required, or a DB marked as retentive (S7-300: non-retentive in DB properties cleared; or use SET/CLR with AR2 pointers in the startup OB100 to reload).
Memory sizing note. 15 000 × REAL = 60 000 bytes. 15 000 × DINT = 60 000 bytes. A 317-2 DP has 256 KB of work memory; a 315-2 DP has 128 KB. On smaller CPUs split the arrays across multiple DBs and apply the search only to the active range, or downscale to 5 000 records (≈ 20 KB per array).

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 GetDataRecordNumberLoadDataRecord. 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:

  1. Index wraparound. A 16-bit INT loop variable i with 15 000 iterations is safe, but a larger range (e.g. 50 000) would need DINT.
  2. 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

  1. Open the WinCC Flexible project and create a recipe (e.g. Correcties) with five REAL elements matching the "Correcties_Open" UDT.
  2. 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).
  3. Add a recipe view to the desired screen. The view shows record name + record number. Operators can browse and edit values.
  4. Configure an event on the recipe view's OnRecordLoaded event to set a "Fetch complete" status bit the FB reads.
  5. Add two buttons (Fetch / Save) on the HMI. Each button sets a BOOL tag wired to FETCH_CORRECTIE / SAVE_CORRECTIE for exactly one scan (use the HMI's SetBit + delayed ResetBit cycle, or a one-shot pulse via the panel's tag limit).
  6. 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 clears FETCHED/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 RecipeExport and RecipeImport scriptable 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

  1. Compile FB1200 in STEP 7 / TIA Portal; the cross-reference list must show all six DBs referenced from a single FB instance.
  2. Download the project to the CPU. In the online view, force Active_Artikel = 1 and toggle FETCH_CORRECTIE once. Watch "Correcties_Open".Hoek1 — it should snap to the value stored at "Corr_Hoek1".Index[1].
  3. Edge test: set Active_Artikel = 42, force FETCHED high by hand in the VAT, then change Active_Artikel = 99. The block must clear FETCHED automatically.
  4. Save test: enter values into "Correcties_Open", toggle SAVE_CORRECTIE once. Re-fetch the same article. The values must round-trip.
  5. Append test: set Active_Artikel to a value that is not in the index DB, set the working fields, save. The first zero entry of "Corr_Artikel".Index must now contain the new article number.
  6. Power-cycle test: stop the CPU, restart. The data must persist (this requires the DBs to be retentive or stored to MMC).
  7. 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 GetDataRecordNumberLoadDataRecord, 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.

Back to blog