1. Problem Definition and Engineering Scope
A typical industrial measurement task requires a single statistical summary (minimum, maximum, arithmetic mean) from two redundant process sensors over a defined observation window. The window is fixed at 30 minutes in this reference design, and the controller is a Siemens SIMATIC S7-1500 CPU programmed in Structured Control Language (SCL) under TIA Portal. The two inputs are REAL (32-bit IEEE-754) temperature values in °C, typically delivered by PT100/RTD modules (e.g., SM 1231 on S7-1200 or SM 531 on S7-1500) or by analog input cards with 4–20 mA transmitters.
The required outputs are:
-
Q_MIN— minimum value observed in the rolling window -
Q_MAX— maximum value observed in the rolling window -
Q_AV— arithmetic mean of the samples in the rolling window
The design is built as a reusable Function Block (FB) with its own Instance Data Block (IDB), so it can be called from any cyclic OB (typically OB1) or from a time-driven OB such as OB30 (cyclic interrupt) if a deterministic sample period is required.
Temperature value, and that composite is the only value stored in the rolling buffer. If you need to track min/max per sensor instead, you must allocate two parallel buffers and two parallel statistics calculations.2. Prerequisites
Confirm the following before coding the FB:
- Hardware — S7-1500 CPU (any firmware version that supports TIA Portal V15 or later; the SCL syntax used here is compatible with all current S7-1500 firmware). See the SIMATIC S7-1500 product page for the current CPU portfolio.
- Software — TIA Portal with the SCL compiler installed. Open Project → Properties → Task cards → SCL to verify the package is active.
-
Sensor scaling — the two inputs
I_TEMPERATURE_1andI_TEMPERATURE_2must already be scaled to engineering units (°C) as REAL. A typical scaling block isSCALE/NORM_XplusSCALE_Xfrom the TIA Portal instruction palette. -
Time base — a 1-second pulse
I_PULSEgenerated by a clock bit (e.g., the system clockClock_1Hzin the CPU properties → System and clock memory) or by a cyclic-interrupt OB. - Memory budget — a 30-minute window at 1-second sampling requires 1800 REAL samples (1800 × 4 bytes = 7200 bytes) plus a 4-byte pointer. A 30-second window needs only 1800 / 60 = 30 samples for 30 seconds at 1 Hz, so verify your sample period before sizing the array.
3. Algorithm: Rolling Buffer with a Single-Pass Statistic
The implementation uses a circular buffer indexed by POINTER. Each pulse, the oldest sample is overwritten by the newest composite value. After every write, a single FOR loop walks the entire array once, computing min, max, and the running sum in the same pass. This is the same pattern recommended for aggregate functions in general-purpose languages: initialize the accumulators from the first element, then iterate.
The complexity is O(n) per output update and O(1) memory beyond the buffer itself. On an S7-1500 with a 1-second pulse and 1800 elements, the loop runs well inside the cycle-time budget; the OB1 scan is not measurably impacted.
4. Step-by-Step: Build the FB in TIA Portal
4.1 Declare the Block Interface
Create a new Function Block named FB_StatWindow (number range 1000–1999 is conventional). In the Block interface editor, declare the following variables exactly:
| Section | Name | Type | Initial value | Comment |
|---|---|---|---|---|
| Input | I_TEMPERATURE_1 | Real | 0.0 | Sensor #1 in °C |
| Input | I_TEMPERATURE_2 | Real | 0.0 | Sensor #2 in °C |
| Input | I_PULSE | Bool | FALSE | 1 s sample enable |
| Input | I_FIRST_SCAN | Bool | FALSE | Init flag (one-shot) |
| Output | Q_MIN | Real | 0.0 | Window minimum |
| Output | Q_MAX | Real | 0.0 | Window maximum |
| Output | Q_AV | Real | 0.0 | Window mean |
| Temp | Temperature | Real | — | Composite per-scan value |
| Temp | i | Int | — | Loop counter |
| Static | BUFFER | Array[1..CONST_PERIOD] of Real | — | Rolling window |
| Static | POINTER | Int | 1 | Circular write index |
| Const | CONST_PERIOD | Int | 30 | Window length (interpret as seconds here; see §6) |
Real (or a DInt if your input is Int). Accumulating Int with 1800+ elements overflows at 32 767. The accumulator here is Real, so overflow is not an issue up to ~3.4 × 10³⁸.4.2 Network 1 — Average the two sensors
// Composite sample: arithmetic mean of the two sensors
Temperature := (I_TEMPERATURE_1 + I_TEMPERATURE_2) / 2.0;
4.3 Network 2 — First-scan initialization
On the very first call (driven from OB100 startup or a one-shot FirstScan tag in the IDB), the array is filled with the current composite so that min/max/mean are well-defined before the first full window elapses.
IF I_FIRST_SCAN THEN
POINTER := 1;
// FILL_BLK_INI would also work; a loop keeps the FB self-contained
FOR i := 1 TO CONST_PERIOD DO
BUFFER[i] := Temperature;
END_FOR;
END_IF;
4.4 Network 3 — Gating on the sample pulse
Statistics are only updated on a rising edge of I_PULSE. This keeps the FB idempotent if it is called more often than the sample period.
IF NOT I_PULSE THEN
RETURN; // leave Q_MIN / Q_MAX / Q_AV unchanged this scan
END_IF;
4.5 Network 4 — Write the new sample into the ring buffer
POINTER := POINTER + 1;
IF POINTER > CONST_PERIOD OR POINTER < 1 THEN
POINTER := 1;
END_IF;
BUFFER[POINTER] := Temperature;
4.6 Network 5 — Single-pass statistic
Initialize all three accumulators from BUFFER[1] and walk the rest of the array. This pattern (init from element 0 / 1, then iterate from element 1 / 2) is the standard idiom and is also documented for the Power Fx aggregate functions Average, Max, and Min.
Q_AV := BUFFER[1];
Q_MIN := BUFFER[1];
Q_MAX := BUFFER[1];
FOR i := 2 TO CONST_PERIOD DO
Temperature := BUFFER[i];
Q_AV := Q_AV + Temperature;
IF Q_MIN > Temperature THEN
Q_MIN := Temperature;
END_IF;
IF Q_MAX < Temperature THEN
Q_MAX := Temperature;
END_IF;
END_FOR;
Q_AV := Q_AV / INT_TO_REAL(CONST_PERIOD);
5. Wiring the FB in OB1
Call the FB from the main cyclic OB. FirstScan is the standard first-cycle flag available on every S7-1500 CPU; Clock_1Hz must be enabled in CPU properties → System and clock memory.
// OB1 - Main
"iStatDB"( // Instance DB auto-generated by TIA Portal
I_TEMPERATURE_1 := "Raw_Temp_1",
I_TEMPERATURE_2 := "Raw_Temp_2",
I_PULSE := "Clock_1Hz",
I_FIRST_SCAN := "FirstScan",
Q_MIN => "Stat_MIN",
Q_MAX => "Stat_MAX",
Q_AV => "Stat_AV"
);
6. Sizing the Window Correctly
The CONST_PERIOD parameter in the example above is set to 30. The unit depends entirely on the pulse rate driving I_PULSE:
| Desired window | Pulse source | Required CONST_PERIOD | Static memory |
|---|---|---|---|
| 30 seconds | 1 Hz clock | 30 | 120 bytes |
| 5 minutes | 1 Hz clock | 300 | 1 200 bytes |
| 30 minutes | 1 Hz clock | 1 800 | 7 200 bytes |
| 1 hour | 1 Hz clock | 3 600 | 14 400 bytes |
| 30 minutes | 10 Hz clock | 18 000 | 72 000 bytes |
For a 30-minute window at 1 Hz, change the constant to CONST_PERIOD : Int := 1800;. The arithmetic-mean divisor must be updated accordingly: Q_AV := Q_AV / INT_TO_REAL(CONST_PERIOD);.
7. LAD Implementation on S7-1200 and S7-1500
The same logic is implementable in LAD if SCL is not available on the target package. The recommended structure is:
-
Add instruction → Math functions →
ADD+DIVby 2.0 to compute the per-scan composite (useREALarithmetic, notINT). -
Sample-and-hold on pulse — feed the composite into a
MOVE_BLK/FILL_BLKchain that updates a DB array indexed by anINC-style counter that wraps atCONST_PERIOD. -
Min — use the Compare instruction
<inside a loop; on the S7-1500 the Comparison group includes a dedicatedMIN/MAXinstruction that returns the smaller / larger of two REALs. -
Max — same as min, using the
>compare and theMAXinstruction. -
Mean — accumulate into a
REALtag and divide once at the end.
For the loop on S7-1200/1500 LAD, the cleanest approach is to write a small SCL FB and call it from LAD, rather than building a long ladder chain with explicit compares per array element.
8. S7-300 Variant — Memory-Indirect Addressing in STL
On an S7-300 (or any classic S7-300/400 target), the S7-1500-style SCL array syntax is not always available; you typically use STL with memory-indirect addressing. The user requirement is the same: rolling min, max, mean from a 1 Hz sample. The implementation pattern is:
// STL sketch (S7-300, SCL not licensed)
// DBx.DBX0.0 = composite value (REAL, 4 bytes)
// DBx.DBD4 = pointer / index (DINT)
// DBx.DBD100 = sum accumulator (REAL)
// DBx.DBD104 = current min (REAL)
// DBx.DBD108 = current max (REAL)
L DBx.DBD4 // pointer
L 1
+I
T DBx.DBD4
L CONST_PERIOD // e.g. 1800
>I
JC RES
L 0
<I
JC RES
JU CONT
RES: L 1
T DBx.DBD4
CONT: L DBx.DBD4
SLD 3 // index * 8 because stride is 8 here
LAR1 P#DBx.DBX200.0 // base of array
+AR1
L DBx.DBD0 // composite
T DBD [AR1,P#0.0]
The scan-loop that recomputes min / max / mean uses a separate counter from 0 to CONST_PERIOD–1 and the same indirect-addressing pattern. For a REAL array the index stride is 4 bytes (or 8 for a paired DINT); for a pure INT array the stride is 2 bytes. The source notes the stride difference explicitly: REAL/DINT addresses change by 4, INT addresses change by 2. Use a DINT accumulator when summing INT to prevent overflow.
9. Verification and Commissioning Steps
-
Static check — in TIA Portal, right-click the FB and select Compile → SCL. Fix any syntax errors. Open the IDB and confirm the
BUFFERarray size matchesCONST_PERIOD. -
Online watch — go online with the CPU, open the IDB in Monitoring mode, and force
I_TEMPERATURE_1 = I_TEMPERATURE_2 = 25.0. Within one scan,Stat_MIN = Stat_MAX = Stat_AV = 25.0. -
Min/max step test — toggle the inputs to known fixed values (e.g. 20.0 / 30.0 / 22.5 / 27.0) at every pulse, and verify that
Q_MINsettles at the lowest value within the window andQ_MAXat the highest. -
Ring-buffer wrap test — set
CONST_PERIOD := 5temporarily, set a 1 Hz pulse, and feed a saw-tooth pattern. TheQ_MAXshould equal the largest of the last five samples, andQ_MINthe smallest. -
Mean sanity check — feed ten identical samples (e.g. all 50.0).
Q_AVmust equal 50.0 exactly. Then feed samples of 50 and 100 in alternation. After an even number of samples,Q_AVmust be 75.0. - Cycle-time check — open the CPU diagnostics view and confirm the OB1 cycle time is well below the configured maximum (typically < 50 ms for a small S7-1511 with this block). If the cycle time approaches the limit, raise the constant or move the FB into a slower OB30 with a 100 ms cyclic interrupt.
10. Common Pitfalls and Field-Notes
| Symptom | Likely cause | Fix |
|---|---|---|
| Q_AV stuck at 0.0 after power-up | First-scan init never fired, and the array is still zero-filled | Drive I_FIRST_SCAN from OB100 startup or use the CPU's FirstScan flag |
| Q_MIN / Q_MAX are constant while inputs vary |
I_PULSE is wired to a 10 ms or 100 ms clock but CONST_PERIOD is set for a 1-second window; the buffer wraps too fast |
Match CONST_PERIOD to the actual pulse period × window duration |
| Mean is slightly off (e.g. 24.997 instead of 25.000) | Mixing REAL and DINT in the accumulator, or dividing with integer math | Cast CONST_PERIOD to REAL before division: Q_AV := Q_AV / INT_TO_REAL(CONST_PERIOD);
|
| Q_MIN / Q_MAX equal Q_AV on a single anomaly sample | Single corrupted reading overwrites the buffer; a 4–20 mA loop fault produced a -3276.8 °C value | Add a plausibility check: discard samples outside a process-defined range before writing to the buffer |
| CPU goes to STOP with SF LED on, diagnostic buffer mentions array bounds | Buffer access violation in the FOR loop; CONST_PERIOD was reduced without recompiling callers |
Re-compile all callers; ensure the loop bounds match the array declaration |
| On S7-300: STL reports "Invalid pointer" | AR1 / AR2 clash with the FB's address register use | Save and restore AR1 / AR2 with TAR1 / LAR1 at FB entry and exit |
11. Extensions and Production-Grade Variants
-
Median — copy the buffer into a temporary array and run a simple in-place sort; the median is
BUFFER[CONST_PERIOD / 2]. Useful when one sensor occasionally produces an outlier. -
Standard deviation — store the running sum of squares
Σx²in parallel with the running sumΣx. Thenσ = sqrt( (Σx² / n) − (Σx / n)² ). The Power Fx aggregate reference also listsStdevPandVarPfor population-based dispersion. -
Per-sensor statistics — duplicate the buffer as
BUFFER_1andBUFFER_2and run two parallel single-pass loops. -
HMI trend — publish
Q_MIN,Q_MAX,Q_AVto an HMI tag and graph them on a WinCC / Unified faceplate for operator visibility. -
Alarm on excursion — if
Q_MAX − Q_MINexceeds a process-defined spread (e.g. > 5 °C for a tank), raise a "sensor drift" alarm to drive maintenance.
12. Frequently Asked Questions
Why do I get a wrong mean when the sensors disagree by a large amount?
Because the algorithm averages the two readings into one composite before storing it in the buffer. If one sensor is faulty, the faulty value is halved and contaminates the entire 30-minute window. Add a plausibility filter (e.g. reject samples more than 2 °C from the previous value) or switch to per-sensor statistics with a separate fault flag.
What is the minimum CPU class that can run a 30-minute / 1 Hz window?
Any S7-1500 CPU from the 1511-1 PN upward. The block needs ~7.2 KB of static data and a single FOR loop over 1800 REALs, well inside the cycle-time budget of even the smallest S7-1511. See the S7-1500 product selector for current models and work-memory sizes.
Can I run the same FB on an S7-300 in STL instead of SCL?
Yes, but the SCL array syntax becomes manual memory-indirect addressing in STL. The key rules: stride is 4 bytes per REAL/DINT element and 2 bytes per INT element, and the accumulator must be a DINT (or REAL) to avoid overflow when summing thousands of INT samples. Save and restore AR1 / AR2 around the loop because the FB uses them.
How do I trigger a 1 Hz sample deterministically without using a clock memory byte?
Use a cyclic interrupt OB such as OB30 with a 1000 ms period and call the FB from there, or use a hardware timer in the analog input module. Driving I_PULSE from CPU properties → System and clock memory → Clock_1Hz is the simplest approach and is sufficient for non-safety statistics.
Is the algorithm numerically stable for thousands of samples?
Yes, as long as the accumulator is a Real (IEEE-754 single precision) and the divisor is also a Real. For windows beyond a few hours at high sample rates, consider a compensated summation algorithm (Kahan) or switch to LReal (64-bit double) on the S7-1500 to reduce rounding drift.