Siemens S7-300 OB35 Timer: Alternating Q0.0/Q0.1 15/5 Min Cycle

David Krause13 min read
HMI ProgrammingSiemensTutorial / How-to
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

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.0 and Q0.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).
OB35 ceiling. On S7-300, OB35 period can be set between 1 ms and 60 000 ms. On S7-1500 the equivalent OB (OB30..OB38) supports 1 ms to 60 000 ms as well. To get 1-minute granularity, 60 000 ms is the largest legal value. For sub-minute timers use a shorter OB and a finer counter resolution.

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.

40-Minute Alternating Cycle (Q0.0 / Q0.1) Q0.0 Q0.1 min 0 5 10 15 20 25 30 35 40 ON (15 min) OFF (5 min) ON (15 min) OFF (5 min)
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
Why ranges are 1..15 and 21..35 (not 0..14, 20..34). The first OB35 call after a STOP-to-RUN transition occurs at T = OB35 period, not at T = 0. With OB35 set to 60 000 ms the first call is 1 minute after start and 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.

S5TIME math. 15 minutes expressed as 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
60 000 ms ceiling. STEP 7 rejects OB35 periods above 60 000 ms with diagnostic-buffer entry "OB35 cycle time exceeds permissible range". If you need 5-minute granularity without using a counter, raise the OB period only up to 60 000 ms and increment by 5 inside the OB.

STEP 7 V5.x Configuration Procedure

  1. Open SIMATIC Manager and your S7 project.
  2. In HW Config, double-click the CPU and select Cyclic Interrupts.
  3. Set OB35 execution period to 60000 ms.
  4. Compile and download HW Config to the CPU.
  5. Right-click the Blocks container and insert S7 Block > Organization Block > OB35.
  6. Switch the OB35 editor to SCL source view via the language selector and paste the source from Solution 1 - Step 4.
  7. Insert Data Block DB_Cycle with the structure shown in Solution 1 - Step 2.
  8. Insert Organization Block OB100 with the priming code shown in Solution 1 - Step 3.
  9. Mark the DB_Cycle as Non-Retain = False in the DB properties so the minute counter survives a power-cycle (see retention note below).
  10. Compile all blocks, download to CPU, and switch the CPU to RUN.
Retain behaviour. In an S7-300, by default all data blocks are retentive up to the CPU's battery-backed limit. To make the cycle counter non-retentive (so it always restarts at minute 1 after power-off) clear the Retain attribute on DB_Cycle. To keep the cycle across power cycles leave retain on. OB100 only runs on warm/cold restart, not on power-on if the CPU is in RUN, so a retentive DB is the only way to preserve minute count through a brown-out.

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 TIME data type for sub-second accuracy if you scale OB35 to 100 ms.
  • Replace the IF-ELSE chain with a CASE statement 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

  1. Open Monitor / Modify on DB_Cycle.
  2. Force DB_Cycle.time_val := 1 and confirm Q0.0 LED illuminates on the SM 322.
  3. Force DB_Cycle.time_val := 16 and confirm both outputs drop.
  4. Force DB_Cycle.time_val := 25 and confirm only Q0.1 LED is lit.
  5. Force DB_Cycle.time_val := 40 and 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.0 to a Profinet/Profibus slave word by adding a PUT/GET or by moving the bits into a peripheral word such as QW64. The OB35 logic does not change.
  • Adding a third output. Add a third comparator against the same time_val with the new window, e.g. Q0.2 ON during minutes 6..18. No timer chain changes required.
  • HMI display of remaining time. Expose time_val and 40 - time_val on 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.

Back to blog