STEP 7 ANY Pointer Format: L# Parameter Type Syntax Explained

David Krause14 min read
S7-300SiemensTechnical Reference
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

STEP 7 ANY Pointer Format: L# Parameter Type Syntax Explained

The ANY data type in SIMATIC STEP 7 (classic, STL/SCL) is a 10-byte pointer that can describe either a data area (DB, M, I, Q, L) or a parameter type (TIMER, COUNTER, block reference). When an ANY is passed to a block interface that is declared as a parameter type rather than a data type, the user enters the pointer using a different textual syntax: the L# form. This article decomposes that syntax, contrasts it with the P# syntax used for data types, and shows how to read, build, and verify ANY pointers in STL and SCL.

1. Overview: Two Flavors of the ANY Pointer

STEP 7 stores all ANY values as a 10-byte image in the working stack (LSTACK) and in instance DBs. The same 10 bytes are interpreted two different ways depending on the data-type identifier in bytes 0 and 1:

  • Data type ANY – bytes 0-1 hold a primitive data type code (BOOL, INT, REAL, STRING, etc.) and bytes 4-9 describe a memory area and byte/bit address. The textual syntax is P# AreaID Byte.Bit DataType RepetitionFactor.
  • Parameter type ANY – bytes 0-1 hold a parameter type code (TIMER, COUNTER, BLOCK_FB, BLOCK_FC, BLOCK_DB, POINTER, ANY) and bytes 4-5 hold the instance/block number. There is no memory-area byte. The textual syntax is L# Number ParameterType RepetitionFactor.

The L# prefix is the unambiguous signal to the compiler/editor that the value being entered is a parameter-type ANY, not a data-type ANY. If you enter P#COUNTER 1 the editor rejects it; if you enter L#1 COUNTER 1 it accepts it and stores the 10-byte image with the COUNTER type code in bytes 0-1.

Important: The textual forms P#… and L#… exist for the programmer. At runtime the CPU never sees either prefix – only the 10 raw bytes. Tools that read ANY pointers directly (HMI tag simulation, custom FBs that parse the image) must look at the type code in bytes 0-1 to decide which interpretation to apply.

2. Internal 10-Byte Layout

Whether entered as P# or L#, the resulting image is always the same physical 10 bytes. The difference is the meaning of bytes 0-1 (data type vs. parameter type code) and bytes 4-9 (which fields are used).

Byte Width Data-type ANY meaning Parameter-type ANY meaning
0-1 WORD Primitive data type code (e.g. 0x05=INT, 0x08=REAL, 0x01=BOOL) Parameter type code (0x19=TIMER, 0x1C=COUNTER, 0x17=BLOCK_FB, 0x18=BLOCK_FC, 0x1A=BLOCK_DB, 0x1B=POINTER, 0x1D=ANY)
2-3 WORD Repetition factor (number of elements of the stated data type) Repetition factor (number of consecutive parameter objects)
4-5 WORD DB number (0 if not a DB area) Number of the TIMER, COUNTER, or block (FB/FC/DB)
6 BYTE Memory area code (0x80=DB, 0x81=M, 0x82=I, 0x83=Q, 0x84=L, 0x85=VL) Sub-area code – for parameter type ANY this byte typically carries a small sub-code (e.g. 0x01=TIMER, 0x02=COUNTER) used by the editor/runtime for legacy purposes
7-8 WORD Byte offset within the area (start address) 0 – not used for parameter types
9 BYTE Bit offset (0-7) for BOOL/individual bit access 0 – not used for parameter types

The layout is documented in the STEP 7 online help under "ANY pointer structure" and in the manual Automating with STEP 7 in STL and SCL (Hans Berger, Publicis, ISBN 978-3895784125), page 618. The same layout also appears in the SIMATIC Programming in STEP 7 Lite V2.0 manual, section A.3.4.1 "Format of an ANY for parameter types" which lists L#1COUNTER 10 as the canonical example.

3. The P# Form for Data Type ANY

When the FB/FC interface declares the parameter as ANY and the actual argument is a memory area, the editor accepts the P# syntax:

P#M 10.0   BYTE  10   // 10 bytes starting at MB10 (M area)
P#DB5.DBX 0.0 BOOL  8   // 8 bits starting at DBX0.0 in DB5
P#I  1.0   WORD  4    // 4 words starting at IW1
P#Q  20.0   REAL 5    // 5 REALs starting at QD20
P#L  0.0   DINT 1    // 1 DINT in L-stack at offset 0

Field-by-field meaning:

  • P# – fixed prefix for data-type ANY.
  • AreaID – one of DBx.DBX/DBB/DBW/DBD, M, I, Q, L, VL.
  • Byte.Bit – start address; the bit field is only meaningful for BOOL.
  • DataType – primitive data type (BOOL, BYTE, WORD, DWORD, INT, DINT, REAL, S5TIME, TIME, DATE, TOD, DT, STRING, CHAR).
  • RepetitionFactor – count of consecutive data-type elements; 1 for a single element.

The repetition factor is what makes ANY useful for array-like access: a single parameter can describe 10 REALs or 32 BOOLs in one call.

4. The L# Form for Parameter Type ANY

When the interface parameter is intended to receive a TIMER, COUNTER, FB, FC, or DB number, you cannot use the P# form because the parameter does not live in M/I/Q/L/DB. The L# form tells the editor to encode bytes 0-1 with the parameter type code and to put the instance number into bytes 4-5.

L#1  COUNTER 1     // counter C1
L#1  COUNTER 10    // counters C1..C10 (repetition factor 10)
L#5  TIMER   1     // timer T5
L#17 BLOCK_FB 1    // FB17
L#1  BLOCK_FC 4    // FCs 1..4
L#20 BLOCK_DB 1    // DB20
L#0  POINTER  1    // NULL pointer (used for uninitialized ANY)
L#0  ANY      1    // NULL ANY (uninitialized)

Field-by-field meaning:

  • L# – fixed prefix for parameter-type ANY. The L historically meant Lokale Beschreibung (local declaration) but is now simply the marker for parameter-type encoding.
  • Number – the instance number. Valid range depends on the parameter type:
    • TIMER: 0-255 (CPU-dependent, e.g. S7-300: 0-127, S7-400: 0-255)
    • COUNTER: 0-255 (S7-300: 0-255)
    • BLOCK_FB / BLOCK_FC / BLOCK_DB: 0-65535 (limited by project)
  • ParameterType – one of TIMER, COUNTER, BLOCK_FB, BLOCK_FC, BLOCK_DB, POINTER, ANY.
  • RepetitionFactor – count of consecutive parameter objects starting at Number. For example, L#1 COUNTER 10 means C1, C2, C3, …, C10.
Repetition factor with parameter types: Although the editor accepts L#1 COUNTER 10 and stores repetition factor 10, most FC/FB calls ignore the repetition and use only the first object. Repetition factors > 1 on parameter-type ANYs are mainly useful when the called block loops over the range itself (e.g. an FC that clears N consecutive counters).

5. Parameter Type Codes Reference

The type code placed in bytes 0-1 of the ANY image is the same regardless of whether you enter the pointer as P#, L#, or build it manually with LAR1 / L W [AR1,P#0.0].

Type code (hex) Type code (decimal) Parameter type Object addressed
0x17 23 BLOCK_FB Function block (FB)
0x18 24 BLOCK_FC Function (FC)
0x19 25 TIMER IEC-style or S5-style timer (T)
0x1A 26 BLOCK_DB Data block (DB)
0x1B 27 POINTER 6-byte POINTER
0x1C 28 COUNTER Counter (C)
0x1D 29 ANY 10-byte ANY

For comparison, the primitive data type codes that appear in data-type ANY pointers are:

Code (hex) Data type Width
0x01 BOOL 1 bit
0x02 BYTE 8 bits
0x03 CHAR 8 bits
0x04 WORD 16 bits
0x05 INT 16 bits
0x06 DWORD 32 bits
0x07 DINT 32 bits
0x08 REAL 32 bits
0x09 S5TIME 16 bits
0x0A TIME 32 bits
0x0B DATE 16 bits
0x0C TIME_OF_DAY 32 bits
0x0D DATE_AND_TIME 64 bits
0x0E STRING n × 8 bits
0x13 DTL 144 bits

6. STL Examples: Building and Reading the Image

You rarely need to construct an ANY manually because the editor does it when you pass an actual argument. But when writing generic FBs that consume ANY pointers (for example, a block-copy FC), you must read the 10-byte image directly. The following STL snippets show the canonical patterns.

6.1 Reading the 10-Byte Image

Assume the input parameter is declared in: ANY. The compiler copies the caller's 10-byte image into the FB's temp in area at IN/OUT offset 0. The VL address of the start of the parameter is placed in AR2 automatically by the calling convention (assuming TEMP variables in the instance/in the LSTACK follow the standard layout).

// Assume AR2 points to the start of the in-parameter area
// Offset 0 of in holds the first 2 bytes of the ANY image

      L     W [AR2,P#0.0]      // bytes 0-1: data/parameter type code
      T     #iTypeCode         // store
      L     W [AR2,P#2.0]      // bytes 2-3: repetition factor
      T     #iRepetition
      L     W [AR2,P#4.0]      // bytes 4-5: DB or instance number
      T     #iNumber
      L     B [AR2,P#6.0]      // byte 6: area code / sub-code
      T     #bArea
      L     W [AR2,P#7.0]      // bytes 7-8: byte offset
      T     #wByteOffset
      L     B [AR2,P#9.0]      // byte 9: bit offset
      T     #bBitOffset

6.2 Branching on Parameter Type

      L     #iTypeCode
      L     W#16#19            // TIMER
      ==I
      JC    TIM                // jump to TIM branch

      L     #iTypeCode
      L     W#16#1C            // COUNTER
      ==I
      JC    CNT                // jump to CNT branch

      L     #iTypeCode
      L     W#16#17            // BLOCK_FB
      ==I
      JC    BFB                // jump to BFB branch
      // ...handle other types...

6.3 Constructing an ANY Manually

If you must build an ANY in a TEMP or DB area, lay the 10 bytes out in big-endian word order:

// Build a COUNTER ANY in TEMP "tAny" that references C5..C8
      LAR1  P##tAny            // AR1 -> start of tAny
      L     W#16#1C            // COUNTER type code
      T     W [AR1,P#0.0]
      L     4                  // repetition factor = 4
      T     W [AR1,P#2.0]
      L     5                  // starting counter C5
      T     W [AR1,P#4.0]
      L     B#16#0             // sub-code 0 (legacy)
      T     B [AR1,P#6.0]
      L     0
      T     W [AR1,P#7.0]
      T     B [AR1,P#9.0]
Endian caveat: The 10 bytes are stored exactly as the layout in section 2. Words are big-endian on S7-300/400. S7-1200/1500 in classic compatibility mode (POINTER/ANY) follows the same convention.

7. SCL Examples

In SCL the textual form is usually sufficient – the compiler builds the image when you assign an actual argument. When you need to inspect it, you can use the POINTER/ANY conversion functions or the variant VariantGet on S7-1500.

FUNCTION_BLOCK FB_CounterMultiReset
VAR_INPUT
   cAny : ANY;     // expects L#1 COUNTER n from the caller
END_VAR
VAR_TEMP
   i    : INT;
   wType: WORD;
   wNum : WORD;
   wRep : WORD;
END_VAR

BEGIN
   // Read the 10 bytes of the ANY image
   wType := DWORD_TO_WORD(SHR(IN:=DWORD_FROM_ANY_BYTES(cAny), N:=24));
   wRep  := DWORD_TO_WORD(SHR(IN:=DWORD_FROM_ANY_BYTES(cAny), N:=8) AND W#16#FFFF);
   wNum  := WORD_FROM_ANY_BYTES(cAny, OFFSET:=4);

   IF wType = W#16#1C THEN                // COUNTER
      FOR i := 0 TO WORD_TO_INT(wRep)-1 DO
         // Clear counter wNum + i
         // (IEC counter calls require an instance DB; placeholder below)
         CU_CLEAR(C := INT_TO_COUNTER(WORD_TO_INT(wNum) + i));
      END_FOR;
   END_IF;
END_FUNCTION_BLOCK

The DWORD_FROM_ANY_BYTES helper is not a standard SCL function – the snippet above is illustrative. In production SCL code, use the PEEK family or assign the ANY to a temporary ARRAY[0..9] OF BYTE via an AT overlay (a technique supported on S7-300/400 SCL and on S7-1500 SCL):

VAR_TEMP
   aBytes : ARRAY[0..9] OF BYTE;
END_VAR
BEGIN
   aBytes := cAny;          // copy of the 10-byte image
   // aBytes[0..1] type code, [2..3] repetition, [4..5] number, ...
END

8. Declaring FB/FC Interfaces that Accept L# ANY

An interface parameter declared simply as ANY accepts both P# and L# forms. If you want to restrict the caller to a single parameter type, you can declare the parameter as that specific type:

FUNCTION FC100 : VOID
VAR_INPUT
   tIn   : TIMER;            // accepts T5
   cCnt  : COUNTER;           // accepts C1..C255
   iDB   : BLOCK_DB;          // accepts DB20
   fbRef : BLOCK_FB;          // accepts FB17
   fcRef : BLOCK_FC;          // accepts FC1
END_VAR

For a TIMER input the caller writes the timer symbol (e.g. T5). Internally STEP 7 still encodes it as a 2-byte object; the ANY is not used because a TIMER is not a memory area. BLOCK_FB/BLOCK_FC/BLOCK_DB are 2-byte block numbers. The L# syntax is only required when the interface itself is declared as ANY.

9. Comparing P# and L# at a Glance

Aspect P# form (data type) L# form (parameter type)
Prefix P# L#
Addressed object Memory area (M, I, Q, L, DB, VL) TIMER, COUNTER, FB, FC, DB, POINTER, ANY
Bytes 0-1 contain Primitive type code (0x01..0x13) Parameter type code (0x17..0x1D)
Bytes 4-5 contain DB number (0 if not DB) Instance/block number
Byte 6 contains Memory area code (0x80..0x85) Sub-code (typically 0x00 for COUNTER/TIMER)
Bytes 7-9 contain Byte/bit address 0 (unused)
Repetition factor Number of elements of stated data type Number of consecutive parameter objects
Example P#M10.0 BYTE 10 L#1 COUNTER 10

10. Verifying an ANY Image at Runtime

When you write a generic utility FB (block copy, search, log) that accepts an ANY, you must validate the image before dereferencing it. A typical validation sequence:

  1. Read the 10-byte image into a TEMP ARRAY[0..9] OF BYTE.
  2. Check bytes 0-1: if the type code is < 0x17 the caller passed a data type; if ≥ 0x17 the caller passed a parameter type. Branch accordingly.
  3. For data type ANY: verify the area code in byte 6 is one of the supported values (0x80..0x85). Verify the byte/bit address in bytes 7-9 is within the CPU's address range (e.g. M 0..8191 bytes on an S7-315-2 PN/DP).
  4. For parameter type ANY: verify the type code is one you support, and verify the number in bytes 4-5 is within range (e.g. counter number ≤ 255 on S7-300).
  5. Return an error code (e.g. ENO := FALSE with a status word) if validation fails. Never dereference an out-of-range address – on S7-300/400 a bad pointer causes a SF (System Fault) and the CPU may go STOP if OB121/OB122 are not loaded.
Safety: Always download OB121 (programming error) and OB122 (I/O access error) to the CPU. Without them, a single bad ANY pointer will STOP the PLC. With them, the OB can log the error and either continue or signal a controlled shutdown.

11. Common Mistakes and Field-Notes

  • Confusing the two forms. P#1 COUNTER 1 is a syntax error. The editor highlights it as "Invalid data type for ANY constant". Use L#1 COUNTER 1.
  • Forgetting the repetition factor. The syntax requires the repetition factor as the last token, even when it is 1. Omitting it gives "Incomplete ANY specification". L#1 COUNTER is invalid; L#1 COUNTER 1 is correct.
  • Assuming repetition factor 1 means "pointer to one object". It does, but the runtime convention in most standard blocks is to consume exactly the first object. If you write a custom FB that needs the full range, the FB must loop over the repetition count itself – STEP 7 does not auto-iterate.
  • Mixing P# and L# in a multi-instance call. A single ANY parameter can hold only one type. If the caller passes L#5 TIMER 1 and your FB expects a data area, the bytes 6-9 will be zero and your area-dispatch logic will fall into the "unspecified" branch. Validate the type code first.
  • Porting between S7-300/400 and S7-1500. S7-1500 with TIA Portal deprecates the 10-byte ANY in favor of the VARIANT pointer (12 bytes with additional flags). The legacy L# form is still accepted for compatibility but generates a compiler warning on newer firmware. Use VARIANT for new development on S7-1500/1200.
  • Reading the image with the wrong endian assumption. On S7-300/400, bytes 0-1 are the high byte / low byte of the type code as it appears in the source. When you L W [AR2,P#0.0] you get the type code as a 16-bit word directly – no swap needed.

12. Summary

The L#1 COUNTER 10 syntax is the textual form of an ANY pointer whose bytes 0-1 contain the parameter type code for COUNTER (0x1C) and whose bytes 4-5 contain the starting counter number (1). The trailing 10 is the repetition factor and refers to the count of consecutive counters starting at C1. At runtime the CPU sees only the 10 raw bytes; the L#/P# prefix exists solely for the editor and the programmer's readability. Use the P# form when the ANY describes a memory area (DB, M, I, Q, L) and the L# form when it describes a TIMER, COUNTER, or block. When consuming the pointer in a generic FB, always read the 10-byte image and branch on the type code in bytes 0-1 before touching bytes 4-9.

What does the prefix L# mean in a STEP 7 ANY pointer?

The L# prefix marks the pointer as a parameter-type ANY (as opposed to a data-type ANY marked by P#). It tells the editor to encode bytes 0-1 with a parameter type code (TIMER, COUNTER, BLOCK_FB, BLOCK_FC, BLOCK_DB, POINTER, or ANY) and to put the instance or block number into bytes 4-5. The L stands for the parameter-type family and has no relation to the L (local) memory area.

How is L#1 COUNTER 10 different from L#1 COUNTER 1?

L#1 COUNTER 1 is the canonical form and stores a repetition factor of 1, addressing only counter C1. L#1 COUNTER 10 is accepted by the editor and stores a repetition factor of 10, which most FBs/FCs interpret as a range C1..C10. The block being called must loop over the range itself – STEP 7 does not iterate automatically. For a single counter reset, always use L#1 COUNTER 1 to avoid surprises.

Why does my ANY parameter reject P#COUNTER 1 in the editor?

P# is reserved for data-type ANY pointers that describe a memory area. Counters, timers, and blocks are not memory areas, so P#COUNTER is invalid. Use the L# form: L#1 COUNTER 1. The two prefixes select two different encodings of the same 10-byte image; the editor enforces which form is valid for which interface type.

Can I mix L# and P# in the same ANY call?

No. A single ANY image holds exactly one type: either a data-type code (≤0x13) or a parameter-type code (≥0x17) in bytes 0-1. If you need to pass both kinds of data to one FB, declare two separate ANY inputs (one for the data area, one for the parameter type) and have the caller fill each with the appropriate form.

What replaces the L# ANY pointer on S7-1500 with TIA Portal?

S7-1500/1200 firmware V4.0 and later deprecates the 10-byte ANY in favor of the VARIANT pointer (12 bytes) which supports a wider set of types including UDTs and PLC data types. The legacy L# form is still accepted for compatibility with old libraries but generates a compiler warning. New code on S7-1500 should use VARIANT and the Type / VariantGet / VariantPut SCL instructions instead of manually parsing the 10-byte image.

Back to blog