nxmodbus NuttX Modbus

NxModbus is a lightweight Modbus protocol stack implementation for NuttX RTOS. It provides client and server functionality for RTU, ASCII, and TCP transports with a clean, callback-based API.

NxModbus follows the Modbus Organization’s modern terminology (adopted September 2020):

  • Client: Device that initiates requests (formerly “Master”)

  • Server: Device that processes requests and returns responses (formerly “Slave”)

  • Device: Remote Modbus device (in exception codes)

This aligns with current industry standards and matches other modern protocols (HTTP, MQTT, OPC-UA).

NxModbus uses 0-based addressing. Addresses passed to application callbacks match the Modbus wire protocol directly: address 0 on the wire is address 0 in the callback. This is consistent with libmodbus, pymodbus, and the Modbus Application Protocol specification.

This differs from FreeModBus, which adds 1 to wire addresses before passing them to callbacks (PLC register numbering convention). If migrating from FreeModBus, remove any addr - 1 adjustments in your callback functions.

Features

  • Multiple Transports: RTU (serial), ASCII (serial), TCP, and raw ADU

  • Client/Server Modes: Full support for both roles

  • Callback Architecture: No internal data storage, application controls all data

  • Conditional Termios: Supports systems with and without termios

  • Thread-Safe: Mutex-protected context operations

  • Multi-Instance: Supports concurrent instances with independent configuration

  • Extensible: Custom function code registration via nxmb_register_custom_fc()

Supported Function Codes

NxModbus implements the following Modbus function codes:

Bit Access:

  • FC01 (0x01): Read Coils

  • FC02 (0x02): Read Discrete Inputs

  • FC05 (0x05): Write Single Coil

  • FC15 (0x0F): Write Multiple Coils

Register Access:

  • FC03 (0x03): Read Holding Registers

  • FC04 (0x04): Read Input Registers

  • FC06 (0x06): Write Single Holding Register

  • FC16 (0x10): Write Multiple Holding Registers

  • FC23 (0x17): Read/Write Multiple Holding Registers

File Access (server-side only):

  • FC20 (0x14): Read File Record

  • FC21 (0x15): Write File Record

Diagnostics and Identification:

  • FC08 (0x08): Diagnostics (sub-function 0x0000 Return Query Data)

  • FC17 (0x11): Report Server ID

Each server-side function code handler can be individually enabled or disabled via Kconfig (all enabled by default except FC20 and FC21, see below). Disabled handlers respond with Illegal Function exception. Client-side request functions are always available when CONFIG_NXMODBUS_CLIENT is enabled. Custom function codes can be registered via nxmb_register_custom_fc() when CONFIG_NXMODBUS_CUSTOM_FC is enabled.

Transport Modes

  • RTU (CONFIG_NXMODBUS_RTU): Binary encoding over serial with CRC16 error checking. Uses T1.5 and T3.5 character timing for frame delimiting. Supports configurable baud rate and parity.

  • ASCII (CONFIG_NXMODBUS_ASCII): Hex-encoded text over serial with LRC error checking. Frames are delimited by ‘:’ start and CR/LF end markers. Character timeout is configurable via CONFIG_NXMODBUS_ASCII_TIMEOUT_SEC.

  • TCP (CONFIG_NXMODBUS_TCP): Modbus frames encapsulated in TCP/IP using the MBAP (Modbus Application Protocol) header. Default port 502. Supports both server (listening) and client (connecting) modes. Server mode supports multiple simultaneous client connections (configurable via CONFIG_NXMODBUS_TCP_MAX_CLIENTS, default 1, up to 8). Requires CONFIG_NET_TCP.

  • Raw ADU (CONFIG_NXMODBUS_RAW_ADU): Application-provided callbacks for transmitting and receiving raw Modbus frames. Enables custom transport backends such as TLS, CAN, BLE, or MQTT.

Architecture

NxModbus uses a three-layer architecture:

  1. Core Layer (core/): Protocol logic, function handlers, exception handling

  2. Transport Layer (transport/): RTU, ASCII, TCP, and raw ADU implementations

  3. Public API (include/nxmodbus/): Application interface

The transport layer uses a function pointer-based abstraction (nxmb_transport_ops_s) allowing runtime selection of transport mode.

Configuration Options

NxModbus configuration is integrated into the NuttX Kconfig system:

Role Selection:

  • CONFIG_NXMODBUS_SERVER – Enable Modbus server support

  • CONFIG_NXMODBUS_CLIENT – Enable Modbus master (client) support

Transport Selection:

  • CONFIG_NXMODBUS_RTU – Enable Modbus RTU (serial) transport

  • CONFIG_NXMODBUS_ASCII – Enable Modbus ASCII (serial) transport

  • CONFIG_NXMODBUS_TCP – Enable Modbus TCP transport (requires CONFIG_NET_TCP)

  • CONFIG_NXMODBUS_RAW_ADU – Enable raw ADU transport (custom backends)

Function Code Selection (server-side, all enabled by default except FC20 and FC21):

  • CONFIG_NXMODBUS_FUNC_READ_COILS – FC01 Read Coils

  • CONFIG_NXMODBUS_FUNC_READ_DISCRETE – FC02 Read Discrete Inputs

  • CONFIG_NXMODBUS_FUNC_READ_HOLDING – FC03 Read Holding Registers

  • CONFIG_NXMODBUS_FUNC_READ_INPUT – FC04 Read Input Registers

  • CONFIG_NXMODBUS_FUNC_WRITE_COIL – FC05 Write Single Coil

  • CONFIG_NXMODBUS_FUNC_WRITE_HOLDING – FC06 Write Single Holding Register

  • CONFIG_NXMODBUS_FUNC_DIAGNOSTICS – FC08 Diagnostics

  • CONFIG_NXMODBUS_FUNC_WRITE_COILS – FC15 Write Multiple Coils

  • CONFIG_NXMODBUS_FUNC_WRITE_HOLDINGS – FC16 Write Multiple Holding Registers

  • CONFIG_NXMODBUS_FUNC_REPORT_SERVER_ID – FC17 Report Server ID

  • CONFIG_NXMODBUS_FUNC_READWRITE_HOLDINGS – FC23 Read/Write Multiple Holding Registers

  • CONFIG_NXMODBUS_FUNC_READ_FILE – FC20 Read File Record (default: n)

  • CONFIG_NXMODBUS_FUNC_WRITE_FILE – FC21 Write File Record (default: n)

Instance and Buffer Configuration:

  • CONFIG_NXMODBUS_MAX_INSTANCES – Maximum concurrent Modbus instances (1-16, default: 1)

  • CONFIG_NXMODBUS_BUFFER_SIZE – ADU buffer size in bytes (64-256, default: 256)

FC17 Report Server ID:

  • CONFIG_NXMODBUS_REP_SERVER_ID_BUF – Buffer size for Report Server ID response data (4-253, default: 32). Holds server ID, run indicator, and optional additional data configured via nxmb_set_server_id().

Timeouts:

  • CONFIG_NXMODBUS_CLIENT_TIMEOUT_MS – Default client response timeout in milliseconds (100-60000, default: 1000). Can be overridden at runtime via nxmb_set_timeout().

  • CONFIG_NXMODBUS_TCP_MAX_CLIENTS – Maximum simultaneous TCP client connections per server instance (1-8, default: 1).

  • CONFIG_NXMODBUS_TCP_TIMEOUT_SEC – TCP idle connection timeout in seconds (1-3600, default: 60). Connections with no activity beyond this period are closed.

  • CONFIG_NXMODBUS_RTU_IDLE_TIMEOUT_MS – RTU inter-frame idle timeout in milliseconds (1-1000, default: 50). Fallback select() timeout when waiting for a new frame. T3.5 character timing is still used for frame delimiting during active reception.

  • CONFIG_NXMODBUS_ASCII_TIMEOUT_SEC – Character timeout for ASCII mode in seconds (1-60, default: 1)

Serial Configuration (RTU/ASCII):

  • CONFIG_SERIAL_TERMIOS – Enable termios-based serial configuration

    If disabled, serial ports are used in raw mode without baud rate or parity configuration. This is useful for systems without termios support or when using pre-configured serial devices.

Extensibility:

  • CONFIG_NXMODBUS_CUSTOM_FC – Enable custom function code handler registration

File Records (FC20 and FC21)

FC20 and FC21 move whole files, such as a configuration or a firmware image, to a device that has only a Modbus link. Like the other function codes, they keep no data inside the stack: the application provides the file_cb member of struct nxmb_callbacks_s:

int file_cb(FAR uint8_t *buf, uint16_t file, uint16_t record,
            uint16_t nregs, enum nxmb_regmode_e mode, FAR void *priv);

The callback is called once for each group of nregs 16-bit records that a sub-request names, starting at record of file (file is 1 to 0xffff and record is 0 to 9999, as on the wire). buf holds the registers in wire order, big endian, 2 bytes each: the application fills it for NXMB_REG_READ (FC20) and consumes it for NXMB_REG_WRITE (FC21). Return -ENOENT for a file or record that does not exist, which becomes exception 2 (Illegal Data Address); any other error is reported as a device failure (exception 4).

The sub-requests are validated in full before the first callback: the reference type must be 6, a group must stay inside the 10000 records of its file, and the response of an FC20 must fit in 0xf5 bytes and in the ADU. A request carries at most 35 sub-requests.

An FC21 is not atomic, as the Modbus specification does not make it so: if the callback fails, the groups before it stay written. An application that needs all-or-nothing behavior has to write to a temporary area and commit it itself, for example with a final write that the application treats as the end of the transfer.

Both options are off by default so that existing configurations do not change in size. On an Arm Cortex-M4 the handlers take about 612 bytes (FC20) and 362 bytes (FC21) of code. The nxmbserver example serves two files from RAM and can be used to try them; nxmbclient has no commands for FC20 and FC21.

Termios Support

NxModbus conditionally uses termios for serial port configuration based on CONFIG_SERIAL_TERMIOS:

With termios (CONFIG_SERIAL_TERMIOS=y):

  • Full baud rate configuration (9600, 19200, 38400, 57600, 115200)

  • Parity configuration (none, even, odd)

  • Automatic raw mode setup (no echo, no canonical mode)

  • Original settings saved and restored on cleanup

Without termios (CONFIG_SERIAL_TERMIOS=n):

  • Serial device opened in raw mode

  • No baud rate or parity configuration

  • Device must be pre-configured or support raw I/O

  • Useful for:

    • Systems without termios support

    • Pre-configured serial backends

    • Custom serial drivers

    • Non-standard serial devices

API Reference

Instance Management:

  • nxmb_create() – Create and initialize an NxModbus instance

  • nxmb_destroy() – Destroy an instance and release resources

  • nxmb_enable() – Enable transport and start processing

  • nxmb_disable() – Stop processing and release transport

Server Configuration:

  • nxmb_set_callbacks() – Register application callbacks for data model access

  • nxmb_set_server_id() – Configure FC17 Report Server ID response data

  • nxmb_poll() – Execute one server-side polling iteration

Client Functions (require CONFIG_NXMODBUS_CLIENT):

  • nxmb_read_coils() – Read coils (FC01)

  • nxmb_read_discrete() – Read discrete inputs (FC02)

  • nxmb_read_holding() – Read holding registers (FC03)

  • nxmb_read_input() – Read input registers (FC04)

  • nxmb_write_coil() – Write single coil (FC05)

  • nxmb_write_holding() – Write single holding register (FC06)

  • nxmb_write_coils() – Write multiple coils (FC15)

  • nxmb_write_holdings() – Write multiple holding registers (FC16)

  • nxmb_readwrite_holdings() – Read/write multiple holding registers (FC23)

  • nxmb_set_timeout() – Set client-side response timeout

Extensibility (require CONFIG_NXMODBUS_CUSTOM_FC):

  • nxmb_register_custom_fc() – Register a custom function code handler

Exception Codes

NxModbus implements standard Modbus exception codes:

  • NXMB_EX_NONE (0x00): No exception

  • NXMB_EX_ILLEGAL_FUNCTION (0x01): Function code not supported

  • NXMB_EX_ILLEGAL_DATA_ADDRESS (0x02): Register address out of range

  • NXMB_EX_ILLEGAL_DATA_VALUE (0x03): Invalid data value

  • NXMB_EX_DEVICE_FAILURE (0x04): Device failure

  • NXMB_EX_ACKNOWLEDGE (0x05): Request acknowledged (long operation)

  • NXMB_EX_DEVICE_BUSY (0x06): Device busy

  • NXMB_EX_MEMORY_PARITY_ERROR (0x08): Memory parity error

  • NXMB_EX_GATEWAY_PATH_FAILED (0x0A): Gateway path unavailable

  • NXMB_EX_GATEWAY_TGT_FAILED (0x0B): Gateway target failed to respond

Comparison with FreeModBus

NxModbus is an alternative to FreeModBus with several improvements:

  • Modern client/server terminology

  • Cleaner API with opaque handles

  • Multi-instance support

  • Conditional termios support

  • Thread-safe by design

  • No global state

  • Raw ADU transport for custom backends (TLS, CAN, BLE, MQTT)

  • Extensible function code table via nxmb_register_custom_fc()