What is Modbus?
Modbus is the oldest and most widely used industrial communication protocol, developed by Modicon in 1979. Originally designed for serial communication between programmable logic controllers (PLCs), it has remained the dominant field bus for industrial automation for over four decades.
Two transport variants are in active use today: Modbus RTU runs over RS-485 serial lines and is common in legacy equipment, sensors, and embedded devices. Modbus TCP encapsulates the same protocol over standard Ethernet on port 502, enabling easy integration with modern IT infrastructure and cloud SCADA systems.
Modbus is found in PLCs, variable-frequency drives (VFDs), energy meters, SCADA systems, building automation controllers, solar inverters, battery management systems, and thousands of other industrial devices.
Ubuntu/Debian: sudo apt install libmodbus-dev
Modbus Data Model
Every Modbus device exposes four address spaces. Each space has its own function codes for reading and writing.
| Space | Width | Access | Typical Use |
|---|---|---|---|
| Coils | 1-bit | Read / Write | Digital outputs (DO) — relay states, actuator commands |
| Discrete Inputs | 1-bit | Read only | Digital inputs (DI) — limit switches, push-buttons |
| Holding Registers | 16-bit | Read / Write | Analog outputs / configuration — setpoints, mode selectors |
| Input Registers | 16-bit | Read only | Measurements — voltage, current, temperature, flow |
Function Codes
| Code | Name | Direction |
|---|---|---|
| FC01 | Read Coils | Master reads 1-bit outputs from slave |
| FC02 | Read Discrete Inputs | Master reads 1-bit inputs from slave |
| FC03 | Read Holding Registers | Master reads 16-bit R/W registers from slave |
| FC04 | Read Input Registers | Master reads 16-bit R/O registers from slave |
| FC05 | Write Single Coil | Master forces one coil ON or OFF |
| FC06 | Write Single Register | Master writes one 16-bit holding register |
| FC15 | Write Multiple Coils | Master forces a block of coils in a single request |
| FC16 | Write Multiple Registers | Master writes a block of holding registers in one request |
RTU vs TCP
Types
type
TMBContext = Integer; { Modbus context handle }
TMBMode = (
mbRTU = 0, { serial RTU }
mbTCP = 1 { TCP/IP }
);
TMBParity = (
mbParNone = 0, { 'N' }
mbParEven = 1, { 'E' — most common in Modbus }
mbParOdd = 2 { 'O' }
);
TMBError = (
mbeIllegalFunction = 1,
mbeIllegalDataAddress = 2,
mbeIllegalDataValue = 3,
mbeSlaveDeviceFailure = 4,
mbeAcknowledge = 5,
mbeSlaveDeviceBusy = 6,
mbeMemoryParityError = 8,
mbeGatewayPathUnavailable = 10,
mbeGatewayTargetFailed = 11
);
API Reference
Context Creation
| Function | Description |
|---|---|
| NewRTU(Device, BaudRate, Parity, DataBits, StopBits) | Create a Modbus RTU context. Device is the serial port path ('/dev/ttyUSB0', 'COM3'). BaudRate: 9600, 19200, 115200, etc. DataBits is almost always 8. StopBits: 1 or 2. |
| NewTCP(Host, Port) | Create a Modbus TCP context. Host is the slave IP address or hostname. Standard port is 502. |
| FreeContext(Ctx) | Release all memory associated with the context. Call after Disconnect. |
Connection
| Function | Description |
|---|---|
| Connect(Ctx) | Open the serial port (RTU) or establish a TCP connection (TCP). Raises an exception on failure. |
| Disconnect(Ctx) | Close the connection and release the socket or serial port handle. |
| SetSlave(Ctx, SlaveID) | Set the target slave device address (1..247 for RTU; usually 1 or 255 for TCP). Must be called before any read/write. |
| SetTimeout(Ctx, TimeoutMs) | Set the response timeout in milliseconds. Default is 500 ms. Increase for slow serial links or noisy networks. |
| SetDebug(Ctx, Enable) | Enable or disable debug output to stderr. Prints raw Modbus frames — useful for protocol-level troubleshooting. |
Read Functions
| Function | Description |
|---|---|
| ReadCoils(Ctx, Address, Count) | FC01. Read Count coils starting at Address. Returns array of Boolean. |
| ReadDiscreteInputs(Ctx, Address, Count) | FC02. Read Count discrete inputs starting at Address. Returns array of Boolean. |
| ReadHoldingRegisters(Ctx, Address, Count) | FC03. Read Count 16-bit holding registers. Returns array of Integer. |
| ReadInputRegisters(Ctx, Address, Count) | FC04. Read Count 16-bit input registers. Returns array of Integer. |
| ReadFloat(Ctx, Address) | Read a 32-bit IEEE 754 float from two consecutive holding registers (big-endian ABCD word order). Returns Single. |
| ReadInt32(Ctx, Address) | Read a 32-bit integer from two consecutive holding registers. Returns Integer. |
Write Functions
| Function | Description |
|---|---|
| WriteCoil(Ctx, Address, Value) | FC05. Force a single coil ON (True) or OFF (False). |
| WriteRegister(Ctx, Address, Value) | FC06. Write a single 16-bit holding register. |
| WriteCoils(Ctx, Address, Values) | FC15. Write a block of coils from an array of Boolean. |
| WriteRegisters(Ctx, Address, Values) | FC16. Write a block of holding registers from an array of Integer. |
| WriteFloat(Ctx, Address, Value) | Write a 32-bit IEEE 754 float to two consecutive holding registers (big-endian ABCD). |
| WriteInt32(Ctx, Address, Value) | Write a 32-bit integer to two consecutive holding registers. |
TCP Slave (Server) Mode
| Function | Description |
|---|---|
| NewTCPSlave(Port) | Create a listening TCP slave socket on the given port. Returns a socket handle used with TCPAccept. |
| TCPAccept(Ctx, Socket) | Accept an incoming client connection on the slave socket. Returns the new client socket descriptor. |
Error Handling
| Function | Description |
|---|---|
| GetLastError | Return a human-readable description of the last error (from errno or libmodbus internal error state). |
| GetExceptionCode | Return the Modbus exception code from the last failed request (1–11), or 0 if the error was not a Modbus-level exception. |
Code Example — Energy Meter via Modbus TCP
Read voltage, current, and active power from a three-phase energy meter that exposes its measurements as IEEE 754 float pairs in holding registers.
uses libmodbus;
var
Ctx: TMBContext;
Regs: array of Integer;
Voltage, Current, Power: Single;
begin
Ctx := NewTCP('192.168.1.100', 502);
Connect(Ctx);
SetSlave(Ctx, 1);
SetTimeout(Ctx, 1000);
{ Read 6 holding registers starting at address 0 }
Regs := ReadHoldingRegisters(Ctx, 0, 6);
{ Convert register pairs to floats (big-endian IEEE 754) }
Voltage := ReadFloat(Ctx, 0); { V_RMS }
Current := ReadFloat(Ctx, 2); { I_RMS }
Power := ReadFloat(Ctx, 4); { Active power W }
WriteLn('Voltage: ', Voltage:6:1, ' V');
WriteLn('Current: ', Current:6:3, ' A');
WriteLn('Power: ', Power:6:1, ' W');
Disconnect(Ctx);
FreeContext(Ctx);
end.
RTU Example — Serial RS-485 Connection
Configure a Modbus RTU master on a USB-to-RS485 adapter. Even parity and 1 stop bit are the Modbus standard default framing.
uses libmodbus;
var
Ctx: TMBContext;
Regs: array of Integer;
begin
{ /dev/ttyUSB0, 9600 baud, Even parity, 8 data bits, 1 stop bit }
Ctx := NewRTU('/dev/ttyUSB0', 9600, mbParEven, 8, 1);
Connect(Ctx);
SetSlave(Ctx, 1);
{ Read 10 input registers (measurements) from slave address 1 }
Regs := ReadInputRegisters(Ctx, 0, 10);
WriteLn('Register[0] = ', Regs[0]);
{ Write setpoint to holding register 100 }
WriteRegister(Ctx, 100, 1500);
Disconnect(Ctx);
FreeContext(Ctx);
end.
libmodbus uses zero-based register addresses. If a device datasheet says “Holding Register 40001” (Modbus classical notation), pass address 0 to the API. Subtract the base (40001, 30001, etc.) and subtract 1 for zero-based offset.
Error Handling
All read and write functions raise an exception on transport or protocol failure. Use GetLastError for the system-level message and GetExceptionCode to distinguish Modbus exception responses from the slave device.
uses libmodbus;
var Ctx: TMBContext;
begin
Ctx := NewTCP('192.168.1.50', 502);
try
Connect(Ctx);
SetSlave(Ctx, 1);
WriteRegister(Ctx, 200, 9999);
except
var Code := GetExceptionCode;
if Code = 3 then
WriteLn('Slave rejected value: illegal data value')
else if Code = 2 then
WriteLn('Slave rejected address: illegal data address')
else
WriteLn('Error: ' + GetLastError);
end;
Disconnect(Ctx);
FreeContext(Ctx);
end.
Package Info
Install
ppm install libmodbusRequires the system library:
sudo apt install libmodbus-dev
Functions
- NewRTU / NewTCP
- FreeContext
- Connect / Disconnect
- SetSlave
- SetTimeout / SetDebug
- ReadCoils
- ReadDiscreteInputs
- ReadHoldingRegisters
- ReadInputRegisters
- ReadFloat / ReadInt32
- WriteCoil / WriteRegister
- WriteCoils / WriteRegisters
- WriteFloat / WriteInt32
- NewTCPSlave / TCPAccept
- GetLastError
- GetExceptionCode
- LibModbusVersion
TMBError Codes
2 IllegalDataAddress
3 IllegalDataValue
4 SlaveDeviceFailure
5 Acknowledge
6 SlaveDeviceBusy
8 MemoryParityError
10 GatewayPathUnavailable
11 GatewayTargetFailed
Did You Know?
Modbus is spoken by millions of PLCs worldwide. Introduced in 1979, it is one of the oldest surviving industrial protocols still in active use — outlasting dozens of competing standards.