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
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.
syslog()
- to make full use of this,
perform openlog()
on program startup.modbus_init()
modbus_cleanup()
modbus_destroy()
on any busses still open!openlog()
on program startup, perform closelog()
on program shutdown to close the syslog facility.
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()
.
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.
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).
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()
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.
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.
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.
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.
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
= 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.
= 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 the holding registers in a slave and put the data into an array.
Read the input registers in a slave and put the data into an array.
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.
Sets a value in one holding register in the slave device.
Takes an array of ints and sets or resets the coils on a slave appropriatly.
Copy the values in an array to an array on the slave.
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.
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()
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.
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).
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.
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()
.