Slow code and erroneous reading issue MODBUS MASTER library (from Doc Walter)

I all,

I am trying to implement the Modbus master library (from Doc Walter) on an project, however I am experiencing some issues such as the code running slowly and erroneous readings when I am trying to read more than one register at a time.

In the code bellow, if I call only one of the "read" functions in the loop, the refreshing rate is very fast (<500 ms). If I call more than one function, the refreshing rate is slow (1 sec. aprox) and only one variable returns the correct value, the other readings are erroneous (value remains 0). When I add the delays in between the functions call (in the loop) the readings are correct, however they “block” the code.
Even though I don't need a very fast refreshing rate, I can't afford "blocking" delays. I have tried calling the functions sequentially using "non-blocking delays" (timing them using the millis function) however my code still runs very slowly and "blocks" other functions (e.g. slows down other readings, or slows down button click reaction)

I have tried it on arduino Uno and Mega boards, using various TTL to RS485 converter boards, however the problem persists will all of them. I have hooked some pull up and pull down resistors (392 ohms) between the A and VCC terminal and between B and GND terminal of the RS485 module, as well as 120 omhs termination resistors between A and B but didn't notice any change.

I would be very grateful if you could give me an insight on that.

Thanks in advance,

Aurel

#include <ModbusMaster.h>
//#include <SPI.h>
//#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>
#define I2C_ADDR    0x3F 
#define BACKLIGHT_PIN     3
#define En_pin  2
#define Rw_pin  1
#define Rs_pin  0
#define D4_pin  4
#define D5_pin  5
#define D6_pin  6
#define D7_pin  7
#define baudRate  9600




ModbusMaster node;          // instantiate ModbusMaster object
LiquidCrystal_I2C lcd (0x3F, 2, 1, 0, 4, 5, 6, 7);  // set the LCD address to 0x27 for a 16 chars and 2 line display // Set the LCD I2C address



String modbusStatusMessage;
int modbusStatus;
float fluidMfcCapacity;
float fluidMfcTemp;
float fluidMfcFlow;
float fluidMfcCounterValue;
unsigned long fluidMfcValveOutput;




void setup() {

  lcd.begin(20, 4);  // initialize the lcd for 24 chars 4 lines, turn on backlight
  lcd.setBacklightPin(BACKLIGHT_PIN, POSITIVE);
  lcd.setBacklight(HIGH);    // Switch on the backlight
  lcd.home (); // go home

  Serial.begin(baudRate, SERIAL_8E1);    // use Serial (port 0); initialize Modbus communication baud rate
  node.begin(1, Serial);     // communicate with Modbus slave ID 1 over Serial (port 0)




 
}

void loop() {

    readMfcFlow ();
    //delay (250);
    readMfcCapacity();
    //delay (250);
    readMfcCounterValue ();
    //delay (250);
    readMfcFluidTemp ();
    //delay (250);
    readMfcValveOutput ();
    //delay (250);
     statusModbus();
     printMfcParameters ();
 
}


void readMfcFluidTemp (){

  
  uint8_t result;
  
  union {
  float temp;
  uint16_t t[2];
    } unitedTempVar;


  
  result = node.readHoldingRegisters(41272, 2);           

  if (result == node.ku8MBSuccess)      
              {

    unitedTempVar.t [0] = node.getResponseBuffer(0x01);
    unitedTempVar.t [1] = node.getResponseBuffer(0x00);
    
          }

   fluidMfcTemp= unitedTempVar.temp;
   modbusStatus=result;   

  
  }


void readMfcCapacity (){

  uint8_t result;
  
  union {
  float capacity;
  uint16_t cap[2];
    } unitedVar;

  
  result = node.readHoldingRegisters(33128, 2);          
  

  if (result == node.ku8MBSuccess)      
             {

    unitedVar.cap [0] = node.getResponseBuffer(0x01);
    unitedVar.cap [1] = node.getResponseBuffer(0x00);

    
    fluidMfcCapacity= unitedVar.capacity;
    
  
  }

  
  modbusStatus=result;     


}



void readMfcFlow (){

  
  uint8_t result;
  
  union {
  float flow;
  uint16_t b[2];
    } unitedVar;


  result = node.readHoldingRegisters(41216, 2);           

  if (result == node.ku8MBSuccess)      { 

    unitedVar.b [0] = node.getResponseBuffer(0x01);
    unitedVar.b [1] = node.getResponseBuffer(0x00);

    
        }

    fluidMfcFlow= unitedVar.flow;
    modbusStatus=result;  

}



void readMfcCounterValue (){

  uint8_t result;
  
  union {
  float counterValue;
  uint16_t b[2];
    } unitedVar;


  
  result = node.readHoldingRegisters(59400, 2);           
  

  if (result == node.ku8MBSuccess)       
             {

    unitedVar.b [0] = node.getResponseBuffer(0x01);
    unitedVar.b [1] = node.getResponseBuffer(0x00);

    
    
    
  
  }

  fluidMfcCounterValue= unitedVar.counterValue;
  modbusStatus=result;     


}



void readMfcValveOutput (){

  uint8_t result;
  
  union {
  unsigned long valveOutput;
  uint16_t b[2];
    } unitedVar;


  
  result = node.readHoldingRegisters(61960, 2);           
  

 if (result == node.ku8MBSuccess)      
            {

    unitedVar.b [0] = node.getResponseBuffer(0x01);
    unitedVar.b [1] = node.getResponseBuffer(0x00);

    
    
    
  
 }

  fluidMfcValveOutput= unitedVar.valveOutput;
  modbusStatus=result;     

}




void statusModbus() {




  if (modbusStatus == node.ku8MBIllegalFunction) {
    modbusStatusMessage = "Modbus Illegal Func.";
  } else if (modbusStatus == node.ku8MBIllegalDataAddress) {
    modbusStatusMessage = "Modbus Illegal Addr.";
  } else if (modbusStatus == node.ku8MBIllegalDataValue) {
    modbusStatusMessage = "Modbus Illegal Value";
  } else if (modbusStatus == node.ku8MBSlaveDeviceFailure) {
    modbusStatusMessage = "Modbus lave failure ";
  } else if (modbusStatus == node.ku8MBInvalidSlaveID) {
    modbusStatusMessage = "Modbus Inval.slaveID";
  } else if (modbusStatus == node.ku8MBInvalidFunction) {
    modbusStatusMessage = " Modbus Inval.Func. ";
  } else if (modbusStatus == node.ku8MBResponseTimedOut) {
    modbusStatusMessage = "  Modbus timed out  ";
  } else if (modbusStatus == node.ku8MBInvalidCRC) {
    modbusStatusMessage = " Modbus Invalid CRC ";
  } else {
    modbusStatusMessage = "   Modbus succes    ";
  }



}

void  printMfcParameters (){

    lcd.setCursor(0,0);
    lcd.print(modbusStatusMessage);
    lcd.print("     ");

        lcd.setCursor(0,1);
        lcd.print("t:");
        lcd.print(fluidMfcTemp,2);
        lcd.print("  ");
  
    
    lcd.setCursor(0,2);
    lcd.print("Cap:");
    lcd.print(fluidMfcCapacity,2);
    lcd.print("  ");
    
    lcd.setCursor(0,3);
    lcd.print ("FL:");
    lcd.print(fluidMfcFlow,2);
    lcd.print("  ");
    
    
    lcd.setCursor(10,1);
    lcd.print("V");
    lcd.print(fluidMfcValveOutput,1);
        if (fluidMfcValveOutput <100){
          lcd.print("   ");
          }else lcd.print("  ");

    lcd.setCursor(10,2);
    lcd.print("CV:");
    lcd.print(fluidMfcCounterValue);
    lcd.print("     ");


}

however I am experiencing some issues such as the code running slowly

How do you check that? Which part do you think is running slowly?

String modbusStatusMessage;

Bad idea to use the String class on AVR Arduinos.

In the code bellow, if I call only one of the "read" functions in the loop, the refreshing rate is very fast (<500 ms). If I call more than one function, the refreshing rate is slow (1 sec. aprox) and only one variable returns the correct value, the other readings are erroneous (value remains 0). When I add the delays in between the functions call (in the loop) the readings are correct, however they "block" the code.

That's because Doc Walker's code doesn't enforce the Modbus timing. between two messages there must be a pause in the length of 3.5 characters, so in your case a sleep(4) should be sufficient.

I have tried it on arduino Uno and Mega boards, using various TTL to RS485 converter boards, however the problem persists will all of them. I have hooked some pull up and pull down resistors (392 ohms) between the A and VCC terminal and between B and GND terminal of the RS485 module, as well as 120 omhs termination resistors between A and B but didn't notice any change.

Can you increase the baud rate? What's the device you're requesting?

Thanks a lot Pylon!
To answer your questions:

  1. Indeed, in the code I posted it might not be obvious to notice the slowdown, although it does happen. In my final project, though, in which the code includes menus, with rotary encoders, and senses voltages from Analog inputs, accessing the menus through the rotary encoder becomes almost impossible (very large delays (several seconds) between clicking and response), and the refreshing delay in the analog reading becomes extremely large (several seconds). I have attached the code of the final project (as attached ino file).

  2. Why are string not recommended? Does it have to do with memory? Could that induce a slow running code?

  3. I didn't find a sleep () function in the library, could you elaborate on that?

  4. I have tried to change the baud rate to the maximum allowed by the device (38400). I didn't notice any change. The device I am trying to request in a Mass flow controller from Bronkhorst. See link bellow. I have attached the Modbus manual that came with it.

Thanks

Aurel

917035-Manual-Modbus-slave-interface.pdf (1.95 MB)

Here is the code of the final project

Menu_encoder_lcd_direct_v25_modbus_9600_16_10_2019.ino (57.7 KB)

  1. Why are string not recommended? Does it have to do with memory? Could that induce a slow running code?

No, but it's often responsible for code not doing what you expect it to do, in many cases it's simply freezing. And yes, the problem is the memory, the String class fragments the memory in no time and sooner or later your allocations cannot be served so the code writes to already allocated memory regions with unpredictable results.

  1. I didn't find a sleep () function in the library, could you elaborate on that?

Please excuse, my fault, I meant delay(4). Please tell us if that helps.

  1. I have tried to change the baud rate to the maximum allowed by the device (38400). I didn't notice any change. The device I am trying to request in a Mass flow controller from Bronkhorst. See link bellow. I have attached the Modbus manual that came with it.

This hints that the Modbus interface is not really your problem, at least not your performance problem.

I suspect the ClickEncoder library. Using a timer interrupt every millisecond is simply a waste of resources for such a task.

Your while(numberHeld==true) loop is also quite spooky. What's the reason for that?

Learn to use arrays!

Learn to format your code. The IDE may help your (Ctrl-T).

Here it goes:

  1. the delay (4) did help a lot. The code now runs better, and it would be usable, as long as I don't request too many variables, I presume. Indeed in the case that I would need to request many variable, the sum of delays may be a limitation. I'm surprised though, that there isn't any non-blocking way to request variables.

  2. The spooky while loop is a way to the run the code through the "menu" related function only in the cases that the menu needs to be accessed by the user. I couldn't come up with any simpler way than that (as you saw, I am not a programming expert!)

  3. I presume your suggestion on using more arrays has to do with simplifying the code. Does it have repercussions on memory usage?

  4. I didn't know the Ctrl+T functionality! That's fantastic!!! They should invent an auto-comment code tool! :smiley: :smiley: :smiley: :smiley:

  1. the delay (4) did help a lot. The code now runs better, and it would be usable, as long as I don't request too many variables, I presume. Indeed in the case that I would need to request many variable, the sum of delays may be a limitation. I'm surprised though, that there isn't any non-blocking way to request variables.

How does a non-blocking way look like? The pause is part of the Modbus protocol. You can increase the baudrate to make that pause shorter, as I wrote, it's the time equivalent of 3.5 characters.

  1. The spooky while loop is a way to the run the code through the "menu" related function only in the cases that the menu needs to be accessed by the user. I couldn't come up with any simpler way than that (as you saw, I am not a programming expert!)

Introduce a state variable and react on it (the simplest kind of state machine).

  1. I presume your suggestion on using more arrays has to do with simplifying the code. Does it have repercussions on memory usage?

It depends on which kind of memory you meant. It simplifies your code and save a lot of program space. It doesn't save RAM in most situations.

Thanks again, Pylon. I have a few more changes to make on my code, as I want to hook up 3 more modbus apparatuses on the arduino. Let's see how it goes...