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:
- The
LENinput does not match the byte count encoded in theANYpointer. - The
ANYpointer data type (TYP) is incompatible with the data being sent (for exampleBOOL,INT, orSTRINGpacked into aBYTE-typed ANY). - The memory area byte specifies an Instance DB (B#16#85) but the pointer was built against a global DB number, or vice versa.
- The pointer references DBX offset space that exceeds the actual instance DB length.
- The STRING header (max length, current length) has not been initialized before the call.
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:
-
LEN vs ANY length mismatch. The
LENinput tells the CP how many bytes of source data to read. The CP cross-checks this against the byte count encoded in theNUMfield of theANYpointer. If theANYis typedBYTE(TYP = 02h) andNUM = N, the byte count the CP expects isN. IfLEN <> N, the CP rejects the call with8F22or8F24depending on the direction of the mismatch. -
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 returns8F24when the DB number and area code are inconsistent. -
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. -
Type 02h for typed aggregates. When the FB collects
BOOL,INT, andSTRINGinto a contiguous byte area, the cleanest approach is to map the structure into a global or instance DB and point theANYat the whole structure withTYP = B#16#02andNUM= total byte length. This is what the failing code already does, so the type itself is not the bug; the bug is downstream in theLENreconciliation or in the DB length. -
STRING not initialized. If the FB reads a
STRINGfrom the instance DB and the header (max length, current length) is still0at the time of the call, the CP will see an inconsistent area. Initialize the string (for exampleMyString := 'INIT';) before triggeringACT.
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:
-
Task list / OB assignment.
AG_LSENDmust 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 callAG_LSENDinternally with the right task. - 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.
-
Operator-controlled visibility.
STATUSfromAG_LSENDshould 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
- Open the SCL FB and add a temporary
wDiag := wStatusline in the call site. WatchwDiagin the online SCL debugger (STEP 7 V5.x: Debug > Monitor; TIA Portal: Online > Monitor). - In the online DB, set the breakpoint on the line before
AG_LSENDand inspectpAnySendbyte-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
- 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.
- 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. - Cross-check the
STATUSdecoder list in the SIMATIC NET manual. Status16#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.
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:
PUTandGETtake the source / destination as aWORDcount 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/BRCVpair send up to 64 KB per call. They have their ownSTATUScodes (notably 0x8F22, 0x8F23, 0x8F24), but the rules for length encoding are simpler becauseBSENDdoes not require the LEN / ANY reconciliation — theLENparameter 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/GETwith multi-element SDT pointers and replacesAG_SEND/AG_LSENDwith TSEND / TRCV blocks that accept the newVARIANTpointer. The error codes differ (typically 0x80xx series) but the underlyingLEN/ 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
LADDRmust 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).