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 0
. AIN0
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 pass0
.Length
: The number of bytes after the "Length" parameter, per the Modbus spec.Unit ID
: Not used. Pass1
to fit with convention.Function #
:3
,4
,6
, or16
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 #055001
: Error Frame #055002
: Modbus Error #055003
: 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