RS-485 modbus library

@developer_bt, the errors are caused by the Arduino pre-processor problems. If you compile for system firmware v 0.6.0, it will work.

1 Like

@peekay123 Thanks whether you will soon update to work with 0.6.1?
And generally which a better library for modbus master?

@developer_bt, Particle will be releasing 0.6.2 soon, which fixes the Arduino compatibility issues. To be honest, I ported the modbus library for some members and I don’t personally use it. The existing IDE modbus master library is the same as mine and seems to be the one most commonly used.

Thank you @peekay123 ,
I’ll wait for the new version to not return to 0.6.1
In any case I will test both libraries.

1 Like

Hello Folks,
I will share some of my experiences with mod-bus libraries test that I have done so far, kindly ask suggestion for a problem that I having right now with particle photon.

My intention is pulling some data from holding register on VFD Control techniques.
For some reason i din’t have success using this library,Smarmengol-Modbus Library, the problem I have no idea how to address a holding register like this one 401811, if i try to set 1811 register the arduino or photon
hangs and do nothing.

With this library “4-20ma” work fine with arduino… I use software serial to connect the MAX485, and that work perfect, I was able to read any register the VFD… I tried that same library with the ESP8266 and works too… the problem… if one disconnect the max485 from the ESP8266 or simple disconnect the cable from slave, the ESP8266 crash after a few intents of read the registers…timeouts…
I tried to determine the problem… but I am not so advanced programmer to dig into the library … so I gave up … I read somewhere thi is because the library Software Serial + mod-bus disable the GPIO pin interrupts very often . Now I move to try with Particle photon. initially I was having a problem very often got timeout while trying to read 10 holding registers… mysteriously solved
for my test I just used the “4-20ma” library without any modification.
this is the sample code:

#include <ModbusMaster.h>
/* 
  We're using a MAX485-compatible RS485 Transceiver.
  Rx/Tx is hooked up to the hardware serial port at 'Serial'.
  The Data Enable and Receiver Enable pins are hooked up as follows:
*/
#define MAX485_DE      A3
#define MAX485_RE_NEG  A2
#define CT_VFD(m,n)  (m *100+ n-1)  // formula to acces VFD registers

// instantiate ModbusMaster object
ModbusMaster node;
#define reg_qty 11
void preTransmission()
{
  digitalWrite(MAX485_RE_NEG, 1);
  digitalWrite(MAX485_DE, 1);
}

void postTransmission()
{
  digitalWrite(MAX485_RE_NEG, 0);
  digitalWrite(MAX485_DE, 0);
}

void setup()
{
  pinMode(MAX485_RE_NEG, OUTPUT);
  pinMode(MAX485_DE, OUTPUT);
  // Init in receive mode
  digitalWrite(MAX485_RE_NEG, 0);
  digitalWrite(MAX485_DE, 0);

  // Modbus communication runs at 115200 baud
  Serial.begin(19200);
  Serial1.begin(19200);
  // Modbus slave ID 1, Serial1 on the Particle Photon
  node.begin(1, Serial1);
  // Callbacks allow us to configure the RS485 transceiver correctly
  node.preTransmission(preTransmission);
  node.postTransmission(postTransmission);
}

bool state = true;

void loop()
{
  uint8_t result;
  uint16_t data[6];

  // Toggle the coil at address 0x0002 (Manual Load Control)
  //result = node.writeSingleCoil(0x0002, state);
  //state = !state;

  // Read 16 registers starting at 0x3100)
  //result = node.readInputRegisters(0x3100, 16);

  // read   holding register VFD Control_techniques  starting at Pr 1811  ( modbus 401811- 401814)
   result=node.readHoldingRegisters(CT_VFD(18,11), reg_qty);
  if (result == node.ku8MBSuccess)
  {
    Serial.println("Data: ");
   // Serial.println(node.getResponseBuffer(1));
    for (uint8_t j = 0; j < reg_qty; j++)  Serial.println(node.getResponseBuffer(j),DEC);

                  Serial1.flush();
                 // node.clearTransmitBuffer();
  }
  else {
    Serial.println("NO- RESPONSE"); // i got not response  very oftem
    //mySerial.flush();

  }
 //node.clearResponseBuffer();
//  node.clearTransmitBuffer();
  delay(5000);
 Serial.println("running...");
}

Questions that I have so maybe some one can provide some advise
-The modbus master library ported by @peekay123 do not work for me . i got some errors… I see this library was ported 3 years ago… so maybe that is the reason… Not clear if 4-20ma library specially ported for spark has any advantage over the un-ported 4-20ma master library.
-this is another diferent library Modbus_RS485 that seems pretty interesting… but as is it right now I can’t get it to read any modbus registers on the VFD using photon not sure if i did right to addressee the register using this line -->telegram[1].u16RegAdd = 1811; // start address in slave

1 Like

A few questions:

  1. The RS-485 device you are using, is it 1-based or 0-based? If it is 0-based. It should be:
#define CT_VFD(m,n) (m *100+ n)
  1. Is the reg_qty size actually 11? Meaning the data package you are looking for has a size of 2*11= 22 bytes? According to your comment, it seems your reg_qty for 1811 should be 4 but reg_qty is defined as 11.
  2. Does your MODBUS device default to a “sleep” state if nothing is communicating with it? If so you need to ping the device with a read register command, wait a few milliseconds, and try again.

Here is some code I wrote up for you to try out. It handles some of the conditions I stated above.

// Turn serial output on or off
#define DEBUGON 1

// Transmission delay between modbus calls
#define XMITDELAY 100

// 0 for 0-based, 1 for 1-based numbering
#define BASED_NUMBERING 1

/*********************************************************************************
* Retrieves holding register data while handling device sleeping conditions
**********************************************************************************/

bool getHoldingRegisterData(uint16_t registerAddress, uint16_t regSize, uint16_t* data){

    uint8_t j, result;

    if(DEBUGON){
      Serial.print(F("Reading register: "));
      Serial.print(registerAddress);
      Serial.print(F(" regSize: "));
      Serial.print(regSize);
      Serial.print(F(" sizeof(data): "));
      Serial.print(sizeof(&data));
      Serial.print(F(" XMITDELAY: "));
      Serial.println(XMITDELAY);
    }
	
	// Delay and get register data.
    
    result = node.readHoldingRegisters(registerAddress-BASED_NUMBERING, regSize);
	delay(XMITDELAY);
	
	// Device may be sleeping, ping it a couple more times.  
	
	if(result ==node.ku8MBResponseTimedOut){

            if(DEBUGON){
              Serial.println(F("Response timed out. Trying again. "));
            }	
			
			int i =0;
			
			while(i < 2){

				result = node.readHoldingRegisters(registerAddress-BASED_NUMBERING, regSize);
				delay(XMITDELAY);
				
				if(DEBUGON) Serial.printlnf("Timeout iteration# %d. ", i);
				
				if(result == node.ku8MBResponseTimedOut){
					if(DEBUGON) Serial.println(F("Failed. Response timed out. Adjust the XMITDELAY and/or ModbusMaster::ku8MBResponseTimeout? "));
				}
				else break;
				
				i++;
				
			}

    }

    if (result == node.ku8MBSuccess) {
      if(DEBUGON) Serial.print(F("Success, Received data: "));

      for (j = 0; j < regSize; j++) {

        data[j] = node.getResponseBuffer(j);
        if(DEBUGON){
          Serial.print(data[j], HEX);
          Serial.print(F(" "));
        }
      }
	  
	  if(DEBUGON){
		  Serial.println("");
	  }

	  node.clearResponseBuffer();
	  node.clearTransmitBuffer();
          return true;

    }
    else{
	  if(DEBUGON){
		Serial.print(F("Failed, Response Code: "));
		Serial.println(result, HEX);
	  }
    }
	
	node.clearResponseBuffer();
	node.clearTransmitBuffer();
    return false;
    }
1 Like

Thanks @rama appreciate your support i will try that.


VFD has 16 bits register and 32 bit, I am trying to access to a 16bit ones, application register are contiguous, So i am using the function that read multiple registers, where i set the first register to read and then the qty.
I am using this RS485 breakoout board RS485
my idea is to try a reliable Modbus library that can be used with photon, so i will design a PCB board to place the photon and all the required components to connect it to CT(EMERSON ) ac/dc drives that has mod-bus rtu as standard communication protocol, see the picture of how register can be addressed ( I am using modbus standard protocol on the VFD)

Ok so for a 16 bit value, reg_qty= 1. 32 bit -> reg_qty = 2.

I think there are problems in the way you are implementing the Modbus libraries and you need to write simple unit tests to confirm functionality. It’s difficult to debug.

If you can make a USB-Serial connection with your device, my suggestion is to first download and use this program to make sure your command structure is correct.

Is your MODBUS device configured for a baud rate of 19200 @ 8N1? (this is default for Serial1 I believe). If for example its 8E1, then you need to change to port configure:

Serial1.begin(19200, SERIAL_8E1);

You will know your port is configured properly if you ping the device repeatedly and get at least an error response from your device (qmodmaster can confirm this).

Next, write a simple program testing the variations of the following (hard coded values).

result = node.readHoldingRegisters(1800,1);
result = node.readHoldingRegisters(1801,1);
result = node.readHoldingRegisters(1800,2);
1 Like

I tested your piece of code and it work as well, your is far better organized that mine… once again thanks.

2 Likes

@luisgcu no problem… it took me a month to figure it all out :blush:

2 Likes

Great job guys :smile:
Whether you can put the full code from the example that you have done for reading holding registers?
Or all the code it is seen in the picture?

hello @developer_bt as soon as I get a bit of free time i will share what I have.

here is picture of my test set with Emerson Control-Techniques VFD.

1 Like

The port @peekay123 was a great start for me on my modbus project. I did see some other ModbusMaster versions out there too.

Some mods I did to my copy were:

  • Added directives so the library could compile with Particle and Arduino
  • Abstracted (is this the right word?) the MBSerial object to work with CustomSoftwareSerial (Arduino) and for initializing SerialX objects with various baud_rate and parity configs ie. SERIAL_8E1, SERIAL_8N1
  • Added half duplex functionality. ie. using a TX enable pin.

I did see in another ModbusMaster copy that preTransmission and postTransmission callbacks were added in the ModbusMasterTransaction.

I definitely don’t mind uploading the mods, I just don’t know the best way to do it. There’s a few different versions of this library and I don’t want to make another on the internet lol.

1 Like

I have only one problem, and is when register on the device get a negative value for example -1 , then mod-bus reading display 65535.

Here is the piece of code that I use to read a few VFD modbus holding registers.
I use this library… I have no made any modification to the code: Github user 4-20ma “Modbus Master”
see the modbus master files inside src folder, notice I copy crc16.h and word.h to the same folder.

#include <ModbusMaster.h>

/*!
  We're using a MAX485-compatible RS485 Transceiver.
  Rx/Tx is hooked up to the hardware serial port at 'Serial'.
  The Data Enable and Receiver Enable pins are hooked up as follows:
*/
#define MAX485_DE      A3
#define MAX485_RE_NEG  A2
#define CT_VFD(m,n)  (m *100+ n-1)  // formula to acces VFD registers (not used in this example)
// Comment this out if you don't want serial output
#define DEBUGON 1

// Transmission delay between modbus calls
#define XMITDELAY 2000

// 0 for 0-based, 1 for 1-based numbering
#define BASED_NUMBERING 1

/*********
* Retreives holding register data while handling device sleeping conditions
**********/

// instantiate ModbusMaster object
ModbusMaster node;
#define reg_qty 11 (not used in this example)
void preTransmission()
{
  digitalWrite(MAX485_RE_NEG, 1);
  digitalWrite(MAX485_DE, 1);
}

void postTransmission()
{
  digitalWrite(MAX485_RE_NEG, 0);
  digitalWrite(MAX485_DE, 0);
}

void setup()
{
  pinMode(MAX485_RE_NEG, OUTPUT);
  pinMode(MAX485_DE, OUTPUT);
  // Init in receive mode
  digitalWrite(MAX485_RE_NEG, 0);
  digitalWrite(MAX485_DE, 0);

  // Modbus communication runs at 115200 baud
  Serial.begin(19200);
  Serial1.begin(19200);
  // Modbus slave ID 1, Serial1 on the Particle Photon
  node.begin(1, Serial1);
  // Callbacks allow us to configure the RS485 transceiver correctly
  node.preTransmission(preTransmission);
  node.postTransmission(postTransmission);
}

bool state = true; // (not used in this example)

void loop()
{
  uint16_t data[6];
  
 getHoldingRegisterData( 407,  1,  data);  //symetrical current limit in % 0.1 dec place
 delay(1000);
 getHoldingRegisterData( 506,  1,  data); //motor rated frecuency in hz 0.1 dec place

}

bool getHoldingRegisterData(uint16_t registerAddress, uint16_t regSize, uint16_t* data){

uint8_t j, result;

if(DEBUGON){
  Serial.print(F("Reading register: "));
  Serial.print(registerAddress);
  Serial.print(F(" regSize: "));
  Serial.print(regSize);
  Serial.print(F(" sizeof(data): "));
  Serial.print(sizeof(&data));
  Serial.print(F(" XMITDELAY: "));
  Serial.println(XMITDELAY);
  Serial.println("");
}

// Delay and get register data.

result = node.readHoldingRegisters(registerAddress-BASED_NUMBERING, regSize);
delay(XMITDELAY);

// LT is sleeping, ping it a couple more times.

if(result ==node.ku8MBResponseTimedOut){

        if(DEBUGON){
          Serial.println(F("LT: Response timed out. Trying again. "));
        }

		int i =0;

		while(i < 2){

			result = node.readHoldingRegisters(registerAddress-BASED_NUMBERING, regSize);
			delay(XMITDELAY);

			if(DEBUGON){
				Serial.printlnf("LT: Timeout iteration# %d. ", i);
			}

			if(result == node.ku8MBResponseTimedOut){
				if(DEBUGON) Serial.println(F("LT: Failed. Response timed out. Adjust the XMITDELAY and/or ku8MBResponseTimeout? "));
			}
			else break;

			i++;

		}

}

if (result == node.ku8MBSuccess) {
  if(DEBUGON){
    Serial.print(F("LT: Success, Received data: "));
  }

  for (j = 0; j < regSize; j++) {

    data[j] = node.getResponseBuffer(j);
    if(DEBUGON){
      Serial.print(data[j], DEC);
      Serial.print(F(" "));
    }
  }

  if(DEBUGON){
	  Serial.println("");
  }

  node.clearResponseBuffer();
  node.clearTransmitBuffer();
  return true;

}
else{
  if(DEBUGON){
	Serial.print(F("Failed, Response Code: "));
	Serial.println(result, HEX);
  }
}

node.clearResponseBuffer();
node.clearTransmitBuffer();
return false;
}

1 Like

hi @rama what is you recommendation to efficiently program routine to read all this registers and then store the result in structure or an array?

is this the right whay to do it?

int regs[24]={200,400,401,403,416,418,419,425,500,501,502,503,504,703,704,733,734,1039,1810,1811,1812,1813,1814,1815};
 int regs_data[24];
uint8_t k;
uint8_t j;
 for (k = 0; k < 24; k++) {
getHoldingRegisterData( regs[k],  1,  data);  //symetrical current limit in % 0.1 dec place
delay(100);
  for (j = 0; k < 24; j++) {
    regs_data[j]=data[0];
  }
delay(100);
 }



@luisgcu that last was bad… this is the right way to do it…

uint8_t k;
 for (k = 0; k < 24; k++) {
getHoldingRegisterData( regs[k],  1,  data); 
 delay(100);
    regs_data[k]=data[0];
    Serial.printf("Register data : %d: ", regs[k]);
    Serial.print(regs_data[k], DEC);
    Serial.println(F(" "));
 }

@luisgcu It’s -1 because you’re using a uint16_t datatype. It needs to be converted.

I’ll create an extension library for ModbusMaster and add it to my Github. I’m trying to figure this out myself.

Since most modbus prototcol sets are tabular and they have a pattern of Register Address, register size, and description, I can write a simple Python script that can output a data struct I can copy over.

This is what I’m experimenting with currently on my Modbus project. The code may not be the right way, although it may show the logic.

enum rgDTYPE{
  RG16BITS = 1,
  RG32BITS = 2,
  RGCHAR = 2,
  RGSHORT = 2,
  RGUSHORT = 2,
  RGLONG = 4,
  RGULONG = 4,
  RGFLOAT = 4,
  RGTIME = 6,
  RGDOUBLE = 8,
  RGSTRING = 32,
};

typedef struct _rgRegister {
    uint16_t regAddress;
    uint16_t  regSize;
    rgDTYPE  dataType;
    char  desc[30];
} rgRegister;

static const rgRegister PROGMEM _rgDeviceCommonTable[] = {

  {9010, 1, RGUSHORT, "MaxDataLogs"},
  {9011, 2, RGULONG, "TotDataLogMem"},
  {9013, 3, RGULONG, "TotBattTicks"},
  {9019, 32, RGSTRING, "DeviceName"},
  {9051, 32, RGSTRING, "SiteName"},
  {9097, 3, RGTIME, "CurrTimeUTC"},
  {9100, 2, RG32BITS, "DeviceStatus"},
  {9102, 2, RGULONG, "UsedBattTicks"},
  {9104, 2, RGULONG, "UsedDataBytes"},  
  
};

static const rgRegister _rgLoggedRecordTable[] ={
  {9600, 2, RGULONG, "num_log_recs"},
  {9602, 2, RGULONG, "log_rec_id"},
  {9604, 3, RGTIME, "rec_timestamp"},
  {9607, 2, RGFLOAT, "P1_press_measVal"},
  {9609, 1, RGUSHORT, "P1_press_DQID"},
  {96010, 2, RGFLOAT, "P2_temp_measVal"},
  {96012, 1, RGUSHORT, "P2_temp_DQID"},
  {96013, 2, RGFLOAT, "P3_level_measVal"},
  {96015, 1, RGUSHORT, "P3_level_DQID"},       
};

static const rgRegister _rgDeviceVoltageTable[] = {

  {109, 2, RGUSHORT, "int_voltage"},
  {110, 2, RGUSHORT, "ext_voltage"},
};

static const rgRegister _rgSensorCalibrationTable[] = {
    {100, 2, RGFLOAT, "Specific_Gravity" },
    {102, 2, RGFLOAT, "Poffset" },
    {104, 2, RGFLOAT, "Lref"},
    {106, 2, RGFLOAT, "Pref"},
};

The other problem is converting an array of uint16_t data to useful data types. I made the following union data types and the corresponding helper functions.

    union
    {
      uint16_t u[2];
      int i;
    } intData;

	union
    {
      uint16_t u[2];
      unsigned long ul;
    } ulongData;

    union
    {
      uint16_t u[2];
      float f;
    } floatData;

    union
    {
      uint16_t u[2];
      time_t t;
    } timeData;

    union
    {
      uint8_t u[64];
      char c[32];
    } stringData;



float _convertHEXtoFLOAT(uint16_t* data){

  for(int i=0; i<2; i++)
    floatData.u[1-i] = data[i];

  return floatData.f;

}

int _convertHEXtoINT(uint16_t* data){

		intData.u[0] = data[1];
		intData.u[1] = data[0];
		
		return intData.i;
}

String _convertHEXtoSTRING(uint16_t* data){

  String s;  

  for(int i=0; i<32; i++)
  {
    s.concat((char)data[i]);
  }
  s.trim();
  return s;
  
}



time_t _convertHEXtoTIME(uint16_t* data){
  // 3 register number
  // first 4 bytes represent time in seconds since 00:00:00 January 1, 1970 UTC
  
  for(int i=0; i < 3; i++)
      timeData.u[2-i] = data[i];

  return timeData.t;
}

Work in progress

@rama what ever yo do for the improvement of the modbus library count with me for testing purposes.
now I will try once again the modbus master library that @peekay123 ported for photon, not sure why it didn’t work for me.

I just made quick test with the modbus master library pkourany/ModbusMaster it gave me some error when compiling Modbus.ino
anyone knows if there is any other version that is OK?