Before writing contacts in TIA Portal, understand when the Siemens CPU executes your code: once during startup, in the main cycle, or in an interrupt that preempts the scan. Correct logic can still behave incorrectly when it runs at the wrong time. This simulation uses a railway level crossing to show those timing differences. It is a teaching example, not a validated railway safety design. Any safety function belongs to a safety relay or safety PLC, not this standard control-logic example.
Before OB1 starts, the CPU runs any startup OB in the program, such as OB100 (Startup), once. It then repeats the three steps below. A loop takes a few milliseconds, with its duration changing as different code executes. Code that needs exact timing, or a reaction faster than one loop, cannot live here alone.
🚆 Problem 1: reacting before the next scan. A train trips the approach sensor while OB1 may already be busy elsewhere in its scan. If the reaction is handled only in the main cycle, it must wait for execution to reach that code. The crossing simulation lets you compare that wait with an interrupt response.
🔁 Problem 2: maintaining the interval. 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 uses Organization Blocks as entry points that the operating system calls. Interrupt OBs run at a higher priority than OB1. Follow the three blocks below to see when each one executes in the crossing simulation.
Below is the crossing up top and a logic-analyzer view of the CPU itself underneath. The display shows which block is executing in this simulation. OB1 runs the gate sequence in the gaps. OB30 runs on a 500 ms interval to flash the lamps. Press 🚆 Send a train and OB40 preempts OB1 when the sensor triggers the hardware interrupt. Follow the lanes to see how higher-priority work interrupts the main cycle.
👀 The lamps change on the yellow OB30 ticks. That block follows the OS clock rather than the variable duration of the main scan. When the train reaches the sensor, the red OB40 bar appears on top of the petrol OB1 bar. The diagram shows OB1 paused while the higher-priority interrupt handles the sensor event, before cyclic execution continues.
A rail line has many crossings with the same behaviour. Write the common logic once as a Function Block, FB_Crossing, then call it twice. Each call gets its own instance DB: storage for that crossing's state. Both crossings use the same code with separate memory. Send a train through one crossing and watch which instance data changes.
// 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
With these two FB instances, the code is shared, the data is not. Fix a bug in FB_Crossing once and every crossing using it gets the fix. Each instance keeps its own gateState, timer and train flag. An FC has no instance DB of its own to hold that state between calls.
If you have worked with older Siemens PLCs, check the access setting when moving to the S7-1200/1500. Data blocks are optimized by default: variables have no fixed byte address. You access them by name while the compiler arranges their storage for speed. Switch a DB to standard access and variables use a fixed byte.bit offset for absolute addressing. Adding a variable can shift the following offsets. Toggle the setting below and compare the resulting memory map.
✅ Optimized: no offsets and symbolic access only ("Main_St".gateState). The CPU arranges the layout for fast access; on an S7-1500 each BOOL takes a whole byte. With a memory reserve set on the block, you can add variables and download without reinitializing its values. This is Siemens' default and recommended access setting.
⚠️ Standard: fixed offsets support absolute addresses such as %DB10.DBX4.0 and the tools that require them. BOOLs pack eight to a byte, but the next INT starts on an even byte, so padding bytes appear, and inserting a variable renumbers the addresses below it. An HMI tag that still uses the old address can then read the wrong value.
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?