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.
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 FILLsemantics. The legacy two-argumentFILL(value, target)form is rejected by S7-1200 firmware; the three-argumentFILL_BLK(IN, COUNT, OUT)form is the documented standard-extension alternative. TheCOUNTparameter expects aDINTon S7-1200, hence the explicitUSINT_TO_DINTcast. - 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_Averagewould 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;
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
-
Watch-table step injection. In TIA Portal, open the instance DB and force
AI_Raw = 100.0, then cycle the CPU. WatchSumsettle to100.0 · Nafter N samples, andAI_Averageequal100.0onceBuffer_Fullgoes TRUE. -
Step response test. Switch
AI_Rawfrom 0.0 to 100.0 mid-cycle. The outputAI_Averagemust ramp linearly to 100.0 over N sample periods — a flat output indicates the pointer or sum update is broken. -
Noise rejection. Apply ±5 LSB Gaussian noise on
AI_Rawfrom a function-generator block. With N = 16 the standard deviation ofAI_Averageshould drop by approximately √16 = 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.
-
Long-term drift test. Run for ≥ 24 h on a constant DC input and compare the running
Sumagainst the expected valueN · AI_Raw. Drift larger than 0.1% means promoteSumto 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.