PLC Previous Value Comparison in Ladder Logic: Scan-to-Scan

David Krause11 min read
HMI ProgrammingSiemensTechnical Reference
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

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).

Memory model prerequisite. The previous sample must be retained across PLC cycles. Use a static variable inside a Function Block (FB) with an assigned Instance DB, or a global tag in a non-volatile DB (RETAIN / SETUP tag attribute). Losing it on power cycle defeats the trend-detection logic and will produce false alarms on cold start.

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:

  1. SUB — compute the delta: delta = previous - current
  2. Compare — if delta >= threshold AND current < low_limit, set the alarm.
  3. MOVE — copy current into previous for 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;
Watch the priority. Cyclic OBs run at priority 7–15 in S7-1500 (configurable), and can preempt OB1. If OB1 also writes to 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_value and current_value from 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

  1. Watch table test. Create a watch table with current_value, previous_value, delta, and alarm_pressure. Force current_value from 10.0 → 5.0 in 0.5 bar steps; verify delta updates each cycle and the alarm sets at the expected point.
  2. Power-cycle test. Trigger the alarm, STOP the CPU, power off for 30 s, power on, RUN. The retained previous_value must still match the last sample. The alarm should not self-clear.
  3. 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_value correctly.
  4. HMI acknowledge. Trigger the alarm from the watch table, then press the Ack button on the HMI; verify alarm_pressure resets and the alarm disappears from the alarm log.
  5. 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.
  6. 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.
Safety implication. A latched alarm should never be used as the sole means of preventing equipment damage. Pair it with a hardware safety relay or a Failsafe (F-CPU) module if the pressure drop can lead to overpressure, implosion, or personal injury. The standard IEC 61508 and IEC 61511 define the SIL requirements; consult a safety engineer for risk classification.

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.

Back to blog