1. Overview: The Scan-to-Scan Comparison Problem
Detecting a decreasing trend on a process variable is a recurring requirement in PLC applications: drop in line pressure, falling tank level, derating of a motor current, loss of vacuum in a pick-and-place head, or creep of an analog setpoint. The classic engineering pattern is to keep the value of the previous execution in non-volatile memory, subtract the current value from it, and arm an alarm when the difference crosses a threshold.
The reference requirement used throughout this article is:
- Read a 4–20 mA pressure transmitter scaled to engineering units (bar).
- Each cycle, compare the previous sample with the current sample.
- If the previous sample − current sample ≥ 4 bar, latch an alarm bit.
- If the current sample is also < 4 bar, raise a hard alarm.
The same logic structure works for any single-value comparison: it is independent of whether the variable is real, integer, or scaled analog. The principles transfer directly to SIMATIC S7-1200 Programmable Controller and SIMATIC S7-1500 Programmable Controller with the TIA Portal engineering framework (V17, V18, V19, or V20).
2. The Core Pattern: Previous, Current, Delta
The control-flow structure is the same in every IEC 61131-3 dialect and reduces to three operations executed in order every cycle:
-
SUB — compute the delta:
delta = previous - current -
Compare — if
delta >= thresholdANDcurrent < low_limit, set the alarm. -
MOVE — copy
currentintopreviousfor the next cycle.
The reason for storing previous last is fundamental: executing the MOVE before the SUB would corrupt the difference. In ladder, FBD, and SCL the ordering must be respected or the result is always zero on the first cycle and undefined thereafter.
| Step | Instruction | Inputs | Output | Purpose |
|---|---|---|---|---|
| 1 | SUB (REAL) | IN1 = previous, IN2 = current | delta | Magnitude and sign of change |
| 2 | GE / LE / GRT | IN1 = delta, IN2 = 4.0 | decreasing_flag | Trend test |
| 3 | < (less than) | IN1 = current, IN2 = 4.0 | low_flag | Absolute limit test |
| 4 | AND | IN1 = decreasing_flag, IN2 = low_flag | alarm_set_input | Combine conditions |
| 5 | S (Set coil) | Bit = alarm_bit | latched alarm | Stick the alarm |
| 6 | MOVE | IN = current, OUT = previous | previous | Update history |
3. Implementing the Pattern in TIA Portal (S7-1200 / S7-1500)
Create a new FB in the project tree (right-click Program blocks > Add new block > Function Block), assign an Instance DB automatically, and switch to the FBD or LAD editor. The following ladder segment implements the core pattern in FBD-style contacts because the network is small and reviewable.
3.1 Tag Declaration in the FB
VAR
current_value : REAL; // scaled pressure in bar
previous_value : REAL := 0.0; // retained across scans
delta : REAL;
threshold_drop : REAL := 4.0; // bar
low_limit : REAL := 4.0; // bar
alarm_pressure : BOOL; // latched output
cycle_enable : BOOL; // run the comparison
END_VAR
VAR RETAIN
previous_value : REAL; // survive warm restart
END_VAR
Mark previous_value as Retain so that a brief power dip does not zero the history. S7-1500 supports fine-grained retain; S7-1200 supports retain on a per-DB basis. Refer to the S7-1500 system manual, section on retain behavior.
3.2 Ladder Network
Network 1 - Compute delta and test the two conditions
cycle_enable ( )
|-----[ SUB ]-----( delta := previous_value - current_value )-----|
| |
|-----[ GE ]-----( decreasing_flag := delta >= threshold_drop )-----|
| |
|-----[ LT ]-----( low_flag := current_value < low_limit )-----|
| |
|-----[ AND ]-----( trigger := decreasing_flag AND low_flag )-----|
| |
|-----[ S alarm_pressure ]-----|
Network 2 - Save current as previous for next scan
cycle_enable ( )
|--------[ MOVE current_value → previous_value ]--------|
The S (Set) coil is used so the alarm sticks until an operator acks it from the HMI. Use R (Reset) on a separate network driven by AlarmAck.
4. Choosing the Execution Context: OB1, OB30–OB38, or Timer
A common question is whether to run the comparison in the main OB (OB1 on Siemens), in a cyclic interrupt (OB30–OB38), or inside a timer-driven block. The choice depends on scan time, signal noise, and whether the comparison is a fast control loop or a slow trend detector.
| Execution block | Typical period | Best for | Notes |
|---|---|---|---|
| OB1 (main cyclic) | 5–50 ms | Fast reacting loops, sub-second threshold tests | Risk of high-frequency noise triggering alarms. Add hysteresis or debounce. |
| OB30 | 5 ms (S7-1500 only) | High-speed trend detection | Requires the CPU to support 5 ms phase; verify in hardware catalog. |
| OB32 | 100 ms | General trend monitoring | Default choice for 1 Hz trend logging. |
| OB35 | 1 s (default) | Slow drift, leak detection, 5-sec sample equivalent | Most common in process control. Align with HMI refresh. |
| OB38 | 10 s | Very slow trends, batch-end verification | Watch the priority class — lower than OB1 by default. |
| IEC timer (TP / TON / TOF) | User-defined | Event-driven sampling without a dedicated OB | Easier to reconfigure at runtime; slightly more overhead. |
Cyclic interrupt OBs run at a fixed phase regardless of OB1 scan time. The phase is configured under Properties > Cycle time of the OB. Refer to the S7-1200 system manual and the S7-1500 system manual for the supported phase values per CPU type.
For a 5-second comparison (the example requirement) configure OB35 with a 1 s phase and use a counter that triggers the comparison every 5th call:
VAR
tick_counter : INT;
compare_now : BOOL;
END_VAR
IF tick_counter >= 5 THEN
compare_now := TRUE;
tick_counter := 0;
END_IF;
tick_counter := tick_counter + 1;
previous_value you will create a race condition. The FB that owns the previous/current pair must be called from one location only.5. Building a Reusable Function Block (FB) in SCL
Once the pattern is implemented, encapsulate it into a typed FB so the same block can be reused for pressure, temperature, level, and flow. The following SCL code is a complete, copy-paste ready FB for TIA Portal V17+.
FUNCTION_BLOCK "FB_TrendDropAlarm"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
VAR_INPUT
i_currentValue : REAL; // current scaled value
i_enable : BOOL; // run comparison
i_dropThreshold : REAL := 4.0; // magnitude of decrease that triggers
i_lowLimit : REAL := 4.0; // absolute low threshold
i_acknowledge : BOOL; // rising edge clears latched alarm
END_VAR
VAR_OUTPUT
o_delta : REAL; // previous - current (positive = dropping)
o_alarmActive : BOOL; // latched alarm
o_justTriggered : BOOL; // one-shot on the cycle the alarm set
END_VAR
VAR RETAIN
s_previousValue : REAL; // retained across power cycle
END_VAR
VAR
s_edgeAck : BOOL; // ack edge memory
END_VAR
BEGIN
// Compute the delta first, before the previous value is overwritten
o_delta := s_previousValue - i_currentValue;
// Trend + absolute condition
o_alarmActive := o_alarmActive
OR (i_enable
AND (o_delta >= i_dropThreshold)
AND (i_currentValue < i_lowLimit));
// One-shot trigger for HMI / event logging
o_justTriggered := (i_enable
AND (o_delta >= i_dropThreshold)
AND (i_currentValue < i_lowLimit));
// Operator acknowledgement clears the latch
IF i_acknowledge AND NOT s_edgeAck THEN
o_alarmActive := FALSE;
END_IF;
s_edgeAck := i_acknowledge;
// Save the current as previous for the next scan
s_previousValue := i_currentValue;
END_FUNCTION_BLOCK
Drag the FB into a cyclic OB and connect:
"iDB_TrendDropAlarm"(i_currentValue := "Scale_PT_101".bar,
i_enable := TRUE,
i_dropThreshold := 4.0,
i_lowLimit := 4.0,
i_acknowledge := "HMI".Ack_PT101_Low);\code>
The same FB can be called multiple times with different instance DBs for each monitored tag. TIA Portal will automatically generate one instance DB per call site.
6. Edge Cases and Field-Proven Caveats
| Edge case | Symptom | Mitigation |
|---|---|---|
| First scan / cold start |
previous_value = 0, so a real value of 5 bar creates a 5 bar delta and a false alarm |
Initialize previous_value := current_value on the first scan using a startup flag (OB100 / OB102) or a first-cycle BOOL |
| Sensor power loss | Input reads 0 bar, delta is huge, alarm sets | Add a signal valid check from the analog module diagnostic interrupt (OB82) before the comparison |
| Spike / noise | A single noisy sample crosses the threshold | Add hysteresis: only set the alarm if the condition is true for N consecutive cycles; or low-pass filter the analog input |
| Loss of retain | After CPU STOP/RUN, history is lost | Use a separate retain DB and bind s_previousValue to a retain tag rather than the optimized FB instance |
| Floating-point NaN | If the AI returns -32768 (Siemens S7 analog overflow), comparison fails silently | Test the input with IS_VALID_REAL or check for value < -3E38 before the FB |
| Scan jitter in OB1 | Variable delta magnitude on each scan | Move the FB to OB35 with a fixed 1 s phase, or to OB30 if faster response is needed |
7. Platform-Specific Implementations
7.1 Allen-Bradley CompactLogix / ControlLogix (RSLogix 5000 / Studio 5000)
Use an Add-On Instruction (AOI) with the same three steps: SUB, GRT (Greater Than), MOV. The retention is achieved by marking the AOI tag as “Retained” in the tag properties. The cyclic execution is provided by a Periodic Task with a configurable period (10–2000 ms). Refer to the Logix5000 Controllers Design Considerations reference manual.
[ PeriodicTask 100ms ]
XIC(Enable) SUB(SourceA := previous, SourceB := current, Dest := delta)
GRT(SourceA := delta, SourceB := 4.0) XIO(AlarmActive) ONS(TriggerOneShot)
OTE(AlarmActive)
MOV(Source := current, Dest := previous)
7.2 Beckhoff TwinCAT 3 (PLC)
Use a FUNCTION_BLOCK in Structured Text called from a MAIN task. For cyclic execution independent of the PLC task, use the PlcTaskDef with CycleTime := T#1s. The same SUB / GT / MOVE structure applies; use __RETENTION for the previous_value attribute so it survives an online change.
7.3 CODESYS V3 (Wago, Schneider M241/M251, others)
The pattern is identical. Use PRG blocks called from a cyclic task. For persistence, mark previous_value as RETAIN or PERSISTENT. CODESYS also provides the BLINK and FT_BLINK library blocks for the cyclic interrupt alternative if a dedicated task is not desired.
8. HMI / SCADA Visualization and Trend Setup
For Siemens WinCC Unified / TIA Portal HMI panels, expose o_delta and o_alarmActive as HMI tags. Configure a trend view with the following parameters:
- Trend source:
previous_valueandcurrent_valuefrom the instance DB. - Sampling: 1 s (must be a multiple of the OB35 phase).
- Update cycle: 1 s.
- Alarm logging: configure a discrete alarm on the rising edge of
o_justTriggered.
For WinCC Professional / SCADA, map the same tags to a tag logging group with a 500 ms acquisition cycle and 10 s archive cycle. Refer to the WinCC Unified System Manual.
9. Commissioning Checklist and Verification
-
Watch table test. Create a watch table with
current_value,previous_value,delta, andalarm_pressure. Forcecurrent_valuefrom 10.0 → 5.0 in 0.5 bar steps; verifydeltaupdates each cycle and the alarm sets at the expected point. -
Power-cycle test. Trigger the alarm, STOP the CPU, power off for 30 s, power on, RUN. The retained
previous_valuemust still match the last sample. The alarm should not self-clear. -
First-scan test. From a clean project download (or after memory reset), the FB should not raise an alarm on the very first sample. Verify the first-cycle / startup OB initializes
previous_valuecorrectly. -
HMI acknowledge. Trigger the alarm from the watch table, then press the Ack button on the HMI; verify
alarm_pressureresets and the alarm disappears from the alarm log. - Negative-going vs positive-going direction. Confirm whether the requirement is previous − current (drop) or current − previous (rise). Swap IN1 / IN2 of the SUB block to change direction.
- Cyclic interrupt verification. In TIA Portal, enable Online & Diagnostics > Cycle time for the OB. Confirm the configured phase is being met and that OB1 is not also calling the FB.
10. Frequently Asked Questions
What is the simplest ladder logic to compare the previous scan value with the current value?
Use a SUB instruction to compute the delta (previous minus current), a comparison instruction (LE or GRT) against the threshold, and a MOVE instruction at the end of the network to copy the current value into the previous-value tag. The MOVE must be last or the difference will always be zero on the next cycle.
Should the comparison run in OB1, a cyclic interrupt, or a timer?
Use OB1 if you need sub-second response and the process signal is clean. Use a cyclic interrupt (OB32 at 100 ms or OB35 at 1 s on Siemens S7-1500) for fixed-phase sampling, which is the most common choice for trend monitoring. Use an IEC timer (TP / TON) if you want runtime-configurable periods without creating a new OB.
How do I prevent a false alarm on the first scan or after a CPU restart?
Initialize the previous-value tag in OB100 (startup) or use a first-cycle BOOL in OB1. Mark the previous-value tag as RETAIN so a brief power dip does not zero it, but still initialize it explicitly during a cold start (OB102) because retained values are not guaranteed after a full download or memory reset.
Can one FB instance be used for multiple sensors at once?
Yes. Declare the FB as a typed block (as opposed to multi-instance) and TIA Portal will generate a separate instance DB for each call site, each with its own retained previous_value. The same logic runs in parallel with no interference as long as each call writes only to its own instance.
How do I avoid false alarms from analog noise or a sensor cable break?
Add a signal-valid check (analog module diagnostic interrupt OB82 on Siemens), low-pass filter the input with a PT1 block, or implement hysteresis by requiring the condition to be true for N consecutive cycles. Siemens analog input modules report 0x7FFF (32767) on wire break, which you can test for and exclude from the comparison.