Problem Overview
When integrating a PROFINET stepper controller or any third-party PROFINET IO device on a Siemens S7-1500 CPU (for example a 1511F), the cyclic process image typically exposes control and status words as a contiguous block of input double words (%IDxxx) and output double words (%QDxxx). The engineering challenge arises when you want to encapsulate access to that block inside a reusable Function Block (FB): a single FB instance must be able to work with different starting offsets in the process image so that the same code can drive several physical stepper controllers.
The naive approach - hard-coding %ID400 or %ID500 inside the FB - forces you to create a different FB for every device instance. The proper engineering approach is to pass the start address as a parameter to the FB and let the FB read the consecutive double words itself. This reference covers five production-grade methods to do exactly that on the S7-1500 platform, with full SCL code, addressing rules, error handling, and commissioning diagnostics.
%ID400 means "double word starting at byte offset 400" - occupying bytes 400, 401, 402, and 403. Incrementing the address by 4 reaches the next double word for %ID404. Any code you write must respect that 4-byte alignment; the start offset must always be on a 4-byte boundary when the block reads double words, otherwise PEEK straddles two DWORDs and yields garbage.Prerequisites and System Architecture
Before you write the FB, the following conditions must be met in the TIA Portal project:
- CPU: Any S7-1500 (typical: 1511F, 1515F, 1518F-4 PN/DP). Firmware V2.5 or higher is recommended for the Variant and PEEK_DINT instructions used in this article. The 1511F part number is 6ES7511-1FK02-0AB0 (F-version with firmware V2.9 onwards).
-
Engineering: TIA Portal V16 minimum, V17 or V18 recommended for enhanced
MOVE_BLK_VARIANTdiagnostics. - Stepper controller: PROFINET IO device with installed GSDML file in TIA Portal; cyclic process image provides a contiguous I/Q area of at least 64 bytes input and 64 bytes output (16 DWORDs per direction is a typical 8-axis register map; 128 bytes is required for a 16-axis register map).
- Programming language: SCL (Structured Control Language) for the FB. The techniques in this article use SCL pointer and Variant semantics that are cumbersome or impossible in LAD/FBD.
- Process image assignment: The slave's I/O slots must be in the process image of the CPU (PIP 0 by default, or a dedicated PIP if the slot is assigned to a high-priority OB).
- Device name and IP: The PROFINET device name must be assigned to the stepper controller (topology editor or PRONETA) and the IP address must match the project's configuration.
For CPU 1511F reference, consult the SIMATIC S7-1500 Programmable Controller System Manual (09/2019). For the instruction set, open TIA Portal Help and search "PEEK / POKE" under Basic instructions > Miscellaneous. For PROFINET design rules, see the PROFINET specification (IEC 61784-2) and the SIMATIC S7-1500 / ET 200MP Automation System manual set.
Addressing Background: %ID, %QD, and PROFINET I/O
The S7-1500 absolute addressing is byte-oriented, with bit 0 in the least significant bit of the byte. The relevant data type prefixes are:
| Prefix | Data Width | Byte Range Occupied | Typical PROFINET Use |
|---|---|---|---|
%IB<n> |
1 byte | n | Single status flag, error code byte |
%IW<n> |
2 bytes | n, n+1 | 16-bit status word, scaled encoder |
%ID<n> |
4 bytes | n, n+1, n+2, n+3 | 32-bit position, velocity, double-word status |
%QB<n>, %QW<n>, %QD<n>
|
1 / 2 / 4 bytes | n ... n+3 | Control words sent to the stepper |
The starting address is always a byte offset. The expression %ID400 does not refer to a slot number; it refers to byte 400 of the process image inputs. If your device view shows the stepper's input range starting at IB512 in the I address list, then the first input double word is %ID512, occupying bytes 512-515.
For 16 consecutive 32-bit values you need 64 contiguous bytes (n to n+63). The starting address parameter that you pass into your FB is therefore a byte offset - it is a UDINT, not a slot index. The FB increments that offset by 4 for every read.
Method Comparison: When to Use Each Approach
| Method | Start Address | Performance | Flexibility | Best For |
|---|---|---|---|---|
| 1 - UDT with AT | Compile-time constant | Fastest (single load) | Low (one FB per address) | Single device, fixed slot |
| 2 - PEEK_DINT loop | Runtime variable (UDINT) | 1 extra indirection per read | High (any address, any count) | Generic multi-instance FB |
| 3 - MOVE_BLK_VARIANT | Compile-time AT, runtime Variant | Slowest, but type-safe | High (any structured source) | Reusable libraries |
| 4 - ARRAY indirect index | Compile-time constant | Fast (base + index * stride) | Medium (index varies, base fixed) | Multi-axis motion, indexed access |
| 5 - RDREC / WRREC | By PROFINET record index | Asynchronous, 1-3 PN cycles | Highest (parameters not in PI) | Non-real-time parameters |
Method 1: UDT Symbolic Mapping (Fixed Start Address)
If the start address of the stepper's process image is fixed across all instances of your FB, the cleanest solution is to use a User-Defined Type (UDT) with the AT view construct. The UDT defines the structure of the 16 double words; the AT construct overlays the UDT onto the absolute process image area.
Step 1 - Define the UDT. In TIA Portal, right-click PLC data types > Add new data type and name it "UDT_Stepper16Axis". Create 16 members of type DINT with descriptive names (axis1Position, axis1Status, axis2Position, ...). Each member occupies 4 bytes, so the UDT is exactly 64 bytes long.
Step 2 - Declare the AT view in the FB. In the FB's static or temp section add:
VAR
// Overlays the UDT onto the input process image starting at %ID400
stInput AT %ID400 : "UDT_Stepper16Axis";
END_VAR
Step 3 - Access members symbolically inside the FB:
// stInput.axis1Position = %ID400
// stInput.axis1Status = %ID404
// stInput.axis2Position = %ID408
// stInput.axis2Status = %ID412
// ...
// stInput.axis8Status = %ID500
Step 4 - For output doubles, declare a second UDT "UDT_Stepper16AxisControl" with the matching 16 output members and overlay it on the Q area:
VAR
stControl AT %QD400 : "UDT_Stepper16AxisControl";
END_VAR
// Usage:
// stControl.axis1Command := 16#0001; // Enable
// stControl.axis1Setpoint := axis1TargetPosition;
Why this works: The TIA Portal compiler resolves the absolute address at compile time. The optimizer keeps the access symbolic, so reading stInput.axis1Position is identical in machine code to reading %ID400. This is the fastest runtime path on the S7-1500.
AT construct in SCL requires a constant absolute address. You cannot write AT %ID{startAddr} or pass a variable into it. If your start address must be runtime-variable (different FB instances point to different stepper controllers at different addresses), you must use one of the dynamic methods (PEEK, Variant, ARRAY indexing) below.Method 2: SCL PEEK_DINT with a Computed Byte Offset
The PEEK_DINT instruction (and its PEEK / PEEK_WORD / PEEK_BOOL siblings) reads a value of a given width from any memory area. The area is selected by an integer constant, and the byte offset is calculated at runtime. This is the most common technique for "give me a starting address and a count" applications.
Area constants for the S7-1500 PEEK/POKE instructions:
| Area Constant | Value (decimal) | Value (hex) | Memory Area |
|---|---|---|---|
| PE (process image inputs) | 16 | 16#10 | I / E |
| PA (process image outputs) | 17 | 16#11 | Q / A |
| M (bit memory) | 18 | 16#12 | M |
| DB (data block) | 19 | 16#13 | DB |
Reference: TIA Portal Help > Basic instructions > Miscellaneous > PEEK / POKE. Note that the legacy 16#81-16#84 area constants used on S7-300/400 are not valid on the S7-1500.
Step 1 - FB interface declaration:
FUNCTION_BLOCK "FB_ReadMultiID"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
VAR_INPUT
iStartByte : UDINT; // Start address, e.g. 400
iCount : UINT; // Number of DINT values (1..16)
END_VAR
VAR_OUTPUT
arrValues : ARRAY[0..15] OF DINT;
bError : BOOL;
iStatus : INT; // 0 = OK, 1 = count out of range, 2 = alignment
END_VAR
VAR
iIdx : UINT;
iOffset : DINT;
END_VAR
BEGIN
bError := FALSE;
iStatus := 0;
// Range check
IF iCount = 0 OR iCount > 16 THEN
bError := TRUE;
iStatus := 1; // 1 = invalid count
RETURN;
END_IF;
// Alignment check: DINT must be on a 4-byte boundary
IF (iStartByte MOD 4) <> 0 THEN
bError := TRUE;
iStatus := 2; // 2 = misaligned start
RETURN;
END_IF;
FOR iIdx := 0 TO iCount - 1 DO
// byte offset = start + index * 4 (DINT is 4 bytes)
iOffset := UDINT_TO_DINT(iStartByte) + UINT_TO_DINT(iIdx) * 4;
// PEEK_DINT reads 4 bytes (1 DINT) from the I area
arrValues[iIdx] := PEEK_DINT(area := 16#10,
dbNumber := 0,
byteOffset := iOffset);
END_FOR;
END_FUNCTION_BLOCK
Step 2 - Call the FB from OB1 with a starting address constant:
// Call instance - one per physical stepper controller
"DB_Stepper1"(iStartByte := 400, iCount := 16,
arrValues => "DB_Application".axisStatus,
bError => "DB_Application".stepper1Err,
iStatus => "DB_Application".stepper1Status);
// Second physical controller, different base address:
"DB_Stepper2"(iStartByte := 600, iCount := 16,
arrValues => "DB_Application".axisStatus2,
bError => "DB_Application".stepper2Err,
iStatus => "DB_Application".stepper2Status);
Why this works: PEEK_DINT generates an absolute byte read at runtime. The FB never references a hard-coded %ID400, so a single FB source compiles to 16 instances with 16 different starting addresses. The cost is one extra indirection per access compared to Method 1.
Symmetric write example using POKE_DINT:
FUNCTION_BLOCK "FB_WriteMultiQD"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
VAR_INPUT
iStartByte : UDINT;
iCount : UINT;
arrValues : ARRAY[0..15] OF DINT;
END_VAR
VAR_OUTPUT
bError : BOOL;
END_VAR
VAR
iIdx : UINT;
END_VAR
BEGIN
bError := FALSE;
IF (iStartByte MOD 4) <> 0 OR iCount = 0 OR iCount > 16 THEN
bError := TRUE;
RETURN;
END_IF;
FOR iIdx := 0 TO iCount - 1 DO
POKE_DINT(area := 16#11, // 16#11 = Q area
dbNumber := 0,
byteOffset := UDINT_TO_DINT(iStartByte) + UINT_TO_DINT(iIdx) * 4,
value := arrValues[iIdx]);
END_FOR;
END_FUNCTION_BLOCK
Method 3: Variant-Based Symbolic Access with MOVE_BLK_VARIANT
S7-1500 firmware V2.0+ supports the VARIANT data type and the MOVE_BLK_VARIANT instruction. The Variant can carry an ANY-style pointer to any data source. Combined with the AT construct, you can declare an AT view of a symbolic tag that points to the start of the process image block, and then pass that view into the FB as a Variant.
This method is most useful when the start address is fixed per instance (so you can declare a fixed AT) but you want to read 16 symbolic values through a generic interface - for example in a library FB that has to consume any structured input from the caller.
Step 1 - Declare a UDT and an AT view in the instance DB:
// In the instance DB (e.g., IDB_Stepper1):
DATA_BLOCK "IDB_Stepper1"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
STRUCT
varDummy : WORD; // alignment padding
stInput AT %ID400 : "UDT_Stepper16Axis"; // process image slice
stControl AT %QD400 : "UDT_Stepper16AxisControl";
END_STRUCT;
END_DATA_BLOCK
Step 2 - Inside the FB, accept a Variant input and use MOVE_BLK_VARIANT:
FUNCTION_BLOCK "FB_Consumer"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
VAR_INPUT
vSource : VARIANT; // ANY pointer to source
iCount : UINT; // elements to read
END_VAR
VAR_OUTPUT
arrDest : ARRAY[0..15] OF DINT;
bError : BOOL;
iStatus : INT;
END_VAR
VAR_TEMP
iRet : INT;
END_VAR
BEGIN
bError := FALSE;
iStatus := 0;
iRet := MOVE_BLK_VARIANT(SRC := vSource,
COUNT := iCount,
DEST := arrDest);
IF iRet <> 0 THEN
bError := TRUE;
iStatus := iRet; // non-zero = error code from the instruction
END_IF;
END_FUNCTION_BLOCK
Step 3 - Call the consumer FB with the instance-DB tag as the Variant source:
"FB_Consumer_DB"(vSource := "IDB_Stepper1".stInput,
iCount := 16,
arrDest => "DB_Application".axisStatus);
Why this works: MOVE_BLK_VARIANT performs a block copy of the source Variant into the destination array, with runtime type checking. The source can be the UDT, an array, or any other structured tag. The instruction is well suited for libraries (LAD/FBD/ST) that must accept arbitrary inputs from the caller.
Method 4: ARRAY Indirect Indexing on a Process Image Slice
If you want the same FB to read 16 double words, but the start address is known at compile time, you can use the AT construct to overlay a fixed-size ARRAY[0..N] OF DINT on the process image. The FB then reads each element by index.
FUNCTION_BLOCK "FB_IndexedArray"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
VAR_INPUT
iIndex : UINT; // 0..15 - which axis to read
END_VAR
VAR_OUTPUT
diPosition : DINT;
diStatus : DINT;
bError : BOOL;
END_VAR
VAR
arrInput AT %ID400 : ARRAY[0..31] OF DINT; // 32 DINTs = 128 bytes
END_VAR
BEGIN
bError := FALSE;
IF iIndex > 15 THEN
bError := TRUE;
diPosition := 0;
diStatus := 0;
RETURN;
END_IF;
// Two consecutive DINTs per axis: position then status
diPosition := arrInput[iIndex * 2];
diStatus := arrInput[iIndex * 2 + 1];
END_FUNCTION_BLOCK
Why this works: The TIA Portal compiler generates a base-offset + (index * stride) address calculation. With a 4-byte stride, the generated code is identical to a hand-written %ID[400 + i*4] expression. The compiler optimizer removes the array bounds check in optimized access mode (which is on by default for new FBs in TIA Portal V16+).
This method is excellent for motion applications: the start address is fixed by the slot's process image range, and 16 axes × 2 doubles = 32 doubles per device is a typical 1-cycle update. For PROFIdrive motion applications, the standard telegram mapping (e.g., Telegram 103 for SINAMICS drives) provides exactly this kind of structured layout, and the technique is documented in the S7-1500 Programmable Controller system manual section on technology objects and in the SIMATIC S7-1500/ET 200MP Motion Control function manual.
Method 5: RDREC / WRREC for Acyclic PROFINET Records
Some stepper controllers do not map every parameter into the cyclic process image. For parameters that must be read or written only on demand (electronic gear ratio, ramp time, position-capture mode, firmware version), PROFINET offers acyclic record access using RDREC (read record) and WRREC (write record). The record index is the parameter number, the data length is the parameter width.
This method is a complement, not a replacement, for the cyclic read methods above. Use it for non-real-time parameters and for diagnostics.
FUNCTION_BLOCK "FB_AcyclicRead"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
VAR_INPUT
iHwId : HW_IO; // hardware identifier of the slave
iIndex : UINT; // PROFINET record index (e.g., 0x7FFF vendor-specific)
iLen : UINT; // record length in bytes
bExecute : BOOL; // rising edge triggers read
END_VAR
VAR_OUTPUT
arrData : ARRAY[0..63] OF BYTE;
bBusy : BOOL;
bDone : BOOL;
bError : BOOL;
iStatus : INT;
END_VAR
VAR
instRdrec : RDREC;
srcVariant : VARIANT;
rTrig : R_TRIG;
END_VAR
BEGIN
rTrig(CLK := bExecute);
srcVariant := arrData;
instRdrec(REQ := rTrig.Q,
ID := iHwId,
INDEX := iIndex,
MLEN := iLen,
VALID => bDone,
BUSY => bBusy,
ERROR => bError,
STATUS => iStatus,
LEN => , // actual length read
DATA := srcVariant);
END_FUNCTION_BLOCK
For vendor-specific records, the index is typically 0x7FFF (manufacturer-specific range) and the slot/submodule is the PROFINET slot of the stepper. See the PROFINET specification (IEC 61784-2) for the record-handling model and the stepper controller's GSDML description for the available indices. Note that record indices 0..0x7FFE are reserved for PROFINET profile or vendor-specific definitions per the specification; verify the actual range against the device documentation.
RDREC returns within one to three PROFINET cycles. Use BUSY/DONE or trigger on a rising edge of REQ (as shown above). Polling VALID at OB1 cycle rate can lose data on the rising edge. The hardware identifier is taken from the device view's Properties > System constants > Hardware identifier for the slot in question.Complete Stepper Controller Example: Register Map and FB Architecture
For a typical 8-axis stepper controller, the vendor's register map often looks like the following snippet (numbers are illustrative - check the actual GSDML/EDS for your device):
| Offset (%ID) | Length | Direction | Name | Description |
|---|---|---|---|---|
| %ID400 | DINT | Input | Axis1 ActualPosition | Current position in counts |
| %ID404 | DINT | Input | Axis1 StatusWord | Bit-packed (homed, moving, error) |
| %ID408 | DINT | Input | Axis2 ActualPosition | |
| %ID412 | DINT | Input | Axis2 StatusWord | |
| %ID416 | DINT | Input | Axis3 ActualPosition | |
| %ID420 | DINT | Input | Axis3 StatusWord | |
| ... | DINT | Input | ... | ... |
| %ID496 | DINT | Input | Axis8 ActualPosition | |
| %ID500 | DINT | Input | Axis8 StatusWord |
Output map at %QD400 to %QD500 follows the same pattern: command word and setpoint per axis.
FB architecture for a single stepper controller instance:
-
Input interface:
iStartByteInput (UDINT),iStartByteOutput (UDINT),iAxisCount (UINT, 1..16). -
Output interface: an
ARRAY[0..15]of axis status structures (position, status, error) and an analogous control array. - Static: a multi-instance DB holding the PEEK/POKE state machine and any UDT used for symbolic field access.
-
Cycle body:
FORloop reads each%ID<startByte + n*4>with PEEK_DINT, then a second loop writes each%QD<startByte + n*4>with POKE_DINT. The two loops are independent - reading and writing happen in the same OB1 cycle. -
Error handling: a watchdog timer verifies that PEEK returns non-zero data (i.e., the slot is healthy). If the read returns the same value for several cycles on a status word that should toggle, the FB raises
bError. -
Edge detection:
R_TRIG/F_TRIGinstances flag rising and falling edges of key status bits for HMI animations.
State diagram for the FB read cycle:
This architecture gives you a single FB source that you instantiate once per physical stepper, and each instance has its own starting address. Commissioning is reduced to setting the start byte on each instance.
Multi-Instance vs. Single-Instance
For a control program that must read 16 stepper controllers, the right structural choice is one of:
-
Multi-instance FBs in one parent FB: declare 16 instances of
FB_ReadMultiIDin a "FB_MachineController" parent FB. Each child FB has its own instance data; the multi-instance DB holds the parent's data plus all 16 child DBs. -
16 separate instance DBs: the same source FB, but each call uses its own DB (
DB_Stepper1,DB_Stepper2, ...). Use this if the FBs have very different update rates (some steppers polled at 1 ms, others at 10 ms).
For most applications, multi-instance is preferred because it keeps all stepper data in one DB and simplifies HMI binding.
Commissioning and Verification
Use the following procedures to verify the implementation and diagnose faults.
Online verification with watch table
- Open Watch table in TIA Portal and add
"DB_Application".axisStatus[0]through"DB_Application".axisStatus[15]. - Add the absolute tags
%ID400through%ID500for cross-reference. - Go online with the CPU. The two columns must match value-for-value. If the array shows zeros while the %ID tags show real data, the FB is not being called or the start offset is wrong.
Forcing the process image for bench test
- In the device view, open the stepper's slot properties and set IO addresses > Operating mode to "Manual" if supported.
- Use the Modify command to write a known value (e.g., 16#12345678) into
%ID400. The corresponding array slot must show 305419896 (the decimal equivalent). - If only the high word or low word changes, the byte offset is off by 2 - recheck the start address.
Online & Diagnostics for PROFINET
- Right-click the stepper in the device view and select Online & Diagnostics.
- Open PROFINET diagnostics > I/O status. The "Status of I/O" must show "OK" for both inputs and outputs.
- If the status shows "Module failure" or "Substitute values active", the cyclic process image is invalid - the FB will read whatever value is configured as a substitute (typically 0).
Trace recording for timing analysis
- Open Trace > New trace in TIA Portal and record the FB execution time plus one of the
%IDxxxtags. - Run the trace for 1000 OB1 cycles.
- The maximum FB cycle time should be below the OB1 cycle time minus 1 ms safety margin. For a 16-element PEEK loop, expect 30-80 microseconds on a 1511F; for a 16-element Variant copy, expect 80-200 microseconds.
Troubleshooting Matrix
| Symptom | Root Cause | Remediation |
|---|---|---|
| All 16 array values are zero | FB is never called, or the start address points outside the configured I area | Verify OB1 / cyclic OB calls the FB; verify start byte in the device view |
| Array values are shifted by one element | Byte offset off by 4 (one DINT) | Recheck the start byte; the first axis should be at the lowest offset |
| Values are word-swapped | Vendor uses big-endian byte order while S7-1500 is little-endian | Apply SWAP_DWORD instruction or use the vendor's byte-order flag |
| Values change for a cycle then revert to zero | PROFINET station failure - substitute values active | Check wiring, slave diagnostics, and PROFINET name assignment |
| Compiler error: "AT address must be a constant" | Tried to use a variable inside AT construct | Use PEEK_DINT (Method 2) or pass a Variant (Method 3) |
| Compiler error: "Invalid area constant" | Wrong area number for PEEK (e.g., used 16#81 instead of 16#10) | Use the S7-1500 area constants: 16#10 = I, 16#11 = Q, 16#12 = M, 16#13 = DB |
| PEEK_DINT returns 0 even though %ID has data | Symbolic I/O is configured as "PIP 0" only (not in process image) | In device view, set the slot to "Update process image" or use a dedicated PIP |
| MOVE_BLK_VARIANT returns error 16#80C8 | Source Variant is not initialized or is the wrong type | Check the source tag is a structured variable of matching size |
| FB executes but OB1 cycle time increased by > 2 ms | Variant or PEEK loop with too many iterations | Reduce iCount, or move the FB to a slower OB (OB35 at 100 ms) |
| RDREC returns status 16#DF80B082 | Record index not supported by the device | Verify the index against the GSDML or vendor manual |
| Data is interleaved between two steppers | Two instances of the FB have overlapping start addresses | Verify each instance has a unique, non-overlapping iStartByte |
HMI Integration Notes
When binding HMI tags to the FB output array:
- Use the
DB_Application.axisStatus[i]tag in the HMI, not the absolute%ID400. The HMI should consume the symbolic, processed value. - For WinCC Unified / Comfort Panels, set the Acquisition mode to "Cyclic in operation" with a 100 ms update rate. Faster rates overload the HMI tag logging with no visible benefit.
- For the status bit, expose it as a separate BOOL tag in the FB output (e.g.,
axisStatus[i].bHomed) so the HMI can animate without bit-masking.
Performance and Cycle-Time Considerations
Approximate execution times on a 1511F (CPU 6ES7511-1FK02-0AB0) at firmware V2.9, measured with trace:
| Method | 16 elements, time | 1 element, time |
|---|---|---|
| UDT + AT (direct symbol) | ~ 5 microseconds | ~ 0.3 microseconds |
| PEEK_DINT in FOR loop | ~ 35 microseconds | ~ 2 microseconds |
| ARRAY indirect index | ~ 8 microseconds | ~ 0.5 microseconds |
| MOVE_BLK_VARIANT (UDT to array) | ~ 90 microseconds | ~ 6 microseconds |
For 1 ms OB1 cycle time, all methods are acceptable for 16 elements. For 250 microsecond cycle times (high-speed motion), restrict to Method 1 or Method 4 with a single element per cycle.
FAQ
Can the AT construct be used with a variable start address?
No. The S7-1500 compiler requires a constant absolute address for AT %ID<addr>. If you need a runtime-variable start, use PEEK_DINT (Method 2) with the byte offset as a UDINT input, or use a Variant source with MOVE_BLK_VARIANT (Method 3).
What is the difference between PEEK_DINT and symbolic access to %ID400?
Symbolic access (myTag AT %ID400) is fully resolved at compile time and runs as a single load instruction. PEEK_DINT is resolved at runtime by computing the address from the area constant, dbNumber, and byte offset. The runtime cost is one extra pointer load per call, but the flexibility lets you change the start address per FB instance without recompiling.
How do I handle byte alignment when the start address is not a multiple of 4?
DINT reads must be 4-byte aligned. Add an alignment check in the FB (e.g., IF (iStartByte MOD 4) <> 0 THEN bError := TRUE; RETURN; END_IF;). For 2-byte values, use PEEK_INT and require 2-byte alignment. If the vendor cannot guarantee alignment, read a WORD at the correct offset and shift the bits yourself with the standard S7 bit-masking operations.
What if the stepper controller uses acyclic records instead of cyclic I/O?
Use RDREC and WRREC (Method 5) for acyclic access. The hardware ID comes from the device view's "Hardware identifier" property of the slot. Indices 0..0x7FFE are standardized PROFINET records; 0x7FFF is manufacturer-specific. Expect 1-3 PROFINET cycles latency; do not poll in a tight OB1 loop. Use an R_TRIG on the execute input to avoid losing the rising edge.
How do I confirm the start byte in TIA Portal?
Open Devices & Networks > Device view, select the stepper's slot, and read Properties > I/O addresses > Start address. The "Input start address" and "Output start address" are listed in bytes. Use the input start address as iStartByteInput and the output start address as iStartByteOutput in your FB call. If the start address is shown as 0x200 (hex), pass 512 (decimal) - the field is in hex.
Why does PEEK return zero on a slot that shows data in the watch table?
The slot is not in the process image. Open the slot's properties and set "Update process image" to "Yes". For slots assigned to a non-default process image partition (PIP 1-3), you must either move them to PIP 0 or use a different PEEK area constant (S7-1500 only supports the default PIP for PEEK; OB1 must be assigned to the same PIP as the slot, or use a direct PEEK to the slot's HW ID via RDREC / SFB52).
Can I pass a starting address that is a slot number instead of a byte offset?
No. The starting address parameter in all methods above is a byte offset in the process image. Slot numbers are used only for RDREC / WRREC hardware identifiers. To find the byte offset of a slot, open the device view and read the "Start address" field under I/O addresses.