libmodbus clib Industrial & Automation

Modbus RTU and TCP communication for PascalAI via libmodbus. Read and write coils, discrete inputs, and 16-bit registers on PLCs, VFDs, energy meters, and SCADA systems — over RS-485 serial or standard Ethernet, with full support for master and slave modes.

ppm install libmodbus

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.

CLib package — requires libmodbus-dev

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.

SpaceWidthAccessTypical Use
Coils1-bitRead / WriteDigital outputs (DO) — relay states, actuator commands
Discrete Inputs1-bitRead onlyDigital inputs (DI) — limit switches, push-buttons
Holding Registers16-bitRead / WriteAnalog outputs / configuration — setpoints, mode selectors
Input Registers16-bitRead onlyMeasurements — voltage, current, temperature, flow

Function Codes

CodeNameDirection
FC01Read CoilsMaster reads 1-bit outputs from slave
FC02Read Discrete InputsMaster reads 1-bit inputs from slave
FC03Read Holding RegistersMaster reads 16-bit R/W registers from slave
FC04Read Input RegistersMaster reads 16-bit R/O registers from slave
FC05Write Single CoilMaster forces one coil ON or OFF
FC06Write Single RegisterMaster writes one 16-bit holding register
FC15Write Multiple CoilsMaster forces a block of coils in a single request
FC16Write Multiple RegistersMaster writes a block of holding registers in one request

RTU vs TCP

Modbus RTU RS-485 serial bus. Binary framing with CRC16 error detection. Up to 247 slave devices per bus segment. Cable runs up to 1200 m at low baud rates. Common baud rates: 9600, 19200, 115200. Parity: Even is most common in Modbus RTU. Requires a USB-to-RS485 adapter on PC.
Modbus TCP Standard Ethernet, port 502. Wraps the PDU in an MBAP header (6 bytes). No distance limit — works over any IP network. No CRC needed (TCP handles integrity). Slave ID (Unit ID) usually 1 or 255. Easy integration with IT systems, firewalls, and cloud SCADA.

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

FunctionDescription
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

FunctionDescription
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

FunctionDescription
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

FunctionDescription
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

FunctionDescription
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

FunctionDescription
GetLastErrorReturn a human-readable description of the last error (from errno or libmodbus internal error state).
GetExceptionCodeReturn 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.
Register addressing:

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

Version1.0.0
Typeclib
C Librarylibmodbus
ProtocolsRTU, TCP
System Reqlibmodbus-dev

Install

ppm install libmodbus

Requires 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

1 IllegalFunction
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.

See Also