modbus: Modbus Support - Release Notes
July XXX, 2016
- Added support for specifying absolute Modbus addresses in the asyn "addr" field.
Previously each driver was limited to addressing at most 125 registers (read operations)
or 123 registers (write operations), and the asyn "addr" field specified an offset
relative to the modbusStartAddress passed as the fifth argument to drvAsynModbusConfigure().
Now if the modbusStartAddress=-1 then the driver will use absolute addressing, and
the asyn "addr" specifes the absolute Modbus address in the range 0 to 65535. In
this case the modbusLength argument to drvAsynModbusConfigure() is the maximum length
required for any single Modbus transaction by that driver. This would be 1 if all
Modbus reads and writes are for 16-bit registers, but it would be 4 if 64-bit floats
(4 16-bit registers) are being used, and 100 (for example) if an Int32 waveform
record with NELM=100 is being read or written.
- Added error checking to make sure the length of the reply from the Modbus server
is the expected value. Previously garbled communications could cause a crash because
of array bounds violation.
February 11, 2016
- Improved the logic in the poller thread.
- Previously there was a call to epicsThreadSleep at the end of the poller loop.
This meant that the poller always ran at least once, which might not be desirable.
It also meant that one could only temporarily disable the poller by calling pasynManager->lockPort(),
or to make it run infrequently by setting POLL_DELAY to a long sleep time. In R2-5
the MODBUS_READ command was added. However, this just did a read operation without
triggering the poller thread, so records with I/O Intr scan would not process. It
also required disabling the poller thread with pasynManager->LockPort.
- The poller thread has been changed so that it calls epicsEventWait or epicsEventWaitWithTimeout
at the beginning of the poller loop. If the POLL_DELAY is >0 then it calls epicsEventWaitWithTimeout,
using the POLL_DELAY as the timeout. If POLL_DELAY is ≤0 then it calls epicsEventWait,
with no timeout. This allows the poller to be suspended indefinitely and only run
when the epicsEvent is signaled. It also means if POLL_DELAY ≤0 then the poller
does not run at all until the epicsEvent is signaled. The MODBUS_READ command now
signals the epicsEvent to trigger the poller, rather than directly doing a read
- Added a new template file, poll_trigger.template which loads a record with the
MODBUS_READ drvInfo string to trigger the poller.
- Fixed problems when using waveform records with the asynInt32Array interface.
- If the Modbus data type was not UINT16 or INT16 then the data were not read into
the waveform record correctly. This bug has been fixed, and new files iocBoot/iocTest/array_test.cmd
and array_test.substitions were added to test it using the Modbus Slave Simulator.
New adl files, modbusTestArray.adl and array_test.adl were added for these tests.
modbusTestArray.adl also contains controls for POLL_DELAY and POLL_TRIGGER to verify
the improvements to the poller thread above.
- Previously the first element in the waveform record was always the first register
in that modbus driver, i.e. it did not allow specifying a register offset using
the asyn ADDR address. This was improved so now one can specify an offset using
the OFFSET macro parameter, as with the non-array records in modbus.
May 6, 2015
- Minor changes to allow building dynamically on Cygwin.
August 19, 2014
- Added support for function code 23, Read/Write Multiple Registers. Because of
the architecture of the EPICS Modbus support, a driver that uses function 23 is
restricted to being either a read-only driver where no data is written, or a write-only
driver where no data is read. It is preferable to use functions codes 3, 4, 6, or
16 if they are supported. However, some older devices only support function code
April 10, 2014
- Fixed a problem with function code 6 (write single register) on Wago devices when
the MASK was not 0x0 or 0xFFFF. In this case a read/modify/write operation is required,
and the driver was reading from the wrong address. On Wago devices the readback
address for a register is offset by 0x200 from the write address. This offset was
not being applied.
- Added 2 new records to statistics.template. $(P)$(R)HistTimeAxis is a waveform
record that contains the X time axis for histogram plots, and MsPerBin is the time
per histogram bin in msec.
October 2, 2013
- Fixed a mutex issue with the input poller. Previous versions had a potential problem
with input records which did not use Scan=I/O Intr. In this case there was no mutex
protecting access to the read buffer between the device support thread and the poller
thread that was reading the Modbus data. It is likely that this problem was rarely
if ever encountered because input records were almost always I/O Intr scanned. The
fix was to remove the existing mutex in the driver and to simply call pasynManager->lockPort()
for the entire duration of the polling cycle. This fix has a positive side effect:
it is now possible for external clients, for example other drivers that call the
Modbus driver, to temporarily disable the poller by calling pasynManager->lockPort().
This can be used to allow atomic sequences of Modbus read/write operations with
no possibility of interference from the poller.
- The above fix means that Modbus input drivers can now block on read operations,
because they may need to wait for the poller to complete. These drivers are thus
now created with the ASYN_CANBLOCK flag, and the documentation has been changed
to state that input drivers are now asynchronous, not synchronous.
- Changed the wait for interruptAccept in the poller thread. It now works to have
a very long polling time, but still having one initial read cycle and callback on
I/O Intr scanned records.
- Added a new command to the Modbus driver, which has the drvUser string "MODBUS_READ".
This command is implemented on the asynInt32 interface. Calling asynInt32->write()
with this command causes a Modbus I/O cycle for this driver. It will typically be
used to force a Modbus input driver to do a read operation independent of the poller
- Added a new test driver called testModbusSyncIO.cpp. This driver is derived from
asynPortDriver. It is designed to demonstrate using another asyn driver to communicate
through the Modbus driver. This concept is currently being used to have a motor
driver that communicates through the Modbus driver. It demonstrates two types of
- Using pasynInt32SyncIO calls to make "synchronous" calls to the Modbus driver.
These calls are blocking and are queued.
- Doing an atomic "read/modify/write" cycle using the following sequence of operations.
This uses pasynManager->lockPort to disable the poller and allow this thread to
directly call the asynInt32->write() and asynInt32->read() functions. It uses the
This new example driver can be tested with the new files iocBoot/iocTest/sim3.cmd
- Lock the output port. Needed because we are directly calling the write() function.
- Lock the input port, which disables the poller and because we are directly calling
- Sleep for 1 second so we can prove the poller was idle.
- Force a read of the Modbus input by writing on asynInt32 interface with the MODBUS_READ
- Read the input value on the asynInt32 interface.
- Add the value passed to this function to the current value just read.
- Sleep for 2 seconds so we can prove the poller was idle.
- Write the new value to the output driver.
- Unlock the input port.
- Unlock the output port.
- Minor change to structure packing declarations to allow building on ARM architecture
with GCC 4.2.1
May 22, 2012
- Improved support for different Modbus data types.
- Added support for 7 new Modbus data types:
- 16-bit signed integer
- 32-bit integer, little-endian
- 32-bit integer, big-endian
- 32-bit float, little-endian
- 32-bit float, big-endian
- 64-bit float, little-endian
- 64-bit float, big-endian
- Added support to allow the Modbus data type to be specified on a per-record bas
using the asyn drvUser field. Previously all records connected to a Modbus asyn
port driver had the same Modbus data type, because each Modbus port driver was limited
to a single data type.
- If the drvUser string in the link specification is omitted, or if it is the default
of MODBUS_DATA then the record will use the modbusDataType defined in drvModbusAsynConfigure.
This is backwards compatible, and existing IOCs will continue to work with no changes
to databases or startup scripts.
- Added new example IOC script, substitutions file, and medm screens to test all
data types. The testing was done with a Modbus slave simulator.
- Added special treatment for Wago devices. These device are different from other
Modbus devices because the address to read back a register is not the same as the
address to write the register. The readback address is the write address plus 0x200.
This means that in previous versions of this driver the initial readback value for
Modbus write operations to Wago devices was incorrect. This was fixed by adding
the 0x200 offset to the readback address if the plcType argument to drvModbusAsynConfigure
contains the substring "Wago" (case sensitive).
- Added support for passing status information back to device support in callbacks
for I/O Intr scanned records. Support for this was added in asyn R4-19. This means
that if the Modbus device communications returns errors that I/O Intr scanned records
will now have their alarm status set correctly.
Sept 9, 2011
- build changes
- added .opi files for CSS-BOY
March 13, 2011
- R2-0 introduced a 20 ms delay before each Modbus write. This was not correct,
delays should only be needed for serial RTU. The Modicon Modbus Protocol Reference
Guide says this must be at least 3.5 character times, e.g. about 3.5ms at 9600 baud,
for Serial RTU. An additional writeDelayMsec parameter was added to modbusInterposeConfig.
It is the final parameter, so if it is not specified the default value is zero,
which is appropriate when not using Serial RTU. In the startup script lines like
should be changed to:
for no delay, or to:
for a 20 ms delay.
November 7, 2010
- Bug fix. Non-automatic connection to the Modbus server uses pasynCommonSyncIO->connectDevice().
The pasynUser being used for that operation was being created with pasynOctetSyncIO->connect().
That was always an error, it must be created with pasynCommonSyncIO->connect().
This error became serious with asyn R4-14, and non-automatic connection no longer
- Previous releases of modbus recommended setting NoAutoConnect=1 when configuring
the TCP or serial port. That was probably because of problems in connection management
in earlier versions of asyn. With asyn R4-14 this is no longer necessary, and NoAutoConnect=0,
the normal default, can be used with no problems. The example scripts Koyo1.cmd
and Koyo2.cmd in the iocBoot directory have been changed to enable automatic connection
to the IP or serial driver.
November 26, 2009
- Moved the slave address handling from the asynInterpose layer to the Modbus driver
layer. This was done because handing it in the the interpose layer only allowed
1 slave address per asyn serial port or IP port. This did not allow a single serial
port to be used with multiple Modbus devices on an RS-485 bus, for example. NOTE:
This requires all startup command scripts to be changed, because the syntax of the
modbusInterposeConfig and drvModbusAsynConfigure commands has changed. Thanks
to Yves Lussignol from CEA in France for making these changes.
September 19, 2008
- Changed modbusInterpose.c to replace pasynOctet->writeRaw() and pasynOctet->readRaw()
with pasynOctet->write() and pasynOctet->read(), because the raw routines have been
removed in asyn R4-10.
- Changed the driver to use the asynStandardInterfaces interfaces added to asyn
September 6, 2007
- Fixed bug in computing byteCount in WRITE_MULTIPLE_COILS function code.
There is a known limitation with using serial interfaces. It is not currently possible
to have multiple Modbus servers connected to a single serial port. This is a limitation
of the asynInterposeInterface architecture used. It is fixed in release 2-0.
April 30, 2007
Initial release of modbus module.