TIA Portal · SIMATIC S7-1500 · how the CPU thinks

The Execution Model

Most people open TIA Portal and start dragging contacts. But a Siemens CPU has opinions about when your code runs — a startup pass, a main cycle, and interrupts that barge in mid-scan. Miss that and your logic is correct yet your machine misbehaves. We'll learn it by guarding a railway level crossing, where "when" is the whole job.

⏱️ OB1 main cycle🔁 OB30 cyclic interrupt⚡ OB40 hardware interrupt
01 · The cycle

A Siemens CPU never just "runs your program"

Before a single rung executes, the CPU runs OB100 (Startup) once. Then it loops forever through three steps. The catch: that loop takes a few milliseconds, and the time wobbles with how much code runs — so anything that needs exact timing, or a reaction faster than one loop, cannot live here alone.

STEP 1 · PII
Read all inputsThe CPU snapshots every input into the Process Image of Inputs. Your code reads the copy, not the live wire.
STEP 2 · OB1
Run your blocksOB1 (and any FCs/FBs it calls) executes top-to-bottom on that snapshot. This is where your machine logic lives.
STEP 3 · PIQ
Write all outputsThe Process Image of Outputs is pushed to the real terminals — all at once, at the end. Then it loops.

🚆 Problem 1 — too slow to wait. A train trips the approach sensor. If that wait sits in OB1, the CPU might be busy at the far end of its scan. At line speed, even a few milliseconds of "I'll get to it" is metres of track.

🔁 Problem 2 — must be exact. The warning lights must flash at a precise, regulated rate. If their blink depends on a scan time that drifts from 4 ms to 9 ms, the cadence drifts too. Unacceptable for a signal.

Siemens' answer is Organization Blocks — extra entry points the operating system calls on its own schedule, at higher priority than OB1. Let's watch three of them run a crossing.

02 · The execution theatre

Three blocks, three jobs, one crossing

Below is the crossing up top and a logic-analyzer view of the CPU itself underneath — exactly what an engineer watches in TIA. OB1 runs the gate sequence in the gaps. OB30 fires on an exact 500 ms drumbeat to flash the lamps. Hit 🚆 Send a train and OB40 slams in the instant the sensor trips — preempting whatever OB1 was doing. Watch the lanes: higher priority always cuts the line.

OB1 · main cycle
gates: OPEN
CPU execution — newest on the rightcycle 0
OB40 HW int
OB30 cyclic
OB1 main
speed

👀 The lamps blink on the yellow OB30 ticks — rock-steady, because the OS fires that block on a clock, not on your scan. When the train hits, the red OB40 bar appears on top of the petrol OB1 bar: OB1 was literally paused mid-instruction so the crossing could react now, not next cycle.

03 · Reuse with memory

One block of code, two crossings that don't talk

A rail line has many crossings, all behaving identically. You don't copy-paste the logic — you write it once as a Function Block, FB_Crossing, then call it twice. Each call gets its own instance DB: a private notebook holding that crossing's state. Same code, separate memory. Send a train through one and watch only its notebook change.

// written ONCE — the behaviour of any crossing
FUNCTION_BLOCK "FB_Crossing"
  VAR  gateState : Int;  stepTimer : Time;  lampPhase : Bool;  trainPresent : Bool;  END_VAR
// called TWICE — each call carves out its own instance DB
"Main_St"();  // instance DB10 — Hauptstraße crossing
"Bahnhof"();  // instance DB11 — Bahnhofweg crossing

This is the whole point of an FB: the code is shared, the data is not. Fix a bug in FB_Crossing once and every crossing on the line inherits the fix — yet each keeps its own gateState, timer and train flag. An FC couldn't do this: it has no instance DB, so it remembers nothing between scans.

04 · The famous gotcha

Optimized vs. standard data blocks

Here's the thing that trips up everyone arriving from older Siemens PLCs. On the S7-1200/1500, data blocks are optimized by default: variables have no fixed byte address — you reach them by name only, and the compiler packs them for speed. Flip a DB to standard (the old way) and every variable gets a frozen byte.bit offset you can address absolutely… until you add one variable and the whole map shifts. Toggle it.

📦 DB10 "Main_St"

Memory map

An HMI tag pointing at "Setpoint"

Optimized — no offsets, symbolic only ("Main_St".gateState). Faster, no wasted bytes, and you can add variables without shifting anything or forcing a re-init. Siemens' default and recommended path.

⚠️ Standard — fixed offsets let you use absolute addresses like %DB10.DBX4.0 and tools that need them — but BOOLs waste bytes, and inserting a variable renumbers everything below it. That's how a working HMI tag silently starts reading the wrong value.

05 · Check yourself

Two that catch people out

1 · Your warning lamps must flash at a dead-steady 1 Hz no matter how heavy the program gets. Where does that logic belong?

2 · You add one BOOL to the top of a standard DB that an HMI reads by absolute address %DB10.DBX4.0. After download, what happens?