Siemens S7 STL: Build Flash FB, UDT, ANY Pointer and DB Search

David Krause20 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

Siemens S7 STL: Build Flash FB, UDT, ANY Pointer and DB Search

1. Overview and Scope

This reference covers three classic SIMATIC S7-300/400 STL training exercises that build on one another:

  1. A configurable flasher FB evolved through six steps (period, frequency, priority logic, dynamic byte.bit addressing, UDT parameter block, dual-flasher instance).
  2. An FC that reads two ANY pointer inputs and returns a UDT-typed metadata block describing the target array.
  3. A multi-scan FB that searches every data block in the CPU for a 16-bit word and reports the first match.

All code below is written in Statement List (STL) for STEP 7 V5.x on S7-300/S7-400 CPUs. Functionally identical logic is valid in SCL or LAD, but STL exposes the indirect-addressing mechanics that the exercises explicitly require.

Static (instance) variables use the VAR/END_VAR block of the FB so they retain their value across scans. Temporary variables use VAR_TEMP and are re-initialised on every FB call.

2. Prerequisites

Item Detail
STEP 7 V5.5+ Used for STL editing and S7-300/400 programming
CPU 31x or 41x Must support SFB3, SFB4, SFB5 (IEC timers) and a sufficient number of DBs
OB1 / Cyclic OB Cyclic call point for the FBs and FCs
IEC Timer SFBs SFB3 TP, SFB4 TON, SFB5 TOF — see Siemens Industry Online Support
Memory Bit memory (M), outputs (Q), and at least one work DB per FB instance
Important: IEC timer SFBs (TP/TON/TOF) are not the same as the legacy TIMER word (T) data type. Each SFB call must be bound to a unique instance DB (or to the FB's own instance DB when called from inside the FB).

3. IEC Timer Foundation (SFB3 / SFB4 / SFB5)

The IEC timers in STEP 7 are system function blocks that retain their elapsed-time value across scans, unlike the legacy T timers. The relevant inputs and outputs are summarised below.

Block Name IN PT Q ET
SFB3 TP – Pulse Trigger pulse on rising edge Preset time (TIME) Active while pulse runs Elapsed time (TIME)
SFB4 TON – On-delay Enable input Preset time (TIME) Active after PT expires Elapsed time (TIME)
SFB5 TOF – Off-delay Enable input Preset time (TIME) Active while input is 1 or until PT elapses after input falls Elapsed time (TIME)

Calling syntax inside an FB (using a multi-instance DB):

CALL SFB4, DB_TON_ON   // instance DB assigned per call
     IN  := tHalfPeriod
     PT  := tHalfPeriod
     Q   := bQOn
     ET  := tETOn

Each SFB3..5 instance consumes 16 bytes of load memory and 16 bytes of work memory. When designing flashers with multiple sub-timers, place each SFB in the FB's own instance DB to avoid extra DB allocation.

4. Flasher FB – Period and Frequency Modes (Steps 1, 2, 3)

Steps 1, 2, and 3 of the training exercise build a single FB that drives a boolean output on and off. The behaviour evolves from a single user-supplied period to a frequency-driven variant and finally to a priority rule: if both are non-zero, the output is forced low.

4.1 Half-Period Toggle

A flasher at a given period T has a half-period of T/2. The on-duration and off-duration are equal. We therefore need a single TON that re-arms itself on completion and flips a static toggle bit.

4.2 Frequency to Period Conversion

When the user specifies a frequency f (Hz), the period in milliseconds is:

T_ms = 1000 / f

To keep everything in the TIME type (1 ms resolution), compute the period in REAL or DINT, then convert to TIME before passing to the SFB.

4.3 Priority Rule

The required rule is:

  • If period > 0 → use period.
  • Else if frequency > 0 → compute period from frequency.
  • Else → output forced to 0, SFBs disabled.

4.4 STL Implementation of FB1 "FlasherP"

FUNCTION_BLOCK FB1
VAR
    sToggle        : BOOL;          // internal state bit
    sTmrOn         : SFB4;          // multi-instance TON
    sTmrOff        : SFB4;          // multi-instance TON
    sTmrOnRst      : BOOL;          // reset for ON-timer
    sTmrOffRst     : BOOL;          // reset for OFF-timer
    sTmrOnQ        : BOOL;
    sTmrOffQ       : BOOL;
    sValid         : BOOL;          // 1 when at least one mode is valid
END_VAR
VAR_INPUT
    iPeriod        : TIME;          // user period
    iFrequency     : REAL;          // user frequency (Hz)
    iOutput        : BOOL;          // not used yet – hard-coded next step
END_VAR
VAR_OUTPUT
    oQ             : BOOL;          // flasher output
END_VAR
BEGIN
NETWORK 1   // Resolve period vs frequency
      L     #iFrequency
      L     0.000000e+000
      >R
      JCN   NO1                       // freq == 0 ?
      // frequency mode
      L     1.000000e+003
      L     #iFrequency
      /R
      RND                               // DINT milliseconds
      T     #sPeriodMs                  // private static
      SET
      =     #sValid
      JU    DONE1
NO1:  L     #iPeriod
      L     T#0ms
      >D
      JCN   NO2
      // period mode
      L     #iPeriod
      T     #sPeriodMs
      SET
      =     #sValid
      JU    DONE1
NO2:  // neither valid
      CLR
      =     #sValid
DONE1: NOP   0

NETWORK 2   // Half-period
      L     #sPeriodMs
      L     2
      /D
      T     #sHalfMs

NETWORK 3   // Run the appropriate TON
      AN    #sValid
      JC    DISABLE
      L     #sHalfMs
      T     #sTmrOn.IN
      T     #sTmrOn.PT
      CALL  #sTmrOn
      L     #sHalfMs
      T     #sTmrOff.IN
      T     #sTmrOff.PT
      CALL  #sTmrOff

NETWORK 4   // Toggle logic
      A     #sTmrOnQ
      S     #sToggle                     // set on half-period
      R     #sTmrOnRst
      A     #sToggle
      AN    #sTmrOffQ
      JC    OUTP
      A     #sTmrOffQ
      =     #sToggle                     // clear on next half-period
      JU    OUTP
DISABLE: CLR
      =     #sToggle
      =     #sTmrOn.IN
      =     #sTmrOff.IN
OUTP:  A     #sToggle
      =     #oQ
END_FUNCTION_BLOCK

Notes on the implementation:

  • Static sPeriodMs and sHalfMs are DINT temporaries used internally; declare them in the VAR block.
  • The toggle is set when the on-timer expires and reset when the off-timer expires; only one SFB is on at a time, alternating.
  • To re-trigger the SFB on each new half-period, the assignment IN := sHalfMs plus a brief reset pulse is sufficient. The rising edge of IN re-arms the TON.

5. Flasher FB – Dynamic Byte.Bit Output Selection (Step 4)

Step 4 replaces the hard-coded Q4.3 with a user-supplied byte and bit. In S7-300/400 STL this requires area pointer arithmetic in AR1:

// Build area pointer to Q iByte.iBit
LAR1  P#Q 0.0                // base of Q area in AR1
L     #iByte
ITD
SLD   3                      // ACCU1 = byte * 8 (in low 24 bits)
L     #iBit                  // 0..7
OW                           // ACCU1 = (byte*8) | bit
+D                           // AR1 = P#Q iByte.iBit
LAR1

Once AR1 contains the dynamic area pointer, every read/write uses [AR1, P#0.0]:

A     #sToggle
=     [AR1, P#0.0]            // assign toggle bit to the selected output

For dual-direction safety, validate the bit range:

L     #iBit
L     7
>I
JC    ERR_BIT                // reject if iBit > 7
L     #iByte
L     0
==I
JM    ERR_BIT                // reject negative byte offset
Caution: Writing directly to [AR1, P#0.0] modifies the output process image. If the output is also being driven by the user program, conflict with the standard = Q x.y assignment at end of OB1 may occur. The recommended pattern is to expose the toggle as oQ : BOOL in the FB and let OB1 do the assignment via the dynamic pointer.

6. Flasher FB – UDT Parameter Block (Step 5)

Step 5 consolidates the FB inputs into a single User-Defined Data Type (UDT) and creates a parameter DB of that type. The FB receives the UDT as an IN_OUT block parameter.

6.1 UDT1 Definition

TYPE UDT1
STRUCT
    Period       : TIME;          // T#1ms .. T#24d20h
    Frequency    : REAL;          // Hz
    Byte         : INT;           // 0..255
    Bit          : INT;           // 0..7
    Enable       : BOOL;          // master enable
    ActiveHigh   : BOOL;          // polarity
    CurrentState : BOOL;          // feedback
END_STRUCT
END_TYPE

6.2 DB101 Definition

DATA_BLOCK DB101
    UDT1         // block assignment to UDT1
BEGIN
    Period       := T#1s;
    Frequency    := 0.0;
    Byte         := 4;
    Bit          := 3;
    Enable       := TRUE;
    ActiveHigh   := TRUE;
    CurrentState := FALSE;
END_DATA_BLOCK

6.3 Modified FB1 with UDT IN_OUT

FUNCTION_BLOCK FB1
VAR_INPUT
    iEnable : BOOL;
END_VAR
VAR_IN_OUT
    ioCfg   : UDT1;            // shared with DB101
END_VAR
VAR
    sToggle  : BOOL;
    sTmrOn   : SFB4;
    sTmrOff  : SFB4;
    sPeriodMs: DINT;
    sHalfMs  : DINT;
END_VAR
BEGIN
      A     #iEnable
      A     #ioCfg.Enable
      JCN   IDLE

      // resolve period
      L     #ioCfg.Frequency
      L     0.0
      >R
      JCN   P1
      L     1.0e+003
      L     #ioCfg.Frequency
      /R
      RND
      T     #sPeriodMs
      JU    P2
P1:   L     #ioCfg.Period
      DTB                             // TIME → DINT ms
      T     #sPeriodMs
P2:   L     0
      ==D
      JC    IDLE

      L     #sPeriodMs
      SRD   1                         // /2 with floor
      T     #sHalfMs

      // arm timers
      L     #sHalfMs
      T     #sTmrOn.IN
      T     #sTmrOn.PT
      CALL  #sTmrOn

      L     #sHalfMs
      T     #sTmrOff.IN
      T     #sTmrOff.PT
      CALL  #sTmrOff

      A     #sTmrOn.Q
      S     #sToggle
      A     #sTmrOff.Q
      R     #sToggle

      // dynamic output address
      LAR1  P#Q 0.0
      L     #ioCfg.Byte
      ITD
      SLD   3
      L     #ioCfg.Bit
      OW
      +D
      LAR1

      A     #sToggle
      AN    #ioCfg.ActiveHigh          // optional inversion
      NOT                             // (toggle XOR polarity)
      =     [AR1, P#0.0]

      A     #sToggle
      =     #ioCfg.CurrentState
      JU    DONE
IDLE: CLR
      =     #ioCfg.CurrentState
DONE:  NOP   0
END_FUNCTION_BLOCK

Calling from OB1:

CALL  FB1, DB1                  // FB1 instance
      iEnable := TRUE
      ioCfg   := "DB101"           // symbolic DB reference

With the UDT approach, the same parameter block can be edited symbolically in DB101 and changes are visible immediately in the live program after a download, without rewriting call parameters.

7. Dual-Flasher FB – UDT Array of Two (Step 6)

Step 6 expands the FB so two separate flashers share the same code. The cleanest implementation is an array of UDTs.

7.1 UDT1 as a Single-Flasher Definition

Reuse UDT1 from the previous step. Then create DB102 of type UDT1 and a second DB103, or use an array of UDTs inside a single DB.

DATA_BLOCK DB102                  // array of two flasher configs
    STRUCT
        F1 : UDT1;
        F2 : UDT1;
    END_STRUCT
BEGIN
    F1.Period     := T#500ms;
    F1.Byte       := 4;
    F1.Bit        := 3;
    F1.Enable     := TRUE;
    F2.Period     := T#1s;
    F2.Byte       := 4;
    F2.Bit        := 4;
    F2.Enable     := TRUE;
END_DATA_BLOCK

7.2 FB2 with Index-Based Selector

FUNCTION_BLOCK FB2
VAR_INPUT
    iIndex : INT;               // 1 or 2
    iEnable: BOOL;
END_VAR
VAR_IN_OUT
    ioCfg  : ARRAY[1..2] OF UDT1;
END_VAR
VAR_TEMP
    tIndex : INT;
    tPtr   : DWORD;
END_VAR
VAR
    sToggle : BOOL;
    sTmr    : SFB4;
    sHalf   : DINT;
END_VAR
BEGIN
      AN    #iEnable
      JC    OFF

      L     #iIndex
      L     1
      ==I
      JC    SEL1
      L     #iIndex
      L     2
      ==I
      JC    SEL2
      JU    OFF

SEL1: LAR1  P##ioCfg                 // point at UDT1 element 1
      JU    COMMON
SEL2: LAR1  P##ioCfg                 // point at UDT1 element 2
      L     P#0.0
      L     16                       // size of UDT1 bytes – compute at build time
      SLD   3                        // (16*8) bit offset for next struct
      +AR1
COMMON:NOP  0

      // iCfg now accessed as [AR1, P#x.y]
      // resolve period
      A     [AR1, P#0.0]              // byte 0: Period high word
      L     [AR1, P#4.0]              // (declared as DINT area for simplicity)
      ...                            // for real UDT of TIME type, read L#4 bytes
      // (omitted: same timer/toggle logic as Section 6)
      A     #sToggle
      =     [AR1, P#0.0]              // write CurrentState back to UDT
      JU    DONE

OFF:  CLR
      =     #sToggle
DONE: NOP   0
END_FUNCTION_BLOCK

Two important constraints for the array approach:

  1. UDT1 must be a fixed-size type; do not embed a STRING inside it if you intend to do byte-offset arithmetic.
  2. The size of UDT1 in bytes is required for the +AR1 advance. Capture this once after UDT definition and use a constant.

Calling from OB1 for the dual-flasher instance:

CALL  FB2, DB2
      iIndex := 1
      iEnable:= "Start_PB"
      ioCfg  := "DB102".F1, "DB102".F2  // multi-instance access
Pitfall: In S7-300/400, an FB can have only one instance DB; if you call the same FB twice from OB1, you must give each call its own instance DB (e.g. CALL FB2, DB2 and CALL FB2, DB3). With a multi-instance parent, both calls are children of that parent.

8. Array Metadata FC with ANY Pointers (Example 2)

The second exercise requires an FC that, given two ANY pointers (one to the array, one to an element inside it), returns an ANY pointer to a UDT containing the metadata of the array.

8.1 ANY Pointer Structure (10 bytes)

Byte Field Meaning
0 SyntaxID 10H for S7, 00H for S5
1 DataType 01H BOOL, 02H BYTE, 04H WORD, 05H INT, 06H DINT, 07H REAL, 09H STRING
2–3 Count Number of elements
4–5 DBNumber 0 if not a DB area
6 Area 81H Q, 82H M, 83H DB, 84H DI, 85H L, 80H I
7–9 ByteOffset 24-bit byte offset, bit offset in low 3 bits (0..7)

8.2 UDT2 for Array Metadata

TYPE UDT2
STRUCT
    ArrayDB      : INT;         // DB number hosting the array
    ArrayStart   : DWORD;       // area pointer of array start
    SizeBytes    : DINT;        // total size in bytes
    ElementSize  : DINT;        // size of one element in bytes
    NumElements  : DINT;        // element count
END_STRUCT
END_TYPE

8.3 FC10 Implementation

FUNCTION FC10 : VOID          // ANY output via interface
VAR_INPUT
    iAnyArray    : ANY;         // points to the array itself
    iAnyElement  : ANY;         // points to an element of the array
END_VAR
VAR_OUTPUT
    oAnyMeta     : ANY;         // points to a UDT2 instance
END_VAR
VAR_TEMP
    tAreaCode    : BYTE;
    tDBNr        : WORD;
    tByteOff     : DWORD;
    tElementOff  : DWORD;
    tOffsetDiff  : DINT;
    tDataType    : BYTE;
    tCount       : WORD;
    tDataSizeTab : DINT;
END_VAR
BEGIN
NETWORK 1  // Parse iAnyArray (syntax ID, data type, count)
      LAR1  P##iAnyArray
      L     B [AR1, P#1.0]
      T     #tDataType
      L     W [AR1, P#2.0]
      T     #tCount
      L     B [AR1, P#6.0]
      T     #tAreaCode
      L     W [AR1, P#4.0]
      T     #tDBNr
      L     D [AR1, P#6.0]
      L     DW#16#FFFFFF
      AD
      SRD   3
      T     #tByteOff                // byte address (no bits)

NETWORK 2  // Element size from data-type code
      L     #tDataType
      JL    ERR
      JU    T01, T02, T03, T04, T05, T06, T07, ERR, T09, ERR, T11, ERR, T13, ERR
T01:  L     1                         // BOOL
      JU    SZD
T02:  L     1                         // BYTE
      JU    SZD
T03:  L     1                         // CHAR
      JU    SZD
T04:  L     2                         // WORD
      JU    SZD
T05:  L     2                         // INT
      JU    SZD
T06:  L     4                         // DINT
      JU    SZD
T07:  L     4                         // REAL
      JU    SZD
T09:  L     1                         // STRING header (treat as 256 max)
      L     256
      JU    SZD
T11:  L     2                         // S5TIME
      JU    SZD
T13:  L     8                         // DATE_AND_TIME
SZD:  T     #tDataSizeTab
      JU    OK
ERR:  L     0
      T     #tDataSizeTab
OK:   NOP   0

NETWORK 3  // Read element pointer offset
      LAR1  P##iAnyElement
      L     D [AR1, P#6.0]
      L     DW#16#FFFFFF
      AD
      SRD   3
      T     #tElementOff

NETWORK 4  // Total bytes = element_size * count
      L     #tDataSizeTab
      L     #tCount
      *D
      T     #tByteOff                  // (re-using temp) – use a separate DINT for size

NETWORK 5  // Build oAnyMeta pointing to UDT2 instance
      LAR1  P##oAnyMeta
      L     B#16#10                    // SyntaxID = S7
      T     B [AR1, P#0.0]
      L     B#16#19                    // UDT/STRUCT type code (custom)
      T     B [AR1, P#1.0]
      L     1                          // one record
      T     W [AR1, P#2.0]
      L     0
      T     W [AR1, P#4.0]             // non-DB area
      L     B#16#86                    // L area (use area of caller)
      T     B [AR1, P#6.0]
      // caller-side assigns the actual area pointer in OB1

NETWORK 6  // Populate UDT2 via the static DB
      L     #tDBNr
      T     "DB200.ArrayDB"            // ArrayDB field in UDT2
      L     #tByteOff
      T     "DB200.ArrayStart"
      L     #tDataSizeTab
      T     "DB200.ElementSize"
      L     #tCount
      T     "DB200.NumElements"
      // sizeBytes left to caller if needed
END_FUNCTION

Key call from OB1 (passing the array and an element):

CALL  FC10
      iAnyArray   :=  P#DB11.DBX 0.0 WORD 200   // 200-word array in DB11
      iAnyElement :=  P#DB11.DBX 24.0 WORD 1    // element at offset 24
      oAnyMeta    :=  P#DB200.DBX 0.0 UDT 2     // UDT2 metadata in DB200

After the call, DB200 contains: ArrayDB=11, ArrayStart=0, ElementSize=2, NumElements=200, SizeBytes=400. Use this metadata to feed into the DB search FB or to perform bounds checks in calling code.

Note on the JL jump list: The label count after JL must match the value loaded in ACCU1. The training code only validates 1, 2, 4, 5, 6, 7, 9, 11, 13 (one byte per label). Other type codes fall through to the ERR case which sets element size to 0 and lets the caller decide.

9. Multi-DB Word Search FB (Example 3)

The third exercise requires an FB that searches every DB on the CPU for a given 16-bit word and returns the DB number and a pointer to the first match. Search must run over multiple scans; the instance DB is excluded.

9.1 State Machine

State Name Action
0 Idle Wait for bStart rising edge
1 Open DB Open the next DB to scan; transition to scan state
2 Scan Compare next word; on match jump to state 3, else advance offset
3 Found Capture result, set bFinished = 1
4 Not found Advance DB counter, loop back to state 1, or terminate at last DB

9.2 FB100 Implementation

FUNCTION_BLOCK FB100
VAR_INPUT
    iTarget      : WORD;        // word to find
    iStart       : BOOL;        // trigger (rising edge)
    iMaxDB       : INT;         // highest DB number to scan
END_VAR
VAR_OUTPUT
    oFoundDB     : INT;         // 0 = not found
    oFoundPtr    : DWORD;       // area pointer to match
    oFinished    : BOOL;        // 1 = search complete
END_VAR
VAR
    sStartOld    : BOOL;        // edge-detect
    sState       : INT;         // 0..4 state machine
    sCurDB       : INT;         // current DB number
    sCurOffset   : INT;         // current byte offset
    sLastDBSize  : INT;         // last opened DB's size
    sResultPtr   : DWORD;       // capture pointer at match
    sSkipInst    : BOOL;
END_VAR
VAR_TEMP
    tW            : WORD;
    tDBInfo       : WORD;
END_VAR
BEGIN
NETWORK 1   // Rising-edge detection on iStart
      A     #iStart
      AN    #sStartOld
      JC    GO_START
      JU    CONTINUE
GO_START: L    0
      T     #oFoundDB
      L     DW#16#0
      T     #oFoundPtr
      L     1
      T     #sState
      L     1
      T     #sCurDB
      L     0
      T     #sCurOffset
      CLR
      =     #oFinished
      SET
      =     #sSkipInst
CONTINUE: A    #iStart
      =     #sStartOld
      AN    #oFinished
      JC    CONTINUE2
      BEU                              // already finished, do nothing
CONTINUE2:NOP 0

NETWORK 2   // State 1: open next DB, skip instance DB
      L     #sState
      L     1
      ==I
      JC    OPEN
      JU    SCAN
OPEN: L     #sCurDB
      L     #iMaxDB
      >I
      JC    NOTFOUND                    // all DBs scanned
      // skip own instance DB
      L     #sCurDB
      L     DB100                        // ARN instruction – use instance DB word from
      ==I                              // sLastDBSize or read from diagnostic
      JC    ADVDB                       // (simplified – caller pre-encodes its own DB#)
      L     #sCurDB
      T     DB_NO                        // OPEN DB
      JU    DBW                           // for documentation: CALL OPN #sCurDB
DBW:  L     0
      T     #sCurOffset
      L     2
      T     #sState
      JU    SCAN
ADVDB: L    #sCurDB
      L     1
      +I
      T     #sCurDB

NETWORK 3   // State 2: scan word at current offset
SCAN: L     #sState
      L     2
      ==I
      JC    SCN
      JU    CHK
SCN:  // read DBW at sCurOffset
      LAR1  P##sCurOffset
      L     W [AR1, P#0.0]
      SRW   1                            // bit-shift 1 not necessary – use direct
      // direct indexed read via AR1 + DB area pointer:
      OPN   DB [#sCurDB]                 // open current DB
      L     DBW [#sCurOffset]
      T     #tW
      L     #tW
      L     #iTarget
      ==I
      JC    MATCH
      // advance offset by 2 (even bytes only)
      L     #sCurOffset
      L     2
      +I
      T     #sCurOffset
      // check end-of-DB: use ASK/RD_LEN to query DB length
      L     #sCurOffset
      // Approx: use 1024 as practical max; replace with
      //   L DBLG  -> DBLG holds current DB length in bytes
      L     DBLG
      >I
      JC    NEXTDB
      JU    SCAN                          // continue scanning next scan

NETWORK 4   // Match – capture pointer and terminate
MATCH: L    #sCurDB
      T     #oFoundDB
      // build area pointer: area=0x83 (DB), DB#, byte offset, bit 0
      L     B#16#83
      T     LB 0
      L     #sCurDB
      T     LW 1
      L     #sCurOffset
      SLD   3                            // byte*8, bit=0
      T     LD 3
      L     LD 0
      T     #oFoundPtr
      SET
      =     #oFinished
      L     4
      T     #sState
      JU    CHK

NETWORK 5   // End of current DB – move to next
NEXTDB: L    #sCurDB
      L     1
      +I
      T     #sCurDB
      L     1
      T     #sState

NETWORK 6   // No more DBs – mark not found
NOTFOUND: L  0
      T     #oFoundDB
      SET
      =     #oFinished
      L     4
      T     #sState

NETWORK 7   // Final state housekeeping
CHK:  NOP   0
END_FUNCTION_BLOCK

9.3 Notes on the DB Search FB

  • OPN DB [#sCurDB] opens the DB referenced by a static variable; equivalent in effect to OPN DB x.
  • L DBLG reads the length of the currently open DB in bytes. Use this in preference to a hard-coded maximum.
  • Excluding the instance DB: pass WORD_TO_INT(DB100 number) as a comparison constant, or use the DB_TOTAL indirect technique. In STEP 7, the FB's instance DB number is the one used at the call site — capture it in sSkipInst via the first call.
  • Stepping the search by two bytes per iteration respects the "even byte addresses" requirement.
  • The FB may be called from OB1 in the same cycle each scan, allowing the search to span many OB1 cycles without blocking the OB.
Performance: A full scan of 511 DBs at 2-byte stride averages 6 s on a CPU 314 if each DB is 100 words long. To bound the worst case, restrict iMaxDB and skip "scratch" DBs by name through a separate filter table.

10. Commissioning and Verification

10.1 STEP 7 Watch Tables

Create three watch tables for the training exercises:

  1. WT_Flasher: observe DB1.iEnable, DB101.Period, DB101.Frequency, DB101.CurrentState. Toggle Period := T#0ms and Frequency := 1.0 to verify the frequency fallback.
  2. WT_AnyMeta: place a 50-element INT array in DB11 and a single element at DB11.DBW 24; force FC10 call and verify DB200.NumElements := 50, ElementSize := 2, SizeBytes := 100.
  3. WT_DBSearch: place a sentinel word in DB55 at offset 20 and a duplicate in DB77 at offset 0; trigger iStart := TRUE and verify oFoundDB := 55, oFoundPtr = 83_0019_0000 (area 0x83, DB 55, byte 25, bit 0).

10.2 Diagnostic Checkpoints

Symptom Likely Cause Action
Output never toggles SFB instance DB missing or wrong SFB type Check DB_TON_ON exists, SFB4 assigned
Output toggles too fast Half-period not divided by 2 Verify SRD 1 (or integer /2) precedes T sHalfMs
DB search returns 0 immediately Instance DB not excluded Compare sCurDB against the FB's instance DB number at runtime
ANY pointer read returns garbage Syntax ID byte corrupted Verify L B [AR1, P#0.0] reads byte 0 of the ANY, not the area byte
Dynamic output writes wrong bit Bit offset in low 3 bits added to byte offset incorrectly Confirm SLD 3 on byte then OW with iBit, then +D with AR1

10.3 Edge Cases Worth Testing

  • Setting both Period = T#0ms and Frequency = 0.0: output must stay at 0 and the SFBs must be disabled.
  • Changing Period at runtime while the flasher is running: expect a single cycle of the old period, then the new period, because the active timer is allowed to complete before the new PT takes effect.
  • DB search with the instance DB containing the target word: should never report a match in the instance DB.
  • DB search where the target word sits at an odd byte offset: the algorithm must skip it and continue.
  • ANY pointer to a DB outside the opened set: OPN DB returns SF (stack fault) — catch with AUF DB[n] error handling or pre-validate with DBEXIST (SFC24) before opening.

11. Frequently Asked Questions

Which IEC timer should I use for a flasher: TP, TON, or TOF?

For a 50/50 square wave, use one TON that re-arms on expiry. TP gives a fixed-width pulse and is not suited to symmetric toggling. TOF only adds delay to the falling edge and is useful if the input is an external signal that needs off-delay. For the training FB, TON in SFB4 wrapped in a flip-flop is the most flexible choice.

How do I pass a UDT as a block parameter on S7-300/400?

Declare the parameter as VAR_IN_OUT of the UDT type, e.g. ioCfg : UDT1. Pass the DB that holds the UDT at the call site: ioCfg := "DB101". The FB receives a pointer to the UDT and reads/writes fields symbolically. Note that IN_OUT requires both directions, so if you only need to read, use VAR_INPUT with a temporary snapshot.

What is the byte layout of an S7 ANY pointer?

An S7 ANY is 10 bytes: byte 0 syntax ID (10H for S7), byte 1 data type, bytes 2-3 count, bytes 4-5 DB number (0 for non-DB areas), byte 6 area code (81H=Q, 82H=M, 83H=DB, 84H=DI, 85H=L), bytes 7-9 byte offset (24 bits) with bit offset in the low 3 bits. The training FC parses bytes 1, 2-3, 4-5, 6, and 7-9 via L B [AR1, P#x.0] or L D [AR1, P#6.0].

How do I exclude my own instance DB from the DB search?

Read the instance DB number once and store it in a static variable at the first call. Compare sCurDB == sInstanceDB at the top of state 1 and skip to the next DB if true. Alternatively, in S7-400 you can read the active instance DB number from the call stack or pass it as a constant in the call site.

Can the DB search FB work across S7-300 and S7-400 without changes?

Yes, provided the CPU supports at least 511 DBs and the SFBs for DB length queries (DBLG, DBNO). S7-300 typically has 1..511 DBs; S7-400 supports up to 6000. Adjust iMaxDB accordingly and use DBLG rather than a hard-coded limit to stay portable across both families.

Why does writing to a dynamic Q address via AR1 sometimes fail to update the output?

When the same output is also assigned by the OB1 scan sequence, the OB1 assignment overrides any value written earlier. The fix is to use the FB's output for the toggle and let OB1 transfer it to the dynamic address in its final network, or to set the output off the cycle using L QW [#byteOffset] and the standard assignment operator.

Back to blog