Decoding INT32-M10K Modbus Registers in Rapid SCADA

Daniel Price8 min read
ModbusOther ManufacturerTechnical Reference
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

Problem Overview

Energy meters and power-quality devices frequently publish 32-bit quantities in INT32-M10K (signed 32-bit, Modulus-10000) format rather than a straight two's-complement 32-bit integer. A generic Modbus master that reads two registers and casts them to int32 returns a wrong number, because the two words are not binary-weighted halves — they are decimal-weighted digits.

Typical register map entry from a device documentation table:

Label Address Registers Format
kWh del 40089 2 INT32-M10K

Rapid SCADA has no built-in M10K element type in the Modbus driver, so the decode has to be done in the configuration database using channel formulas. The pattern below splits the pair into two 16-bit reads and recombines them in a calculated channel.

How INT32-M10K Is Encoded

The value is expressed as two signed decimal groups of four digits, with the high group weighted by 10,000:

Value = RegisterHigh * 10000 + RegisterLow

Both registers are signed 16-bit integers, and both carry the sign of the overall value. Worked example for -12,345,678:

Word Decimal Hex (16-bit, two's complement) Contribution
Register high (40089) -1234 FB2E -1234 × 10000 = -12,340,000
Register low (40090) -5678 E9D2 -5,678
Result -12,345,678

Key consequences of the encoding:

  • The low word is constrained to -9999 … 9999. Any low word outside that band means you are reading the wrong register, the wrong word order, or the device is not actually using M10K.
  • A plain two's-complement 32-bit cast of FB2E E9D2 yields -81,530,414, not -12,345,678. This mismatch is the fastest way to confirm a device is using M10K.
  • Zero has one representation only when both words are zero; there is no separate negative-zero condition to trap.
  • The same family often extends to INT64-M10K across four registers with weights 1012, 108, 104, 1. Verify the weighting against your device manual before reusing the formula for four-register points.
Word order: the example places the high group at the lower register address (40089) and the low group at 40090. Do not assume this for every device — confirm the order in the device register map, and re-verify after any firmware change on the meter.

Communicator Configuration: Read the Words Separately

Configure the Modbus device in Communicator so each register lands in its own tag, rather than letting the driver combine them.

  1. Add the Modbus device and create a holding-register element group starting at the mapped address. Remember the 4xxxxx convention: 40089 normally corresponds to protocol address 88 (zero-based) with function code 03. If the device documentation is zero-based already, use 89. Confirm by reading a register with a known constant value.
  2. Define two elements of 1 register each — for example kWh_del_hi and kWh_del_lo — instead of one 2-register int element.
  3. Select a 16-bit element type. Reading as unsigned (ushort) is acceptable and often more portable, because the sign is restored in the formula. If the driver offers a signed 16-bit type and the device conforms strictly to the format, that also works and removes one conversion step.
  4. Bind each element to an input channel in the configuration database. Note the channel numbers — the decode formula references them.

Practical channel allocation example:

Channel Name Role
101 kWh del high (raw) Input, raw 16-bit word, hidden from operators
102 kWh del low (raw) Input, raw 16-bit word, hidden from operators
103 kWh del Calculated, engineering value in kWh

Formula Implementation

Rapid SCADA compiles channel formulas as C# expressions, and you can add reusable helper methods in the formula source of the configuration database (Formulas table in v5, Scripts in v6). Add one helper and call it from a calculated channel:

// Reusable helper: decode signed 32-bit Modulus-10000
// hi, lo are raw 16-bit register values read as unsigned (0..65535)
public double DecodeM10K(double hi, double lo)
{
    if (hi > 32767) hi -= 65536;   // restore two's complement sign
    if (lo > 32767) lo -= 65536;
    return hi * 10000 + lo;
}

Formula for the calculated channel 103:

DecodeM10K(Val(101), Val(102))

If the Communicator element type is already signed 16-bit, the two if lines are no-ops and can be dropped:

Val(101) * 10000 + Val(102)

A minimal two-channel variant, applying a scaling formula on each raw input channel and summing them, also works — multiply the high channel by 10,000 in its own input-channel formula (the raw value is available as Cnl in the input channel formula) and add the two in the display channel. That approach is simpler to type but harder to maintain, because the ×10,000 scaling is invisible on the calculated channel and breaks silently if the sign conversion is missing on one of the two channels.

Quality propagation: a calculated channel returns a number even when one source channel is stale or in error. Guard the result with the source channel status so the operator does not see a plausible-looking half-decoded value, for example by testing Stat(101) and Stat(102) before returning the decoded value and returning an undefined status otherwise. Check the formula reference for your Rapid SCADA version for the exact status constants and the current status-setting syntax.

Version Notes and Data Typing

Item Rapid SCADA 5 Rapid SCADA 6
Helper code location Formulas table in Administrator Scripts / formula source in Administrator
Channel that carries the decode Input channel of calculated type, Formula field Channel with data type set to calculated, Formula field
Reference to another channel Val(n), Stat(n) Val(n), Stat(n)
Raw value of own channel Cnl Verify in the version's formula reference

Channel values are stored as double precision. A 53-bit mantissa represents every integer up to 253 exactly, so the full M10K range decodes with no rounding error — including the INT64-M10K case up to 1016 only if you stay below 9,007,199,254,740,992. Above that, the least significant digits of a 64-bit totalizer are no longer exact in a double.

Driver and module source, plus the issue tracker for the current release line, are published in the official repository: https://github.com/RapidScada/scada-v6. If your device family needs M10K natively, the Modbus driver source there is the starting point for adding a custom element type instead of doing the decode in formulas.

Verification and Troubleshooting

  1. Static check. With the device running, note the two raw values in the Communicator device data page. Compute hi * 10000 + lo by hand and compare with the meter's local display. They must match digit for digit.
  2. Sign check. Force or wait for a negative reading (import/export energy, reactive power, signed power factor). Both words must go negative together. If only the high word changes sign, the device violates the format — see below.
  3. Word-order check. If the decoded value is unstable and huge while the meter display is small, the words are swapped. Swap the arguments: DecodeM10K(Val(102), Val(101)).
  4. Range check. Add a temporary calculated channel that outputs Val(102) alone. If it ever exceeds ±9999, the low register is not an M10K low word.
  5. Rollover check. Watch the transition as the low word crosses 9999 → 0. The high word must increment by exactly 1 and the decoded total must increase by 1, with no step of 10,000 or 65,536.

Common Failure Modes

Symptom Likely cause Fix
Value is roughly 6.5× too large or negative when it should be small Unsigned 16-bit raw not sign-corrected; a value of 65,536 or 655,360,000 offset appears Apply the > 32767 → -65536 correction to both words
Decoded value off by a multiple of 65,536 only when negative Non-conforming device sends the low word unsigned while the high word is signed Sign-correct the high word only, then add the unsigned low word; validate against the meter display across zero
Result matches a plain int32 cast, not M10K Point is actually INT32 or UINT32, not M10K Re-read the register map; use the driver's native 2-register integer element
Energy totalizer resets or jumps backwards Counter rollover at the device limit, or the two registers were read in separate Modbus transactions across an update boundary Read both registers in one request block; add rollover handling in the formula or use a totalizer channel
Value updates only on one word The two elements are in different polling groups with different periods Place both registers in the same element group so they are acquired atomically
Atomicity matters for energy counters. If the high and low words are fetched in different requests, a read that straddles a rollover produces a one-time error of up to 10,000 counts. Keep both registers inside a single contiguous element group and single function-03 request.

Scaling to Engineering Units

M10K decoding produces the raw counter value only. Apply the device's documented energy scale factor separately — many meters publish kWh in whole units, others in 0.1 kWh or Wh. Extend the calculated channel formula rather than changing the raw channels, so the raw counter stays available for diagnostics:

DecodeM10K(Val(101), Val(102)) * 0.1   // device reports 0.1 kWh per count

Set the channel's unit and display format in the configuration database so the operator sees the correct decimal places, and set the channel type to a counter/totalizer type if you want Rapid SCADA to compute consumption between periods.

What does INT32-M10K mean in a Modbus register map?

It is a signed 32-bit value encoded as two decimal-weighted 16-bit registers: Value = RegisterHigh * 10000 + RegisterLow. Both registers are signed, and the low register is limited to -9999 through 9999.

Why does my int32 read of an M10K point return the wrong number?

A standard int32 cast treats the words as binary-weighted (high × 65536), but M10K weights the high word by 10,000. For example, FB2E E9D2 casts to -81,530,414 but decodes as -12,345,678 in M10K.

How do I convert an unsigned 16-bit Modbus register to a signed value in a Rapid SCADA formula?

Subtract 65536 when the raw value exceeds 32767: if (v > 32767) v -= 65536;. Apply it to both the high and low words before combining them.

Where do I put reusable decode code in Rapid SCADA?

Add the helper method to the formula source in Administrator (Formulas table in version 5, Scripts in version 6), then call it from a calculated channel's Formula field using Val(n) to reference the raw input channels.

How can I confirm the high and low registers are not swapped?

Check that the register mapped as the low word always stays within -9999 to 9999 and that the decoded total matches the meter's local display. If the value is wildly large and jumps unpredictably, swap the two arguments in the decode call.

Back to blog