This article should be useful if you are writing your own Modbus communication software. These issues generally don’t cause problems if you are using existing Modbus software or devices.
Confusion about Register Addresses
In the Modbus.org standard documents, holding register addresses are given a prefix of “4” to distinguish them from other register types. For example, in their documentation a holding register at address 1001 is referred to by “41001”. However, the leading “4” is not really part of the address and most programs dispense with this extra prefix, especially when the program context makes it clear that it is referring to a holding register. Also, the Modbus.org standard documents refer to register addresses using “one-based” numbering. However, the addresses that are actually sent in a Modbus command message are “zero-based”. For example, to read register 1001, the address that is actually sent in the command message is 1000.
Confusion about Little-Endian vs. Big-Endian Word Order
Although Modbus.org standard documents provide some guidance for implementing the Modbus protocol, they do not address the question of word order beyond the 16-bit register level. For the 16-bit register values, the most significant byte always precedes the least significant byte (in other words, the register value is transferred in Big-Endian byte order). This is because when the Modbus standard was created in the late 1970’s, most processors used a Big-Endian memory architecture (where the most significant part of a multi-byte value is stored at a lower memory address). However when the need for transferring 32-bit (i.e. 4 byte) values with the Modbus protocol later came about in the 1980’s, Little-Endian Intel processors dominated the PC market so most vendors chose to map the least significant word onto the lower address of the register pair.
This lack of standardization for values larger than 16 bits has resulted in a situation where Modbus implementers have to make an arbitrary choice as to which address of the register pair contains the most significant word of 32-bit values such as IEEE-754 single-precision floats and signed or unsigned 32-bit integers. Most programs for communicating with Modbus slaves can be configured for either register word order.
Since the most common default word order today is Little-Endian, that is the word order that is used in the WattNode® meter. For example, a 32-bit integer value of decimal 16909060 (or 0x01020304 hexadecimal) is mapped onto two holding registers, 1001 and 1002, where register 1001 contains the least significant word (0x0304) and register 1002 contains the most significant word (0x0102).
The WattNode meter’s EnergySum register is available as either a 32-bit float or a signed 32-bit integer by reading registers 1001 and 1002 or registers 1201 and 1202, respectively. For signed integers you must allow for negative values (2’s complement). An example of the C language code for doing a signed integer conversion on a Little-Endian machine is:
Byte Result; // Each Byte is an unsigned char Byte ReplyBuf; // Reply buffer. First 3 bytes are address, function // code and byte count. 4th byte is start of Energy read Int32 IntEnergy; // Signed 32-bit integer energy sum for all 3 phases float FltEnergy; // Float energy sum for all 3 phases // 5th byte from meter (MS byte of Big-endian 16-bit register which is LS word of Little-endian 32-bit value) Result = ReplyBuf; // 4th byte from meter (LS byte of Big-endian 16-bit register which is LS word of Little-endian 32-bit value) Result = ReplyBuf; // 7th byte from meter (MS byte of Big-endian 16-bit register which is MS word of Little-endian 32-bit value) Result = ReplyBuf; // 6th byte from meter (LS byte of Big-endian 16-bit register which is MS word of Little-endian 32-bit value) Result = ReplyBuf; // Cast 4-byte buffer to a signed 32-bit integer (EnergySum read from WattNode registers 1201 and 1202) IntEnergy = *((Int32 *)Result);
When reading the float EnergySum from registers 1001 and 1002, the only difference is in how the cast is done:
// Cast 4-byte buffer to a float (EnergySum read from registers 1001 and 1002) FltEnergy = *((float *)Result);
In the examples above, the size and type of the variables are defined by the following C language “typedef” statements that are usually included in a C header file (also called an “include file”):
typedef unsigned char Byte; typedef signed long Int32;
This avoids subtle type conversion problems that can arise due to inconsistencies in the default behavior of various C compilers (e.g. some compilers treat a char as a signed 8-bit value while others treat it as an unsigned value). By explicitly telling the compiler what the size and type (signed vs. unsigned) of the variables are, the typedef syntax avoids reliance upon default compiler behavior and the potential type conversion bugs that could result from that.