arduino-libraries / ArduinoModbus

250 stars 118 forks source link

Modbus RTU library Problem of time consuming when multiple slave on same bus #43

Open mktime opened 3 years ago

mktime commented 3 years ago

Hi

I had problem of time consuming of pool() method in my design using this library.

I used an Arduino Nano Every, but now I tried simple way :

When sending to device 42

[RTU]>Tx > 13:36:22:648 - 2A  03  00  00  00  01  82  11  
[RTU]>Rx > 13:36:22:673 - 2A  03  02  00  00  9C  42  

Everything fine, pool method using 15ms of time.

When sending to device 41 ( who do not exist )

[RTU]>Tx > 13:36:32:289 - 29  03  00  00  00  01  82  22  

Nobody reply, so normal but the pool() method on the arduino adr42 take 7ms and then 500ms.

So this 500ms block the rest of the program in the arduino.

So there is a problem in this library. That's so strange that nobody had this issue before, it is so simple to encounter.

Hope someone can give me directions because.

/*
  Modbus RTU Server Kitchen Sink

  This sketch creates a Modbus RTU Server and demostrates
  how to use various Modbus Server APIs.

  Circuit:
   - MKR board
   - MKR 485 shield
     - ISO GND connected to GND of the Modbus RTU server
     - Y connected to A/Y of the Modbus RTU client
     - Z connected to B/Z of the Modbus RTU client
     - Jumper positions
       - FULL set to OFF
       - Z \/\/ Y set to OFF

  created 18 July 2018
  by Sandeep Mistry
*/

#include <ArduinoRS485.h> // ArduinoModbus depends on the ArduinoRS485 library
#include <ArduinoModbus.h>

unsigned int cycle;

const int numCoils = 10;
const int numDiscreteInputs = 10;
const int numHoldingRegisters = 10;
const int numInputRegisters = 10;

void setup() {  
  Serial.begin(9600);
  while (!Serial);

  Serial2.begin(115200);

  Serial2.println("Modbus RTU Server Kitchen Sink");

  // start the Modbus RTU server, with (slave) id 42
  if (!ModbusRTUServer.begin(42, 9600)) {
    Serial2.println("Failed to start Modbus RTU Server!");
    while (1);
  }

  // configure coils at address 0x00
  ModbusRTUServer.configureCoils(0x00, numCoils);

  // configure discrete inputs at address 0x00
  ModbusRTUServer.configureDiscreteInputs(0x00, numDiscreteInputs);

  // configure holding registers at address 0x00
  ModbusRTUServer.configureHoldingRegisters(0x00, numHoldingRegisters);

  // configure input registers at address 0x00
  ModbusRTUServer.configureInputRegisters(0x00, numInputRegisters);
}

void loop() {
  cycle = millis();

  // Pool for modbus requests
  ModbusRTUServer.poll();

  cycle = millis() - cycle ;
  if ( cycle > 2 )
  {
    Serial2.print("ms: ");
    Serial2.println(cycle);
  }  

  // map the coil values to the discrete input values
  for (int i = 0; i < numCoils; i++) {
    int coilValue = ModbusRTUServer.coilRead(i);

    ModbusRTUServer.discreteInputWrite(i, coilValue);
  }

  // map the holiding register values to the input register values
  for (int i = 0; i < numHoldingRegisters; i++) {
    long holdingRegisterValue = ModbusRTUServer.holdingRegisterRead(i);

    ModbusRTUServer.inputRegisterWrite(i, holdingRegisterValue);
  }
}
ghost commented 3 years ago

Not sure if I have the same issue, but the effect is the same...

Actual only one single Modbus RTU server is created with this library and Arduino. If this server is polled to fast from client (one request every second), the complete arduino program stops working. If the Modbus connection from Client is stoped, arduino program continious running...

If the polling is changed to one request each 3 seconds it much more stabel and effects only in some read errors (~5%) and does not block the arduino program. But also this seems to be a issue.

As Client I'm actual using the software Modbuspoll with USB/RS485 converter.

dmoimas commented 2 years ago

I have an issue which I think it's related to this one: I have two slaves (Nano 33 IOT) on RS485 bus.

When I poll slave A from the master, slave B get stuck (I added a blikning LED in the main loop and it won't blink while other slave is being accessed) and vice-versa. It keeps blinking while serving requests addressed to itself. To solve the issue (apart from putting only 1 device per bus), I had to add lots of wait states on master side which is not good for my application.

I printed some debug on _modbus_rtu_receive function in modbus-rtu.cpp and got:

12:58:51.501 -> Request for slave 2 ignored (not 1)
12:58:51.501 -> _modbus_receive_msg Returned 0, The next expected message is a confirmation to ignore
12:58:52.093 -> Confirmation to ignore
12:58:52.684 -> Request for slave 2 ignored (not 1)
12:58:52.684 -> _modbus_receive_msg Returned 0, The next expected message is a confirmation to ignore
12:58:53.408 -> Request for slave 4 ignored (not 1)
12:58:53.408 -> _modbus_receive_msg Returned 0 on a confirmation to ignore
12:58:53.408 -> Confirmation to ignore
12:58:53.408 -> Request for slave 15 ignored (not 1)
12:58:53.408 -> _modbus_receive_msg Returned 0, The next expected message is a confirmation to ignore
12:58:55.494 -> Confirmation to ignore

seems like some byte got lost or there is a framing error, so an intermediate byte is interpreted as address (no one is accessing slave 4 or 15, just 1 and 2)... with this code:

static int _modbus_rtu_receive(modbus_t *ctx, uint8_t *req)
{
    int rc;
#ifdef ARDUINO
    modbus_rtu_t *ctx_rtu = (modbus_rtu_t*)ctx->backend_data;
#else
    modbus_rtu_t *ctx_rtu = ctx->backend_data;
#endif

    if (ctx_rtu->confirmation_to_ignore) {
        rc = _modbus_receive_msg(ctx, req, MSG_CONFIRMATION);
        if (rc == 0) {
            if (ctx->debug) {
                Serial.println("_modbus_receive_msg Returned 0 on a confirmation to ignore");
            }
        }
        /* Ignore errors and reset the flag */
        ctx_rtu->confirmation_to_ignore = FALSE;
        rc = 0;
        if (ctx->debug) {
            printf("Confirmation to ignore\n");
            Serial.println("Confirmation to ignore");
        }
    } else {
        rc = _modbus_receive_msg(ctx, req, MSG_INDICATION);
        if (rc == 0) {
            /* The next expected message is a confirmation to ignore */
            if (ctx->debug) {
                Serial.println("_modbus_receive_msg Returned 0, The next expected message is a confirmation to ignore");
            }
            ctx_rtu->confirmation_to_ignore = TRUE;
        }
    }
    return rc;
}
dmoimas commented 2 years ago

Sorry for previous post. Looks like it needs very frequent polling to avoid data loss and consequent framing errors. Every slave should process all Modbus data to avoid framing error /next header to be taken in the middle of the message.

Slave A should be able to process all slave B data (both directions) to keep aligned on the FSM which parse message bytes. Same for slave B on slave A data. Master will just wait for addressed slave, not the other ones.

I changed polling speed according to baud rate to avoid any issue and it seems okay now.

cdjackson commented 1 year ago

I also came across this issue and I think I understand what and why it is happening...

As @mktime stated in the initial report, the problem occurs if the slave doesn't exist and this is also the situation I have here. From what I can see, the code as it stands is programmed to ignore the next message if the previous message was not addressed to i. This check is in the _modbus_rtu_check_integrity function -:

    /* Filter on the Modbus unit identifier (slave) in RTU mode to avoid useless
     * CRC computing. */
    if (slave != ctx->slave && slave != MODBUS_BROADCAST_ADDRESS)
    {
        Serial1.print(millis());
        Serial1.println(" - Request from incorrect slave");
        if (ctx->debug)
        {
            printf("Request for slave %d ignored (not %d)\n", slave, ctx->slave);
        }
        /* Following call to check_confirmation handles this error */
        return 0;
    }

Then in modbus-rtu.cpp we have the following check in _modbus_rtu_receive -:

        rc = _modbus_receive_msg(ctx, req, MSG_INDICATION);
        if (rc == 0)
        {
            /* The next expected message is a confirmation to ignore */
            ctx_rtu->confirmation_to_ignore = TRUE;
        }

_modbus_receive_msg will return 0 if the message is not addressed to the local slave ID or broadcast.

II believe (or assume) it does this to ignore the assumed response from the other slave, but in the event that the other slave doesn't exist, this times out and results in the 1/2 second delay in execution.

IMHO, I don't see a need for this check. If the other slave responds, then the message should also be ignored since it won'\t be addressed to the local ID (ie it will likely be addressed to the master) and if the other slave doesn't exist, then we shouldn't wait for a response that may not come - or may arrive after the 500ms timeout...

I've not yet tested this since I only have a single slave in my test system, but for me the fix is to remove the check that sets confirmation_to_ignore to TRUE. In fact as this seems to be the only reason that confirmation_to_ignore can be true (from what I can see), it seems the code around this can be completely removed and simplified.

Maybe I'm missing something - I'm not a Modbus expert, but in most other protocols I've worked on, just ignoring any message not addressed to the local device should be sufficient. I'm happy to hear if others see a need for this check...

mktime commented 1 year ago

@cdjackson Hi Thanks for your advice My project was a for my own usage and since then i didn't had time to correct the problem, so my system work but bad. As i'm not a modbus expert too, i didn't touch the library for now.

But thanks for your advice, i will try as soon as i can.

wanghuifen987 commented 1 year ago

As @mktime stated in the initial report, the problem occurs if the slave doesn't exist and this is also the situation I have here. From what I can see, the code as it stands is programmed to ignore the next message if the previous message was not addressed to i. This check is in the _modbus_rtu_check_integrity function -:

    /* Filter on the Modbus unit identifier (slave) in RTU mode to avoid useless
     * CRC computing. */
    if (slave != ctx->slave && slave != MODBUS_BROADCAST_ADDRESS)
    {
        Serial1.print(millis());
        Serial1.println(" - Request from incorrect slave");
        if (ctx->debug)
        {
            printf("Request for slave %d ignored (not %d)\n", slave, ctx->slave);
        }
        /* Following call to check_confirmation handles this error */
        return 0;
    }

Then in modbus-rtu.cpp we have the following check in _modbus_rtu_receive -:

        rc = _modbus_receive_msg(ctx, req, MSG_INDICATION);
        if (rc == 0)
        {
            /* The next expected message is a confirmation to ignore */
            ctx_rtu->confirmation_to_ignore = TRUE;
        }

_modbus_receive_msg will return 0 if the message is not addressed to the local slave ID or broadcast.

II believe (or assume) it does this to ignore the assumed response from the other slave, but in the event that the other slave doesn't exist, this times out and results in the 1/2 second delay in execution.

IMHO, I don't see a need for this check. If the other slave responds, then the message should also be ignored since it won'\t be addressed to the local ID (ie it will likely be addressed to the master) and if the other slave doesn't exist, then we shouldn't wait for a response that may not come - or may arrive after the 500ms timeout...

I've not yet tested this since I only have a single slave in my test system, but for me the fix is to remove the check that sets confirmation_to_ignore to TRUE. In fact as this seems to be the only reason that confirmation_to_ignore can be true (from what I can see), it seems the code around this can be completely removed and simplified.

Maybe I'm missing something - I'm not a Modbus expert, but in most other protocols I've worked on, just ignoring any message not addressed to the local device should be sufficient. I'm happy to hear if others see a need for this check...

Thank you for your information! I have come across similar problem. I search 500 in ArduinoModbus/src file folder, and find this in modbus-private.h:

/* Timeouts in microsecond (0.5 s) */
#define _RESPONSE_TIMEOUT    500000
#define _BYTE_TIMEOUT        500000

The doesn't exist over time is defined here and you can change this time to 000000 to cancel the doesn't exist over time or change to other proper value.

By the way, A modbus node use the response to judge whether last-turn communication is over, so I think it is better to comply with the MODBUS Protocol.

cdjackson commented 1 year ago

By the way, A modbus node use the response to judge whether last-turn communication is over, so I think it is better to comply with the MODBUS Protocol.

Can you please provide a reference to this since I'm not sure what timeout you mean here. I think this is fine (and necessary) for the master since it is initiating the transactions, but a slave doesn't need to know if "last-turn communication is over" - it just needs to respond to any messages that get sent to it.

wanghuifen987 commented 1 year ago

By the way, A modbus node use the response to judge whether last-turn communication is over, so I think it is better to comply with the MODBUS Protocol.

Can you please provide a reference to this since I'm not sure what timeout you mean here. I think this is fine (and necessary) for the master since it is initiating the transactions, but a slave doesn't need to know if "last-turn communication is over" - it just needs to respond to any messages that get sent to it.

When a slave receive an "ask" not sent to it, it would wait for the "answer" until timeout time is reached. I'm not a specialist of modbus, but have found this phenomenon: If I send a slave an "ask" for other slave directly, it would wait a _RESPONSE_TIMEOUT, but if I send a slave an "answer" sent by other slave directly, it would not wait a _RESPONSE_TIMEOUT, it wait a _BYTE_TIMEOUT. It seems that in this modbus lib, the slave has different state to deal with ”ask“ and ”answer“, so it need to deal with every "ask" and "answer" to filter out the "ask" sent to itself. A good news is I have some other modbus equipments, they always work well as you imagine. So I think this is depend on equipment, not by the modbus protocol.

cdjackson commented 1 year ago

When a slave receive an "ask" not sent to it, it would wait for the "answer" until timeout time is reached.

Thanks, but can you provide the protocol reference please? To me this makes no sense since the slave will never send a request anyway, so what is this timeout actually for? All it does is block the slave from doing anything - if the master sends a request, then the slave cannot respond. I'd just like to read the exact wording in the protocol doc and I can't find this at the moment.

Thanks.