Troubleshooting AG_LSEND Status 16#8F24: SCL ANY Pointer Fix

David Krause13 min read
HMI ProgrammingSiemensTroubleshooting
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

Problem Summary

When calling AG_LSEND from a Siemens S7-300 or S7-400 controller programmed in SCL, the function block returns STATUS = 16#8F24 on the rising edge of ACT. The block reports ERROR = TRUE and DONE = FALSE, the send job is rejected, and no data crosses the PROFIBUS CP interface. The same code compiled for global-DB based telegrams works on identical hardware, so the failure is local to the ANY pointer construction and parameter wiring inside the SCL FB.

The 16#8F24 status is one of several error codes in the 8F2x cluster that the SIMATIC NET AG_SEND / AG_LSEND blocks emit when the local CP rejects the call because of an internal data description error before the job is dispatched onto PROFIBUS. In most field cases, the cause is one of the following:

  1. The LEN input does not match the byte count encoded in the ANY pointer.
  2. The ANY pointer data type (TYP) is incompatible with the data being sent (for example BOOL, INT, or STRING packed into a BYTE-typed ANY).
  3. The memory area byte specifies an Instance DB (B#16#85) but the pointer was built against a global DB number, or vice versa.
  4. The pointer references DBX offset space that exceeds the actual instance DB length.
  5. The STRING header (max length, current length) has not been initialized before the call.
Field rule: Treat every 8F2x status from AG_SEND / AG_LSEND as a local pointer/parameter error. The CP itself is not at fault. Do not replace CP hardware until the ANY pointer has been validated against the table in this guide.

AG_SEND and AG_LSEND Function Block Overview

AG_SEND and AG_LSEND are the S7-300 / S7-400 send blocks for open PROFIBUS communication services via a SIMATIC NET CP (for example CP 443-5, CP 343-5, CP 342-5). They are documented in the SIMATIC NET programming and operating manual and the corresponding online help of STEP 7 V5.x / SIMATIC Manager.

Reference: AG_SEND / AG_LSEND (PROFIBUS) for S7-300 / S7-400 — SIMATIC NET documentation portal.

Function block interface (STEP 7 Standard Library > SIMATIC_NET_CP > CP 300/400 > AG_SEND / AG_LSEND):

Parameter Direction Type Meaning
ACT IN BOOL Trigger bit, edge-triggered
ID IN INT Connection ID from NetPro (0..15 on S7-300, 0..65535 on S7-400)
LADDR IN WORD Logical base address of the CP (for example W#16#0100)
SEND IN ANY Pointer to the data area to be sent
LEN IN INT Length of the send area in bytes
DONE OUT BOOL 1 = job completed without error
ERROR OUT BOOL 1 = job completed with error
STATUS OUT WORD Error / status word

AG_LSEND is the S7-400 variant. It accepts the same data area description as AG_SEND; the difference is the call signature and the internal data-block handling for the CP's job buffer. From a parameter-correctness perspective the rules below apply equally to both blocks.

ANY Pointer Structure on S7-300 / S7-400

The ANY pointer is a 10-byte descriptor. In SCL on S7-300 / S7-400 you can build it by direct assignment to a UDT or by using the P# literal syntax. Building the pointer with named members (the approach in the failing SCL code) is also valid because the underlying bit layout is identical to what the BLKMOV and communication blocks expect.

Byte offset Field name Type Value in failing code Meaning
0 ID (SyntaxID) BYTE B#16#10 10h = ANY pointer for S7-300/400
1 TYP (DataType) BYTE B#16#02 02h = BYTE (byte-wise access)
2-3 NUM (Count) WORD DBLengthSend Repeat factor in units of the data type
4-5 DBNR WORD DBnumber DB number (0 if not a DB)
6 MemoryArea BYTE B#16#85 85h = Instance DB
7-8 ByteAddress DWORD 0 / 0 Byte offset inside the area (24-bit)
9 reserved BYTE Internally managed

Memory area codes (byte 6) for the S7-300 / S7-400 ANY pointer:

Code Area
B#16#80 Reserved
B#16#81 Process inputs (PE / I)
B#16#82 Process outputs (PA / Q)
B#16#83 Bit memories (M / MK)
B#16#84 Global DB
B#16#85 Instance DB
B#16#86 Local data of calling FB / FC (L stack)

Data type codes (byte 1) for the S7-300 / S7-400 ANY pointer:

Code Data type Unit
B#16#00 NULL (0 length)
B#16#01 BOOL 1 bit
B#16#02 BYTE 1 byte
B#16#03 CHAR 1 byte
B#16#04 WORD 2 bytes
B#16#05 INT 2 bytes
B#16#06 DWORD 4 bytes
B#16#07 DINT 4 bytes
B#16#09 REAL 4 bytes
B#16#0A TIME / TOD 4 bytes
B#16#0C REAL (legacy)
B#16#0E DATE_AND_TIME / DT 8 bytes
B#16#13 STRING 1 byte (header = 2 bytes + chars)
B#16#17 WCHAR (S7-400 only) 2 bytes

See the SIMATIC S7-300 / S7-400 system and standard functions reference manual for the canonical table. The values above are reproduced from the Siemens S7-300/400 programming manuals (system data types section).

Root Cause Analysis of 16#8F24

Status 16#8F24 from AG_SEND / AG_LSEND belongs to the 0F1x / 8F1x / 8F2x error class. Specifically it indicates that the CP's internal pointer validation on the SZL or data description buffer has rejected the ANY pointer. The most common root causes, in order of frequency, are:

  1. LEN vs ANY length mismatch. The LEN input tells the CP how many bytes of source data to read. The CP cross-checks this against the byte count encoded in the NUM field of the ANY pointer. If the ANY is typed BYTE (TYP = 02h) and NUM = N, the byte count the CP expects is N. If LEN <> N, the CP rejects the call with 8F22 or 8F24 depending on the direction of the mismatch.
  2. Wrong memory area code. Using 85h (Instance DB) when the data lives in a global DB — or vice versa — produces a pointer that the CP cannot resolve. The CP's SZL handler returns 8F24 when the DB number and area code are inconsistent.
  3. Offset beyond DB length. The 24-bit byte offset plus the implied length must lie entirely inside the target DB. If the offset is past the end, the CP returns 8F24.
  4. Type 02h for typed aggregates. When the FB collects BOOL, INT, and STRING into a contiguous byte area, the cleanest approach is to map the structure into a global or instance DB and point the ANY at the whole structure with TYP = B#16#02 and NUM = total byte length. This is what the failing code already does, so the type itself is not the bug; the bug is downstream in the LEN reconciliation or in the DB length.
  5. STRING not initialized. If the FB reads a STRING from the instance DB and the header (max length, current length) is still 0 at the time of the call, the CP will see an inconsistent area. Initialize the string (for example MyString := 'INIT';) before triggering ACT.

Step-by-Step Solution in SCL

Apply the following corrections. They are derived from the SIMATIC NET manual, the S7-300 / S7-400 system reference, and the SCL programming reference.

Step 1 — Reconcile LEN with the ANY pointer.

Use the same source expression for both the NUM field of the ANY pointer and the LEN input of AG_LSEND. Never derive one from a hard-coded literal and the other from a sized input.

// Build pointer
pAnySend := P#DB.DBX0.0 BYTE ;

// Call
AG_LSEND(ACT  := bSendTrigger,
         ID   := iConnID,
         LADDR:= wCPLADDR,
         SEND := pAnySend,
         LEN  := nTotalBytes,  // must equal BYTE count in pAnySend
         DONE => bDone,
         ERROR=> bError,
         STATUS=> wStatus);

Step 2 — Verify the DB number at runtime.

In a PCS 7 environment the instance DB number is assigned by CFC / the compiler and is not known at code-write time. Read it with:

// In a static or TEMP variable of the FB
wInstDB := WORD_TO_INT(BLOCK_DB_TO_WORD(THIS));  // instance DB number of THIS FB

Or, in an FB that needs a separate global data DB:

// In the DB's INIT section, or in startup OB100
iMyGlobalDB := 200;  // hard-code the global DB number assigned in NetPro / Symbol Table

Step 3 — Choose the correct memory area code.

  • Instance DB (the FB's own background DB) → B#16#85.
  • Global DB (created in the project, numbered by the CPU) → B#16#84.

Step 4 — Initialize STRING fields before sending.

// In OB100 / once-only initialisation
IF sMyString = '' THEN
    sMyString := 'INIT';
END_IF;

Step 5 — Apply a single source of truth for the length.

FUNCTION_BLOCK FB_AgSendAny
VAR
    pAnySend   : ANY;
    wStatus    : WORD;
    bDone      : BOOL;
    bError     : BOOL;
    bTrigger   : BOOL;
    iConnID    : INT := 1;
    wCPLADDR   : WORD := W#16#0100;
    iInstDB    : INT;
    nLen       : INT;  // single source of truth
END_VAR
BEGIN
    // Length of the telegram, in bytes
    nLen := 32;
    // 0: syntax ID = ANY (S7-300/400)
    pAnySend := P#DB.DBX0.0 BYTE nLen;
    // Trigger send on rising edge of bTrigger
    AG_LSEND(ACT   := bTrigger,
             ID    := iConnID,
             LADDR := wCPLADDR,
             SEND  := pAnySend,
             LEN   := nLen,
             DONE  => bDone,
             ERROR => bError,
             STATUS=> wStatus);
END_FUNCTION_BLOCK

PCS 7 Specific Considerations

PCS 7 (SIMATIC PCS 7 V9.x and earlier) wraps every chart block in a CFC. The SCL FB is typically called from a CFC action (chart-in-chart) or from a runtime block. PCS 7 imposes three additional constraints that do not exist in a "plain" STEP 7 project:

  1. Task list / OB assignment. AG_LSEND must be called in an OB that is in the project task list and is not exclusive to PCS 7 chart runtime. In PCS 7 the recommended OBs are OB1, OB35, or OB82; never OB100 only, because the send job is then only issued in startup. Use the Drivers library in PCS 7 (APL block family — SEND / RECV blocks) for new projects. They call AG_LSEND internally with the right task.
  2. Global DB allocation range. PCS 7 reserves a number range for instance DBs. To allocate a global DB for AG_LSEND data, open the CFC editor menu Options > Customize > Compile / Download and define a number range outside the instance-DB area.
  3. Operator-controlled visibility. STATUS from AG_LSEND should be exposed via the block's standard I/O and not hidden in a separate global, so that the OS can pick it up for alarm/operator logging.

LEN vs ANY Length Reconciliation Matrix

ANY.TYP ANY.NUM unit LEN expects Typical bug
B#16#02 (BYTE) 1 byte byte count LEN = word count when source is BYTE
B#16#05 (INT) 2 bytes 2 * NUM NUM = 1, LEN = 1 (should be 2)
B#16#01 (BOOL) 1 bit 0 or bit-precise BOOL ANY via AG_LSEND is not allowed; use BYTE
B#16#13 (STRING) 1 byte max+2 of the string LEN = current length (should be max length + 2 header bytes)
B#16#04 (WORD) 2 bytes 2 * NUM NUM = N, LEN = N (should be 2 * N)
B#16#06 (DWORD) 4 bytes 4 * NUM NUM = N, LEN = N (should be 4 * N)

The BYTE case is the cleanest and is the recommended path for aggregated telegrams that mix BOOL/INT/STRING. Convert the typed structure into a global or instance DB of BYTE length, and send the whole DB with one call.

Verification and Diagnostic Procedure

  1. Open the SCL FB and add a temporary wDiag := wStatus line in the call site. Watch wDiag in the online SCL debugger (STEP 7 V5.x: Debug > Monitor; TIA Portal: Online > Monitor).
  2. In the online DB, set the breakpoint on the line before AG_LSEND and inspect pAnySend byte-for-byte. Verify:
    • Byte 0 = 0x10
    • Byte 1 = 0x02 (or the correct type)
    • Bytes 2-3 = NUM = LEN
    • Bytes 4-5 = DB number that exists on the CPU (cross-check in Accessible Nodes)
    • Byte 6 = 0x85 (Instance DB) or 0x84 (Global DB)
    • Bytes 7-8 = offset inside the DB
  3. Confirm the DB is loaded on the CPU with the configured length. Use the menu PLC > Information > Module Information > Memory to see the DB list and lengths.
  4. Trigger the call and capture STATUS. If it is still 0x8F24, repeat the inspection; if it changes to 0x0000 or 0x7000, the fix is good.
  5. Cross-check the STATUS decoder list in the SIMATIC NET manual. Status 16#0000 = job completed without error; 16#7F00 = job still in progress; 16#8F22 = source area error (LEN vs ANY mismatch in the other direction); 16#8F24 = pointer description rejected by the CP.
Tip: The status 16#8F22 and 16#8F24 differ in detail, but for field work the remediation is the same: align the LEN with the ANY pointer.

Alternative Implementations

For projects that can move off classic S7-300 / S7-400, two alternatives eliminate this class of error:

  • Use PUT / GET on S7-400 for S7-internal peer-to-peer: PUT and GET take the source / destination as a WORD count and a DB number, not a full ANY pointer. Reference: S7-400 system and standard functions reference manual.
  • Use BSEND / BRCV on S7-400 for variable-length data: BSEND / BRCV pair send up to 64 KB per call. They have their own STATUS codes (notably 0x8F22, 0x8F23, 0x8F24), but the rules for length encoding are simpler because BSEND does not require the LEN / ANY reconciliation — the LEN parameter is the actual byte count of the SDT/DB to be sent.
  • Move to S7-1500 with TIA Portal. The S7-1500 family has PUT / GET with multi-element SDT pointers and replaces AG_SEND / AG_LSEND with TSEND / TRCV blocks that accept the new VARIANT pointer. The error codes differ (typically 0x80xx series) but the underlying LEN / pointer mismatches still produce dedicated error codes — the discipline of explicit length reconciliation remains.

Edge Cases and Field Caveats

  • Multi-instance OBs. When the FB is instantiated in a multi-instance OB and called from OB1 of a standard S7-300 (CPU 314/315/317), the LADDR must be the CP's logical base address set in HW Config (not the diagnostic address).
  • Optimized / symbolic access. S7-300 and S7-400 do not have "optimized block" access (that is S7-1500 / S7-1200). On S7-300/400, DBs are always byte-addressed and the ANY pointer's offset is always a byte offset. Mixing with SCL symbolic offsets is not an issue because the compiler resolves them to byte offsets at compile time.
  • ANY pointer aliasing inside the same FB. If the SCL FB is re-entered (for example, called from a cyclic interrupt OB and a time-of-day interrupt OB), the local ANY variable is on the L stack and is therefore valid for the duration of the call. AG_LSEND itself copies the data into the CP's buffer on the rising edge of ACT, so a subsequent re-entry does not corrupt the prior job.
  • STRING with maximum length 254. In STEP 7, a STRING has a 2-byte header (max length, current length) followed by up to 254 characters. If the ANY pointer's TYP is 0x13 and the LEN is the current string length, the CP will still read the header. The safe choice is TYP = 0x02 (BYTE) and LEN = max string length + 2.

FAQ

What does AG_LSEND status 16#8F24 mean?

It means the SIMATIC NET CP has rejected the call because the ANY pointer is internally inconsistent: the LEN parameter, the TYP/NUM fields, the DB number, the area code, or the byte offset do not describe a valid source area on the local CPU. Re-check the pointer layout and the LEN/ANY length match first; replace the CP only after the pointer is validated.

Should I use B#16#84 or B#16#85 for the memory area byte when sending the FB's own instance DB?

Use B#16#85 for an Instance DB (the FB's own background DB) and B#16#84 for a Global DB. Mixing them produces an inconsistent pointer and a status in the 8F2x range, typically 16#8F24.

Why does my AG_LSEND call fail with 16#8F24 only when I add a STRING to the telegram?

Most often, the STRING's header (max length, current length) is uninitialized, or the LEN is set to the current character count instead of max length + 2. Initialize the string once in OB100 / startup and use TYP = B#16#02 (BYTE) with LEN = max length + 2 to send it.

Can AG_LSEND be called from a PCS 7 CFC chart?

Yes, but in PCS 7 V9.x the recommended path is to use the APL drivers (SEND / RECV from the PCS 7 Advanced Process Library). They wrap AG_SEND/AG_LSEND, run in the right OB (typically OB1 or OB35), and expose STATUS to the OS for operator visibility.

Is AG_LSEND available on S7-1500?

No. AG_SEND and AG_LSEND are S7-300 / S7-400 PROFIBUS CP blocks documented in the SIMATIC NET library. On S7-1500, use the TSEND / TRCV (TCP), TUSEND / TURCV (UDP), or PUT / GET blocks from the standard library. The pointer concept is similar (VARIANT instead of ANY), and a mismatched length still produces a dedicated error code (typically 0x80xx series).

Back to blog