Modbus library for Linux

by Frank Rysanek
of FCC Prumyslove Systemy s.r.o.
[ rysanek AT fccps DOT cz ]

Based on earlier works of
Paul McRae and Philip Costigan

1. INTRODUCTION

The key concept of this library is the use of objects called modbus_serial (RTU/ASCII) and modbus_tcp, that work as wrappers around individual serial ports or TCP sockets. To make the library compatible with bare C, some very simple C++'ish polymorphism is faked using nested structs and function pointers. The common Modbus guts for all sorts of busses are stored in a "struct modbus_c" that is included by all the media-specific flavours.

Don't #include <modbus.h> directly. Instead, include modbus_asc.h or modbus_rtu.h or modbus_tcp.h. This file <modbus.h> will be included automatically (indirectly via the media-specific header).

The library is thread-safe - all init/shutdown actions and bus transactions are serialized via Posix mutexes and some other candy along those lines.
You can open()/close() and use the busses from several threads simultaneously, without wrapping the calls in explicit mutexes.

The library has both master and slave capability, as well as "tagged queuing" for TCP master and slave busses.

The library actually employs two to three transaction queues and four threads per bus to do all the queuing, timing out etc. Which is another reason (apart from mutexes) why it needs libpthread.


[GIF] A simple relay application


[GIF] A simple master application


[GIF] A simple slave application


2. LIBRARY INIT AND CLEANUP

3. CREATING AND DESTROYING INDIVIDUAL BUSSES (PORTS)

3.1. Introduction

As a sign of its polymophism, the library offers several calls to new_modbus_<media>_<master/slave>(). Each has a different set of arguments.

On the other hand, the library has a single common function, modbus_destroy(), to deallocate all the polymorphic sorts of busses. This function ultimately calls free() - don't call free() explicitly after modbus_destroy().

3.2. Functions

   new_modbus_asc_master()                 // see modbus_asc.h
   new_modbus_asc_slave()                  // see modbus_asc.h
   new_modbus_rtu_master()                 // see modbus_rtu.h
   new_modbus_rtu_slave()                  // see modbus_rtu.h
   new_modbus_tcp_master_hostname()        // see modbus_tcp.h
   new_modbus_tcp_master_ipaddr()          // see modbus_tcp.h
   new_modbus_tcp_slave_hostname()         // see modbus_tcp.h
   new_modbus_tcp_slave_ipaddr()           // see modbus_tcp.h

   modbus_destroy()                        // see modbus.h

See the individual header files for full function prototypes.

The argument called dispatch_func with the new_* functions is mandatory on slave busses. If you only plan to use the simple query functions (==> on master busses), it is safe to pass a NULL for the dispatch_func.

3.3. A note about Modbus/TCP slaves

The Modbus/TCP slave bus is peculiar. The call to new_modbus_tcp_slave() actually creates a TCP server / listener pseudo-bus (a struct modbus_c* look-alike). This server "bus" has its own listening socket (stored in its "fd") and spawns ephemeral children = slave busses for incoming TCP sessions from clients = masters.

The morale is: call modbus_destroy() only on the master bus, but not on the ephemeral children - these die of their own discretion when the underlying TCP session closes (for whatever reason).

3.4. Adjusting timeouts

The busses usually assume reasonable defaults for the various timeouts taking place. Some of the timeouts can be adjusted, using manipulator functions, at runtime or just after new_modbus_*. The unit is microseconds.

   adjust_frame_brk_timeout()
   adjust_tx_timeout()
   adjust_pend_timeout()

3.5. Adjusting pending queue depth

Master busses have a pending queue. A transaction, that has been transmitted on the bus, becomes pending, until a matching response comes back, or until the transaction times out in the pending queue.

Please note that you can queue more transactions to the bus, than its current pending queue depth. If there are no pending tokens available, or if the bus just can't transmit that fast, the transactions will patiently wait for transmission in the TX queue (and potentially time out in there, too).

Pending queue depth greater than 1 only makes sense with TCP master busses, because only Modbus/TCP supports transaction tagging. The default queue depth with TCP master busses is 30.

All other master busses have a pending q_depth of 1. (But you can queue multiple transactions into the TX queue, see above.)

   adjust_q_depth()      // adjusts the pending queue

Queue depth can be adjusted right after new_modbus_tcp_master_*(), or anytime at runtime.

Slave busses don't have an explicit pending queue, but they do support receiving any number of pending transactions. Transactions received by any slave bus inherently remember both the bus of origin and the original TCP transaction tag, so that "TCQ" with arbitrary depth is inherently supported on all slave busses.

3.6. Error handling tweaks for dispatch_func() on master busses

On master busses, the dispatch_func() is called for transactions that are not locally originated (i.e. not sent by run_trans()).

The dispatcher thread on master busses normally doesn't return locally failed transactions (timed out, dead of other errors) to the dispatch_func() - rather, locally failed transactions just vanish (via free_modbus_trans()).
Please note that any valid Modbus error response frames, generated by slave devices, being valid Modbus frames in the first place, are normally returned to the dispatch_func().

Some relay-style applications, operating at transaction level, may be interested to know even about local failures. Especially timeouts (to generate GW no response from target), but maybe others too. These error responses can be enabled using the following two shorthands:

   enable_nonlocal_timeouts()
   enable_all_trans_errs()

This functionality is rather obscure. If you don't quite understand it, you probably don't need it. Both of these options are off by default.

4. OPENING AND CLOSING BUSSES

After creating a bus, you have yet to

   modbus_open()

the bus to activate it. Similarly, before destroying a bus, call

   modbus_close().

See modbus.h for full function prototypes.

Again, the with TCP slave busses, only explicitly open the (explicitly created) listener bus. The ephemeral children are born already open and get closed implicitly.

In some scenarios, especially with TCP master busses, it makes sense to defer the actual open() operation, or rather leave it up to the bus-private threads to take care of it. The prototype of modbus_open() makes this background open() possible. This way the application program is able to start up quickly, and leaves any respective error handling up to the busses and threads that are directly affected.
The program's main thread of control returns immediately from the call to modbus_open(), regardless of whether or not the TCP session has been connected, even regardless of whether or not there is a chance to get connected.

5. SIMPLE SHORT-HAND QUERY FUNCTIONS

Higher-level functions for local use on master busses.

Owing to run_trans() (see below), these functions are thread-safe. Multiple application threads can call them simultaneously on a single master bus.

5.1. Common notes about function arguments

See also the accompanying README.MODBUS.

Register addresses are 16bit integers, zero-based. The functions DO NOT subtract a one (do not accept one-based addresses) and DO NOT decompose the uniform Modbus/Fieldbus register address space.

For the 16bit output/input operations, the source/destination buffer is an array of u16.

For the single-bit operations, the source/destination buffer is an array of "bool_type", which can be defined as u8 or u16 (if you switch this in the header file, you need to recompile the library). This arrangement was chosen for the lack of a proper C++ bool type. Each array element contains the truth value of one coil (i.e, the values are not packed by 8 in a byte as in the Modbus frame format).
The one exception to that rule is force_single_coil() that takes its single truth value strictly in a single 16bit argument.

For truth values, use the predefined macros of TRUE or FALSE.

Returned value:
0 if OK
Less than 0 == exception errors

5.2. A listing of individual functions

read_coil_status()

= block operation on single-bit input (coil "read-back"). Reads the boolean status of coils and expands the packed-bits transfer format (8 coils per byte) into the destination array (1 coil per byte), so that dest[n] = TRUE or FALSE.

read_discrete_input()

= block operation on single-bit input (discrete input pins). Reads the boolean discrete inputs and expands the packed-bits transfer format (8 pins per byte) into the destination array (1 pin per byte), so that dest[n] = TRUE or FALSE.

read_holding_registers()

Read the holding registers in a slave and put the data into an array.

read_input_registers()

Read the input registers in a slave and put the data into an array.

force_single_coil()

Takes the single truth value in a single u16 integer. This is apparently compliant with the underlying Modbus request frame encoding. Nevertheless, this wrapper function converts TRUE internally into 0xFF00. If this is not appropriate for the particular Modbus device, refer to the source code of force_single_coil() in the respective C file. If appropriate, modify it to pass values through unchanged (e.g. if you need to talk to devices from several different vendors).

The appropriate encoding of true/false in force_single_coil() appears to be vendor specific for the modbus slave. Consult your hardware manual.

preset_single_register()

Sets a value in one holding register in the slave device.

set_multiple_coils()

Takes an array of ints and sets or resets the coils on a slave appropriatly.

preset_multiple_registers()

Copy the values in an array to an array on the slave.

6. SLAVE-SIDE QUERY CALLBACKS

6.1. Overview

To facilitate implementation of Modbus slave applications, the library allows the application writer to define high-level callback functions and set these to the respective slave bus.

The "struct modbus_c" contains a set of function pointers as its members. All of the callbacks are set to NULL upon bus creation. If desired, the callbacks can be set using a function called

   set_slave_callbacks()

(Or by direct access to the individual struct modbus_c members.)

The set of supported slave callbacks precisely corresponds to the set of master-side high-level short-hand functions. Even the function arguments are pretty much the same. See chapter 5.

All the actual Modbus function callbacks are optional.
It is possible to only define callbacks for functions that are actually going to be used (served by the slave) - set the rest of the callbacks to NULL, to have the bus return an "illegal function" error response frame to the querying master. No need to write empty / dummy callbacks.

The single callback that is mandatory is our_id(). This simple way is used by the library to implement detection of ID's that the slave should respond to. If there should be an index of accepted ID's, its implementation is hereby effectively offloaded onto the application writer :-) The point is, that the Modbus library can remain lightweight - it doesn't have to depend on Glib btrees, C++ STL::map or some such.

The only scenario where a slave bus doesn't need our_id() is when the application writer has defined a custom dispatch_func(). If the bus is created with a NULL dispatch_func(), the constructor-like function actually inserts the generic_slave_dispatch(), that in turn depends on our_id(). If our_id() is not defined, the library yells.

The generic_slave_dispatch() et al. allocate the high-level data arrays as their local variables (on the stack), using the maximum permitted per-transaction data length as the array size. And, they pass a pointer to this array to the user-defined callbacks. Thus, the user-defined callback works with this data array and needn't handle any malloc/free or some such.

The callbacks run within the slave bus'es own dispatcher thread. Hence, the application writer needn't create an explicit slave thread or some such. Just set the callbacks and block the program's main thread - wait for a keypress, do some background maintenance or whatever.

6.2. A list of callbacks

   our_id()      // mandatory, unless custom dispatch_func() is defined
   read_coil_status_cback()
   read_discrete_input_cback()
   read_holding_registers_cback()
   read_input_registers_cback()
   force_single_coil_cback()
   preset_single_register_cback()
   set_multiple_coils_cback()
   preset_multiple_registers_cback()

7. A NOTE ON TRANSACTIONS

A transaction consists of a request and a response. That's two Modbus messages (frames).

Essentially the response can be positive, or an error response, or there can be no response at all - in that case, the transaction has to time out.

Some applications will want to block while waiting for a response (or for a timeout). This will be the case with locally originated queries.
Other applications may avoid blocking and do not quite care about reponses. That is probably the case with relay-style applications that implement asynchronous relaying of requests and responses.

To facilitate relaying and on-the-fly mangling, and to differentiate between master and slave queries, struct modbus_trans actually contains four message buffers (rather than just two):

   rx_req
   tx_req
   rx_resp
   tx_resp

Hence, a typical relay application would essentially need to copy+mangle rx_req to tx_req, and in the backwards direction it would copy+unmangle rx_resp to tx_resp. The unmangling is facilitated by having a full copy of the original received request, which can be used to get the original slave ID etc. The code of tx_thr_slave() actually uses this to paste the Modbus/TCP tags.

8. TRANSACTION-LEVEL INTERFACE TO LOCAL MASTER QUERIES

Feel free to use this to build custom query functions.

   trans = get_modbus_trans()
   run_trans(bus, trans)
   free_modbus_trans(trans)

This is what the shorthands covered in chapter 5 actually use. The call to run_trans() tags the transactions as locally-originated - hence they don't come back in a dispatch_func(), and hence run_trans() is not well suited for relay-style applications. Transactions that have timed out in whichever queue will come back with trans->err_code == ERR_TIMEOUT.

In other words, run_trans() is a blocking call - another reason why it's not appropriate for relay-style apps.
OTOH, run_trans can be called by several application threads simultaneously and is thread-safe (using mutexes, conditions and queuing).

9. TRANSACTION-LEVEL INTERFACE TO SLAVE QUERIES AND RELAYED TRANSACTIONS

This is not the only way anymore to implement slave functionality. More importantly, it's appropriate for relay-style applications.

The key functions are

   dispatch_func()
   q_trans()

The dispatch_func() is set up as a callback upon bus creation. It is called by the dispatcher thread of the respective bus. The dispatcher thread has no other job apart from calling the dispatch_func() - hence it can afford to spend some time on processing application data. Just beware of using indefinitely blocking IO calls in the dispatch_func(), and also beware of deadlocks.
Any transactions that arrive while the dispatcher thread is busy will wait somewhat patiently in the rx_q. Hence the application programmer needn't implement any additional queueing just to offload the dispatcher thread.

The q_trans can be called explicitly. It leaves trans->am_local set to FALSE, hence on master busses the transaction will come back via the dispatch_func(), or won't come at all if it times out. (Compare with run_trans() to see the contrast.)

This can be used to implement relay-style applications. The dispatch_func() of the slave bus can directly call q_trans() of the desired master bus, and vice versa. Beware that any timed-out transactions will completely vanish - hence the request and response halves of the relaying application should run asynchronously, otherwise some additional intricate queuing and threading may be necessary.

q_trans() is thread-safe and can be called by multiple client threads simultaneously on any particular bus, without explicit mutexes.

10. DIRECT FRAME-LEVEL INTERFACE

   bus->send_frame()
   bus->recv_frame()

This interface is probably hardly any use to anyone, except for analyzer-style utilities. If you try to use it, be aware that you need to have the bus open, but at the same time you don't want the standard queuing threads to interfere - thus, you may need to kill some threads, or implement your own modbus_open().