Problem Overview
Engineers integrating a Siemens S7-300/400/1500 PLC with a SIMOTION motion controller over PROFINET, PROFIBUS, or UDP/Ethernet frequently encounter a data-format barrier: the S7 sends floating-point data as raw bytes in IEEE 754 single-precision (REAL) format, and on the SIMOTION side the payload lands in a byte array rather than a typed REAL variable. The bytes themselves are perfectly valid IEEE 754 — the issue is purely a matter of how the SIMOTION runtime interprets the underlying memory layout when data arrives through a non-tag-bound channel (raw socket, UDP receive buffer, untyped I/O slot, or the older BigByteArray buffer used internally by SIMOTION communication functions).
The same conversion is required when the S7 publishes REALs through:
- PUT/GET on S7 communication (fetch/write via
FB14/FB15on S7-400 or the S7-1500PUT/GETinstructions) - Open User Communication (TCP/UDP) on S7-1500 via
TSEND_C/TRCV_C - PROFINET slot-based I/O with raw byte view enabled
- PROFIBUS DP with PPO type 1–5 (parameter/process data) carrying REAL in the PZD area
- SIMOTION-side
_receiveon a UDP socket populated from a S7-1500 broadcast
The diagnostic symptom is consistent: the data displays correctly as four bytes when viewed in hexadecimal, but assigning those bytes to a SIMOTION REAL or LREAL yields a value of 0.0, NaN, or a clearly wrong magnitude (typically a byte-order or sign-bit artifact).
ARRAY OF BYTE region as a REAL. A typed cast or a system conversion function is required. The S7 REAL type is already IEEE 754-compliant (per the STEP 7 data-type conversion documentation), so the bytes are correct — only the view is wrong.
IEEE 754 Single-Precision Format Reference
Both the S7-300/400/1500 and SIMOTION use the IEEE 754 binary32 representation for the REAL data type. The bit layout is identical; byte order is the only axis that varies.
| Bit range | Width | Field | Description |
|---|---|---|---|
| 31 | 1 | Sign (S) | 0 = positive, 1 = negative |
| 30–23 | 8 | Exponent (E) | Biased by +127; stored = exponent + 127 |
| 22–0 | 23 | Mantissa (M) | Fractional part; implicit leading 1 for normal numbers |
Value formula: value = (-1)^S * 2^(E-127) * (1.M)
Encoded across four bytes in the order byte 0 = bits 31..24, byte 1 = bits 23..16, byte 2 = bits 15..8, byte 3 = bits 7..0. Siemens S7 and SIMOTION both use big-endian (network byte order) for REAL over communication by default, but PROFINET slot I/O on some hardware variants can deliver little-endian (LSB first) — verify with a known test value before committing code.
SIMOTION Data Types and System Functions
SIMOTION's ST compiler accepts the following floating-point types, all IEEE 754 compliant:
| Type | Size | Format | Range (approx.) |
|---|---|---|---|
| REAL | 32 bit | IEEE 754 binary32 | ±3.402823E+38 |
| LREAL | 64 bit | IEEE 754 binary64 | ±1.797693E+308 |
Key SIMOTION system functions for byte-array conversion (all declared in the _iec or _standard library, available in SIMOTION SCOUT from kernel V4.1 onward and well-supported in V4.4, V4.5, and V5.x):
-
_BigByteArray_To_AnyType(destPtr, srcPtr, destType)— copies N bytes from a byte buffer into a typed destination, whereNis inferred fromdestType. Works for REAL, LREAL, DWORD, INT, etc. -
_AnyType_To_BigByteArray(destPtr, srcPtr, srcType)— inverse operation for sending typed values back to a byte stream. -
_getBitByteWordDwordand_putBitByteWordDword— granular 8/16/32-bit access to a byte array.
lib is included in the LDBV/IEC project. The S7-1500↔SIMOTION data-type conversion guidance published in the STEP 7 (TIA Portal) V20 documentation set describes the equivalent conversions on the PLC side, and the same IEEE 754 byte layout applies on the SIMOTION side.
Prerequisites
- SIMOTION SCOUT (V4.4 or V5.4 recommended) installed, with a configured motion controller (e.g., SIMOTION D435, D445, C240 PN).
- STEP 7 (TIA Portal) or STEP 7 V5.x project with the source S7 CPU online and a published REAL tag (e.g.,
DB1.DBD0as REAL). - Configured PROFINET IO link between S7 and SIMOTION, OR a UDP/TCP socket pair (S7-1500
TSEND_C/TRCV_C↔ SIMOTION_socketAPI). - Know test value to validate byte order — e.g., S7 sends
REAL#3.14=16#4048F5C3. - SIMOTION program access to the destination
REALvariable (axis data, TO data, or globalVAR_GLOBAL).
Step-by-Step: Converting the Byte Array to a REAL
Method 1 — Direct Cast via _BigByteArray_To_AnyType (preferred)
This is the cleanest, kernel-supported approach and avoids hand-written byte manipulation. It requires the SIMOTION kernel to expose the function (V4.1+, available in _iec.lib).
// SIMOTION ST source
VAR_GLOBAL
rxBuffer : ARRAY[0..255] OF BYTE; // populated by UDP/PN receive
measuredPos : REAL; // target variable
END_VAR
VAR
pBuffer : POINTER TO BYTE;
pValue : POINTER TO REAL;
dummy : DWORD;
END_VAR
// Method 1: typed copy (big-endian REAL on the wire)
pBuffer := ADR(rxBuffer[0]);
pValue := ADR(measuredPos);
_BigByteArray_To_AnyType(pValue, pBuffer, TYPE_REAL);
// measuredPos now holds the IEEE 754 value as it was on the S7
The function copies exactly SIZEOF(REAL) = 4 bytes from pBuffer into pValue, reinterpreting the bits as a REAL. No swap, no shift, no manual assembly.
Method 2 — Manual Byte Assembly (fallback / kernel-agnostic)
If the runtime library does not expose _BigByteArray_To_AnyType, or if you are forced onto an older kernel, build the REAL manually. The procedure below assumes big-endian REAL on the wire (S7 default over S7-communication and PROFINET slot data).
// SIMOTION ST — manual REAL assembly from byte array (big-endian)
VAR_GLOBAL
rxBuffer : ARRAY[0..3] OF BYTE;
measuredPos : REAL;
END_VAR
VAR
dwordBits : DWORD;
END_VAR
// Combine 4 bytes into one 32-bit word, then reinterpret as REAL
// Big-endian: byte[0] is MSB (sign + exponent), byte[3] is LSB (mantissa LSBs)
dwordBits := (DWORD)rxBuffer[0] << 24
OR (DWORD)rxBuffer[1] << 16
OR (DWORD)rxBuffer[2] << 8
OR (DWORD)rxBuffer[3];
// Bit-level cast via a UNION-like alias:
// SIMOTION does not have a native UNION, but a POINTER cast achieves the same effect
IF ADR(dwordBits) <> 0 THEN
// The IEC method: write the DWORD bits to memory, then cast the pointer
measuredPos := REAL#0.0; // initialize
// Use a temporary DWORD alias over a REAL slot
// (see Method 3 for a cleaner pattern)
END_IF;
The shift-and-OR pattern above produces a correct DWORD. To reinterpret it as REAL, you must use either an explicit copy via _getBitByteWordDword into a target, or a pointer alias (Method 3).
Method 3 — Pointer Alias (cleanest IEC-61131-3 pattern)
Create a 4-byte aligned buffer, write a DWORD into it, then read it back as a REAL through a POINTER TO REAL. This is endianness-explicit and kernel-portable.
VAR
rawBytes : ARRAY[0..3] OF BYTE; // shared 4-byte view
pReal : POINTER TO REAL;
pByte : POINTER TO BYTE;
dwordVal : DWORD;
END_VAR
// --- receive path ---
// 1. Fill rawBytes from the fieldbus / UDP buffer
rawBytes[0] := rxBuffer[0];
rawBytes[1] := rxBuffer[1];
rawBytes[2] := rxBuffer[2];
rawBytes[3] := rxBuffer[3];
// 2. Build DWORD (big-endian, adjust for little-endian PN slots)
dwordVal := SHL_DWORD(BYTE_TO_DWORD(rawBytes[0]), 24)
OR SHL_DWORD(BYTE_TO_DWORD(rawBytes[1]), 16)
OR SHL_DWORD(BYTE_TO_DWORD(rawBytes[2]), 8)
OR BYTE_TO_DWORD(rawBytes[3]);
// 3. Reinterpret the DWORD bits as REAL
pByte := ADR(rawBytes);
pReal := ADR(dwordVal); // alias same 4 bytes
// Or, more portably:
rawBytes[0] := DWORD_TO_BYTE(SHR_DWORD(dwordVal, 24) AND 16#FF);
rawBytes[1] := DWORD_TO_BYTE(SHR_DWORD(dwordVal, 16) AND 16#FF);
rawBytes[2] := DWORD_TO_BYTE(SHR_DWORD(dwordVal, 8) AND 16#FF);
rawBytes[3] := DWORD_TO_BYTE( dwordVal AND 16#FF);
// Now alias rawBytes as REAL — using a temporary POINTER
// Note: SIMOTION allows pointer re-aliasing at runtime
For purely runtime-safe code without pointer aliasing, the recommended industrial pattern is to call _BigByteArray_To_AnyType with TYPE_REAL and let the system library do the cast.
Method 4 — Using _getBitByteWordDword for Bit-Grained Access
If you must avoid pointer aliasing entirely (some safety-certified SIMOTION builds restrict direct pointer use), use the system bit-access function:
VAR
realVal : REAL;
realBytes : ARRAY[0..3] OF BYTE;
END_VAR
// Copy first 4 bytes from incoming buffer
realBytes[0] := rxBuffer[0];
realBytes[1] := rxBuffer[1];
realBytes[2] := rxBuffer[2];
realBytes[3] := rxBuffer[3];
// Read 32 bits starting at byte 0 of realBytes, interpret as REAL
realVal := REAL#0.0;
realVal := _getRealFromByteArray(ADR(realBytes), 0);
// (where _getRealFromByteArray is a user FB wrapping _getBitByteWordDword
// to fetch 32 bits and cast the resulting DWORD to REAL)
Wrap the call in a reusable function block so the pattern is consistent across the project.
Byte-Order (Endianness) Considerations
The IEEE 754 bits are identical — only the order of the four bytes on the wire changes. The S7 and SIMOTION defaults both lean toward big-endian for S7-communication-style protocols, but PROFINET slot I/O can present data either way depending on the device GSD and slot configuration.
| Channel | Default byte order | Verify with test value |
|---|---|---|
| S7 PUT/GET (FB14/FB15, S7-400) | Big-endian | REAL#1.0 = 16#3F800000 |
| S7-1500 PUT/GET | Big-endian | REAL#1.0 = 16#3F800000 |
| Big-endian (per PROFIdrive profile) | REAL#1.0 = 16#3F800000 | |
| PROFINET slot, third-party GSD | Little-endian possible | Check GSD; verify with 16#0000803F (LE) vs 16#3F800000 (BE) |
UDP/TCP, S7-1500 TSEND_C with raw REAL |
Big-endian (S7 stores MSB first in DB) | Watch first 4 bytes in Wireshark |
| PROFIBUS PPO type 5 (PZD = 10 words) | Big-endian | REAL#1.0 = 16#3F800000 |
For a quick endianness sanity check, send REAL#1.0 from the S7 and look at the four received bytes. The IEEE 754 encoding of 1.0 is 0x3F800000.
- Bytes arrive as
3F 80 00 00→ big-endian, no swap needed. - Bytes arrive as
00 00 80 3F→ little-endian, reverse the byte order before the cast.
PROFINET-Specific Procedure
When the SIMOTION is the PROFINET IO controller and the S7 is the device (or vice-versa, with the S7 as controller and SIMOTION as I-Device), the REAL appears in a configured I/O slot.
- Configure the slot in HWCN / SCOUT with 4 bytes, type "input" or "output".
- Bind the slot to a SIMOTION I/O address (e.g.,
%IW0for 16-bit, but for a 32-bit REAL use aDWORDalias%ID0). - Reinterpret
%ID0as REAL via a POINTER alias, or copy the bits into aREALvariable usingDWORD_TO_REAL(works on SIMOTION compilers that support the conversion instruction — V4.4+ confirmed).
// SIMOTION ST — PROFINET slot, DWORD in I/O area reinterpreted as REAL
VAR
rawDword AT %ID0 : DWORD; // 4 bytes from PN slot
measuredVal : REAL;
END_VAR
measuredVal := DWORD_TO_REAL(rawDword);
// Or, using the _iec library cast:
// measuredVal := REAL#(rawDword);
AT declaration overlays a variable in place. measuredVal := REAL#(rawDword); may not be a legal IEC typecast in all SIMOTION kernel versions. If the compiler rejects it, use _BigByteArray_To_AnyType with a temporary byte buffer, or use a POINTER TO REAL assigned to ADR(rawDword).
UDP / Open User Communication Procedure (S7-1500 ↔ SIMOTION)
- On the S7-1500 side, configure a UDP connection with
TSEND_C(orTCON+TSEND). Pack the REAL as four bytes using theSerializeinstruction or a manual DB layout. - On the SIMOTION side, declare a UDP socket via the
_socketAPI, bind to a port, and call_receiveasynchronously. - On each receive, copy the first 4 bytes of the payload into a
REALusing Method 1 above.
// SIMOTION ST — UDP receive handler
VAR_GLOBAL
sockHandle : DWORD;
rxBuffer : ARRAY[0..1471] OF BYTE;
rxLength : DINT;
fromAddr : _SocketAddress;
measuredPos: REAL;
END_VAR
// In a cyclic or interrupt task:
IF _isReady(sockHandle) THEN
rxLength := _receive(sockHandle, ADR(rxBuffer[0]), SIZEOF(rxBuffer), ADR(fromAddr));
IF rxLength >= 4 THEN
_BigByteArray_To_AnyType(ADR(measuredPos), ADR(rxBuffer[0]), TYPE_REAL);
END_IF;
END_IF;
Verification
-
Static value test: Set the S7 source REAL to
1.0. Confirm SIMOTION variable displays1.0. Bytes on the wire should be3F 80 00 00. -
Range test: Send
3.14,-3.14,0.0, and a small value (e.g.,0.001=0x3A83126F). Confirm all values match within float precision. -
Edge cases: Send
NaN(e.g.,0/0),+Inf, and the max REAL3.402823E+38. SIMOTION should displayNaN,+INF, and the max value respectively. - Watchdog test: Drive the SIMOTION axis (if the REAL feeds a position/velocity setpoint) and confirm motion is smooth, with no glitches in the trace.
- Online monitor: In SCOUT, place a watchpoint on the target REAL, then toggle the S7 source between 0.0 and 100.0. The watchpoint value should update within one PROFINET cycle (typically 1 ms for IRT, 4 ms for RT).
Troubleshooting Matrix
| Symptom | Likely cause | Fix |
|---|---|---|
| REAL always 0.0 | Bytes not yet written to buffer when _BigByteArray_To_AnyType is called |
Guard conversion with a buffer-valid flag; verify rxLength >= 4
|
| REAL = 0.0 even with valid bytes | Pointer ADR() returns 0 (uninitialized array) |
Use a static VAR_GLOBAL or VAR_TEMP allocated array |
| REAL = small positive garbage (~1E-38) | Byte order swapped (little-endian received, no reverse) | Reverse the 4 bytes before cast, or confirm GSD is big-endian |
| REAL is correct magnitude but sign flipped | Bit 31 (sign) misaligned due to 16-bit packing bug | Verify no 16-bit WORD truncation between the S7 and SIMOTION |
| REAL oscillates between two values | Two PROFINET slots writing the same I/O address, or duplicate UDP listener | Check slot mapping in HWCN and SCOUT; check socket bind uniqueness |
| REAL = NaN in steady state | Source REAL is NaN upstream; SIMOTION propagation is correct | Trace back to the S7 source; clamp/limit upstream |
| Conversion compiles but runtime halts |
_BigByteArray_To_AnyType not in linked library |
Add _iec.lib to LDBV project; confirm kernel V4.1+ |
| Compile error: function not found | SIMOTION kernel older than V4.1 | Upgrade firmware on the SIMOTION D/C/P; confirm via SCOUT device diagnostics |
| Compile error: typecast not allowed | Used REAL#(dwordVar) syntax; not supported in IEC ST for SIMOTION |
Use _BigByteArray_To_AnyType or POINTER alias |
| Stale value, no update on S7 change | Cyclic task running slower than PROFINET update; buffer overwritten before copy | Use a separate receive buffer per cycle; confirm IPO/IPO2 task priority |
Performance and Timing
-
_BigByteArray_To_AnyTypeexecutes in single-digit microseconds on a SIMOTION D435 (kernel V4.5); no measurable impact at PROFINET 1 ms cycle. - Manual byte assembly via shift-OR is comparable (under 1 µs for 4 bytes on the same hardware).
- For high-frequency motion loops (> 4 kHz), place the conversion in the IPO or IPO2 task, not the slower background task.
Safety-Certified Builds
SIMOTION F-variant controllers (e.g., SIMOTION D445-2 DP/PN F) restrict direct pointer use in the safety-related part of the program. Place all byte-array conversions in the standard user program, not in the safety task. Cross-check the F-program's view of the value via the safety I/O, not via the byte buffer directly. The S7-1500F side must use the Serialize/Deserialize instructions from the F-library to keep the payload integrity-checked end-to-end.
Cross-Reference: S7 Side Encoding
The STEP 7 (TIA Portal) data type documentation confirms the S7-1500 REAL is IEEE 754 single-precision, and the conversion instructions (REAL_TO_DWORD, DWORD_TO_REAL) operate on the same bit layout. When serializing a REAL for transmission over a TCP/UDP socket, the S7 stores it in DB byte order; the S7's Serialize instruction writes the bytes in big-endian (network) order by default. For PROFINET slot data, the byte order is dictated by the PROFIdrive profile (also big-endian for standard signal types).
Field-Proven Patterns
- Use a single
FB_RxFloatToRealfunction block with aVALUE : REALoutput and aBUFFER : POINTER TO BYTEinput. This gives a uniform interface for every float received from the S7. - For double-precision, replace
TYPE_REALwithTYPE_LREALand the buffer slice to 8 bytes. The same_BigByteArray_To_AnyTypecall works. - If you have many REALs in a single frame, loop over an indexed buffer and call the conversion in a
FORloop. Expect ~2 µs per conversion on a D435. - Document the endianness assumption in the program header comment. Future maintainers will thank you.
FAQ
Does SIMOTION use the same IEEE 754 REAL format as the S7-300/400/1500?
Yes. SIMOTION's REAL (32-bit) and LREAL (64-bit) are IEEE 754 binary32 and binary64, identical to the S7-300/400/1500. The data on the wire is already compatible — the only issue is reinterpreting a byte buffer as a typed variable.
Which SIMOTION function should I use to convert bytes to a REAL?
Use _BigByteArray_To_AnyType(destinationPtr, sourcePtr, TYPE_REAL) from the _iec system library. It is available on SIMOTION kernels V4.1 and newer. The function copies 4 bytes and reinterprets them as REAL — no manual byte assembly needed.
The conversion result is 0.0 even though the bytes are correct — what went wrong?
Most often the byte buffer was empty or a pointer returned 0 (uninitialized array). Verify the source buffer is populated before calling the conversion, guard with a rxLength >= 4 check on UDP, and confirm the buffer is declared as a static VAR_GLOBAL so ADR() returns a valid address.
How do I detect little-endian vs big-endian REAL delivery on PROFINET?
Send a known test value such as REAL#1.0 (IEEE 754 bits 0x3F800000) from the S7. Big-endian delivery shows bytes 3F 80 00 00 in the SIMOTION buffer; little-endian shows 00 00 80 3F. Reverse the byte order before the cast for little-endian sources.
Can I avoid conversion entirely by binding the PROFINET slot directly to a SIMOTION REAL tag?
Yes, if the PROFINET slot is configured as a typed signal (e.g., PROFIdrive standard telegram 1–110) and the SIMOTION-side I/O address is declared as REAL or as a DWORD that is then re-cast. For raw user-defined slots, use the _BigByteArray_To_AnyType approach described above.