EPICS Controls Argonne National Laboratory

Experimental Physics and
Industrial Control System

1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024  2025  <2026 Index 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024  2025  <2026
<== Date ==> <== Thread ==>

Subject: Re: [External] Re: MODBUS User-Defined Function Codes
From: Torsten Bögershausen via Tech-talk <tech-talk at aps.anl.gov>
To: William Jamieson <wjamieso at pppl.gov>, Mark Rivers <rivers at cars.uchicago.edu>
Cc: "tech-talk at aps.anl.gov" <tech-talk at aps.anl.gov>
Date: Thu, 5 Feb 2026 13:26:22 +0100
Hej again,
I think that it could make sense with a custom driver.
But:
I still do not understand, why there should be a CRC error.
I have compied the code into a small C-program:
=============================
#include <stdio.h>
/* rest stolen dem modbusInterpose.c */
int main()
{
const static unsigned char reply_good[] = { 0xe6, 0xe4, 0x05, 0x1b, 0x34 }; const static unsigned char reply_bad[] = { 0xe6, 0xe4, 0x05, 0x1b, 0x35 };
  unsigned char CRC_Hi;
  unsigned char CRC_Lo;

  int nbytesActual = (int)sizeof(reply_good);
  computeCRC((char*)&reply_good[0], nbytesActual, &CRC_Lo, &CRC_Hi);

  printf("good: nbytesActual=%d CRC_Lo=%u CRC_Hi=%u\n",
         nbytesActual, CRC_Lo, CRC_Hi);

  computeCRC((char*)&reply_bad[0], nbytesActual, &CRC_Lo, &CRC_Hi);
  printf("bad: nbytesActual=%d CRC_Lo=%u CRC_Hi=%u\n",
         nbytesActual, CRC_Lo, CRC_Hi);
}
=============================

And it clearly says that the CRC is good.
Why do we see the line
2026/02/03 07:59:40.435 modbusInterpose::readIt, CRC error/
in the log.
I don't get it. What is going on ? What do I miss ?



On 2026-02-04 19:28, William Jamieson wrote:
Mark,

See attached. The non-standard function code is described starting on page 4. I will reply to this thread once I have a solution.

Will

On Wed, Feb 4, 2026 at 1:23 PM Mark Rivers <rivers at cars.uchicago.edu <mailto:rivers at cars.uchicago.edu>> wrote:

    Hi William,

    Can you send the manual that describes the non-standard function
    code?  The best solution is probably to create a version of the
    driver that implements that function code.  If you put that in a git
    branch then you can continue to merge future changes from the master
    branch as long as they don't conflict with your modifications.  It
    is probably just a few dozen lines of code that you would need to add.

    Mark


    ------------------------------------------------------------------------
    *From:* Tech-talk <tech-talk-bounces at aps.anl.gov <mailto:tech-talk-
    bounces at aps.anl.gov>> on behalf of William Jamieson via Tech-talk
    <tech-talk at aps.anl.gov <mailto:tech-talk at aps.anl.gov>>
    *Sent:* Wednesday, February 4, 2026 8:46 AM
    *To:* Torsten Bögershausen <tboegi at edom.se <mailto:tboegi at edom.se>>
    *Cc:* tech-talk at aps.anl.gov <mailto:tech-talk at aps.anl.gov> <tech-
    talk at aps.anl.gov <mailto:tech-talk at aps.anl.gov>>
    *Subject:* Re: [External] Re: MODBUS User-Defined Function Codes
    Mark,

    Unfortunately for the command I am trying to issue, the only method
    mentioned in the manual is the non-standard function code. I have
    reached out to the manufacturer to see if there are other options
    not listed in the manual.

    Torsten,

    I have tried both in "\xe6\xe4\x05\x1b\x34" and in "\xe6\xe4\x05" on
    the .proto file with the same result. As this data is a reply from
    the device, modbusInterpose does not tack on a CRC for it. However
    the reply from the slave device fails the CRC check in both cases. I
    believe this is due to the fact that the reply does not make sense
    in the context of standard MODBUS RTU implementations. Usually a
    reply in MODBUS RTU is the slave address, followed by the same
    command code sent by the master, followed by the number of data
    bytes about to be transmitted, followed by the message, followed by
    the CRC. In this case, the reply is the slave device address,
    followed by the command code 228 (hex: e4), which is different from
    the command code 100 (hex 64) sent out by the master.

    Will

    On Wed, Feb 4, 2026 at 12:45 AM Torsten Bögershausen <tboegi at edom.se
    <mailto:tboegi at edom.se>> wrote:

        Ansering myself:
        Writing a new driver, as Mark suggested, can be an option.
        We may probably end up with a/the modbus driver that is enhanced
        to handle custom parameters ?

        Back to the roots, as I read RS485v here.
        How (and when) is the swicthing between sending and receiving done ?
        Could this be a problem ?
        Is streamdevice "instructed" to read the answer ?
        That is, does the proto file have an input session ?

        Double checking: The response  "e6 e4 05 1b 34" seems to be correct,
        e6 e4 05 gives the CRC 341B

        Ah, may be I read it wrong?
        Does the proto file say
          >  in "\xe6\xe4\x05\x1b\x34" ?
        The it should probably be
            in "\xe6\xe4\x05"
        in the proto file.
        (since the CRC is eaten, digested and handled by the interpose
        layer)




        On 2026-02-03 14:48, Torsten Bögershausen via Tech-talk wrote:
         > Hm,
         > Not (yet) being a modbus expert, and fumbling in the dark:
         > There are 7 bytes send, and 5 received, right ?
         > That is all good, or ?
         >
         > It looks as if the CRC send from the device is not matching what
         > the IOC code in modbusInterpose::readIt() expects.
         >
         > What is wrong here ?
         > The CRC from the device, or the IOC code ?
         > Or does somebody have a better explanation ?
         >
         >
         >
         > On 2026-02-03 14:16, William Jamieson wrote:
         >> Thank you for the information. I have implemented everything
        across
         >> the same virtual and physical port using the below settings
        in st.cmd:
         >> /drvAsynSerialPortConfigure("rs485_port", "/dev/ttyUSB0", 0,
        0, 0)
         >> asynSetOption("rs485_port",0,"baud","19200")
         >> asynSetOption("rs485_port",0,"parity","even")
         >> asynSetOption("rs485_port",0,"bits","8")
         >> asynSetOption("rs485_port",0,"stop","1")
         >> modbusInterposeConfig("rs485_port",1,1000,0)/
         >>
         >> The below commands are used to read in data via function 3
        (st.cmd):
         >> /drvModbusAsynConfigure("oxigraf_data_1", "rs485_port", 230,
        3, 0, 7,
         >> 0, 200, "oxigraf")
         >> drvModbusAsynConfigure("channel_o2_readings", "rs485_port",
        230, 3,
         >> 35, 4, 0, 200, "oxigraf")
         >> drvModbusAsynConfigure("pump_drive_level", "rs485_port",
        230, 3, 10,
         >> 1, 0, 200, "oxigraf")
         >> drvModbusAsynConfigure("command_codes", "rs485_port", 230,
        3, 97, 3,
         >> 0, 200, "oxigraf")
         >> /
         >> I also began tracing the port to a file (st.cmd):
         >> /asynSetTraceMask("rs485_port", 0, 0x09)
         >> asynSetTraceIOMask("rs485_port", 0, 0x04)
         >> asynSetTraceFile("rs485_port",0,"rs485_port-trace.txt")/
         >>
         >> For the streamDevice portion of the communication, I am
        using one
         >> simple proto file that directly sends binary to the serial
        and waits
         >> for a reply. It is following the command structure specified in
         >> Section 3.3 of Modbus User Defined Command Format of the "MODBUS
         >> INTERFACE GUIDE OXIGRAF MO2i FAMILY OXYGEN SENSORS" Manual,
        dated
         >> January 8, 2008. User- defined command 100 is used.
         >>
         >> Format of message specified by manual:
         >> /Command: Addr 100 1 O2.H O2.L CRC.L CRC.H/
         >> /Response: Addr 228 5 CRC.L CRC.H /
         >>
         >> My proto file:
         >> /Terminator = ;
         >> ReplyTimeout = 200; # milliseconds
         >> ReadTimeout = 2000;
         >>
         >> calibrateLow{
         >>          out "\xe6\x64\x01\x08\x2a"; #e6 is the modbus slave
        address,
         >> 64 is user-defined command 100, 01 is the command specific
        to the
         >> device "low end calibration", 08 2a is an integer describing
        cal gas
         >> concentration (2090 for 20.90% O2). When I enable modbus
        interpose
         >> config, the CRC is calculated and sent automatically as the
        trace
         >> below shows.
         >>          in "\xe6\xe4\x05\x1b\x34"; # e6 is the modbus slave
        address,
         >> e4 is the expected response 228, 05 is the expected response
        05, and
         >> 1b 34 are the expected CRC from the slave device
         >> }/
         >>
         >> When I process the stream record that calls the calibrateLow
        section
         >> of the above proto file, the below trace happens:
         >> /2026/02/03 07:59:40.410 /dev/ttyUSB0 write 7
         >> e6 64 01 08 2a e5 39
         >>
         >> /
         >> /2026/02/03 07:59:40.434 /dev/ttyUSB0 read 5
         >> e6 e4 05 1b 34
         >> 2026/02/03 07:59:40.435 modbusInterpose::readIt, CRC error/
         >>
         >> The following errors also appear in the IOC shell:
         >> /2026/02/03 07:59:40.434526 rs485_port lowCalibrate_BO:
        asynError in
         >> read:
         >> 2026/02/03 07:59:40.434555 rs485_port lowCalibrate_BO: I/O
        error after
         >> reading 0 bytes: ""
         >> 2026/02/03 07:59:40.434568 rs485_port lowCalibrate_BO:
        Protocol aborted
         >> 2026/02/03 07:59:41.383 drvModbusAsyn::doModbusIO port
         >> channel_o2_readings error calling writeRead,
         >> error=asynManager::queueLockPort queueRequest timed out,
        nwrite=0/6,
         >> nread=0
         >> 2026/02/03 07:59:41.415 drvModbusAsyn::doModbusIO port
         >> pump_drive_level error calling writeRead,
         >> error=asynManager::queueLockPort queueRequest timed out,
        nwrite=0/6,
         >> nread=0
         >> 2026/02/03 07:59:41.447 drvModbusAsyn::doModbusIO port
        command_codes
         >> error calling writeRead, error=asynManager::queueLockPort
        queueRequest
         >> timed out, nwrite=0/6, nread=0
         >> 2026/02/03 07:59:41.562 drvModbusAsyn::doModbusIO port
        oxigraf_data_1
         >> error calling writeRead, error=, nwrite=6/6, nread=5
         >> 2026/02/03 07:59:43.935 drvModbusAsyn::doModbusIO port
         >> channel_o2_readings writeRead status back to normal having had 2
         >> errors, nwrite=6/6, nread=10
         >> 2026/02/03 07:59:44.862 drvModbusAsyn::doModbusIO port
        command_codes
         >> writeRead status back to normal having had 2 errors,
        nwrite=6/6, nread=8
         >> 2026/02/03 07:59:44.990 drvModbusAsyn::doModbusIO port
        oxigraf_data_1
         >> writeRead status back to normal having had 2 errors,
        nwrite=6/6, nread=16
         >> 2026/02/03 07:59:45.118 drvModbusAsyn::doModbusIO port
         >> pump_drive_level writeRead status back to normal having had
        2 errors,
         >> nwrite=6/6, nread=4/
         >>
         >> The slave device accepts the command and behaves as
        expected, but
         >> modbusInterpose throws the above CRC error. In addition,
        some nuisance
         >> errors seem to appear from the brief break in modbus polling.
         >>
         >> I think that modbusInterpose is interfering with what the stream
         >> expects to see on its "in" line. I am not sure how to mend
        this. I
         >> have tried getting rid of the CRC (\x1b\x34) on the "In"
        line of the
         >> proto file as well, but that still results in the same
        "2026/02/03
         >> 07:59:40.435 modbusInterpose::readIt, CRC error."
         >>
         >> William Jamieson
         >>
         >> On Tue, Feb 3, 2026 at 2:32 AM Torsten Bögershausen
        <tboegi at edom.se <mailto:tboegi at edom.se>
         >> <mailto:tboegi at edom.se <mailto:tboegi at edom.se>>> wrote:
         >>
         >>
         >>
         >>     On 2026-02-02 18:54, William Jamieson via Tech-talk wrote:
         >>      > Does anyone have experience using user-defined
        function codes
         >>     alongside
         >>      > the standard function codes that can be handled by
        the asyn MODBUS
         >>      > driver? I am currently using the MODBUS driver for
        the standard
         >>     03 "READ
         >>      > HOLDING REGISTERS" function while simultaneously using
         >> streamDevice
         >>      > driver for executing user-defined functions. This is
        resulting in
         >>      > conflicts between the MODBUS and streamDevice driver.
        I have tried
         >>      > setting up two virtual ports pointing to the same
        serial port:
         >>      > drvAsynSerialPortConfigure("rs485_modbus", "/dev/
        ttyUSB0", 0,
         >> 1, 0)
         >>      > drvAsynSerialPortConfigure("rs485_stream", "/dev/
        ttyUSB0", 0,
         >> 1, 0)
         >>
         >>     I think that this is the wrong approach - according to my
         >> understanding.
         >>     Both the modbus and streamdevice should lock the asynport.
         >>     ("rs485_port")
         >>     Which should the lock the underlying device, "/dev/ttyUSB0".
         >>     If you enable all asyn traces (and direct them to a file)
         >>     there should be some light comming up, if there is a problem
         >>     here in synchronizing things.
         >>     Or if there is a problem with the hardware ?
         >>
         >>
         >>      >
         >>      > Then using a SEQ record that disables the
        rs485_modbus port then
         >>     enables
         >>      > the rs485_stream port before processing the stream
        record. after
         >>      > processing, it disables the rs485_stream port and re-
        enables the
         >>      > rs485_modbus port.
         >>      >
         >>      > I disable and enable ports by writing a 0 (disable) or 1
         >> (enable) to
         >>      > the CNCT field of an ASYN record for each port.
        Example record:
         >>      > record (asyn, "modbusPort_ASYN")
         >>      > {
         >>      >      field (PORT, "rs485_modbus")
         >>      > }
         >>      >
         >>      > Not only does this switchover create a lot of I/O
        Error messages
         >>     in the
         >>      > IOC shell, but this still results in a garbled data
        readback from
         >>     the
         >>      > stream record.
         >>      >
         >>      > Does anyone have a successful method of making these
        two methods
         >>     work in
         >>      > parallel? (or some other tools that make it work) Or
        should I
         >>     just give
         >>      > up and use deviceStream reads/writes instead of just
        the custom
         >>     ones. I
         >>      > prefer to use the MODBUS driver if possible for the
        standard
         >> MODBUS
         >>      > function codes.
         >>      >
         >>      > Thanks for any help!
         >>      >
         >>      > William Jamieson
         >>      > PPPL I&C Engineer
         >>      >
         >>      >
         >>
         >



Replies:
Re: [External] Re: MODBUS User-Defined Function Codes Ralph Lange via Tech-talk
References:
MODBUS User-Defined Function Codes William Jamieson via Tech-talk
Re: MODBUS User-Defined Function Codes Torsten Bögershausen via Tech-talk
Re: [External] Re: MODBUS User-Defined Function Codes William Jamieson via Tech-talk
Re: [External] Re: MODBUS User-Defined Function Codes Torsten Bögershausen via Tech-talk
Re: [External] Re: MODBUS User-Defined Function Codes Torsten Bögershausen via Tech-talk
Re: [External] Re: MODBUS User-Defined Function Codes William Jamieson via Tech-talk
Re: [External] Re: MODBUS User-Defined Function Codes Mark Rivers via Tech-talk

Navigate by Date:
Prev: AreaDetector Collaboration Meeting - February 5th Érico Nogueira Rolim via Tech-talk
Next: Re: [External] Re: MODBUS User-Defined Function Codes Ralph Lange via Tech-talk
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024  2025  <2026
Navigate by Thread:
Prev: Re: [External] Re: MODBUS User-Defined Function Codes Mark Rivers via Tech-talk
Next: Re: [External] Re: MODBUS User-Defined Function Codes Ralph Lange via Tech-talk
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024  2025  <2026
ANJ, 19 Mar 2026 · Home · News · About · Talk · Base · Modules · Extensions ·
· Distributions · Download · Documents · Links · Licensing ·