1. Problem Description
Engineers integrating a third-party energy meter (kWh / Wh register) with a Siemens S7-1200 over Modbus RTU frequently observe the following symptoms:
- The REAL (32-bit IEEE 754 floating-point) energy register reads correctly during the first few hours of operation.
- After a certain magnitude, the value in the PLC DB or tag stops changing. ModScan / Modbus Poll on the same RS-485 bus still shows correct, incrementing values.
- After 2–3 hours the PLC value suddenly jumps, but it does not match the meter display. The mismatch grows with time.
- Other 16-bit registers (V, A, kW, power factor, frequency) update normally.
Engineers initially suspect a REAL data type limitation, a memory-corruption issue, or a firmware bug. The actual root cause is almost always one of three things: byte/word swapping, register address misalignment, or counter rollover. Each is described below with the corresponding TIA Portal fix.
2. Root Cause Analysis
| Symptom | Likely Root Cause | Where to Look |
|---|---|---|
| Value freezes at a round number, e.g. 65536, 16777216, 1677721.6 | Word-swap (ABCD ↔ CDAB) on 32-bit float | MB_DATA_PTR length, MOVE/CAW instructions, byte order in meter manual |
| Value reads as garbage, negative, or extremely large (e.g. 1.0E+38) | Register address off-by-one — reading 16-bit register that overlaps the next parameter | Modbus address table of the meter (Modbus base 0 vs 1) |
| Value matches for a few hours, then drifts lower; no jump on rollover | Energy counter wrapped past 32-bit integer boundary but the meter scales it as integer kWh | Meter's counter format (32-bit uint / 64-bit uint / float32) |
| Value correct for hours, then jumps to wrong number after 2–3 h | Scale factor mismatch (meter transmits Wh, PLC treats as kWh) and counter rollover in single register | Engineering unit / scaling register (often register 0x0002 or similar) |
| Value updates on PLC cycle but lags meter by N scans | MB_CLIENT poll rate set too low, or DONE bit not used to enable new request | MB_CLIENT instance DB, MODE parameter, DONE/ERROR handling |
| Modbus exception code 02 (ILLEGAL DATA ADDRESS) returned | Requested register range not supported by meter in a single transaction (max 125 registers on RTU) | Reduce LEN, split read into multiple MB_CLIENT calls |
3. S7-1200 REAL Data Type and IEEE 754
The S7-1200 REAL data type is a 32-bit single-precision IEEE 754 float:
| Field | Bits | Description |
|---|---|---|
| Sign | 31 | 0 = positive, 1 = negative |
| Exponent (biased by 127) | 30–23 | 8 bits, bias 127 |
| Mantissa (with implicit leading 1) | 22–0 | 23 bits |
Range: approx. ±3.402823e+38. Precision: ~7 significant decimal digits. NaN and ±Inf are valid encodings. The REAL data type itself does not limit the value the energy meter transmits; if the meter scales its energy register correctly into the float32 envelope, the S7-1200 will hold it. The limitation is therefore not in the PLC data type — it is in the interpretation of the four bytes returned by the meter.
Bit pattern that reads as NaN in REAL:
0x7FC00000
When Modbus data lands in the PLC as NaN, the tag will not update on subsequent successful polls until it is overwritten. This is one reason the value appears "frozen" after a transient wiring / addressing error.
4. Modbus RTU Energy Meter Register Architecture
Most modern kWh / MWh meters expose IEEE 754 float values across two consecutive 16-bit Holding Registers. The standard Modbus function code is 03 (Read Holding Registers). A typical register map looks like:
| Address (Modbus, 0-based) | Address (Modbus, 1-based / meter label) | Content | Size | Encoding |
|---|---|---|---|---|
| 0x0000 | 30001 | Voltage L-N | 2 regs (32 bits) | Float32, big-endian (ABCD) |
| 0x0002 | 30003 | Current L1 | 2 regs | Float32 |
| 0x0004 | 30005 | Active Power Total | 2 regs | Float32 |
| 0x0006 | 30007 | Power Factor | 2 regs | Float32 |
| 0x0008 | 30009 | Frequency | 2 regs | Float32 |
| 0x000A | 30011 | Active Energy Import (kWh) | 2 regs | Float32 |
| 0x000C | 30013 | Active Energy Export (kWh) | 2 regs | Float32 |
Two 16-bit registers — e.g. 0x000A and 0x000B — must be combined into one REAL. The order in which those four bytes are placed into the S7-1200 REAL determines whether the value is correct or garbage.
5. Byte Order and Word Swapping
Modbus RTU transmits data in big-endian order at the register level, but meter vendors differ on how the four bytes of a Float32 are split across the two 16-bit registers. The four possible byte orderings are:
| Name | Byte 0 (reg high) | Byte 1 (reg low) | Byte 2 (reg+1 high) | Byte 3 (reg+1 low) | Common Source |
|---|---|---|---|---|---|
| Big-endian (ABCD) | 3 | 2 | 1 | 0 | Modbus standard, most East-Asian meters (Acrel, CHINT, Eastron) |
| Byte-swap (BADC) | 2 | 3 | 0 | 1 | Some Schneider-compatible meters, some Carlo Gavazzi |
| Word-swap (CDAB) | 1 | 0 | 3 | 2 | Many European meters (Janitza, Bender, Phoenix Contact EMpro) |
| Little-endian (DCBA) | 0 | 1 | 2 | 3 | Rare; some WAGO / Beckhoff slaves |
Where the bytes 0…3 are the natural IEEE 754 order: Byte 0 = MSB of mantissa/LSB of exponent, Byte 3 = LSB of mantissa.
The S7-1200 stores REAL with the most significant byte at the lowest address. When the MB_CLIENT instruction places the received Modbus words into a data block, the bytes are written in the order received. If the meter transmits in CDAB but the S7-1200 expects ABCD, the resulting REAL is corrupted.
5.1 Why the value appears to "freeze" at a round number
When a 32-bit float is read with the wrong byte order, common results are:
- A number with the same magnitude and sign, but the mantissa scrambled. This often resolves to a value such as 223 = 8388608, or 224 = 16777216, or to the bit pattern 0x7F800000 which is +Infinity in IEEE 754. The S7-1200 will hold
+INFindefinitely — it never increments. - A near-zero value that drifts in the LSB only, because the mantissa bits of the real energy value are interpreted as the exponent bits of the resulting float.
- NaN (0x7FC00000), which propagates through any arithmetic and stops the user program from writing a new value to the tag.
5.2 Detecting the byte order in TIA Portal
Place the four raw bytes from the meter into a DB as a 4-byte ARRAY of BYTE and inspect them in the watch table. The meter's display value (e.g. 123.456 kWh) has a known IEEE 754 encoding. Compare against the captured bytes.
REAL_to_BYTES : ARRAY[0..3] OF BYTE
// For 123.456 kWh:
// IEEE 754 = 0x42F6E978
// Bytes in order: 42 F6 E9 78
// Modbus (ABCD): regs 0x42F6, 0xE978
// Modbus (CDAB): regs 0xE978, 0x42F6
If the captured first register is 0x42F6, the meter uses ABCD (big-endian). If the first register is 0xE978, the meter uses CDAB (word-swapped).
5.3 Correcting byte order on the S7-1200
The S7-1200 has no built-in byte-swap for a 4-byte REAL. Implement the swap explicitly in the user program. Two methods are common.
Method A — Swap on receive using two 16-bit operations:
// "MB_DATA_PTR" points to a DB that is 4 bytes long
// The Modbus read returns bytes in the order [B3 B2 B1 B0]
// In S7-1200 byte order, we need [B0 B1 B2 B3]
// Swap the two 16-bit words to go from CDAB to ABCD:
#tempWord := %DB#.RawBytes[0..1];
%DB#.RawBytes[0..1] := %DB#.RawBytes[2..3];
%DB#.RawBytes[2..3] := #tempWord;
#kWhValue := REAL_VARIANT_OF(%DB#.RawBytes);
Method B — Swap per-byte (handles all four orderings including BADC and DCBA):
#B0 := %DB#.RawBytes[0];
#B1 := %DB#.RawBytes[1];
#B2 := %DB#.RawBytes[2];
#B3 := %DB#.RawBytes[3];
// Target ABCD order for S7-1200 REAL:
%DB#.RawBytes[0] := #B3;
%DB#.RawBytes[1] := #B2;
%DB#.RawBytes[2] := #B1;
%DB#.RawBytes[3] := #B0;
For systematic handling of multiple 32-bit registers, use the Siemens library LSB / MSB swapping pattern in SCL:
// Reorder 32-bit value from CDAB to ABCD
FUNCTION "Swap32_CDAB_to_ABCD" : DWord
VAR_INPUT
inValue : DWord;
END_VAR
VAR_TEMP
wHi : Word;
wLo : Word;
END_VAR
BEGIN
#wHi := DWORD_TO_WORD(SHR(IN := #inValue, N := 16));
#wLo := DWORD_TO_WORD(#inValue AND 16#0000FFFF);
"Swap32_CDAB_to_ABCD" := SHL(IN := #wLo, N := 16) OR #wHi;
END_FUNCTION
6. Register Address Mapping Pitfalls
The single most common cause of an energy register "freezing" is the off-by-one between the meter's Modbus address and the S7-1200 MB_CLIENT configuration. The conventions are:
| Vendor Convention | Address Listed as "30011" | Meaning in MB_CLIENT |
|---|---|---|
| Meter label (1-based, as in manual) | 30011 = Holding Register 0x000A (11th register) | Use MODE = 0, DATA_ADDR = 10 (zero-based) or 11 (one-based) per TIA Portal help |
| Modbus standard 0-based | 30010 = Holding Register 0x0009 | Use DATA_ADDR = 9 |
| Siemens MB_CLIENT default (TIA Portal v15+) | DATA_ADDR matches the meter's "register number" minus 1 for 4xxxx | Set DATA_ADDR = 10 for label "30011" |
The MB_CLIENT block in the S7-1200 instruction set (TIA Portal V13 SP1 and later) accepts DATA_ADDR as a zero-based offset for holding registers. Read the TIA Portal inline help on the MB_CLIENT instance to confirm the version-specific behaviour; behaviour changed in V15 SP1 and again in V17.
If the configured address points to the wrong register, MB_CLIENT will read a different 32-bit block — for example the next meter's parameter, which may be an integer scaling factor. That value is then interpreted as a Float32, often producing a stable, slowly-incrementing number that does not match the meter's energy display.
7. Energy Counter Rollover and Scale
Energy meters typically have two implementation paths for the cumulative kWh register:
- Float32 with kWh units: no rollover for ~31 years at full load, but limited to ~7 significant digits of precision. Suitable for substation metering, problematic for low-power (e.g. < 1 kW) sites where the fractional part is important.
- Unsigned 32-bit integer with Wh units: 4 294 967 295 Wh max — ~480 years at 1 kW, but only ~12.5 years at 10 kW and ~14 months at 100 kW. Roll-over to 0 is the cause of a sudden "wrong" value after 2–3 hours at high load.
- Unsigned 64-bit integer: typically two consecutive 32-bit registers, no rollover, but requires two Modbus reads and a 64-bit assembly on the S7-1200.
If the meter manual lists the energy register as a 32-bit unsigned integer in Wh, the S7-1200 will read it as a REAL and produce nonsense once the integer exceeds 16 777 216 Wh (224). At 10 kW that occurs after ~70 days; the symptom appears much earlier if the meter stores kWh as an integer in 0.1 kWh steps and the actual value passes 6553.5 kWh.
For long-life installations, prefer meters that expose a 64-bit counter, then assemble on the PLC:
// Reading a 64-bit energy counter from two 32-bit Modbus words
// Register block returned by MB_CLIENT (4 regs = 8 bytes):
// [Lo-DW] [Hi-DW]
// S7-1200 LWORD assembly:
#HiDW := DWORD_TO_LWORD(%DB#.RawDWords[1]);
#LoDW := DWORD_TO_LWORD(%DB#.RawDWords[0]);
#EnergyTotal_Wh := SHL(IN := #HiDW, N := 32) OR #LoDW;
#EnergyTotal_kWh := DINT_TO_REAL(DWORD_TO_DINT(#EnergyTotal_Wh)) / 1000.0;
8. TIA Portal MB_CLIENT Configuration Checklist
| Parameter | Required Setting for Energy Meter Float32 Read | Common Mistake |
|---|---|---|
| REQ | Edge-triggered, fired after DONE of previous call | REQ held high — the block re-issues on every cycle, locking the bus |
| MB_ADDR | Meter Modbus address (1…247) | Using the meter's display ID instead of the RTU address |
| MODE | 0 = Read Holding Registers (function code 03) | Using 1 (coils) or 2 (input registers) by mistake |
| DATA_ADDR | Zero-based Modbus address (e.g. 10 for label 30011) | One-based — shifts read by one register |
| DATA_LEN | 2 for one Float32, 4 for two Float32, etc. | 1 — reads only the high 16-bit word, value appears stuck |
| DATA_PTR | Pointer to a non-optimised DB of length ≥ DATA_LEN × 2 bytes | Pointer to a tag in an optimised (sliced) DB — MB_CLIENT rejects it |
| DONE / ERROR / STATUS | Used to qualify the next REQ | Ignored — bad data is used by the application |
The MB_CLIENT instruction ships with the S7-1200 from firmware V4.0 and is documented in the Siemens function manual S7-1200 Programmable Controller — System Manual, chapter on "Communication". The block source (FB1083 / FB1084 in TIA Portal libraries) is available with the "SIMATIC S7-1200 Modbus/TCP" package on the Siemens Industry Online Support portal.
9. Step-by-Step Diagnostic Procedure
- Verify the bus. Run ModScan or Modbus Poll against the same physical port (or a tap) and confirm the energy register returns the correct value. If ModScan shows it correctly, the meter is not at fault.
- Capture the raw bytes in the PLC. Add a watch-table entry on the four raw bytes of the energy register. Force a known value on the meter (e.g. inject 100.000 kWh from the meter's test mode if supported) and read the bytes from the DB.
-
Compute the expected IEEE 754 bytes for that value. For 100.000 kWh:
0x42C80000→ bytes42 C8 00 00. For 123.456 kWh:0x42F6E978→ bytes42 F6 E9 78. - Compare against the captured bytes in all four orderings. The match identifies the meter's true byte order.
- Apply the swap routine in SCL/ST (Method A or B above) and confirm the REAL in the watch table now displays the meter's reading.
- Verify across the full range. Watch the value for 24 hours. If the value increments smoothly and matches the meter display to within the precision of float32, the swap is correct.
-
Check the register address. If the value is wrong but stable, try
DATA_ADDR = label - 1andDATA_ADDR = label - 40001(the two common conventions) and re-test. - Check the data length. If the value reads as an obviously truncated number, e.g. 16 384.0 or 0.0625, the read is grabbing a single 16-bit register and interpreting it as a Float32; set DATA_LEN to 2.
- Check the data block access mode. Confirm the destination DB is "standard" (non-optimised) access.
- Check the poll rate. Set MB_CLIENT MODE to a periodic trigger (e.g. 1–2 s for a single float read) and confirm DONE/ERROR are observed.
10. Edge Cases and Cross-Platform Notes
10.1 S7-1200 vs S7-1500
The S7-1500 uses the MB_CLIENT block from the same library family but with the "optimised access" flag respected on the destination DB from TIA Portal V16. The S7-1500 also supports the SWAP operation natively on certain data types when using the extended Modbus instructions, but the byte-order result is identical — the engineer must still identify the meter's byte order from the manual.
10.2 PROFINET gateways to Modbus RTU
If the S7-1200 reaches the meter through a PROFINET-to-Modbus-RTU gateway (e.g. Helmholz, Phoenix Contact, Insevis, Siemens PN/Modbus link), the gateway often offers a configurable byte order. Fix the swap once, either in the gateway or in the PLC — not both. Doing it twice will produce a value equally wrong as doing it zero times.
10.3 Modbus TCP to Modbus RTU bridging
When the S7-1200 (which natively supports Modbus TCP via MB_CLIENT on the PN interface) talks to a serial-energy meter through an external TCP-to-RTU converter, the same endian rules apply. Test with mbpoll or a similar Modbus TCP client against the converter before involving the PLC.
10.4 HMI / SCADA tag mirroring
When the REAL value is forwarded to an HMI (WinCC, Ignition, FactoryTalk View) the SCADA layer reads from the PLC tag, not from Modbus directly. The symptom in this case is that the HMI shows a stale value even though the PLC DB updates — this is a separate problem tied to HMI tag polling, and is the same root cause described in Ignition community threads on tag reflection. Confirm the underlying PLC DB is correct before troubleshooting the HMI.
10.5 Energy value stored as integer on the meter, scaled in the PLC
Some older meters expose the energy as a 32-bit unsigned integer in 0.01 kWh or 0.1 kWh steps. In that case, do not interpret the Modbus payload as a Float32. Read the four bytes as a DWORD, convert to DINT, then divide by the scale factor:
#rawDWORD := %DB#.RawBytesAsDWORD;
#kWh_INT := DINT_TO_REAL(DWORD_TO_DINT(#rawDWORD)) / 100.0; // 0.01 kWh units
10.6 Handling 64-bit energy counters
For 64-bit counters stored as two consecutive 32-bit unsigned integers (high DW at the lower Modbus address in big-endian meters, or at the higher address in little-endian meters — check the manual), read four 16-bit registers and assemble as LWORD. The S7-1200 supports LWORD arithmetic from firmware V4.2.
10.7 Save / retain behaviour
Persistent energy values must be stored in a retain DB to survive power cycle. On Modicon M340 and other Schneider controllers this is done with the SAVE attribute, controlled by system bit %S94 as documented in the Schneider Electric FAQ FA197511. The S7-1200 uses the equivalent "Set non-retain to retain" setting on the DB properties, applied to the energy counter DB only — never to the temporary MB_DATA_PTR DB.
11. Verification Procedure
- With the meter supplying a stable, known value, the S7-1200 DB byte layout matches the meter's documented byte order (or the explicit swap routine is in place).
- Over a 24-hour test, the PLC REAL matches the meter's local display to within ±0.01 kWh at every observation point.
- No NaN, INF, or out-of-range values appear in the DB at any point during the test.
- MB_CLIENT STATUS = 0 (no error) continuously; ERROR is not asserted.
- On a forced power cycle, the energy value is retained at the last successfully read value (within the retain-DB setting).
- When the meter counter rolls over (if applicable), the PLC value wraps cleanly to zero and continues incrementing — no negative values, no NaN.
12. Quick-Reference Troubleshooting Matrix
| Observed PLC Value | Modbus Master Sees | Meter Display | Root Cause | Fix |
|---|---|---|---|---|
| 0.0 then suddenly a large constant | Correct | Correct | DATA_ADDR wrong — reading a 16-bit scaling register as float | Set DATA_ADDR to the meter's 0-based float register address |
| Correct sign, but value ~ 1.7e+7 | Correct | Correct | Word-swap (CDAB) | Add word-swap routine in SCL |
| NaN, then stuck at NaN | Correct | Correct | Register boundary crossed, or optimised-DB access violation | Set DATA_LEN = 2; ensure DB is "standard" access |
| Drifts slowly, never matches | Correct | Correct | Scale factor mismatch — meter transmits Wh, PLC divides as kWh | Multiply or divide PLC value by 1000 to match meter units |
| Matches for hours, then jumps back to 0 | Wraps to 0 | Continues counting | Integer 32-bit counter rollover; meter does not transmit float | Switch to 64-bit energy register or use DINT interpretation |
| 0.0 always | 0x0000 / 0x0000 | Correct | Meter slave ID does not match MB_ADDR | Verify MB_ADDR and meter "RS-485 address" setting |
13. Recommended Best Practice
- Always read at least one full 32-bit register as a sanity check against the meter's display before commissioning the rest of the system.
- Store raw Modbus bytes in a separate "standard" DB; convert to REAL in a second, optimised DB. This isolates the Modbus layer from the application layer and lets the HMI/SCADA read the converted value without exposing the byte-swapping logic.
- Configure MB_CLIENT with a watchdog: if STATUS ≠ 0 for more than N consecutive cycles, latch a non-fatal fault and continue using the last good value.
- For a fleet of similar meters, capture the byte order once per vendor model and codify the swap routine in a reusable FB.
- For sites where the energy register is read across a power-cycle, place the energy DB in retain memory; do not use retain on the raw Modbus DB.
The original symptom — values stop updating after a threshold and only refresh every 2–3 hours — is a fingerprint of the IEEE 754 Infinity encoding combined with a slow MB_CLIENT poll rate, or of a misread 16-bit scaling register. Both are resolved by the diagnostic and correction steps above, and do not require a PLC firmware change, a different meter, or a workaround on the HMI.
FAQ
Is the S7-1200 REAL data type the cause of my Modbus energy value freezing?
No. The REAL (Float32) data type can hold values up to ~3.4×1038, far above any practical kWh count. The freeze is almost always caused by a wrong byte order, wrong register address, or wrong DATA_LEN in the MB_CLIENT configuration — the resulting IEEE 754 pattern is often +Inf or NaN, which the S7-1200 will hold indefinitely until overwritten by a successful read.
Why does my energy value match the meter for hours and then jump to a wrong number?
Two common causes: (1) a 32-bit integer energy register wrapped past its maximum and the meter does not transmit a float — switch to its 64-bit counter or interpret the raw DWORD as a DINT; (2) a DATA_ADDR off-by-one means the read occasionally grabs a different register that happens to contain a stable value — verify the address against the meter's Modbus map for the exact firmware version.
What is the right byte order for an IEEE 754 float over Modbus RTU?
There is no universal answer. The Modbus standard says the high-order 16-bit register is transmitted first, but meter vendors split the four bytes of the Float32 across the two registers in any of the four orderings ABCD, BADC, CDAB, or DCBA. Always confirm with the meter's register map; in practice, CDAB (word-swap) is the most common on European meters and ABCD (no swap) on Asian meters.
MB_CLIENT returns STATUS 16#80C8. What does that mean?
In TIA Portal V15+ this is most often a parameter error indicating that the destination DB has optimised block access. Create a dedicated "standard" (non-optimised) DB and point MB_CLIENT's MB_DATA_PTR to it. The raw Modbus bytes can then be copied from this DB into an optimised DB used by the application code.
How do I read a 64-bit energy counter with the S7-1200?
Set DATA_LEN to 4 (eight bytes), point MB_CLIENT at a standard DB of at least 8 bytes, then assemble the two DWORDs into an LWORD: high-shifted-LWORD OR low-DWORD. The LWORD holds up to 1.8×1019 Wh — effectively unlimited for any practical site. The S7-1200 supports LWORD arithmetic from firmware V4.2.