Overview
The SIMATIC S7-300 controller remains a workhorse in rolling-mill, crane, and weighing applications where two or more serial sensors stream real-time process variables (roll force, roll speed, roll torque, strip thickness, tension, temperature) over point-to-point links. In the reference scenario, two MRU (Mill Roll Universal) sensors transmit ASCII frames over RS-422 into a SIMATIC CP-341 communication processor. The S7-300 CPU must (a) receive the raw bytes from each sensor, (b) convert the integer payload into REAL engineering units, (c) compare the values against configurable warning and alarm limits, (d) drive HMI indicators on a WinCC flexible / TIA Panel, and (e) compute the rolling period for each parameter within a sliding window.
A flat OB1 with ten sequential CALL statements works on a one-time prototype, but it scales poorly: every additional sensor duplicates code blocks, every new alarm adds another ODT (On-Delay Timer) instance, and the maintenance engineer must search ten FBs to find a single scaling bug. This reference consolidates the field-proven S7-300 STEP 7 V5.x code pattern that uses one re-entrant FB per functional task (Receive, Convert, Warn, Alarm, Period) and calls each FB twice with different parameter sets and a different instance DB per call. The article documents why multi-instance technology is mandatory, why STAT variables must replace TEMP variables inside reusable FBs, why IEC timers (SFB3 / SFB4 / SFB5) outperform legacy S5 timers (T0–T127), and how to collapse 40 alarm-on-delay networks into a single FB that is instantiated 40 times from a single OB1 cycle.
System Prerequisites
| Component | Specification | Notes |
|---|---|---|
| CPU | SIMATIC S7-300 (e.g. CPU 315-2 PN/DP, CPU 317-2 PN/DP, CPU 319-3 PN/DP) | Firmware ≥ V3.3 for full multi-instance and IEC-timer support |
| Point-to-point module | SIMATIC CP-341 (6ES7341-1xH02-0AE0) configured for RS-422 | One CP-341 per logical RS-422 bus; alternative 6ES7341-1xH01-0AE0 is firmware-downgrade only |
| Serial protocol | 3964(R) or ASCII driver loaded into CP-341 | Use ASCII for free-form MRU frames; 3964R for framed telegrams with BCC |
| Loadable driver | CP 341 Point-to-Point Communication, Parameter Assignment | Stored in the CP-341 flash; selectable from STEP 7 HW Config |
| Programming tool | STEP 7 V5.5 + SP2 (or STEP 7 V5.6) for S7-300 | TIA Portal V18 is supported for S7-300 with restrictions; STEP 7 V5.x is the canonical toolchain |
| Counter / timer resource | CPU-dependent; e.g. CPU 317-2 provides 256 S7-timer words and 2 048 IEC timers | IEC timers are bound only by work memory, not by CPU timer-word table |
| HMI | SIMATIC Comfort Panel or WinCC flexible 2008 SP3 | Tags bound to the DBs that back the FB instance data |
| Sensors | 2 × MRU (Mill Roll Universal) sensors, RS-422, 9600/19200/38400 bit/s, 8N1 | Baud-rate and parity set in CP-341 HW Config and mirrored in MRU |
Program Structure: OB1 → FC → FB Hierarchy
The recommended three-layer structure for the MRU scenario is:
- OB1 (Main cyclic) – calls one wrapper FC per functional task. OB1 contains no logic, only CALL statements.
-
FC (wrapper) – a thin shell that passes an
InputNoparameter (1 or 2) into the corresponding FB. Two FBs called with different instance DBs per sensor is the cleanest design. - FB (re-usable logic) – contains all warnings, alarms, and period math. Each FB instance carries its own state in its instance DB.
OB1 for the dual-MRU case becomes:
// STL – OB1 (cyclic main)
CALL "FB_Receive" // Serial data from CP-341 → DB_Receive structure
CALL "FB_Convert" // INT/DINT payload → REAL engineering units
CALL "FB_Warning" // Limit comparison, HMI warn flag
CALL "FB_Alarm" // Limit comparison, ODT-debounced HMI alarm
CALL "FB_Period" // SFB5 off-delay period measurement
// END OB1
Each FB is called once but uses internal multi-instance DBs to handle two sensors. The alternative — calling FB_Receive twice, once for sensor 1 and once for sensor 2 — is also acceptable and is preferred when the HMI binds to per-sensor instance DBs.
Multi-Instance Technology for Reusable FBs
Multi-instance means that one FB can declare other FBs as STAT variables, each of which receives its own slice of the parent FB's instance DB. This is the official Siemens technique for re-using a function block without creating a separate instance DB for every call. See the Siemens Industry Online Support entry "Multi-instances for S7-300/400" for the canonical description.
Inside FB_Alarm, declare the IEC timer as STAT (not as TEMP):
// FB_Alarm – interface (declaration view)
FUNCTION_BLOCK FB_Alarm
VERSION : 0.1
VAR_INPUT
InputNo : INT; // 1 or 2 – selects which sensor
AlarmCondition : BOOL; // upstream comparator output
AlarmTime : S5TIME; // e.g. S5T#2s – IEC accepts S5TIME
HMI_Ack : BOOL; // operator acknowledge
END_VAR
VAR_OUTPUT
AlarmActive : BOOL; // drives HMI alarm bit
AlarmLatched : BOOL; // latched until Ack
END_VAR
VAR
ton_AlarmDelay : SFB4; // IEC TP / TON declared as STAT
r_AlarmRising : BOOL; // rising-edge memory
END_VAR
OB1 calls FB_Alarm twice with two instance DBs:
// OB1
CALL FB_Alarm , DB_Alarm_S1
InputNo := 1
AlarmCondition := "comp_S1_overload"
AlarmTime := S5T#2s
HMI_Ack := "HMI_Ack_S1"
AlarmActive := "alarm_S1_active"
AlarmLatched := "alarm_S1_latched"
CALL FB_Alarm , DB_Alarm_S2
InputNo := 2
AlarmCondition := "comp_S2_overload"
AlarmTime := S5T#2s
HMI_Ack := "HMI_Ack_S2"
AlarmActive := "alarm_S2_active"
AlarmLatched := "alarm_S2_latched"
Each CALL opens its own DB, populates the IN/OUT/STAT areas with the parameter values, executes the FB body once, and closes the DB. No data collision occurs because the instance DBs are physically separate.
STAT vs TEMP Variables: The Critical Distinction
The most common bug reported when a beginner first calls the same FB multiple times is "only the last call's values are visible at the output." The root cause is almost always TEMP variables that the developer used to store intermediate state, expecting the values to persist between scans. They do not.
| Variable type | Scope | Lifetime | Initialised | Use case |
|---|---|---|---|---|
| TEMP | Local to FB/FC | One scan only — reset when block ends | Unspecified (undefined) on every call | Scratch / intermediate calc only |
| STAT (static) | Local to FB instance | Retained across scans; lives in instance DB | Set in declaration view; can have default | State memory, timers, counters, edge flags |
| IN / OUT / IN_OUT | Caller-supplied | Live only during the call | Caller supplies value | Signal interface to caller |
Rule of thumb for any FB that is called more than once:
- If the variable holds a state that must survive until the next scan (alarm flags, edge bits, timer instances, period counters), it must be STAT.
- If the variable is a pure scratch / intermediate result used only inside one network of the FB (loop index, intermediate sum, pointer copy), it may be TEMP.
- Never assign an actual value to a TEMP and read it back from another FB or from HMI — it will read garbage.
The earlier source snippet of FB code that held TriggerBit as TEMP and lost its state between calls is exactly this mistake; converting TriggerBit from TEMP to STAT (and giving it an initial value of FALSE in the declaration view) fixes the symptom without changing the FB body.
IEC Timers vs S5 Timers (SFB3 / SFB4 / SFB5)
The classic S5 timer words (T0 … T127 on CPU 317) are mapped into a fixed CPU-internal timer-word area. Their number is hardware-limited, they cannot be passed as parameters, and using them inside an FB requires an external instance DB workaround. The IEC 61131-3 timers (SFB0 … SFB7 in S7-300, SFB3 / SFB4 / SFB5 in the classic S7-300/400 family) are pure function blocks that live inside any FB instance DB, so they scale with work memory, not with the CPU's timer-word table.
| Block | Type | Function | Typical use |
|---|---|---|---|
| SFB3 | TP (Pulse) | Generates a fixed-length pulse on a rising edge, independent of input duration | One-shot acknowledge pulse, valve solenoid pulse, brake test trigger |
| SFB4 | TON (On-Delay) | Output goes TRUE after input has been TRUE continuously for the preset time | Alarm debounce, motor-start lockout, lubrication timing |
| SFB5 | TOF (Off-Delay) | Output stays TRUE for the preset time after the input goes FALSE | Cool-down timer, period measurement (the case in this article) |
| SFB6 / SFB7 | TP / TON variants | Same behaviour as SFB3/SFB4 but with additional retention flag | Retentive pulse / on-delay across CPU restart |
| T0 – T127 | S5 timer | Legacy on-/off-/pulse-delay using CPU timer word | Old code only — replace with IEC timers for new FBs |
Inside an FB, an IEC timer is declared as STAT and called with the pound sign:
// FB_Alarm body – debounce with IEC TON
CALL #ton_AlarmDelay // # = local instance inside this FB
IN := #AlarmCondition
PT := T#2S // IEC TIME format (32-bit ms)
Q := #AlarmActive // drives FB output
ET := #AlarmElapsedTime // optional elapsed-time tag
Because #ton_AlarmDelay is declared as STAT of type SFB4, each call to FB_Alarm owns its own copy of the timer — calling FB_Alarm 40 times uses 40 timer instances, but none of them consume the CPU's S5 timer-word resource.
Scaling Alarm Generation Without 40 Separate ODTs
Original problem: 40 alarm conditions each required a 2-second debounce, and the developer considered placing an ODT network before each alarm bit. That approach adds 40 ODT blocks, 40 timer-instance DBs, and 40 network lines. The scalable pattern is one FB_Alarm block instantiated 40 times, with the IEC timer declared as STAT.
FB_Alarm body:
// FB_Alarm – STL body
// Network 1: Rising-edge of alarm condition
A #AlarmCondition
FP #r_AlarmRising // r_AlarmRising is STAT
AN #AlarmCondition
= #AlarmLatched // while TRUE, latched
// Network 2: IEC on-delay timer
CALL #ton_AlarmDelay
IN := #AlarmCondition
PT := DINT_TO_TIME(#AlarmTime_ms)
Q := #AlarmActive
ET := #et_AlarmRemaining
// Network 3: Latch until operator Ack
A #AlarmActive
O #AlarmLatched
AN #HMI_Ack
= #AlarmLatched // reset only on Ack
OB1 calls FB_Alarm in a loop or 40 times manually. For a 40-alarm roll-mill line the OB1 segment looks like:
// OB1 – instantiate FB_Alarm 40 times
CALL FB_Alarm , DB_Alarm[1] InputNo:=1 AlarmCondition:="alarm[1].raw"
CALL FB_Alarm , DB_Alarm[2] InputNo:=2 AlarmCondition:="alarm[2].raw"
…
CALL FB_Alarm , DB_Alarm[40] InputNo:=40 AlarmCondition:="alarm[40].raw"
STEP 7 generates 40 instance DBs DB_Alarm[1] … DB_Alarm[40], each containing its own copy of ton_AlarmDelay. The CPU executes all 40 instances within a single OB1 cycle; work-memory consumption is ~ 60 bytes per instance for SFB4 + FB_Alarm overhead.
Why this is faster than 40 ODTs
Execution of an ODT network in OB1 reads the timer-word area, computes the new time, writes it back, and updates the timer-bit output. An IEC timer (SFB4) executes the same logic but with one important difference: the entire timer state is local to the instance DB and the compiler can inline the call. In practice, 40 FB_Alarm instances run in the same time envelope as 6–8 ODT blocks because the IEC-timer code path is shorter and because the FB call overhead is amortised.
Period Calculation with SFB5 Off-Delay Reset
The "period" of a roll parameter is the time between two successive threshold crossings. The classical pattern uses an SFB5 off-delay whose elapsed-time ET is latched the moment the comparator output becomes TRUE, then the timer is reset so the next cycle starts at zero.
// FB_Period – interface
FUNCTION_BLOCK FB_Period
VAR_INPUT
Trigger : BOOL; // comparator output (e.g. speed > 0)
END_VAR
VAR_OUTPUT
Period_ms : DINT; // last measured period in ms
PeriodValid : BOOL; // TRUE when at least one cycle is complete
END_VAR
VAR
tof_Debounce : SFB5; // IEC off-delay declared as STAT
r_TriggerRise : BOOL; // rising-edge memory (STAT)
d_LatchedET : DINT; // latched elapsed time (STAT)
END_VAR
// FB_Period body – STL
// Network 1: detect rising edge of trigger
A #Trigger
FP #r_TriggerRise
JCN _N01
L 0
T #d_LatchedET // clear previous period
_N01: NOP 0
// Network 2: run SFB5 to measure low-time of trigger
CALL #tof_Debounce
IN := #Trigger
PT := T#60S // cap period at 60 s
Q := #PeriodValid // TRUE while trigger low < 60 s
ET := #d_LatchedET // current elapsed time
// Network 3: latch period when trigger goes low
A #Trigger // while trigger TRUE, do nothing
JCN _N02 // jump if trigger FALSE
L #d_LatchedET
T #Period_ms
_N02: NOP 0
Operation:
- While
Triggeris TRUE,#tof_Debounce.IN = TRUE, the timer does not run. - When
Triggergoes FALSE, SFB5 starts counting from zero up to PT (60 s). - If
Triggerbecomes TRUE again before 60 s elapses, the ET at that moment is the period of one cycle. - If 60 s elapses without a new trigger,
Qgoes FALSE → fault state ("trigger lost").
To reset SFB5 explicitly, simply assign FALSE to the timer's IN input for one scan, then re-assign TRUE. STEP 7 does not provide a dedicated RESET input on SFB5; the recommended reset is driving IN low and high again from the caller.
CP-341 RS-422 Serial Data Handling
The CP-341 receives the MRU ASCII frame and stores it into a configured receive buffer. The standard Siemens pattern uses the loadable ASCII driver and the FB FB_PNT_RCV (or its STEP 7 V5.x wrapper) inside a dedicated cyclic interrupt (e.g. OB35 at 100 ms) to drain the buffer into a shared DB.
| Step | Block | Action |
|---|---|---|
| 1 | OB35 (cyclic, 100 ms) | CALL "P_RCV" (CP-341 ASCII driver wrapper); copies raw bytes into DB_Recv |
| 2 | FB_Receive (OB1) | Strips header/trailer, validates BCC, unpacks INT payload fields into a STRUCT of sensor values |
| 3 | FB_Convert (OB1) | Applies per-channel linearisation: REAL_Value := (INT_Raw / 27648.0) * SensorRange
|
| 4 | FB_Warning / FB_Alarm (OB1) | Compares each REAL against per-channel warn/alarm thresholds |
| 5 | FB_Period (OB1) | Computes rolling period of each channel |
The scaling formula REAL = (Raw / 27648.0) * Range matches the Siemens convention for 16-bit signed INT values that span ±27648, but MRU sensors often emit 0–32767 unsigned or 0–65535 unsigned counts. Use REAL = (UINT_Raw / 32767.0) * Range for 15-bit unsigned and REAL = (UDINT_Raw / 65535.0) * Range for 16-bit unsigned. Confirm with the sensor datasheet before commissioning — a wrong scaling factors doubles the alarm trip rate or hides overload conditions.
Common Pitfalls and Field-Proven Fixes
| Symptom | Root cause | Fix |
|---|---|---|
| Only the last FB call's output is visible | State variables declared as TEMP | Change all state to STAT; rebuild |
| Period value updates only for the first FB instance | Period counter stored as TEMP | Move period counter, edge bit, and SFB5 instance to STAT |
| Timer does not start | SFB4 / SFB5 called with external instance DB instead of multi-instance | Declare SFB4 as STAT of the FB, call with "#" (e.g. CALL #ton_AlarmDelay) — no external DB |
| CPU goes STOP with SF after second alarm fires | S5 timer word table exhausted (T0–T127 all in use) | Replace all S5 timers with IEC SFB4 / SFB5 declared as STAT |
| CP-341 reports overflow, OB122 SF | Receive buffer not drained frequently enough | Move P_RCV into OB35 at ≤ 100 ms, not OB1 |
| Alarms trip on noise / 1-scan spikes | Alarm bit driven directly from comparator without debounce | Insert FB_Alarm with SFB4 TON at 1–2 s before the latched output |
| Period shows 60 s constantly | SFB5 PT set too short or trigger never re-asserts | Raise PT to expected max period × 1.2; monitor Q for "trigger lost" |
| HMI shows stale alarm after Ack | Ack bit read from HMI but not applied to FB input | Wire HMI_Ack tag to FB_Alarm.HMI_Ack input directly in OB1 |
| Code cannot be downloaded — "FB type conflict" | FB interface signature changed (added STAT) but old instance DB kept | Delete all instance DBs of that FB, re-download; STEP 7 regenerates instance DBs from the new interface |
Verification and Commissioning
- Offline build — In STEP 7, compile all FBs and verify that no "block consistency" warnings remain. Any TEMP that should be STAT must be converted before downloading.
-
PLCSIM simulation — Run the program in S7-PLCSIM. Force
TriggerandAlarmConditionvia the watch table and confirm that each FB instance updates independently. The SFB5 ET and the FB_Alarm Q bit must toggle per-instance. -
Online watch table — Open the instance DBs
DB_Alarm[1] … DB_Alarm[40]and confirm that each contains its ownton_AlarmDelaySTRUCT. The instance DB number of the embedded SFB4 is shown in the DB's "Address of instance" column. - CPU diagnostic buffer — After a 24-hour run with all 40 alarms armed, the diagnostic buffer must be free of OB 122, OB 85, and OB 121 entries. Any OB 85 indicates an access error on a multi-instance that has not been declared correctly.
- CP-341 trace — Use the CP-341 diagnostics screen in STEP 7 to confirm receive-buffer drain interval matches the configured telegram cadence. A rising "buffer full" counter indicates a P_RCV cycle that is too slow.
- Load test — Drive the system with a known MRU test rig (calibrated shunt or function generator) and verify that warning thresholds trip at the configured setpoint ± 0.5 % engineering unit, and that alarms latch only after the configured debounce has elapsed.
- Restart test — Power-cycle the CPU and confirm that all latched alarms re-appear after warm restart (statics retained) or remain cleared after cold restart (statics initialised) — depending on the configured restart behaviour.
Field-Proven Patterns Recap
- One FB per functional task; one instance DB per channel per FB; never duplicate the FB body for each sensor.
- Every variable that holds state across scans is STAT — never TEMP. TEMP is reserved for intermediate calc only.
- Timers are IEC SFB3 / SFB4 / SFB5 declared as STAT of the parent FB, called with "#" (multi-instance). No external instance DB and no S5 timer word consumed.
- Alarm debounce uses SFB4 TON, period measurement uses SFB5 TOF with PT set to (max expected period × 1.2).
- CP-341 receive runs in OB35, not OB1 — keeps OB1 deterministic and prevents receive-buffer overflow.
- All HMI tags read from FB instance DBs, never from FB TEMP — guarantees that operator acknowledge and display value use the same source of truth.
Adopting this pattern converts the original flat OB1 with ten CALL statements into a scalable, testable, and reusable program that supports dozens of sensors and hundreds of alarms without proportional engineering effort. References for the underlying primitives are in the Siemens Industry Online Support portal under "SIMATIC S7-300 Automation System", "SIMATIC CP 341 Point-to-Point Communication", and "SFB3 / SFB4 / SFB5 IEC Timer Reference".
FAQ
Why does only the last FB call produce correct output when I call the same FB twice?
State-holding variables were declared as TEMP. TEMP is reset on every block-end, so only the last call's TEMP remains visible at the HMI. Move every persistent variable (alarm flags, edge bits, timer instances, period counters) to STAT. STAT lives in the instance DB and survives across scans for that specific FB instance.
Can I use one instance DB for two CALL statements of the same FB?
No. Each CALL of an FB requires its own instance DB (or its own slice inside a multi-instance DB of a parent FB). Sharing one DB across two CALLs corrupts both instances because STEP 7 opens the DB for write on the first CALL and the second CALL overwrites the same physical bytes. Always generate one instance DB per CALL.
Should I use S5 timers (T0–T127) or IEC timers (SFB3/SFB4/SFB5) inside a re-usable FB?
Use IEC timers. S5 timers consume the CPU's fixed timer-word table (typically 128 words on CPU 317), cannot be passed as FB parameters, and require an external instance DB workaround inside FBs. IEC timers are pure function blocks: declare them as STAT, call them with "#", and they live in the parent FB's instance DB with no resource limit beyond work memory.
How do I reset an SFB5 off-delay timer to measure the next cycle?
SFB5 has no explicit reset input. The pattern is to drive the IN input FALSE for at least one scan to clear ET, then drive IN TRUE again. Inside FB_Period, the rising-edge of Trigger already produces this behaviour automatically because each new TRUE pulse on IN restarts the timer.
How do I collapse 40 alarm-on-delay networks into one scalable block?
Create FB_Alarm with an SFB4 TON declared as STAT and an edge-bit memory as STAT. Instantiate FB_Alarm 40 times in OB1 with 40 instance DBs (or via a loop in a wrapper FC). Each instance owns its own timer; no external timer resource is consumed, and the HMI tags bind directly to the per-instance DB.
My CP-341 receive buffer overflows under load — what should I change?
Move the P_RCV (or FB_PNT_RCV) call from OB1 into a cyclic interrupt OB (OB35 at 100 ms is the standard). OB1 priority is the lowest and may be starved by PROFIBUS / PROFINET interrupts, while OB35 runs at a fixed 100 ms cadence regardless of OB1 load. Also verify the configured telegram-end delimiter matches the MRU frame delimiter exactly (commonly CR + LF).