RDREC/WRREC in SCL WHILE Loops: Use a State Machine for Async I/O

David Krause13 min read
Best PracticesSiemensTIA Portal
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

RDREC/WRREC in SCL WHILE Loops: Use a State Machine for Async I/O

The Siemens standard functions RDREC (read data record) and WRREC (write data record) are the canonical way to exchange parameter and diagnostic data with distributed I/O, SINAMICS drives, and PROFIBUS/PROFINET devices on S7-1200 and S7-1500 controllers programmed in TIA Portal. A recurring design mistake is wrapping these calls in a WHILE ... DO loop that iterates over a list of hardware identifiers (HW_ID) and waits for results. The loop appears to work in the simulator, then collapses in the field with corrupt data, locked scan times, and mystery error codes. This reference explains the underlying asynchronous execution model, the specific failure modes, and the state-machine pattern that replaces the WHILE loop with a deterministic, multi-cycle acquisition sequence.

1. Overview: The Pattern That Fails

Engineers frequently write code of the form:

// FRAGILE - do not deploy
FOR i := 1 TO 25 DO
    rdRecord(REQ := TRUE,
             ID  := hwIdArray[i],
             INDEX := 100,
             MLEN := 8,
             BUSY => busyFlag,
             VALID => validFlag,
             RECORD => dataBuffer);
    // hoping the call completes synchronously inside one OB1 cycle
END_FOR;

The intent is reasonable: poll parameter 100 from 25 identical drives and store the value in a data block indexed by station number. The execution model, however, is wrong. RDREC and WRREC on S7-1200/S7-1500 are asynchronous system functions. They post a job to the I/O subsystem of the CPU and return control to user code on the same OB1 scan, but the actual data transfer to the device takes multiple PLC cycles. The BUSY output stays TRUE and VALID stays FALSE until the device responds or a timeout elapses.

Critical point: A WHILE ... DO loop with a TRUE condition holds the OB1 priority class for the entire duration. The PROFINET/PROFIBUS job cannot complete while the user program is still spinning inside the same OB. The BUSY flag never clears, the watchdog of the I/O subsystem never gets serviced, and the CPU eventually drops the request.

2. Execution Model of RDREC and WRREC

RDREC and WRREC are declared as FC/FB system blocks in the TIA Portal instruction library under Communication > SIMATIC S7 > Communication processor. The function interfaces follow the IEC 61131-3 multi-instance pattern. The relevant I/O of RDREC is summarized below.

RDREC interface (S7-1200/S7-1500, TIA Portal V15+)
Parameter Declaration Type Meaning
REQ INPUT BOOL Rising edge starts a new read job
ID INPUT HW_IO / HW_DEVICE Hardware identifier of the module or submodule
INDEX INPUT INT Data record number (0..65535)
MLEN INPUT INT Maximum byte length of RECORD
VALID OUTPUT BOOL TRUE on a single cycle when new data is available
BUSY OUTPUT BOOL TRUE while the job is active in the I/O subsystem
ERROR OUTPUT BOOL TRUE if the job terminated with an error
STATUS OUTPUT WORD / DWORD Detailed status (see Section 8)
RECORD IN_OUT VARIANT Destination buffer for the read data

The job is dispatched to the PROFINET/PROFIBUS stack only when REQ sees a rising edge. The CPU does not block the OB while the device replies. Instead, on each subsequent call the function re-enters the active job, refreshes BUSY, and eventually returns VALID = TRUE for exactly one cycle or ERROR = TRUE with a STATUS code. The reference semantics and the meaning of BUSY / VALID are documented in the Siemens support entry Use the program example of the control data record with WRREC.

3. Why WHILE-DO Breaks Asynchronous I/O

The SCL WHILE ... DO statement is documented in the S7-1200 manual collection under SCL program control statements. The construct executes the body repeatedly as long as the condition is true. A WHILE TRUE ... END_WHILE; loop is a legal SCL construct; it is also the simplest way to write an infinite loop that monopolises a task.

Three failure modes appear the moment RDREC or WRREC is called from inside such a loop:

  1. Scan-time exhaustion. OB1 keeps re-entering the WHILE body. The PROFINET stack runs at a lower priority in the same cyclic task and never gets CPU time, so BUSY remains latched. The cycle-time watchdog of the CPU trips and the controller goes to STOP with error SF020 (cycle time exceeded) or SF211.
  2. Record corruption. Each iteration re-arms REQ with a new ID and a new RECORD buffer. The I/O subsystem, however, has the previous job still in flight. The new edge cancels the previous job and overwrites the buffer, but only if the cancellation succeeds. If the device has already started transferring the previous record, the stack can interleave bytes from two records into the same buffer. The result is a non-zero STATUS that points to Data record cannot be read/written in the current state (0xDF028481) followed by silently corrupt application data.
  3. Index misalignment. Code that writes DBOutputParams[i] := RECORD; inside the same loop iteration that triggered the read stores the buffer before the data is valid. The buffer is then overwritten on the next iteration, leaving the DB with the value from the last device, not the value from device i.
The same three failure modes appear with WRREC. Writes are even more dangerous because a partially issued write can leave the device in a configuration that the controller thinks it owns, leading to inconsistent parameter sets across the network.

4. The State Machine That Replaces the WHILE Loop

The cure is to treat each RDREC / WRREC call as a multi-cycle transaction. A four-state machine is sufficient for the common case of a fixed list of devices polled round-robin.

State definitions for record acquisition
State Constant Action on entry Transition condition
IDLE 0 Initialise index i := 1 Always (immediate)
ARM 10 Set REQ := TRUE with ID := hwIdArray[i] and target RECORD := dbResult[i] RDREC.BUSY = TRUE
WAIT 20 Hold REQ := FALSE; poll VALID and ERROR VALID = TRUE OR ERROR = TRUE OR timeout
NEXT 30 Store STATUS in diagnostic DB; increment i; wrap if i > 25 Always (immediate)

The state is held in a static INT tag inside an instance DB. Each OB1 cycle executes one branch of the CASE statement and returns. The next OB1 cycle advances the state. The PROFINET stack gets full access to the CPU between OB1 passes, BUSY clears, and VALID pulses for one cycle on completion.

5. SCL Implementation Skeleton

The complete pattern fits in one FB. A condensed version follows.

FUNCTION_BLOCK "fbRecordPoller"
VAR
    iState      : INT;        // current state, 0 / 10 / 20 / 30
    iIndex      : INT;        // 1..25 device pointer
    iRetry      : INT;        // attempts for current device
    tTimeout    : TIME;       // deadline for current job
    rdInstance  : RDREC;      // multi-instance of RDREC
    wrInstance  : WRREC;      // multi-instance of WRREC
END_VAR

BEGIN
    CASE iState OF
        0:  // IDLE
            iIndex := 1;
            iRetry := 0;
            iState := 10;

        10: // ARM - rising edge on REQ
            rdInstance(REQ := TRUE,
                       ID  := "iDB_HwIds".hw[iIndex],
                       INDEX := 100,
                       MLEN  := 8,
                       VALID => ,
                       BUSY  => ,
                       ERROR => ,
                       STATUS => ,
                       RECORD => "iDB_Results".r[iIndex]);
            tTimeout := T#2s;     // leave plenty of margin for the slowest slave
            iState := 20;

        20: // WAIT - poll without re-arming
            rdInstance(REQ := FALSE,
                       ID   := "iDB_HwIds".hw[iIndex],
                       INDEX := 100,
                       MLEN  := 8,
                       RECORD => "iDB_Results".r[iIndex]);
            IF rdInstance.VALID THEN
                iState := 30;
            ELSIF rdInstance.ERROR THEN
                IF iRetry < 3 THEN
                    iRetry := iRetry + 1;
                    iState := 10;        // retry from ARM
                ELSE
                    "iDB_Diag".status[iIndex] := rdInstance.STATUS;
                    iState := 30;        // give up, advance
                END_IF;
            ELSIF tTimeout < T#0s THEN
                iState := 30;            // give up on timeout
            ELSE
                tTimeout := tTimeout - OB1_SCAN_1ms; // simplified
            END_IF;

        30: // NEXT
            iIndex := iIndex + 1;
            IF iIndex > 25 THEN
                iIndex := 1;
            END_IF;
            iRetry := 0;
            iState := 10;
    END_CASE;
END_FUNCTION_BLOCK

Two architectural details matter:

  • Single instance, multiple parameter sets. The same rdInstance / wrInstance is reused for all 25 devices. The block's internal state identifies the active job; only the input operands change between calls. The Siemens support entry on parameter data record with RDREC and WRREC uses the same pattern when reading module parameter records of arbitrary index.
  • Buffer alignment. RECORD is a VARIANT whose target must be byte-aligned and at least MLEN bytes long. For a polled set of drives, the cleanest layout is a ARRAY[1..25] OF ARRAY[0..7] OF BYTE in the instance DB. Each element is exactly MLEN bytes, so the variant cast never returns a length error.

6. Triggering on OB82 for Diagnostic Records

For diagnostic data records (typically INDEX in the range 0..0x7FFF for module diagnostics, 0..15 for channel diagnostics) the read is normally initiated from the diagnostic interrupt OB. The Siemens knowledge base article Read program example for diagnostic data record with RDREC sets the REQ input of RDREC inside OB82 so that a rising-edge trigger fires every time the CPU raises a diagnostic interrupt for the relevant module.

The recommended pattern extends the state machine in Section 4 with an additional entry point:

// In OB82, for the local data ByteBit field 'FaultId'
#startRead := TRUE;
// 'startRead' becomes the trigger of the ARM state in fbRecordPoller

The state machine then takes ownership of the request, leaves the OB82 priority class, and finishes the read across several OB1 cycles. Successful calls can be counted by toggling a BOOL on every VALID = TRUE edge inside the WAIT state.

7. Reading a Control Data Record (INDEX = 47)

Many Siemens drive objects (SINAMICS G120, ET200S, etc.) expose the control data record at INDEX = 47 and a corresponding parameter data record at INDEX = 47 as well. The Siemens example on the control data record with WRREC documents the exact buffer layout. The BUSY output of the call is named busyRD / busyWR in the example, and the VALID output is named checkRD / checkWR for parity with the surrounding code. Treat those names as local aliases; the semantics are identical to the standard RDREC/WRREC interface.

8. Error Codes Worth Memorising

The most common STATUS values returned by RDREC and WRREC on S7-1200/S7-1500 are listed below. The two leading bytes identify the source; the trailing bytes identify the cause.

RDREC / WRREC STATUS codes (excerpt)
STATUS (hex) Source Meaning Recommended action
0000_0000 No error Job completed, VALID is TRUE Process RECORD
0070_0000 Local Job is still running, no result yet Stay in WAIT, keep polling
DF80_Bxxx PROFINET IO Vendor-specific error from the device Inspect device diagnostics buffer
DF80_Cxxx PROFINET IO Access denied / record locked by device Retry with backoff
DF80_D0xx PROFINET IO Record not available Check the INDEX against the device manual
DF80_E0xx PROFINET IO Access to record not supported Verify the module supports the requested record
DF80_F0xx PROFINET IO Record length too short Increase MLEN to match device
DE80_B0xx DP / PROFIBUS Slave rejected the read/write Check slave configuration in HWCN
DE80_4000 DP / PROFIBUS Overflow of internal buffer Reduce polling rate or buffer size
0x80A1_xxxx CPU Job aborted because a new REQ edge was issued Source of the WHILE-loop bug; debounce REQ

A non-zero STATUS should always be persisted in a diagnostic DB, indexed by the same iIndex that owns the failing device. That mapping is the only way to know which station produced a transient error during a round-robin poll.

9. Tuning the Polling Cycle

The complete acquisition of 25 devices takes 25 * (job latency + one OB1 cycle) by design. On a typical S7-1516 with PROFINET IRT, each record returns in 1-3 ms. The 25-device round therefore fits comfortably in a 100 ms OB1. If the same FB is used for WRREC, the writeback half of the cycle must be tracked by a second state machine or a second instance of the same FB with a complementary state set. The two state machines must hand off ownership of the device index through a shared variable to avoid issuing read and write jobs on the same slave in the same scan.

10. Verification Checklist

After deploying the state machine in place of the WHILE loop, verify the following before releasing the program to production:

  1. Cycle time. Observe the OB1 scan time in the online diagnostics of the CPU. With the WHILE-loop implementation the scan time grows until the watchdog trips; with the state machine it stabilises at a value consistent with the polling rate (1.5-3 ms per device for SINAMICS parameter 100 on a 1516).
  2. VALID pulse count. Add a counter that increments on every VALID = TRUE edge. The counter should advance at the rate of one per device per round. A counter that does not advance for a specific iIndex points to either a device that is offline or a record that the device does not support.
  3. STATUS persistence. Confirm that STATUS is captured for every ERROR = TRUE edge and that the index stored alongside the status matches the device that was being polled at that moment.
  4. Retry budget. Confirm that the retry counter resets at the start of the NEXT state, and that a failing device does not stall the rest of the round.
  5. Whatchdog. Deliberately disconnect one slave on the network and verify that the FB does not lock the CPU in WAIT, but instead times out within the configured tTimeout and advances to the next device.
  6. Diagnostic OB. When diagnostic records are involved, force a diagnostic event (e.g. wire break on an AI module) and confirm that OB82 fires and that the state machine reads record 0 in the next cycle.

11. Common Variants and Field-Proven Caveats

  • Reusing the instance vs. multiple instances. The same RDREC instance can serve a long sequence of different ID / INDEX / MLEN parameter sets. There is no need to declare one instance per device, and doing so wastes instance-DB memory. The state machine approach makes this explicit by holding the parameter set in the calling FB and passing the same RDREC instance down.
  • VARIANT payload. RECORD is a VARIANT. When the buffer is an element of an ARRAY OF BYTE, use the [index] selector; passing the whole array will fail at runtime with Variant points to invalid target.
  • OB priority. Calling RDREC directly from OB82 (high priority) is fine because OB82 is short-lived. Do not embed a WHILE loop in OB82; the diagnostic OB has a tight time budget and the same scan-time failure modes apply.
  • PUT / GET vs. RDREC / WRREC. The PUT / GET blocks operate on the S7 communication path and are not the same as data record access. Do not use RDREC to read a DB from another CPU; use PUT or BSEND/BRECV for that.
  • Firmware sensitivity. RDREC and WRREC behave identically on S7-1200 firmware V4.0+ and S7-1500 firmware V1.5+. On older S7-1200 firmware the function may block for the duration of the call; the WHILE-loop anti-pattern is slightly less harmful there but is still wrong because the device index is no longer aligned with the buffer.

Can I really not call RDREC inside a WHILE-DO loop on S7-1200/S7-1500?

No. RDREC and WRREC are asynchronous; their job runs in the PROFINET/PROFIBUS stack, not in the OB that triggered them. A WHILE loop in OB1 prevents the stack from finishing the job, so BUSY stays TRUE, the cycle watchdog trips, and the data is written to the wrong buffer index. Use a state machine in OB1 (or a triggered call from OB82) instead.

Do I need one RDREC instance per device?

No. The same instance is reused for every device. Each call to RDREC takes a new parameter set, and the function's internal state keeps the previous job alive until it completes. Reusing a single instance saves instance-DB memory and is the pattern shown in the Siemens examples for control, parameter, and diagnostic data records.

What is the difference between VARIANT RECORD targets and byte arrays?

RECORD is a VARIANT input. The destination must be byte-aligned, at least MLEN bytes long, and must be passed by reference (e.g. a single element of an ARRAY OF BYTE). Passing the whole array causes a runtime variant error on S7-1500 firmware V2.5+.

What STATUS value indicates a job that was cancelled by a new REQ edge?

STATUS 0x80A1xxxx, often seen as 0x80A10000, is returned when a new REQ edge cancels an in-flight job. This is the typical fingerprint of the WHILE-loop bug: the controller kept firing new REQ edges before the previous job had time to complete.

Can RDREC read parameter 100 from a SINAMICS drive directly?

Only through the drive's parameter channel, which is exposed as data record 47 on the PROFINET submodule. Use RDREC with INDEX 47 and an MLEN of at least the parameter's size, or use the drive's parameter access via the standard telegram and the SINA_SPEED/SINA_PARA blocks if you want a parameter-number abstraction.

Back to blog