« Close

Datasheets and User Guides

App Notes

Software & Driver

 

Protocol Details

Modbus - Protocol Details [referenceable]

Lengthy (and confusing) detail about Modbus TCP can be found at modbus.org, but all the information actually needed is covered in the following.

The LabJack is a Modbus TCP Server.  A Modbus TCP Client can send a command to the LabJack and get back a response.  Sometimes a Server is called the Slave and a Client is called the Master.

Our Modbus TCP interface is quite simple.  It consists of a register map with addresses from 0 to 65535.  Each address points to a 16-bit value that might be readable, writable, or both.  Any function we support can be used to read or write values from any address.  The meaning of the registers are defined in the Modbus Map.

Note that in the Modbus spec, and the function documentation below, a register is specifically a 16-bit value.  In our Modbus Map we define some 32-bit values, and these are often referred to as a register, but when looking at the details of Modbus protocol realize that these are actually 2 registers.  For example, AIN0 is defined as a 32-bit value that is read starting at address 0.  AIN0 is actually stored in 2 registers: the MSW (most significant word) is at address 0 and the LSW (least significant word) is at address 1.

Modbus is big-endian, which means the most significant value is at the lowest address.  With a read of a 16-bit (single register) value, the 1st byte returned is the MSB (most significant byte) and the 2nd byte returned is the LSB (least significant byte).  With a read of a 32-bit (2 register) value, the value is returned MSW then LSW, where each word is returned MSB then LSB, so the 4 bytes come in order from most significant to least significant.

Packet size limits for the T7 are USB=64, Ethernet=1040, and WiFi=500 bytes.  USB Modbus packets on the U3 and U6 are limited to 64 bytes, including the 2 zeros appended to the command.

Modbus Functions

We support standard functions 3 (Read Multiple), 4 (Read One), 6 (Write One), and 16 (Write Multiple).  We also support a custom function called Modbus Feedback (MBFB) that can handle multiple reads & writes in a single packet.

Some Modbus clients will ask you to specify "Coil", "Holding", "Discrete", or "Input".  Choose "Holding", which should tell the client to use function 3, 4, 6, or 16.

Functions 4 and 6 are seldom used, so we will focus our discussion on functions 3, 16, and MBFB:

 

Read Multiple Registers (Function #3)
Standard Modbus function that reads 1 or more sequential registers from the specified starting address.

Command [# Bytes = 12]
Bytes 0-1:  0-65535 (Transaction ID, echoed by device)
Bytes 2-3:  0 (Protocol ID)
Byte 4:  0 (MSB of length)
Byte 5:  6 (LSB of length)
Byte 6:  1 (Unit ID)
Byte 7:  3 (Function #)
Bytes 8-9:  0-65535 (Starting register address, MSB-LSB)
Bytes 10-11:  1-127 (Number of registers to read, MSB-LSB)

Response [# Bytes = 9 + 2*#Registers, limit depends on device]
Bytes 0-3:  Echo of command bytes 0-3 (Transaction ID and Protocol ID)
Bytes 4-5:  3 + 2*#Registers (Length, MSB-LSB)
Byte 6:  1 (Unit ID)
Byte 7:  3 (Function #)
Byte 8:  2*#Registers
Bytes 9+:  Data

 

Write Multiple Registers (function #16)
Standard Modbus function that writes 1 or more sequential registers from the specified starting address.

Command [# Bytes = 13 + 2*#Registers, limit depends on device]
Bytes 0-1:  0-65535 (Transaction ID, echoed by device)
Bytes 2-3:  0 (Protocol ID)
Bytes 4-5:  7 + 2*#Registers (Length, MSB-LSB)
Byte 6:  1 (Unit ID)
Byte 7:  16 (Function #)
Bytes 8-9:  0-65535 (Starting register address, MSB-LSB)
Bytes 10-11:  0-65535 (Number of registers to write, MSB-LSB)
Byte 12:  2*#Registers
Bytes 13+:  Data

Response [# Bytes = 12]
Bytes 0-3:  Echo of command bytes 0-3 (Transaction ID and Protocol ID)
Byte 4:  0 (MSB of length)
Byte 5:  6 (LSB of length)
Byte 6:  1 (Unit ID)
Byte 7:  16 (Function #)
Bytes 8-9:  0-65535 (Starting register address, MSB-LSB)
Bytes 10-11:  0-65535 (Number of registers to write, MSB-LSB)

 

Modbus Feedback (MBFB, function #76)
Custom function that supports multiple frames, where each frame reads or writes 1 or more sequential registers.  Frames are executed in order.

Command
Bytes 0-1:  0-65535 (Transaction ID, echoed by device)
Bytes 2-3:  0 (Protocol ID)
Bytes 4-5:  0-65535 (Length, MSB-LSB)
Byte 6:  1 (Unit ID)
Byte 7:  76 (Function #)
Bytes 8+:  Frames

Read Multiple Frames
Frame Byte 0:  0 (Frame Type)
Frame Byte 1-2:  0-65535 (Starting register address)
Frame Byte 3:  1-255 (Number of registers to read)

Write Multiple Frames
Frame Byte 0:  1 (Frame Type)
Frame Byte 1-2:  0-65535 (Starting register address)
Frame Byte 3:  1-255 (Number of registers to write)
Frame Byte 4+:  Data

Response
Bytes 0-3:  Echo of command bytes 0-3 (Transaction ID and Protocol ID)
Bytes 4-5:  0-65535 (Length, MSB-LSB)
Byte 6:  1 (Unit ID)
Byte 7:  76 (Function #)
Bytes 8+:  Data (Response for read frames)

 

  • Transaction ID:  The device echos this value.  Use to match responses with commands.
  • Protocol ID:  Not used.  Just pass 0.
  • Length:  The number of bytes after the "Length" parameter, per the Modbus spec.
  • Unit ID:  Not used.  Pass 1 to fit with convention.
  • Function #:  3, 4, 6, or 16 are standard Modbus "holding" functions.  76 is our custom MBFB function.
  • Address:  A 16-bit address that points to a 16-bit register.
  • Register:  A 16-bit value pointed to by an address.  Registers are defined in our Modbus Map.
  • Data:  Data to write to registers are data read from registers.


In the event of an error, all functions return the standard Modbus error response, which means you get back the 8-byte header above but bit 7 of byte 7 (function #) will be set, so the value is 131/132/134/144/204 rather than 3/4/6/16/76.  You then get a 9th byte, which is an official Modbus spec errorcode:

#define ILLEGAL_FUNCTION 0x01
#define ILLEGAL_DATA_ADDRESS 0x02
#define ILLEGAL_DATA_VALUE 0x03
#define SLAVE_DEVICE_FAILURE 0x04
#define ACKNOWLEDGE 0x05
#define SLAVE_DEVICE_BUSY 0x06
#define MEMORY_PARITY_ERROR 0x08
#define GATEWAY_PATH_UNAVAILABLE 0x0A
#define GATEWAY_TARGET_NO_RESPONSE 0x0B

Anytime there is an error, further information is available by reading from a group of 4 registers that start at 55000.  There are 8 of these groups with starting addresses from 55000 to 55028.  For example, the first group is:

55000: LabJack Error Code #0
55001: Error Frame #0
55002: Modbus Error #0
55003: Transaction ID #0

The standard Modbus functions (3/4/6/16) only use the first error information group shown above.

MBFB only uses the other 7 groups with starting addresses from 55004 to 55028.  If you get an error response from an MBFB command, look at the upper nibble of the 9th byte to get an offset that tells you which error information group to look at.  So with MBFB the starting address for the error information group of 4 registers, is 55000 + (4 * UpperNibble9thByte).

With MBFB, if a frame generates an error, no further frames are executed.  Frames before the ErrorFrame are executed (e.g. outputs are set), but no data is returned for those frames (since you just get the standard Modbus error response).

On the U3/U6, when a Modbus command is sent by USB the low-level packet must have 2 zeros appended to the front.  This is how the U3/U6 knows that the packet (after the 2 zeros) is Modbus, and not the normal low-level protocol used by the U3/U6.  The response does not have anything added and is pure Modbus.

Examples

Read FIO0

FIO0 is a UINT16 (single register) at address 2000.  It should read 1 if floating, and 0 if you jumper it to GND. Here is a packet captured with Wireshark:

Command:  0x00 0x00 0x00 0x00 0x00 0x06 0x01 0x03 0x07 0xD0 0x00 0x01
Response:  0x00 0x00 0x00 0x00 0x00 0x05 0x01 0x03 0x02 0x00 0x01


The first 8 bytes in both packets are Transaction ID (0x0000), ProtocolID (0x0000), Length (0x0006 or 0x0005), UnitID (0x01), and Function# (0x03).

In the command, the last 4 bytes are Address (0x07D0) and #Registers (0x0001).  0x07D0 is decimal 2000.

In the response the last 3 bytes are 2*#Registers (0x02) and Data (0x0001).  We get 2*#Registers = 2 as expected, and the data value of our read is 1 meaning that FIO0 is reading high.

Read TEST

TEST starts at address 55100.  If you read a UINT32 (2 registers) from here, you should get 0x00112233 (d1122867), or if you read just a UIN16 (1 register) from here you should get 0x0011 (d17).

UINT32 read:
Command:  0x00 0x00 0x00 0x00 0x00 0x06 0x01 0x03 0xD7 0x3C 0x00 0x02
Response:  0x00 0x00 0x00 0x00 0x00 0x07 0x01 0x03 0x04 0x00 0x11 0x22 0x33


UINT16 read:
Command:  0x00 0x00 0x00 0x00 0x00 0x06 0x01 0x03 0xD7 0x3C 0x00 0x01
Response:  0x00 0x00 0x00 0x00 0x00 0x05 0x01 0x03 0x02 0x00 0x11


Write TEST_UINT32

Write the value 0xC0BCCCCD to TEST_UINT32 which starts at address 55120.

Write using function d16 (0x10):
Command:  0x00 0x00 0x00 0x00 0x00 0x0B 0x01 0x10 0xD7 0x50 0x00 0x02 0x04 0xC0 0xBC 0xCC 0xCD
Response:  0x00 0x00 0x00 0x00 0x00 0x06 0x01 0x10 0xD7 0x50 0x00 0x02


Read back using function d3 (0x03):
Command:  0x00 0x00 0x00 0x00 0x00 0x06 0x01 0x03 0xD7 0x50 0x00 0x02
Response:  0x00 0x00 0x00 0x00 0x00 0x07 0x01 0x03 0x04 0xC0 0xBC 0xCC 0xCD


If your Modbus client supports 32-bit integers and/or floating point values, this is a good register to test that.  First write 0xC0BCCCCD as described above.

0xC0BCCCCD is 3233598669 as a decimal 32-bit unsigned integer.  0xC0BCCCCD is -1061368627 as a decimal 32-bit signed integer.  Have your client read 55120 as a UINT32 and INT32 and confirm you get those values.  If instead you get 3436036284 and -858931012 your client has swapped the word order.

0xC0BCCCCD is equal to -5.90 as a float, so if you read 55120 as a 32-bit float you should get -5.90.  If you instead get -107873760.0, your client is swapping the words and interpreting the data as 0xCCCDC0BC.

Write DAC0

DAC0 is a FLOAT32 value starting at address d1000 (0x03E8).  We will set DAC0 to 3.3 volts, which is 0x40533333 in hex.

Command:  0x00 0x00 0x00 0x00 0x00 0x0B 0x01 0x10 0x03 0xE8 0x00 0x02 0x04 0x40 0x53 0x33 0x33
Response:  0x00 0x00 0x00 0x00 0x00 0x06 0x01 0x10 0x03 0xE8 0x00 0x02

Useful FLOAT32 values in hex:
0.0 = 0x00000000
3.3 = 0x40533333
5.0 = 0x40A00000

Read T7 Product ID (Search network for a device)

Searching for a device is typically handled by the LJM_ListAll() function, but this is how to do it yourself.  Broadcast a UDB modbus feedback packet asking for the product ID (address 60000).

Read product ID:
Command:  0x00 0x00 0x00 0x00 0x00 0x06 0x01 0x4C 0x00 0xEA 0x60 0x02
Response:  0x00 0x00 0x00 0x00 0x00 0x06 0x01 0x4C 0x00 0x00 0x00 0x07