Overview
This reference implements an alternating output sequence on a SIMATIC S7-300 PLC: Q0.0 energises for 15 minutes, de-energises for 5 minutes, then Q0.1 energises for 15 minutes, de-energises for 5 minutes, and repeats indefinitely. The total cycle period is 40 minutes.
Three complete solutions are documented: an OB35 cyclic-interrupt counter in SCL, a cascading on-delay timer chain in ladder, and a reusable IEC timer FB. Both STEP 7 V5.5 / V5.6 and TIA Portal V16 / V17 / V18 variants are covered. The cycle is autonomous - no HMI or recipe is required - and restarts cleanly after every cold CPU restart, warm restart, or power-cycle.
Typical use-cases include dual-pump duty-standby rotation, batch-process mixer sequencing, irrigation sector switching, and HVAC cooling-tower alternation. The pattern is intentionally written so that any 15/5 minute pair can be replaced with arbitrary on/off values in a single comparison block without rewiring the I/O.
Prerequisites
Before loading the example, verify the following hardware and software baseline:
- CPU: any SIMATIC S7-300 standard CPU (CPU 312, 314, 315-2 DP/PN, 317, 319) or S7-400 CPU. The same SCL source compiles unchanged on S7-1200/1500 in TIA Portal.
- Firmware: CPU firmware V2.x or later is sufficient. No special technology or motion object is required.
- Engineering tool: STEP 7 V5.5 SP4 / V5.6 or TIA Portal V16 / V17 / V18.
-
Output module: One 24 V digital output channel pair wired to addresses
Q0.0andQ0.1. A SM 322 DO 8x24V/0.5A (6ES7322-1BF01-0AA0) is the typical choice. - OB35: Cyclic-interrupt OB present in the project. For S7-300 the OB is part of the standard library; if it has been deleted, regenerate it from Insert > S7 Block > Organization Block and select OB35.
- System data: HW Config must assign OB35 a period of 60 000 ms (see Properties > Cyclic Interrupts on the CPU).
Timing Diagram & Cycle Definition
The 40-minute cycle is divided into four logical segments: two 15-minute active windows and two 5-minute quiet gaps. The waveform below maps each output against an absolute minute index from 0 to 40.
| Minute index (time_val) | Q0.0 | Q0.1 | State description |
|---|---|---|---|
| 1 - 15 | ON | OFF | Output A active window |
| 16 - 20 | OFF | OFF | Gap (both outputs quiet) |
| 21 - 35 | OFF | ON | Output B active window |
| 36 - 40 | OFF | OFF | Gap (cycle close) |
| At time_val = 40 the counter wraps back to 1 and the cycle repeats. | |||
Solution 1 - OB35 Cyclic Interrupt with Integer Counter (Recommended)
The most compact, scalable, and easy-to-modify approach is to run an integer counter inside OB35 at 1-minute resolution and decide the output state by comparing the counter value against fixed thresholds. One DB holds the cycle state; OB100 resets it on warm restart.
Step 1 - Configure OB35 period
In HW Config, double-click the CPU and open Properties > Cyclic Interrupts. Set OB35 to 60 000 ms. The default is 100 ms; leaving it at default would force you to count 60 000 ticks per minute and is unnecessarily slow.
Step 2 - Create the data block
DATA_BLOCK DB_Cycle
TITLE = 'Alternating Output Cycle State'
STRUCT
time_val : INT; // minute counter, 1..40
first_run : BOOL; // OB100 has primed the counter
END_STRUCT
BEGIN
time_val := 0;
first_run := FALSE;
END_DATA_BLOCK
Step 3 - OB100 cold-start initialisation
OB100 runs exactly once after STOP-to-RUN transition. Use it to zero the counter so the first OB35 call starts at minute 1:
ORGANIZATION_BLOCK OB100
BEGIN
"DB_Cycle".time_val := 0;
"DB_Cycle".first_run := TRUE;
END_ORGANIZATION_BLOCK
Step 4 - OB35 main logic in SCL
ORGANIZATION_BLOCK OB35
BEGIN
// ---------- Increment minute counter ----------
IF "DB_Cycle".time_val >= 40 THEN
"DB_Cycle".time_val := 1; // wrap
ELSE
"DB_Cycle".time_val := "DB_Cycle".time_val + 1;
END_IF;
// ---------- Output A: Q0.0 ON 1..15 ----------
IF "DB_Cycle".time_val >= 1 AND "DB_Cycle".time_val <= 15 THEN
%Q0.0 := TRUE;
ELSE
%Q0.0 := FALSE;
END_IF;
// ---------- Output B: Q0.1 ON 21..35 ----------
IF "DB_Cycle".time_val >= 21 AND "DB_Cycle".time_val <= 35 THEN
%Q0.1 := TRUE;
ELSE
%Q0.1 := FALSE;
END_IF;
END_ORGANIZATION_BLOCK
time_val becomes 1. If you instead initialise time_val := 1 in OB100 and pre-increment in OB35 you can use 0..14 ranges; both styles work as long as they are consistent.Step 5 - Why not use S5 timers (S_PULSE / S_ODT)?
S5 on-delay timers (S_ODT) max out at 2 hours 46 minutes 30 seconds (9990 s in S5TIME format) which is fine for 15 minutes, but four cascaded S5 timers for one cycle produce a hard-to-trace rung that drifts if the CPU enters STOP between two timer segments. An OB35 counter avoids drift because the integer is held in a retentive DB - the cycle resumes from the exact saved minute after any restart.
Solution 2 - Cascading On-Delay Timer Chain (Ladder)
If the controller in question does not have OB35 enabled, or if the engineer prefers a purely ladder implementation without SCL, four cascaded on-delay timers can build the cycle from scratch. The chain uses the self-resetting S_ODT pattern.
| Timer | Preset (TV) | Function |
|---|---|---|
| T1 | T#15M | Energises Q0.0 and starts T2 |
| T2 | T#5M | Resets chain and starts T3 |
| T3 | T#15M | Energises Q0.1 and starts T4 |
| T4 | T#5M | Re-triggers T1 - cycle close |
Ladder excerpt (STEP 7 FBD/LAD notation, four networks):
// Network 1: T1 drives Q0.0
| T1.Q Q0.0 |
|---[ ]---(S)-----| // Q0.0 latched while T1 is timing
| |
| M_RUN T1 |
|--[ ]--(SD T#15M)-| // start T1 on CPU run with 15 min preset
// Network 2: T2 resets Q0.0 and starts T3
| T2.Q Q0.0 |
|---[ ]---(R)-----| // T2 done releases Q0.0
| |
| T1.Q T2 |
|--[ ]--(SD T#5M)--| // T1 done starts T2 with 5 min
// Network 3: T3 drives Q0.1 and starts T4
| T3.Q Q0.1 |
|---[ ]---(S)-----| // Q0.1 latched while T3 is timing
| |
| T2.Q T3 |
|--[ ]--(SD T#15M)-| // T2 done starts T3 with 15 min
// Network 4: T4 resets Q0.1 and re-triggers T1
| T4.Q Q0.1 T1 T2 T3 T4 |
|---[ ]---(R)----(RT)------------------| // clear chain
| |
| T3.Q T4 |
|--[ ]--(SD T#5M)--| // T3 done starts T4 with 5 min
Note: the reset rung uses a single coil per timer - in STEP 7 use the Reset Timer (RT) coil or write R T1, T2, T3, T4 in STL. S5 timers are not retentive across STOP-RUN unless you mark the timer DB as retentive in the CPU properties, so a power cycle restarts the cycle from minute 0.
T#15M is encoded internally as 900 s with a 1 s time base (16-bit word w#16#0258 at base 10). 5 minutes is T#5M = 300 s. The largest legal preset for an S7-300 S5TIME is W#16#3999_9999 with the 10 s base, equating to 9 999 990 s (about 115 days). All four timer presets above are far below that ceiling.Solution 3 - Reusable IEC Timer FB (SCL)
For plants where the cycle may be re-tuned from an HMI or recipe without re-programming, encapsulate the comparator and timer in an FB with an instance-DB. The FB accepts on_A, off_A, on_B, off_B as input parameters and an enable input that pauses the cycle.
FUNCTION_BLOCK FB_AltCycle
VAR_INPUT
enable : BOOL; // master run/pause
on_A : INT; // minute index A goes ON (default 1)
off_A : INT; // minute index A goes OFF (default 16)
on_B : INT; // minute index B goes ON (default 21)
off_B : INT; // minute index B goes OFF (default 36)
period : INT; // total cycle length (40)
END_VAR
VAR_OUTPUT
outA : BOOL; // mapped to Q0.0
outB : BOOL; // mapped to Q0.1
elapsed : INT; // current minute index
END_VAR
VAR
tck : INT; // internal tick counter
END_VAR
BEGIN
IF NOT enable THEN
outA := FALSE; outB := FALSE;
RETURN;
END_IF;
tck := tck + 1;
IF tck >= period THEN tck := 0; END_IF;
elapsed := tck;
outA := (tck >= on_A) AND (tck < off_A);
outB := (tck >= on_B) AND (tck < off_B);
END_FUNCTION_BLOCK
Call the FB from OB35 and connect outputs to process image bits:
// In OB35
"DB_Alt".enable := TRUE;
"DB_Alt".on_A := 1;
"DB_Alt".off_A := 16;
"DB_Alt".on_B := 21;
"DB_Alt".off_B := 36;
"DB_Alt".period := 40;
FB_AltCycle(DB_Alt);
%Q0.0 := "DB_Alt".outA;
%Q0.1 := "DB_Alt".outB;
Time-Base Selection Table
OB35 supports a wide range of periods. The same code structure works at any granularity as long as the integer counter ticks match the chosen period. The table below lists the most useful resolutions:
| OB35 period | Counter range for 40-min cycle | Memory per cycle | Use-case |
|---|---|---|---|
| 1 000 ms (1 s) | 0..2 399 | 4.7 KB (INT array) | Fine audit trail, sub-minute outputs |
| 10 000 ms (10 s) | 0..239 | 0.5 KB | Balanced resolution |
| 30 000 ms (30 s) | 0..79 | 160 B | Half-minute granularity |
| 60 000 ms (1 min) | 0..39 | 80 B | Recommended for 15/5 min cycle |
| (note: ceiling) | - | - | OB35 rejects periods > 60 000 ms |
STEP 7 V5.x Configuration Procedure
- Open SIMATIC Manager and your S7 project.
- In HW Config, double-click the CPU and select Cyclic Interrupts.
- Set OB35 execution period to 60000 ms.
- Compile and download HW Config to the CPU.
- Right-click the Blocks container and insert S7 Block > Organization Block > OB35.
- Switch the OB35 editor to SCL source view via the language selector and paste the source from Solution 1 - Step 4.
- Insert Data Block
DB_Cyclewith the structure shown in Solution 1 - Step 2. - Insert Organization Block
OB100with the priming code shown in Solution 1 - Step 3. - Mark the DB_Cycle as Non-Retain = False in the DB properties so the minute counter survives a power-cycle (see retention note below).
- Compile all blocks, download to CPU, and switch the CPU to RUN.
TIA Portal V16+ Variant
The same logic ports directly to TIA Portal. Two implementation differences:
- OB35 is created via Program blocks > Add new block > Organization Block > OB35 (Cyclic interrupt).
- The hardware period is set under Device configuration > CPU > Properties > Cyclic interrupts > OB35 > Cycle time.
SCL syntax is identical to STEP 7 V5.x. The Portal compiler will warn that the use of %Q0.0 absolute addressing is suboptimal - replace with a tag "OutputA" defined in the PLC tag table for a cleaner project.
// TIA Portal: preferred tag-based assignment
IF "DB_Cycle".time_val >= 1 AND "DB_Cycle".time_val <= 15 THEN
"OutputA" := TRUE;
ELSE
"OutputA" := FALSE;
END_IF;
S7-1500 Migration Notes
On S7-1500 the OB35 numbering is retained, but the periodic time setting is in milliseconds inside the device view. SCL syntax matches. Two refinements are recommended:
- Use
TIMEdata type for sub-second accuracy if you scale OB35 to 100 ms. - Replace the IF-ELSE chain with a
CASEstatement for clarity:
CASE "DB_Cycle".time_val OF
1..15: "OutputA" := TRUE; "OutputB" := FALSE;
16..20: "OutputA" := FALSE; "OutputB" := FALSE;
21..35: "OutputA" := FALSE; "OutputB" := TRUE;
36..40: "OutputA" := FALSE; "OutputB" := FALSE;
END_CASE;
Verification & Commissioning
After loading, perform the following verification on the live CPU before connecting the field wiring.
Online watch table
- Open Monitor / Modify on DB_Cycle.
- Force
DB_Cycle.time_val := 1and confirm Q0.0 LED illuminates on the SM 322. - Force
DB_Cycle.time_val := 16and confirm both outputs drop. - Force
DB_Cycle.time_val := 25and confirm only Q0.1 LED is lit. - Force
DB_Cycle.time_val := 40and on the next OB35 call confirm the counter wraps back to 1.
Diagnostic buffer check
Open CPU > Diagnostic Buffer and confirm no OB35-overrun entry appears. An overrun is logged as event ID 3505 with text "Cyclic interrupt OB35 exceeded the specified cycle time". If this appears, either reduce the SCL execution time below 60 000 ms or extend the OB period.
Long-duration test
Run the program for at least 2 hours (three full cycles) and record the actual Q0.0 and Q0.1 transitions with a stopwatch or HMI trend. The expected drift is the CPU real-time-clock accuracy, typically ±20 ppm (~17 seconds per day), so a single 40-minute cycle is accurate within ~14 ms - effectively zero visible drift.
Edge Cases & Field-Proven Caveats
-
CPU in STOP during a window. If the CPU stops at minute 10 with Q0.0 ON and is brought back to RUN, OB35 resumes counting from
time_val = 10(if retain is on) or from minute 1 (if not). The output follows the new value immediately. This is the desired behaviour for a duty-standby pump rotation - the timer "resets" only if the DB is non-retentive. - Watchdog. S7-300 default scan-observer watchdog is 150 ms; OB35 runs in parallel and does not affect OB1 cycle. With a 60 000 ms period there is no risk of overrun as long as the SCL block executes in under 60 000 ms, which is trivial for a few IF statements.
- Surviving power loss. Without a memory card or battery backup, all DBs in a 31xC CPU are volatile. To persist the cycle position across brown-outs enable the CPU's retain area or insert a flag bit stored in the system's MMC.
-
Replacing the outputs. Map
Q0.0to a Profinet/Profibus slave word by adding a PUT/GET or by moving the bits into a peripheral word such asQW64. The OB35 logic does not change. -
Adding a third output. Add a third comparator against the same
time_valwith the new window, e.g.Q0.2ON during minutes 6..18. No timer chain changes required. -
HMI display of remaining time. Expose
time_valand40 - time_valon a WinCC tag. A faceplate can display "Q0.0 active - 7 min remaining" by subtracting the window edges from the current counter. - Time-base conversion. If you later scale the cycle to a non-integer minute (for example 7.5 minutes) drop OB35 to 30 000 ms and increment the counter by 2 per call; the comparator ranges become 0..29, 30..49, etc.
FAQ
Can I use OB1 instead of OB35 for this cycle?
Yes, but only if you call a TON timer from OB1 - the counter approach above requires a fixed 1-minute tick, which is naturally provided by OB35. Without OB35 you would need a 60 000 ms self-resetting timer in OB1 plus a second counter that decrements, which is more code and less accurate on a slow OB1 scan.
What happens if the CPU is in STOP for 30 minutes?
OB35 stops being called. When the CPU returns to RUN, OB100 (or the cold-start logic) re-zeros time_val and the cycle restarts at minute 1 unless the DB is retentive. For most rotating-equipment applications restarting from minute 1 is the correct behaviour.
Why 60 000 ms and not 100 ms OB35 with a 36 000-tick counter?
Both work. The 60 000 ms version keeps the counter at 0..39 (an INT) instead of 0..23 999, which makes the comparator readable and minimises scan overhead in OB35. The 100 ms variant is preferred only if you also need sub-minute control windows.
Can the on/off times be changed from an HMI?
Yes - use Solution 3 (FB_AltCycle) and bind the on_A / off_A / on_B / off_B INT inputs to HMI tags. Set the new values from a WinCC Comfort panel and the next OB35 call applies them. Allow only values in the range 1..39 to avoid illegal windows.
Does this work on a LOGO! or S7-200 instead of an S7-300?
LOGO! 8 has a similar cyclic-interrupt concept but uses different block numbering. The principle is the same; the parameter ranges differ. S7-200 does not have OB35 - use SM0.5 (1-second pulse) plus a free-running counter to replicate the OB35 tick.