Where does the transfer actually stop?
Follow the packet backwards. The Automation Runtime Modbus TCP client opens a socket to the MPR-47SE on port 502 and turns each configured command block into one MBAP-framed PDU: transaction ID, protocol ID 0, length, unit ID, function code, start address, quantity. Error 9012 fires before any of that exists. It is a build-time consistency check inside Hardware.hw, raised while the hardware configuration is compiled, so cabling, switch port, duplex, IP addressing and firewall rules are all irrelevant to it. Layer one is fine; the configuration is internally contradictory.
The check is narrow and mechanical. Cb_3_NumItems holds the quantity field that command block 3 will put on the wire. The channel list attached to block 3 defines how the returned register block gets carved into process variables. The compiler sums the width of those channels, compares it to the quantity, and refuses to build on mismatch: "Correct the number of items to 2 or set it to 0 for autocalculation." The suggested value of 2 is not a hint about the meter. It is the compiler telling you exactly what your channel list currently covers.
Why does the compiler compute 2 when you asked for 4?
NumItems counts protocol items of the selected function code, not bytes and not variables. For FC3 (Read Holding Registers) and FC4 (Read Input Registers) an item is one 16-bit register. For FC1/FC2 an item is one bit. A 64-bit unsigned value starting at register 216 occupies 216, 217, 218 and 219 — four items. A single 32-bit channel, whether REAL, DINT or UDINT, covers two.
| Channel data type | Width | Registers consumed | NumItems contribution (FC3/FC4) |
|---|---|---|---|
| BOOL (coil / discrete block) | 1 bit | n/a | 1 bit item |
| INT / UINT | 16 bit | 1 | 1 |
| DINT / UDINT / REAL | 32 bit | 2 | 2 |
| 64-bit type, if offered by the channel list | 64 bit | 4 | 4 |
One REAL mapped to block 3 therefore produces a computed width of 2 against a requested 4, and the build stops. Even with the counts reconciled, that channel returns garbage. Register-class integers belong in integer channels.
Which fix: autocalculation, explicit count, or split blocks?
| Approach | Cb_3_NumItems | Channel list for block 3 | Registers on the wire | Trade-off |
|---|---|---|---|---|
| A — Autocalculation | 0 |
Unchanged (one 32-bit channel) | 216–217 only | Build passes immediately, but you silently read half the value with no diagnostic |
| B — Explicit count, widened channels | 4 |
Two 32-bit unsigned channels, or four 16-bit | 216–219 | The 9012 check stays armed and catches any later channel edit |
| C — Two command blocks |
2 each |
One 32-bit channel per block | 216–217 and 218–219 | Two PDUs per cycle, two round trips, halves not sampled atomically |
Approach A is what the error message suggests and it is the trap. Autocalculation makes the quantity follow the channel list, which guarantees the build succeeds and guarantees nothing about the data: with two registers mapped, block 3 requests two registers, returns the high half of the counter, and looks entirely healthy in the I/O mapping. Approach C costs an extra request per poll and lets a counter increment between the two responses, which corrupts the reassembled value at rollover of the low word.
Take approach B. Widen the channel list to the full 64-bit span, then leave Cb_3_NumItems at 4. The explicit value is a cross-check, not overhead — the next engineer who deletes a channel gets a build error instead of a plausible wrong number.
How do you map a 64-bit register in ModbusTcp_any?
- Open
Hardware.hw, select the ModbusTcp_any slave, and expand command block 3. - Confirm the function code matches the register class in the analyzer's map. FC3 for holding registers (documented as 4x references), FC4 for input registers (3x). Reading a 4x address with FC4 returns exception code 02, illegal data address, not data.
- Set the start address in the base the configurator expects. Protocol address 216 is the register documented as reference 40217 when the vendor lists 4x numbers. If the first read returns the neighbouring quantity from the map, you are off by one — shift by 1 and re-read rather than adjusting scaling.
- Build the channel list to total four registers: one 64-bit channel if the data type list offers one, otherwise two 32-bit unsigned channels (high pair, then low pair). Do not use REAL for an integer counter.
- Set
Cb_3_NumItemsto4and rebuild. The 9012 entry disappears from the build log. - Map the channels to process variables in the I/O mapping and recombine in the application:
How do you verify the map matches the wire?
- Watch both 32-bit halves in the watch window across a load change. The low half must increment while the high half stays put. If the high half moves per sample and the low half is static, the word order is reversed — swap the two channels in the reassembly, not in the block.
- Compare the reassembled value against the reading on the analyzer's own display, with the vendor's divisor applied. An energy counter off by exactly 2^32 means one half is missing or misplaced; off by a power of ten means scaling, not mapping.
- Rebuild once more after the transfer and confirm the build log contains no 9012 line for any
Cb_x_NumItemsproperty — the other blocks are checked by the same rule and a widened channel elsewhere will surface here.
What breaks next on this class of link?
Quantity limits bite on the first large block. FC3 and FC4 cap at 125 registers per request, FC1 and FC2 at 2000 bits; a map read in one sweep must be split into blocks under those ceilings regardless of what the channel list would allow. Each block is one function code over one contiguous span — mixing holding and input registers, or jumping a gap, requires separate blocks or filler channels, because autocalculation will otherwise shrink the request to the mapped words and leave the tail unread.
Unit ID is the other recurring stop. Poll rate and response timeout matter on power analyzers that compute over 64-bit accumulators: a timeout tuned for a fast I/O slave shows up as intermittent block failure with a perfectly valid configuration. And when the reassembled counter is finally correct, keep it in a 64-bit or scaled variable — assigning it to a DINT truncates silently at 2,147,483,647 and the trend goes flat with no error anywhere.
FAQ
Why does Automation Studio report error 9012 when the register count matches the device map?
NumItems counts Modbus registers, and the compiler compares it against the summed width of the channels mapped to that block. One 32-bit channel totals 2 registers, so requesting 4 fails until the channel list covers all four registers of the 64-bit value.
Why does setting Cb_3_NumItems to 0 clear the error but leave the value wrong?
Autocalculation sizes the request from the channel list. With two registers still mapped, block 3 requests registers 216–217 only and returns the high half of the counter, which reads as a plausible number with no diagnostic.
Why does the 64-bit value read back as nonsense after the item count is fixed?
Either the channel type or the word order. A 64-bit unsigned counter mapped to a REAL channel is reinterpreted as a float; and if the analyzer transmits the low register pair first, the two halves must be swapped before shifting the high pair left by 32.