S7-1200 Two-Pump Duty-Standby Ladder Logic in TIA Portal V16
Duty/standby sequencing for a lifting-station or hydraulic power-pack pump pair is a classic PLC programming exercise that shows up in nearly every industrial automation curriculum. The job looks trivial on paper (alternate two pumps every 12 hours) but breaks down into several interlocking subsystems once you account for wear, hydraulics, operator override, and field-device health. This article builds a production-grade implementation for a SIMATIC S7-1200 CPU programmed in TIA Portal V16 using ladder logic, with an SCL implementation of the sequencing function block for clarity.
The deliverable is a single re-usable FB_PumpSequence that handles:
- 12-hour cyclic duty/standby rotation (or earlier changeover on stop)
- Asymmetric wear distribution (pump 1 runs more often than pump 2)
- Start counter, totalised run hours, and "next-start" pump selection
- Manual override with short-press / long-press semantics
- Breaker / isolator health feedback that gates every switchover
- Hydraulic no-movement interlock that defers timed changeover when the cylinder is in motion
1. Overview of the Control Problem
A lifting station typically drains a sump or charges a hydraulic accumulator using two identical pumps. The intent of dual pumps is redundancy: if the duty pump fails, the standby must come online within a defined time, and the station must continue to operate. The PLC is the only device that knows which pump is currently duty, when to rotate, and whether the standby is healthy enough to be started.
The three rules the controller must enforce:
- Equal wear over the long term. Run hours and start counts must not diverge so far that one pump becomes the de-facto primary.
- Asymmetric wear over the short term. In practice, you want one pump to be the "fresher" unit at any given moment so that a simultaneous failure of both pumps is unlikely.
- Fail-safe switchover. If the duty pump trips, the standby must start before the process goes into alarm, but only if the standby is electrically and mechanically ready.
P1, P1, P2, P1, P1, P2, … keeps pump 1 about 67% loaded and pump 2 about 33% loaded. The exact ratio is a maintenance-policy decision, not a programming decision, and the FB exposes the duty/standby pattern as a parameter so it can be tuned without recompiling.2. Prerequisites
Before opening TIA Portal, confirm the following items are in place.
| Item | Requirement | Notes |
|---|---|---|
| CPU | SIMATIC S7-1200, firmware 4.4 or later | Needed for the full IEC_TIMER / TOF set in TIA V16 |
| Software | TIA Portal V16, STEP 7 Basic or Professional | Project language ladder (FBD/KOP) |
| Digital inputs (per pump) | Run contactor feedback, breaker / isolator status, low-pressure switch, thermal overload OK | All DI must be 24 V DC sinking into the SM |
| Digital outputs (per pump) | Start command (contactor), optional run-indication lamp, optional alarm beacon | Use relay DQ modules for contactor coils > 24 V |
| HMI | SIMATIC Comfort Panel or Basic Panel with WinCC inside TIA V16 | Tag connection via S7-1200 HMI connection |
| Field devices | Non-return (check) valve on each pump discharge, isolation valve on each suction and discharge, pressure transmitter on common manifold | Mechanical prerequisite independent of PLC |
Reference the SIMATIC S7-1200 Programmable Controller System Manual (entry ID 109759862) and the STEP 7 Professional V16 in the TIA Portal Programming and Operating Manual (entry ID 109773506) for tag, timer, and FB limits.
3. System Architecture and I/O Allocation
The PLC sits between the field sensors and the contactor coils. A clean tag name convention pays off in ladder logic because each network becomes self-documenting.
| Symbol | Address | Type | Description |
|---|---|---|---|
| i_P1_Run | %I0.0 | BOOL | Pump 1 contactor auxiliary closed |
| i_P2_Run | %I0.1 | BOOL | Pump 2 contactor auxiliary closed |
| i_P1_Healthy | %I0.2 | BOOL | Breaker / isolator closed AND overload OK |
| i_P2_Healthy | %I0.3 | BOOL | Breaker / isolator closed AND overload OK |
| i_InMotion | %I0.4 | BOOL | Movement-active signal from machine controller |
| i_StartCmd | %I0.5 | BOOL | Process request (level switch / pressure switch) |
| i_Man_Pump1 | %I1.0 | BOOL | Operator short-press (next start = P1) |
| i_Man_Pump2 | %I1.1 | BOOL | Operator short-press (next start = P2) |
| i_Man_Swap | %I1.2 | BOOL | Operator long-press (immediate swap, > 3 s) |
| q_P1_Coil | %Q0.0 | BOOL | Pump 1 contactor driver |
| q_P2_Coil | %Q0.1 | BOOL | Pump 2 contactor driver |
| q_Alarm | %Q0.2 | BOOL | Common alarm beacon |
The i_Man_Swap input is debounced by an IEC timer with a 3 s preset. A press shorter than 3 s is interpreted as "request the next start to be the other pump." A press longer than 3 s is interpreted as "stop the running pump and start the other one now."
4. Control Philosophy: Duty / Standby Sequencing
The sequence has three states: idle, running on duty, and running on standby after fault. Transitions are governed by two clocks: a 12-hour process timer that requests changeover, and a stop event from the contactor feedback that requests immediate changeover on the next call to start.
The classic 12-hour cyclic changeover is implemented with a single TON timer (tag T_DutyHours) preset to PT = 12 h = 43 200 000 ms. When the duty pump runs continuously for 12 hours, the timer Q output flags a changeover request. The changeover itself is a soft sequence:
- If the standby is healthy, start the standby.
- Wait until
i_Px_Runconfirms the standby is up (preset 2 s in a TOF debounce). - Stop the duty pump.
- Reset the timer and update the duty/standby pointers in the instance DB.
5. Function Block Design: FB_PumpSequence
Encapsulate the logic in an FB so the same code can be dropped into any lifting station. The block is added once to OB1 (or a cyclic OB such as OB35 if you want a fixed 100 ms scan) and called with a single instance DB.
5.1 Block Interface
| Section | Name | Type | Default | Description |
|---|---|---|---|---|
| Input | i_Start | BOOL | FALSE | Process start request |
| Input | i_P1_Healthy | BOOL | FALSE | P1 ready to run |
| Input | i_P2_Healthy | BOOL | FALSE | P2 ready to run |
| Input | i_P1_Run | BOOL | FALSE | P1 actually running |
| Input | i_P2_Run | BOOL | FALSE | P2 actually running |
| Input | i_InMotion | BOOL | FALSE | Movement active (defer swap) |
| Input | i_Man_Next | BOOL | FALSE | Short press: pick other pump next |
| Input | i_Man_Swap | BOOL | FALSE | Long press: immediate swap |
| Input | DutyHours_PT | TIME | T#12h | Changeover interval |
| Input | DutyPattern | INT | 2 | 1=50/50, 2=P1×2,P2 (67/33), 3=P1×3,P2 (75/25) |
| Output | q_P1_Coil | BOOL | — | Drive P1 contactor |
| Output | q_P2_Coil | BOOL | — | Drive P2 contactor |
| Output | q_Alarm | BOOL | — | Common alarm |
| Static | DutyPump | INT | 1 | 1 = P1 duty, 2 = P2 duty |
| Static | NextPump | INT | 2 | Manually pre-selected next start |
| Static | StartCount_P1 | DINT | 0 | Total P1 starts (retain) |
| Static | StartCount_P2 | DINT | 0 | Total P2 starts (retain) |
| Static | RunHours_P1 | REAL | 0.0 | Total P1 hours (retain) |
| Static | RunHours_P2 | REAL | 0.0 | Total P2 hours (retain) |
| Static | PatternStep | INT | 0 | Index into the wear pattern |
| Static | DutyTimer | IEC_TIMER / TON | — | 12 h interval timer |
| Static | ConfirmTimer | IEC_TIMER / TON | — | 2 s confirmation on standby |
| Static | SwapLongTimer | IEC_TIMER / TON | — | 3 s long-press detect |
5.2 SCL Implementation (mirrors the ladder)
The function block is shown in SCL for readability. The ladder program uses the same boolean equations; treat each IF block as one or two ladder networks.
FUNCTION_BLOCK FB_PumpSequence
VAR_INPUT
i_Start : BOOL;
i_P1_Healthy : BOOL;
i_P2_Healthy : BOOL;
i_P1_Run : BOOL;
i_P2_Run : BOOL;
i_InMotion : BOOL;
i_Man_Next : BOOL;
i_Man_Swap : BOOL;
DutyHours_PT : TIME := T#12h;
DutyPattern : INT := 2;
END_VAR
VAR_OUTPUT
q_P1_Coil : BOOL;
q_P2_Coil : BOOL;
q_Alarm : BOOL;
END_VAR
VAR RETAIN
StartCount_P1 : DINT;
StartCount_P2 : DINT;
RunHours_P1 : REAL;
RunHours_P2 : REAL;
END_VAR
VAR
DutyPump : INT := 1;
NextPump : INT := 2;
PatternStep : INT := 0;
DutyTimer : TON;
ConfirmTimer : TON;
SwapLongTimer : TON;
END_VAR
BEGIN
// ---------- 1. Operator manual inputs ----------
SwapLongTimer(IN := i_Man_Swap, PT := T#3s);
IF i_Man_Next AND NOT SwapLongTimer.Q THEN
// Short press: swap "next start" pointer only
IF NextPump = 1 THEN NextPump := 2; ELSE NextPump := 1; END_IF;
END_IF;
IF SwapLongTimer.Q THEN
// Long press: immediate changeover, but only if no movement
IF NOT i_InMotion THEN
IF DutyPump = 1 THEN DutyPump := 2; ELSE DutyPump := 1; END_IF;
PatternStep := 0;
DutyTimer(IN := FALSE);
END_IF;
END_IF;
// ---------- 2. Determine which pump is duty and which is standby ----------
IF NextPump <> 0 THEN
DutyPump := NextPump;
NextPump := 0; // consume the manual selection
END_IF;
// ---------- 3. 12-hour cyclic timer ----------
// Only accumulate time on the running duty pump
IF (DutyPump = 1 AND i_P1_Run) OR (DutyPump = 2 AND i_P2_Run) THEN
DutyTimer(IN := TRUE, PT := DutyHours_PT);
ELSE
DutyTimer(IN := FALSE);
END_IF;
// Changeover request from timer, but defer during movement
IF DutyTimer.Q AND NOT i_InMotion THEN
IF DutyPump = 1 AND i_P2_Healthy AND NOT i_P2_Run THEN
q_P2_Coil := TRUE; // start standby first
ConfirmTimer(IN := NOT i_P2_Run, PT := T#2s);
IF NOT ConfirmTimer.Q AND i_P2_Run THEN
q_P1_Coil := FALSE; // stop duty only after confirmed
DutyPump := 2;
PatternStep := PatternStep + 1;
DutyTimer(IN := FALSE);
END_IF;
ELSIF DutyPump = 2 AND i_P1_Healthy AND NOT i_P1_Run THEN
q_P1_Coil := TRUE;
ConfirmTimer(IN := NOT i_P1_Run, PT := T#2s);
IF NOT ConfirmTimer.Q AND i_P1_Run THEN
q_P2_Coil := FALSE;
DutyPump := 1;
PatternStep := PatternStep + 1;
DutyTimer(IN := FALSE);
END_IF;
END_IF;
END_IF;
// ---------- 4. Start-on-demand logic ----------
IF i_Start AND NOT i_InMotion THEN
IF DutyPump = 1 AND i_P1_Healthy AND NOT i_P1_Run THEN
q_P1_Coil := TRUE;
ELSIF DutyPump = 2 AND i_P2_Healthy AND NOT i_P2_Run THEN
q_P2_Coil := TRUE;
END_IF;
END_IF;
// ---------- 5. Stop-on-demand (release start request) ----------
IF NOT i_Start THEN
q_P1_Coil := q_P1_Coil AND i_P1_Run AND (DutyPump <> 1) AND i_Start;
// (Simplified: drop coils when process no longer demands and no
// changeover in progress.)
END_IF;
// ---------- 6. Counters and run hours ----------
IF i_P1_Run AND NOT q_P1_Coil THEN // rising edge detection done in ladder
; // start counter increment handled in rising-edge network
END_IF;
RunHours_P1 := RunHours_P1 + 0.0002778; // add 1 s per OB1 scan when running
RunHours_P2 := RunHours_P2 + 0.0002778;
// ---------- 7. Alarm: no healthy pump available ----------
q_Alarm := i_Start AND NOT (i_P1_Healthy OR i_P2_Healthy);
END_FUNCTION_BLOCK
TON boxes. The "start standby first" rule is a two-rung network: rung 1 sets q_P2_Coil when DutyTimer.Q AND DutyPump=1 AND i_P2_Healthy; rung 2 resets q_P1_Coil only when i_P2_Run is true. Counter increments go in dedicated networks using rising-edge contacts (P / NEG) on the contactor feedback.5.3 Ladder Network Layout
The ladder program is organised in ten networks. Network numbers must match the comments so they survive renumbering.
| Net | Function | Key contacts / coils |
|---|---|---|
| 1 | Long-press timer |
TON with PT=T#3s on i_Man_Swap
|
| 2 | Short-press "next start" toggle | Rising edge on i_Man_Next AND NOT SwapLongTimer.Q
|
| 3 | Long-press immediate swap | Set/reset DutyPump when SwapLongTimer.Q AND NOT i_InMotion
|
| 4 | Duty timer accumulate |
TON with PT=T#12h, IN wired to the running duty contactor feedback |
| 5 | Start standby first (changeover step 1) | Set q_Px_Coil for the standby |
| 6 | Stop duty after confirmation (step 2) | Reset q_Px_Coil when i_Standby_Run AND ConfirmTimer.Q=FALSE
|
| 7 | Start-on-demand | Set q_Px_Coil on i_Start AND healthy |
| 8 | Stop on demand end | Reset coils when NOT i_Start
|
| 9 | Start counters | CTU on rising edge of contactor feedback |
| 10 | Run-hours accumulation | Add OB1 cycle time to a REAL when contactor closed |
6. Asymmetric Wear Distribution
Equal run hours is the wrong target. The right target is predictable wear so that maintenance can be scheduled with a buffer. A common field pattern is to keep the duty/standby ratio at 2:1 (pump 1 takes two consecutive duty windows, pump 2 takes one). The FB exposes the ratio as DutyPattern.
// PatternStep counts completed changeovers. Modulo arithmetic picks the
// next duty pump from the configured sequence.
CASE DutyPattern OF
1: // 50/50 alternation
IF (PatternStep MOD 2) = 0 THEN DutyPump := 1; ELSE DutyPump := 2; END_IF;
2: // P1, P1, P2
CASE (PatternStep MOD 3) OF
0,1: DutyPump := 1;
2 : DutyPump := 2;
END_CASE;
3: // P1, P1, P1, P2
CASE (PatternStep MOD 4) OF
0,1,2: DutyPump := 1;
3 : DutyPump := 2;
END_CASE;
END_CASE;
The PatternStep counter is incremented only on a successful changeover (standby confirmed running, then duty stopped). If the standby is unhealthy, the changeover is blocked, the pattern does not advance, and an alarm is raised so the operator can intervene.
PatternStep in a RETAIN tag or in a non-volatile DB so a power cycle does not reset the rotation to step 0. The same applies to StartCount_Px and RunHours_Px. See the STEP 7 V16 programming manual, section on retain variables.7. Manual Override, Breaker Health, and Start Counting
7.1 Breaker / Isolator Feedback
Every q_Px_Coil must be gated by i_Px_Healthy. The "healthy" signal is the AND of:
- Motor protection breaker (or fused isolator) in the closed position
- Thermal overload relay in the reset (NC contact) state
- Optional: low oil-pressure switch for a hydraulic power pack
If i_Px_Healthy is FALSE, the FB refuses to start pump X, and q_Alarm goes TRUE if the process is still demanding a pump. The operator HMI must show which pump is in "not healthy" state and why (breaker open, overload tripped, low pressure).
7.2 Manual Selection Semantics
| Operator action | Duration | Effect |
|---|---|---|
| Press "Select P1" button | < 3 s | Next start command goes to P1; current run continues |
| Press "Select P2" button | < 3 s | Next start command goes to P2; current run continues |
| Press and hold either button | >= 3 s | Immediate changeover (only if not i_InMotion) |
| Local selector switch in HAND | Latched | Bypass PLC; operator controls contactors directly. HMI shows "LOCAL" banner. |
7.3 Start Counting and Run Hours
Two statistics are kept per pump and made visible on the HMI:
-
StartCount_Px — total number of contactor closures. Use an IEC
CTUwith a rising-edge contact wired to the contactor auxiliary. Limit the count to DINT (2 147 483 647) which is decades of headroom. -
RunHours_Px — total running time in hours. Accumulate the OB1 scan time in seconds on every cycle where
i_Px_Runis true. Store in a REAL with retain attribute.
For a 10 ms OB1 cycle, the per-scan increment is 0.002777 hours (10 ms = 1/360 000 h). For a 100 ms cycle, use 0.0002777 h. The run-hours total is accurate to within one scan period over the life of the pump.
8. Hydraulic Interlock: No-Movement Switchover
For a hydraulic power pack, the changeover should never interrupt a cylinder movement in progress. The machine controller provides an i_InMotion signal (TRUE while a valve is commanded open and a cylinder is moving). The FB defers every changeover request (timed or manual long-press) until i_InMotion drops. If a changeover is requested during movement:
- The request is latched internally in a static BOOL
SwapPending. - The current duty pump continues to run.
- When
i_InMotiongoes FALSE,SwapPendingis honoured within one scan.
This logic prevents the pressure dip that occurs when one pump stops before the other is up to speed. In a lifting station with a sump, the equivalent interlock is "defer if sump is above high-high level," which is normally satisfied by the level switch itself, but the same latched-pending pattern applies.
9. HMI Tag Mapping
Build a WinCC screen inside TIA Portal V16 with the following tags pulled from the FB_PumpSequence instance DB.
| HMI element | PLC tag | Display |
|---|---|---|
| Pump 1 status icon | DB_Sequence.q_P1_Coil |
Green running, grey stopped, red fault |
| Pump 2 status icon | DB_Sequence.q_P2_Coil |
Same as P1 |
| Duty indicator | DB_Sequence.DutyPump |
Text "DUTY: P1" / "DUTY: P2" |
| Run hours P1 | DB_Sequence.RunHours_P1 |
Numeric field, format 99999.9 h |
| Start count P1 | DB_Sequence.StartCount_P1 |
Numeric field, DINT |
| Time to changeover | DB_Sequence.DutyTimer.ET |
Bar gauge, 0 to DutyHours_PT |
| Manual "Select P1/P2" buttons | Write to DB_Sequence.i_Man_Next via HMI tags |
Bit-button with momentary pulse |
| Alarm banner | DB_Sequence.q_Alarm |
Red bar, top of screen |
Use the WinCC Engineering V16 in TIA Portal manual (entry ID 91174767) for tag-prefix configuration. If you name the instance DB DB_Sequence, all tags are accessible as DB_Sequence.RunHours_P1 from the HMI tag list.
10. Commissioning and Verification
Walk through the following checklist with the pumps locked out and a test PSU feeding the contactor coils. The PLC outputs can be observed in the watch table of TIA Portal before the contactors are energised.
- Tag monitor. Open the instance DB in "Monitor / Modify" and confirm every input toggles when the corresponding field device is actuated. Forcing inputs here is acceptable only for the commissioning engineer with the local selector in OFF.
-
Healthy gating. With the breaker for P1 open, force
i_Start=TRUE. Confirmq_P1_Coil=FALSE,q_P2_Coil=TRUE, andq_Alarm=FALSE(since P2 is healthy). Open the P2 breaker, repeat, and confirmq_Alarm=TRUE. -
Manual short press. In ladder, set
i_Man_Next=TRUEfor one scan, then clear. Confirm theNextPumptag flipped. -
Manual long press. Set
i_Man_Swap=TRUEfor 4 s. ConfirmDutyPumpflipped and the standby started first, then the duty stopped, with the make-before-break order. -
12-hour timer. Temporarily set
DutyHours_PT = T#30sin the instance DB and run a full cycle. Confirm the changeover happens with the standby confirmed running before the duty drops out. -
No-movement interlock. Force
i_InMotion=TRUEand trigger a changeover. Confirm the changeover is deferred and honoured wheni_InMotiongoes FALSE. -
Counters. Force
i_P1_Runfor 10 scan cycles and confirmStartCount_P1increments once on the leading edge, andRunHours_P1advances by 10 x (scan time in seconds). -
Power-cycle retention. Stop the CPU, repower, and confirm the start counts, run hours, and pattern step all survive. If they reset, the
RETAINattribute is missing on those tags. - Local / remote. Switch the local selector to HAND and confirm the PLC outputs go OFF and the field contactors respond to the local pushbuttons. Return the selector to AUTO and confirm normal sequencing resumes on the next call to start.
11. Troubleshooting Matrix
| Symptom | Likely root cause | Where to look in TIA Portal V16 | Fix |
|---|---|---|---|
| Pump never starts | Healthy bit FALSE (breaker open or overload tripped) | Watch table on i_P1_Healthy, i_P2_Healthy
|
Close breaker, reset overload |
| Both pumps run simultaneously | No make-before-break; both coils driven at the same time | Network 5 / 6 ladder; confirm the stop-duty rung is gated on i_Standby_Run
|
Add the standby-confirmed contact before resetting the duty coil |
| Changeover drops process pressure | Duty stopped before standby reaches full speed | Network 6 in ladder; check the 2 s ConfirmTimer preset |
Increase confirm delay or add a pressure-switch interlock |
| Changeover never happens |
i_InMotion stuck TRUE or pattern step saturating |
Monitor i_InMotion, PatternStep, SwapPending
|
Clear the upstream movement signal; verify pattern logic |
| Counters reset on power cycle | Tags not marked RETAIN
|
DB_Sequence property "Retain" | Set Retain on StartCount_Px, RunHours_Px, PatternStep |
| HMI shows old run hours | HMI update rate too low or tag prefix wrong | HMI connection properties, tag list | Bump acquisition cycle to 1 s; verify the tag prefix matches the DB |
| Manual long press does nothing | 3 s timer never elapses because input is not latched | Network 1, SwapLongTimer.IN
|
Use a maintained (latched) pushbutton or repeat the input in the HMI tag while the button is held |
| Alarm always on |
q_Alarm = i_Start AND NOT (i_P1_Healthy OR i_P2_Healthy) evaluates TRUE during commissioning |
Monitor the three inputs simultaneously | Verify healthy wiring; the alarm is correct behaviour if both pumps are in fact not ready |
| Pattern step advances even though standby failed | PatternStep incremented on request, not on success | Network after changeover | Move the increment inside the success branch only |
| CPU goes to STOP on first scan | OB1 calls the FB before the instance DB exists | Project tree; download the DB explicitly | Download the DB to the CPU, then download the program blocks |
For deeper diagnosis, open the CPU's diagnostic buffer via Online > Diagnostics > Diagnostic Buffer in TIA Portal. SF (system fault), BF (bus fault), and OB85 events point to missing or wrong I/O addressing and are the usual culprits on first power-up.
12. Field-Proven Caveats
The following items are not in any single manual but are responsible for the majority of lifting-station callbacks. Treat them as commissioning prerequisites, not nice-to-haves.
- Non-return valves. Each pump discharge needs a check valve rated for the maximum system pressure, with a cracking pressure lower than the pump's dead-head pressure. Without the check valve, a stopped pump back-spins from the common manifold and the contactor auxiliary bounces, confusing the running logic.
- Isolation valves. Suction and discharge isolation on each pump allow a pump to be removed for service while the partner pump keeps the station running. The PLC must not attempt to start a pump whose suction or discharge isolation is closed — wire limit switches from the valves into the healthy chain.
-
Run-dry protection. A level switch or conductivity probe in the sump must inhibit both pumps below a minimum level. The current implementation starts the pumps on
i_Start; gate that input with the low-level signal to avoid burning out the mechanical seal. - Thermal overload relay. Set the overload to the motor's full-load current from the nameplate, not the breaker rating. The overload's NC contact is part of the healthy chain.
- Contactor coil surge. Use RC snubbers on the contactor coils, especially on relay-output DQ modules, to suppress the back-EMF that otherwise couples into the analog level probe.
How do I make the changeover happen every 12 hours instead of on every stop?
Set the IEC TON timer DutyTimer preset to DutyHours_PT = T#12h and wire its input to the running duty contactor feedback rather than to i_Start. The timer only accumulates while the duty pump is actually running, so a 5-minute stop resets nothing; the next 12-hour run continues from the same elapsed time. The changeover triggers on the timer's Q output, not on a stop event, so a 12-hour interval is preserved even with frequent process interruptions.
Should the changeover be make-before-break or break-before-make?
Make-before-break: start the standby first and only stop the duty after the standby contactor auxiliary confirms the new pump is up to speed. Break-before-make causes a process dip in pressure or level and is the leading cause of nuisance low-pressure alarms in the field. The confirm delay (typical 2 s via ConfirmTimer) prevents starting and stopping in the same scan if the standby is slow to close its contactor.
Why use 2:1 wear distribution instead of 50/50?
A 50/50 pattern wears both pumps at the same rate, so a single failure event (such as a power dip that trips both contactors) tends to expose both pumps to maintenance at the same time. A 2:1 pattern (P1, P1, P2) leaves one pump consistently fresher than the other, which means a single pump is more likely to be ready when the other is in the shop. The exact ratio is a maintenance-policy decision; the FB exposes it as the DutyPattern input so it can be tuned without rewriting code.
What stops the PLC from starting a pump whose breaker is open?
Every coil output is gated by an i_Px_Healthy input that AND-combines the breaker / isolator status, the overload relay's NC contact, and any low-pressure switch. If i_Px_Healthy is FALSE the FB refuses to start pump X, the q_Alarm bit goes true if the process is still demanding a pump, and the operator HMI shows which pump is unhealthy and why. Forcing q_Px_Coil in the watch table bypasses this protection and is allowed only during commissioning with the local selector in OFF.
Why does my run-hours total drift on every power cycle?
Drift is normal; truncation is not. If the value resets to zero, the RunHours_Px and StartCount_Px tags are not marked RETAIN. Open the instance DB properties in TIA Portal V16 and set the Retain attribute on those tags. The S7-1200 backs retained tags to internal non-volatile memory and restores them on the next power-up, so the totals survive a CPU stop/start cycle.
Can I add a third pump to the same FB?
The two-pump FB is intentionally minimal so it can be read on a single screen of ladder. For three pumps, instantiate the FB three times (one per pump pair, for example 1+2 and 1+3) and let the pattern logic in the calling OB pick which pair is active. Alternatively, add a third i_Px_Healthy input, a third q_Px_Coil output, and a CASE DutyPattern branch that includes the third pump in the rotation. The S7-1200 has more than enough memory and cycle time for either approach on a lifting-station scale.