Problem Overview: Reusable Code for Ten Identical Motors
A common automation requirement is controlling a fleet of identical motors that share the same I/O footprint: a start command from an HMI, a stop command from an HMI, a digital output to the coil, a digital feedback input confirming the motor is running, a tripped feedback input, a status integer pushed back to the HMI, and a startup watchdog timer. Writing the same ladder logic ten times wastes engineering time, increases the chance of copy-paste mistakes, and makes future firmware changes painful. Siemens STEP 7 and TIA Portal solve this with three reusable-block primitives: Function (FC), Function Block (FB), and Instance Data Block (DB).
This reference walks through the canonical pattern for driving ten identical motor starters from a single piece of compiled code, including the classic STEP 7 V5.x multi-instance technique, the modern TIA Portal MULTI-INSTANCE approach, and the array-of-UDT alternative used when neither FB architecture fits the project.
Prerequisites
- STEP 7 V5.5+ (for the classic STL examples) or TIA Portal V16+ (recommended for new projects)
- S7-300, S7-400, S7-1200, or S7-1500 CPU with sufficient work memory for ten instance DBs
- Defined hardware configuration with consistent I/O addressing for each motor
- PLC tag table populated with the start, stop, coil, running, and tripped symbols for all ten motors
FB vs FC: The Memory Decision
The block-type selection drives whether the motor's runtime state survives between scans. Both blocks accept VAR_INPUT, VAR_OUTPUT, VAR_IN_OUT, and VAR_TEMP declarations. The decisive difference is the STAT region.
| Feature | Function (FC) | Function Block (FB) |
|---|---|---|
| Static (STAT) variables | No | Yes - retained across scans |
| Associated background DB | None | One instance DB per call |
| TEMP scope | Local stack, lost on block exit | Local stack, lost on block exit |
| Multi-instance capability | No | Yes - FB can declare STAT instances of other FBs |
| Call syntax (STEP 7) | CALL FC 100 | CALL FB 100, DB 100 |
| Typical use | Pure calculation, stateless conversion | Stateful machine, latch, timer, counter |
A motor starter that must remember whether it is currently running, latch a trip condition, and count elapsed startup seconds requires FB, not FC. The state must survive OB1 scan termination, which is exactly what the instance DB provides.
Instance Data Block Mechanics
Every call to a function block that transfers parameters is paired with an instance data block. The instance DB stores the actual values of the input, output, in-out, and static parameters at the moment the FB returns control to the calling block. Per the Siemens TIA Portal V20 programming manual on instance data blocks and the Siemens Support Forum thread on FB and instance DB, the instance DB is the memory footprint of the FB instance. Two calls of the same FB cannot share one instance DB - the second call would overwrite the runtime values of the first.
The maximum size of an instance DB in classic STEP 7 is 16 KB on most S7-300 CPUs and up to 64 KB on S7-400 CPUs, well above what ten motor starters require. TIA Portal V20 imposes a CPU-dependent work-memory limit rather than a fixed DB ceiling.
Approach A: Single-Instance FB with One DB per Motor
This is the most explicit and easiest-to-debug pattern. Define FB 100 once, then call it ten times - each call paired with a unique instance DB.
Step 1: Declare FB 100 Interface
FUNCTION_BLOCK FB 100
VAR_INPUT
iStart : BOOL; // HMI start button
iStop : BOOL; // HMI stop button
iRunning : BOOL; // Feedback: motor contactor closed
iTripped : BOOL; // Feedback: thermal or fault relay
iStartupTime : S5TIME; // Allowed startup window
END_VAR
VAR_OUTPUT
qCoil : BOOL; // Output to motor contactor
qStatus : INT; // 0=Stopped, 1=Starting, 2=Running, 3=Tripped, 4=Startup Fault
END_VAR
VAR
sState : INT; // Internal state retained in instance DB
sStartTimer : TIMER; // S5TIME watchdog instance
sFaultLatched : BOOL; // Latched trip flag
END_VAR
Step 2: Implement the State Machine
NETWORK 1 // Latched run command
A #iStart
O #sState // self-hold when state > 0
AN #iStop
AN #iTripped
= #qCoil
NETWORK 2 // Start watchdog timer on rising edge of coil
A #qCoil
AN #sState // first scan after de-energized
L #iStartupTime
SE #sStartTimer
NETWORK 3 // Status and fault evaluation
AN #qCoil
JCN M001
L 0
T #qStatus
L 0
T #sState
JU MEND
M001: A #iTripped
JCN M002
L 3
T #qStatus
L 3
T #sState
JU MEND
M002: A #iRunning
JCN M003
L 2
T #qStatus
L 2
T #sState
JU MEND
M003: AN #iRunning
A #sStartTimer // timer still running = startup incomplete
JCN M004
L 1
T #qStatus
L 1
T #sState
JU MEND
M004: L 4 // timer expired without Running feedback
T #qStatus
L 4
T #sState
MEND: NOP 0
Step 3: Call FB 100 Ten Times in OB1
// Motor 1
CALL FB 100, DB 100
iStart := "Motor1_Start"
iStop := "Motor1_Stop"
iRunning := "Motor1_RunningFb"
iTripped := "Motor1_TrippedFb"
iStartupTime := S5T#3S
qCoil := "Motor1_Coil"
qStatus := "Motor1_Status"
// Motor 2
CALL FB 100, DB 101
iStart := "Motor2_Start"
iStop := "Motor2_Stop"
iRunning := "Motor2_RunningFb"
iTripped := "Motor2_TrippedFb"
iStartupTime := S5T#3S
qCoil := "Motor2_Coil"
qStatus := "Motor2_Status"
Approach B: Multi-Instance FB Pattern
When ten motors waste too many DB numbers or when the project must scale to hundreds of instances, the multi-instance architecture nests the motor FB inside a wrapper FB. The wrapper holds the per-instance STAT copies of FB 100, eliminating the need for DB 100 through DB 109.
Wrapper FB Declaration
FUNCTION_BLOCK FB 1
VAR
Motor1 : FB 100;
Motor2 : FB 100;
Motor3 : FB 100;
Motor4 : FB 100;
Motor5 : FB 100;
Motor6 : FB 100;
Motor7 : FB 100;
Motor8 : FB 100;
Motor9 : FB 100;
Motor10: FB 100;
END_VAR
BEGIN
NETWORK 1
CALL #Motor1
iStart := "Motor1_Start"
iStop := "Motor1_Stop"
iRunning := "Motor1_RunningFb"
iTripped := "Motor1_TrippedFb"
iStartupTime := S5T#3S
qCoil := "Motor1_Coil"
qStatus := "Motor1_Status";
NETWORK 2
CALL #Motor2
iStart := "Motor2_Start"
iStop := "Motor2_Stop"
iRunning := "Motor2_RunningFb"
iTripped := "Motor2_TrippedFb"
iStartupTime := S5T#3S
qCoil := "Motor2_Coil"
qStatus := "Motor2_Status";
// ... repeat for Motor3..Motor10
END_FUNCTION_BLOCK
Single OB1 Call
CALL FB 1, DB 1 // One DB holds all ten motor states
The runtime data of FB 100 is stored inside FB 1's instance DB, segmented by each MOTORn STAT variable. Monitor the DB online to see DB1.Motor1.sState, DB1.Motor2.sState, and so on. This is the cleanest pattern for large fleets and is the recommended best practice in TIA Portal because it localizes the motor logic to a single block number.
Approach C: FC with UDT Array
If state retention is not required - or if the project insists on FCs - the FC plus User-Defined Type (UDT) pattern from the original forum discussion still works. Declare a UDT that mirrors the FC's parameter set, build an arrayed DB of UDT instances, then index into the array each scan.
UDT 50 Definition
TYPE UDT 50
STRUCT
Coil : BOOL;
Status : INT;
FaultLatch : BOOL;
StartTime : S5TIME;
ElapsedSec : INT;
END_STRUCT
END_TYPE
Indexed FC Call
FUNCTION FC 100 : VOID
VAR_INPUT
DBnr : INT; // Number of the motor DB
Posnr : INT; // 1-based motor position
END_VAR
VAR_TEMP
IndexBytes: DWORD;
END_VAR
BEGIN
// Calculate pointer offset: (Posnr-1) * sizeof(UDT)
L #Posnr
L 1
-I
L 10 // sizeof(UDT 50) = 10 bytes
*I
ITD
SLD 3
LAR1
OPN DI [#DBnr]
L DID [AR1,P#0.0]
T LD 0
L DIW [AR1,P#4.0]
T LW 2
// ... process motor logic using LD 0 and LW 2 ...
L LD 0
T DID [AR1,P#0.0]
L LW 2
T DIW [AR1,P#4.0]
END_FUNCTION
Startup Watchdog Timer Implementation
The S5TIME format encodes a time base (10 ms, 100 ms, 1 s, or 10 s) in the upper two bits and a BCD value in the lower 14 bits, occupying 16 bits total. S5T#3S means 3 seconds with a 1 s time base; S5T#2M30S means 2 minutes 30 seconds with a 10 s time base. Choosing the right time base is critical - a 3-second window with a 10 ms base would consume almost the entire 999-tick range.
| S5TIME Constant | Resolution | Max Range | Typical Motor Use |
|---|---|---|---|
| S5T#500MS | 10 ms | 9.99 s | Small fractional-kW motors |
| S5T#5S | 100 ms | 1 min 39 s | Standard DOL starters |
| S5T#30S | 1 s | 16 min 39 s | Soft starters, VFD precharge |
| S5T#2M30S | 10 s | 2 h 46 min 30 s | Large wound-rotor motors |
The watchdog arms on the rising edge of the coil command. If iRunning feedback arrives before the timer expires, the status advances to Running (2). If the timer expires first, the status locks at Startup Fault (4) and the coil de-energizes. Tripping on a thermal fault (iTripped = TRUE) takes priority and reports status Tripped (3).
TIA Portal V20 Implementation Notes
TIA Portal hides the multi-instance pattern behind a simple Multi-instance checkbox when you declare an FB-type STAT variable. The compiler automatically allocates the nested FB's instance data inside the parent's instance DB. Compared with classic STEP 7:
- Block numbers are managed automatically by TIA Portal - manual FB 1 / FB 100 numbering is no longer required.
- The SCL source view (Structured Control Language) is preferred over STL for new motor-control libraries because it expresses state machines more clearly.
- The instance DB appears under Program blocks > System blocks > FB1_MotorFleet_DB rather than as a sibling to FB 100.
- The TIA Portal V20 documentation on instance data blocks confirms that the maximum instance DB size is bounded by CPU work memory, not a hard 16 KB / 64 KB figure.
SCL Equivalent
FUNCTION_BLOCK "MotorStarter"
VAR_INPUT
iStart : BOOL;
iStop : BOOL;
iRunning : BOOL;
iTripped : BOOL;
iStartupTime : TIME;
END_VAR
VAR_OUTPUT
qCoil : BOOL;
qStatus : INT;
END_VAR
VAR
sState : INT;
sFaultLatch : BOOL;
sTon : TON_TIME;
END_VAR
BEGIN
// Latch coil on rising start, drop on stop or trip
qCoil := (iStart OR sState > 0) AND NOT iStop AND NOT iTripped;
// Run startup watchdog while coil energized but feedback absent
sTon(IN := qCoil AND NOT iRunning, PT := iStartupTime);
IF NOT qCoil THEN
qStatus := 0;
sState := 0;
ELSIF iTripped THEN
qStatus := 3;
sState := 3;
ELSIF iRunning THEN
qStatus := 2;
sState := 2;
ELSIF sTon.Q THEN
qStatus := 4;
sState := 4;
ELSE
qStatus := 1;
sState := 1;
END_IF;
END_FUNCTION_BLOCK
Verification Checklist
- Compile the program. Resolve any DB does not exist errors by right-clicking the FB and selecting Generate instance DB.
- Download hardware configuration and software to the PLC in STOP mode.
- Switch to RUN and open the instance DB online. Confirm each motor's
sStatereads 0 (Stopped) before any start command. - Issue a start command from the HMI for motor 1. Verify
DB100.qCoiltransitions TRUE andDB100.qStatusreads 1 (Starting). - Force
iRunningTRUE. Confirm status advances to 2 (Running) within one scan. - Disconnect the running feedback and start a timer. Confirm status transitions to 4 (Startup Fault) exactly at
iStartupTime. - Trigger the trip input and verify the coil drops immediately, status reads 3, and the fault remains latched until the stop input clears it.
- Repeat steps 4-7 for every motor in the fleet.
- Cycle power and verify the instance DB contents persist if the DB is configured non-volatile (default for S7-1500 instance DBs).
Troubleshooting Matrix
| Symptom | Likely Root Cause | Corrective Action |
|---|---|---|
| All motors share the same status | Same instance DB used for multiple CALL FB 100 statements | Generate unique DB for each call (DB 100, DB 101, ...) |
| Status jumps randomly between motors | FC used instead of FB; STAT replaced by TEMP | Convert to FB or move state into a UDT array |
| Timer never expires | S5TIME value larger than 999 ticks at chosen base | Split into two cascaded timers or switch to IEC TON |
| Status stuck at 1 (Starting) forever | Running feedback wired to wrong input symbol | Verify cross-reference for iRunning; add online monitor |
| DB not generated on download | Block was compiled with DB-number conflict | Right-click FB > Generate instance DB; renumber if conflict |
| Online shows DB values all zero | FB called from wrong OB (e.g., OB100 startup only) | Move CALL into OB1 cyclic segment |
| Multi-instance data overwritten between scans | Wrapper FB declared with VAR_TEMP instead of VAR | Change wrapper FB's STAT region to VAR, not VAR_TEMP |
| HMI shows wrong status per motor | Output wired to global tag instead of DB-tag | Wire qStatus to per-motor instance DB tags |
Performance and Memory Sizing
For an S7-1516 with the motor FB defined as above, the per-instance footprint is approximately 32 bytes (BOOL outputs, INT state, TIMER instance, BOOL latch, plus alignment). Ten instances therefore consume ~320 bytes of work memory - negligible against the CPU's 5 MB work area. The I/O-update overhead of calling ten FBs in OB1 is well under 1 ms on an S7-1500. If the fleet scales to 200+ motors, consider cyclic OB partitioning or the arrayed UDT approach to keep OB1 cycle time below 50 ms.
Common Pitfalls and Field-Proven Caveats
-
Watchdog reload on each scan. The
SE(start extended) timer reloads only on a rising edge at the input. Driving it with a continuously-TRUE signal will never restart it. If the application requires periodic retriggering, useSS(start latched) or an IECTONwith manual reset. -
S5TIME rounding. An
S5T#3Sconstant uses the 1 s time base, so the timer expires anywhere between 3.0 and 3.999 seconds. For precision below 1 s, useS5T#2S900MS(3 s at the 100 ms base, 2.90 to 3.00 s range). -
Multi-instance naming collisions. TIA Portal prohibits two STAT variables of the same FB type having identical names within the same wrapper. Use
Motor1,Motor2, ...,Motor10as shown above. - Retentivity. By default, instance DBs of FBs are retentive only if the STAT variable is declared RETAIN. If the motor state must survive a CPU STOP-to-RUN transition without re-initialization, mark the relevant STAT fields as RETAIN.
-
Pointer arithmetic in FCs. The OPN DI / LAR1 / DID / T LD pattern from the original forum post assumes 16-bit word alignment. Any 32-bit DINT or REAL fields in the UDT break the byte-offset math - recalculate
Lenght of the UDT Areaaccordingly.
FAQ
Do I need a separate instance DB for every motor when using one FB?
Yes. Each CALL to a function block must be paired with a unique instance DB; otherwise the second call overwrites the runtime state of the first. The Siemens TIA Portal V20 documentation on instance data blocks states that an instance DB is assigned to every FB call that transfers parameters.
What is the difference between FB and FC for motor control?
FC has no STAT region - any internal state is lost when the block exits. FB has STAT variables stored in a background instance DB and therefore remembers state across scans. Motor starters with latched run, trip, or timer values must use FB, not FC.
When should I use multi-instance FBs instead of one FB with many DBs?
Use multi-instance FBs when the fleet is large (50+ motors), when DB-number management becomes cumbersome, or when you want all motor runtime data inside a single instance DB for centralized HMI tag generation. Single-instance is preferable for small fleets where transparency outweighs block-number savings.
What S5TIME constant should I use for a typical 5 kW DOL motor startup?
Most direct-on-line starters close their contactor in under 500 ms and the auxiliary contact feeds back within 1 second. Use S5T#2S (2 s, 100 ms time base) as the default watchdog, allowing margin for contactor bounce while preventing a runaway condition if the contactor fails mechanically.
Why does my motor status stay at 1 (Starting) even though the motor is running?
The most common cause is the iRunning feedback wired to the wrong tag, an inverted polarity (N/C versus N/O contact), or a debounce filter masking the rising edge. Open the instance DB online, watch sState while forcing iRunning, and confirm the symbol resolves to the physical input terminal.