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.
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 ofDBx.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. TheLhistorically 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 ofTIMER,COUNTER,BLOCK_FB,BLOCK_FC,BLOCK_DB,POINTER,ANY. -
RepetitionFactor– count of consecutive parameter objects starting atNumber. For example,L#1 COUNTER 10means C1, C2, C3, …, C10.
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]
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:
- Read the 10-byte image into a TEMP
ARRAY[0..9] OF BYTE. - 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.
- 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).
- 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).
- Return an error code (e.g.
ENO := FALSEwith 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.
11. Common Mistakes and Field-Notes
-
Confusing the two forms.
P#1 COUNTER 1is a syntax error. The editor highlights it as "Invalid data type for ANY constant". UseL#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 COUNTERis invalid;L#1 COUNTER 1is 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 1and 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
VARIANTpointer (12 bytes with additional flags). The legacyL#form is still accepted for compatibility but generates a compiler warning on newer firmware. UseVARIANTfor 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.