S7-1200 SCL: Implementing Moving Average Filter for AI Inputs

David Krause13 min read
S7-1200SiemensTutorial / 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

Analog inputs on Siemens S7-1200 CPUs (SM 1231, SB 1231, and the onboard AI of CPU 121xC / 121xFC / 151xC variants) routinely pick up coupled noise from variable-frequency drives, contactor coils, thermocouple reference-junction drift, and shared 24 V supply returns. While the analog module offers a configurable hardware integration time, software filtering is required when the noise frequency overlaps the process bandwidth, when several channels must share one filter behavior, or when the filtered value feeds a downstream calculation (PID set-point derivation, energy computation) that needs a deterministic, repeatable algorithm.

This reference builds a complete, production-grade moving-average filter (MAV) as an SCL function block for the S7-1200, with O(1) update cost, first-scan buffer priming via OB100, a ring-buffer pointer that prevents array shifting, and a pulse trigger that decouples acquisition from the OB1 scan time. The implementation deliberately avoids the legacy FILL intrinsic (SFC21), which is not callable on S7-1200 firmware, and uses the SCL FILL_BLK standard extension instead.

Compatibility: Tested against S7-1200 CPU firmware V4.5 and TIA Portal V17 / V18. Optimized block access is required for firmware ≥ V4.2; legacy non-optimized DBs continue to work but lose symbolic visibility in the Web server and OPC UA Server interface.

1. Filter Selection: When to Choose Moving Average

Three digital filter classes are routinely applied to PLC analog inputs:

Filter Mathematical form Group delay Step response Best fit
Simple Moving Average (MAV) y[n] = (1/N)·Σ x[n-k] (N-1)/2 samples Linear ramp over N samples White noise on slowly-changing process values, batch end-point detection
Exponential Smoothing (EMA / RC) y[n] = α·x[n] + (1-α)·y[n-1] ≈ 1/α samples Asymptotic, first-order Heater/regulator loops where memory cost must stay O(1) and α is tuned live
Median y[n] = median(x[n-N+1]..x[n]) (N-1)/2 samples Step-preserving Removing single-sample spikes / impulse rejection

For the use case in the field report — thermocouple noise from a dirty workpiece in a heating application — the moving average is a strong default: the TC drifts slowly when contact is good but bursts with high-frequency noise while contact is intermittent. The MAV suppresses white noise proportional to 1/√N while preserving the slow contact-quality drift.

2. Prerequisites

Item Requirement
CPU S7-1200 any variant (validated on CPU 1214C DC/DC/DC, firmware V4.5)
Analog input SM 1231 (e.g. 6ES7231-4HF32-0XB0), SB 1231, or onboard AI
Engineering TIA Portal V17 SP1 or later (V18 / V19 recommended)
Programming language SCL (Structured Control Language)
Block attribute Optimized block access on FB and instance DB
Memory 8 × N bytes for REAL ring buffer (default N ≤ 64)
Startup OB OB100 available (present by default in every S7-1200 project)

Reference documentation: the Siemens Industry Online Support portal hosts the current S7-1200 product catalog and system manuals, and the S7-1200 programming and SCL reference.

3. Discrete-Time Moving Average Math

The simple MAV of length N for a sample acquired at index n:

y[n] = (1/N) · Σk=0N-1 x[n-k]

Naively this requires N additions and N-1 reads of the buffer every cycle. The running-sum form keeps the work O(1):

S[n] = S[n-1] + x[n] - x[n-N]
y[n] = S[n] / N

where S is the maintained sum and x[n-N] is the oldest sample, evicted from the ring buffer when the write pointer wraps. This is the form implemented below. Stability conditions:

  • S must be wide enough to avoid overflow. For REAL on S7-1200, S is sufficient for typical analog ranges; promote to LREAL only when the input range combined with N exceeds ±107.
  • Sample acquisition must be at a fixed period; jitter on the trigger shifts the effective -3 dB cutoff frequency. A cyclic interrupt OB (e.g. OB30) is preferred over an OB1 flag-driven trigger for any closed loop.
  • The signal must be stationary: large step changes during the first N samples after Init will be biased toward the primed value until the buffer fully rolls over.

4. SCL Function Block Implementation

The block below is MAV_Filter. Add it as a new FB with optimized access in your TIA Portal project. The instance DB stores the ring buffer, pointer, and persistent state across scans.

// MAV_Filter - Simple Moving Average with O(1) running sum
// Target: S7-1200 / TIA Portal V17+
FUNCTION_BLOCK "MAV_Filter"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1

VAR_INPUT
  AI_Raw           : Real;    // scaled AI value (engineering units)
  AI_Buffer_Length : USInt;   // N: 1..64 samples, validated at run time
  Acquire          : Bool;    // capture one sample per TRUE scan
  Init             : Bool;    // TRUE for one scan to re-prime the buffer
END_VAR

VAR_OUTPUT
  AI_Average  : Real;
  Buffer_Full : Bool;         // TRUE once N samples have been processed
  Error       : Word;         // 0 = OK; see error codes below
END_VAR

VAR
  AI_Buffer  : Array[0..63] of Real;  // ring storage, max 64 entries
  Sum        : Real;                  // running sum S[n]
  AI_Pointer : USInt;                 // next write index, 0..N-1
  Samples    : USInt;                 // count of valid samples seen
END_VAR

VAR_TEMP
  TempVal : Real;
  N       : USInt;
END_VAR

BEGIN
  // ---- Parameter validation ---------------------------------
  IF (AI_Buffer_Length = 0) OR (AI_Buffer_Length > 64) THEN
    Error := 16#0001;       // invalid length
    RETURN;
  END_IF;
  N := AI_Buffer_Length;

  // ---- Init / Re-prime --------------------------------------
  IF Init THEN
    FILL_BLK(IN := AI_Raw,
             COUNT := USINT_TO_DINT(N),
             OUT => AI_Buffer);
    Sum := AI_Raw * USINT_TO_REAL(N);
    AI_Average := AI_Raw;
    AI_Pointer := 0;
    Samples := 0;
    Buffer_Full := FALSE;
    Error := 0;
    RETURN;
  END_IF;

  // ---- Idle: no acquisition this scan -----------------------
  IF NOT Acquire THEN
    Error := 0;
    RETURN;
  END_IF;

  // ---- Acquisition & ring-buffer update ---------------------
  TempVal := AI_Buffer[AI_Pointer];
  AI_Buffer[AI_Pointer] := AI_Raw;
  Sum := Sum + AI_Raw - TempVal;
  AI_Average := Sum / USINT_TO_REAL(N);

  // Advance pointer, wrapping at N-1
  IF AI_Pointer >= (N - 1) THEN
    AI_Pointer := 0;
  ELSE
    AI_Pointer := AI_Pointer + 1;
  END_IF;

  // Track sample count until the buffer is full
  IF Samples < N THEN
    Samples := Samples + 1;
    IF Samples = N THEN Buffer_Full := TRUE; END_IF;
  END_IF;

  Error := 0;
END_FUNCTION_BLOCK

Key design points:

  • FILL_BLK is the SCL intrinsic for SFC21 FILL semantics. The legacy two-argument FILL(value, target) form is rejected by S7-1200 firmware; the three-argument FILL_BLK(IN, COUNT, OUT) form is the documented standard-extension alternative. The COUNT parameter expects a DINT on S7-1200, hence the explicit USINT_TO_DINT cast.
  • The Sum variable is maintained as REAL to match typical AI scaling; if the analog range and N together can exceed REAL precision (≥107), promote Sum to LREAL and the divisor accordingly.
  • The Samples counter guards against an under-read during the first N cycles after Init — without it, AI_Average would be biased low until the buffer fully rolls over.
  • Optimized block access is mandatory if the instance DB will be exposed to OPC UA Server or the Web API; legacy absolute access breaks symbolic binding on those interfaces.

5. First-Scan Initialization Pattern

The Init input of MAV_Filter must be pulsed TRUE on the first scan after the CPU goes from STOP to RUN, and again after any warm restart that re-initializes non-retentive process image bits. The canonical Siemens pattern uses OB100 (Startup) for the set and the last network of OB1 for the reset:

// OB100 - Startup (executed once on STOP -> RUN)
"DB_MAV".Init := TRUE;
// OB1 - last network
"DB_MAV".Init := FALSE;
Retention: If the instance DB of MAV_Filter is configured non-retentive (default), the warm-restart handling above is unnecessary because TIA Portal already zeros all STAT variables on STOP→RUN. If the DB is retentive (for example, to survive power loss without a long re-prime on a 1024-sample TC filter), you must re-prime manually with the Init pulse after every restart to avoid a stale sum and a flat-line AI_Average until the buffer flushes.

6. Acquisition Trigger and Timing

For the highest noise rejection, drive Acquire from a cyclic interrupt OB rather than from OB1. A 100 ms OB30 (configured in CPU Properties → Cyclic Interrupts) yields a 10 Hz sample rate; with N = 16 this gives a 1.6 s window and a -3 dB cutoff near 1/(πN·T) ≈ 0.020 Hz.

OB Recommended use Sample period
OB1 (flag-driven) Slow PID set-point averaging OB1 cycle, typically 10–50 ms
OB30 (cyclic interrupt) Default for thermocouple / strain-gauge 1 ms – 60 s, integer multiples
OB40 (hardware interrupt) Synchronization to external zero-crossing Event-driven

A one-shot pulse can be generated inside OB30 by detecting a rising edge on a clock bit or by latching a new sample flag set in the AI module's "Conversion Finished" interrupt.

7. Multiple-Pass and Cascade Filters

A two-pass moving average (cascade of two MAV_Filter instances) yields a triangular-window MAV with sharper roll-off and lower sidelobe level. Per Smith, "The Scientist and Engineer's Guide to Digital Signal Processing", Chapter 15, a k-pass MAV of length N is mathematically equivalent to a single-pass MAV of length N + (k-1)·(N-1), but is computationally cheaper because each pass reuses the same small buffer. For the S7-1200, two passes of N = 8 are typically cheaper than one pass of N = 15, and they yield a smoother time-domain response. Cascade two instances and feed AI_Average of the first into AI_Raw of the second; remember to invoke Init on both blocks together from OB100.

8. Hardware Filtering on the SM 1231

The SM 1231 / SB 1231 / onboard AI provide integration-time selection in TIA Portal under Device Configuration → AI channel → Inputs → Integration. This is a sinc-weighted average performed in the module's sigma-delta modulator. Always enable the longest integration time that still meets your bandwidth requirement; the block-level software MAV then removes residual noise that the integration time cannot, because of mains-cycle pickup at the AI channel.

Integration time Rejection (typ.) Effective resolution Use when
1.25 ms (default) None 12 bits Fast current loop (>100 Hz)
2.5 ms None 12 bits General-purpose
12.5 ms 50 Hz and 60 Hz 14 bits Mains-driven process
20 ms 60 Hz 14 bits 60 Hz regions
100 ms 50 Hz and 60 Hz 16 bits Slow temperature / weighing

Combining 100 ms hardware integration with a 16-sample software MAV on a 100 ms cycle yields 80 dB+ mains-noise rejection for thermocouple inputs without any external RC network.

9. Exponential Smoothing Alternative

If buffer memory is at a premium (for example, on the S7-1211C with 50 KB of work memory) and α must be tuned live from the HMI, replace MAV_Filter with an EMA block:

// EMA_Filter - Single-pole IIR, no buffer required
FUNCTION_BLOCK "EMA_Filter"
{ S7_Optimized_Access := 'TRUE' }

VAR_INPUT
  AI_Raw : Real;
  Alpha  : Real;   // 0 < Alpha ≤ 1, e.g. 0.1 for slow tracking
  Init   : Bool;
END_VAR

VAR_OUTPUT
  AI_Average : Real;
END_VAR

VAR
  Y : Real;        // filtered output, persisted
END_VAR

BEGIN
  IF Init THEN
    Y := AI_Raw;
  ELSE
    Y := (Alpha * AI_Raw) + ((1.0 - Alpha) * Y);
  END_IF;
  AI_Average := Y;
END_FUNCTION_BLOCK

EMA is a discrete-time equivalent of a single-pole RC filter with time constant τ = T·(1-α)/α, where T is the sampling period. It is the recommended choice when:

  • Memory cost must stay O(1) (no ring buffer).
  • α needs to be live-tuned without losing historical samples.
  • The noise is pink (1/f) rather than white — EMA's first-order low-pass character handles this better than a uniform MAV.

10. Commissioning and Verification

  1. Watch-table step injection. In TIA Portal, open the instance DB and force AI_Raw = 100.0, then cycle the CPU. Watch Sum settle to 100.0 · N after N samples, and AI_Average equal 100.0 once Buffer_Full goes TRUE.
  2. Step response test. Switch AI_Raw from 0.0 to 100.0 mid-cycle. The output AI_Average must ramp linearly to 100.0 over N sample periods — a flat output indicates the pointer or sum update is broken.
  3. Noise rejection. Apply ±5 LSB Gaussian noise on AI_Raw from a function-generator block. With N = 16 the standard deviation of AI_Average should drop by approximately √16 = 4×.
  4. Cycle-time impact. Profile OB1 with the Time Measurement trace; one MAV_Filter execution must consume < 0.05 ms on a CPU 1214C at N = 32. A much larger value indicates the compiler emitted an index-loop instead of an inlined pointer access, often caused by a non-optimized DB.
  5. Long-term drift test. Run for ≥ 24 h on a constant DC input and compare the running Sum against the expected value N · AI_Raw. Drift larger than 0.1% means promote Sum to LREAL or schedule periodic Init pulses.

11. Troubleshooting Matrix

Symptom Likely cause Fix
Compiler error "FILL_BLK not supported" Firmware < V4.2 or SCL compiler < V5.5 Upgrade firmware via Online → Accessible Devices → Firmware Update, or replace FILL_BLK with a FOR loop over the buffer.
AI_Average stays at 0 after STOP→RUN Init pulse never fired Verify OB100 contains "DB_MAV".Init := TRUE; and that OB1 last network clears it. Confirm the OB1 last network is reachable (no early RETURN above it).
AI_Average jumps at every Nth sample Pointer not wrapping, or wrap not resetting Samples Confirm the IF AI_Pointer >= (N - 1) THEN ... := 0 branch; for non-optimized DBs, use a symbolic name for the buffer index, not an absolute address.
Slow drift after long uptime REAL rounding in the maintained Sum Promote Sum to LREAL or call Init periodically from a maintenance OB.
Filter too slow to follow step changes N too large or Acquire period too long Reduce N or switch from OB1-driven Acquire to a faster OB30.
AI_Average oscillates wildly Acquire driven from OB1 with cycle-time jitter Use a cyclic interrupt OB30, or hardware interrupt OB40 synchronized to the conversion-finished flag.
DIAG LED on SM 1231 indicates channel fault Input out of range, broken wire, or 24 V missing Verify the field wiring and the channel configuration under AI channel → Diagnostics; the software MAV cannot mask a real hardware fault.

FAQ

Why does FILL not work on my S7-1200 but FILL_BLK does?

The two-argument SCL intrinsic FILL(value, target) is an alias for SFC21 FILL, which is not implemented on S7-1200 firmware. The three-argument form FILL_BLK(IN, COUNT, OUT) is the SCL standard-extension wrapper around SFC21 and is supported from firmware V4.2 onward. Use FILL_BLK as shown in the Init block above, with COUNT as a DINT.

How large can N be before the running sum overflows REAL?

REAL on the S7-1200 is single-precision IEEE-754 with about 7 significant decimal digits. For AI_Raw in engineering units (°C, bar, m³/h) where |AI_Raw| ≤ 104 and N ≤ 103, the sum stays well within ±107 and REAL is sufficient. Beyond that, promote Sum to LREAL; the S7-1200 implements LREAL in software with a measurable cycle-time cost, so bench-test before deploying.

Can I combine the MAV with the SM 1231 hardware integration filter?

Yes, and it is the recommended approach. Set the AI module to the longest integration time (typically 100 ms for 50/60 Hz rejection) and run MAV_Filter on top with N = 4 to 16 samples. The hardware filter removes mains-cycle pickup; the software filter removes broadband noise the sinc average cannot catch. Total noise rejection routinely exceeds 80 dB.

Why does my average drift upward after several hours of uptime?

REAL addition is non-associative; rounding accumulates in the maintained Sum. Two practical mitigations: (1) promote Sum to LREAL; (2) re-issue Init from a maintenance OB every 24 h so the buffer and sum are re-primed from the current AI_Raw. Option 2 is cheaper and is industry-standard practice for non-critical loops.

Is moving average the right choice for thermocouple noise on a heating application?

For slow-changing temperature control where the TC signal drifts a few °C per minute, yes — a 16 to 64-sample MAV at a 100 ms cycle gives 1.6 to 6.4 seconds of smoothing, which fully suppresses the contact-resistance flicker while preserving the underlying temperature trend. If the TC is part of a fast PID loop (≥ 1 Hz update), prefer EMA with α tuned to give the same time constant, since the MAV's group delay of (N-1)/2 samples adds 0.8 to 3.2 seconds of dead time that destabilizes a tightly-tuned loop.

Back to blog