Skip to main content
Skip table of contents

Protocol Details [Direct Modbus TCP]

In-depth information about Modbus TCP can be found at modbus.org. This page summarizes all of the LabJack Modbus protocol implementation details that are required to control LabJack devices via Modbus TCP.

Overview

LabJack T-series devices are Modbus TCP Servers.  The U3 and U6 also have deprecated Modbus support.

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.

Modbus Registers Are 16-bit, LabJack Values Are One or More Modbus Registers

In the Modbus spec and in 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, these are actually 2 Modbus registers.  For example, AIN0 is defined as a 32-bit value that is read starting at address 0AIN0 is actually stored in two registers: the MSW (most significant word) is at address 0 and the LSW (least significant word) is at address 1.

Big-Endian

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

Packet size limits for the T8 are USB=512 and Ethernet=1040 bytes.

Packet size limits for the T7 are USB=64, Ethernet=1040, and WiFi=500 bytes.

Packet size limits for the T4 are USB=64 and Ethernet=1040 bytes.

Modbus packets on the U3 and U6 are limited to 64 bytes, including the two 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 76 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 76 (MBFB):

Fields

  • 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.

Function Details

Read Multiple Registers (Function #3)

Standard Modbus function that reads one 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 one 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 one 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)

Error Responses

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 each Function # field will have a different the value:

Function Name

Function #

Function # with Error

Read Multiple

3

131

Read One

4

132

Write One

6

134

Write Multiple

16

144

Modbus Feedback

76

204

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

Any time there is an error, further information is available by reading from a group of four registers that start at 55000. There are eight 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 seven 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 four registers, is 55000 + (4 * UpperNibble9thByte).

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

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 eight bytes in both packets are Transaction ID (0x0000), ProtocolID (0x0000), Length (0x0006 or 0x0005), UnitID (0x01), and Function# (0x03).

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

In the response the last three 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 FLOAT32, 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 T-Series 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 UDP 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

T8 Response: 0x00 0x00 0x00 0x00 0x00 0x06 0x01 0x4C 0x41 0x00 0x00 0x00

T7 Response: 0x00 0x00 0x00 0x00 0x00 0x06 0x01 0x4C 0x40 0xE0 0x00 0x00

T4 Response: 0x00 0x00 0x00 0x00 0x00 0x06 0x01 0x4C 0x40 0x80 0x00 0x00

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.