RS-485 modbus library

@beck, Sorry for the delay.

I too have been using the library for a few days and noticed the same offset/mapping issues that you speak of. However to be honest, it will still work fine for my application as I will be transferring 16 words at max. I will be bit-packing my coils into holding registers, so the 4x registers is honestly all I need.

I have tested it to my Panasonic FPX PLC outfitted with an RS485 communication cassette and a MAX485 chip on the Photon side. I let it run overnight with the master (my PLC) reading/writing data to two slaves every 100ms. That is WAY faster than I will need in real life, but I wanted to test it. So far no errors on either device, so I am happy with that.

I have used some other Modbus RS485 libraries with Arduino before that resolve some of the addressing issues. Now that I know this works, maybe I will port it in the future if I have some time.

5 posts were merged into an existing topic: Photon and Modbus 485 RTU - Connections & interface requirements

Hello @peekay123 ,
I tried to import the the library ModBusMaster in the WEB IDE but does not compile.
Also I tried with manual input files but without success.
I try and ModBus_RS485_Slave library and compile without problem.It requires a small modification of code to support multiple serial ports for Electron, but Iā€™m still not done testing.

#include "ModbusRtu.h"
#include "Serial2/Serial2.h"
#include "Serial4/Serial4.h"
#include "Serial5/Serial5.h"
#include "application.h"
/**
 * @brief
 * Initialize class object.
 *
 * Sets up the serial port using specified baud rate.
 * Call once class has been instantiated, typically within setup().
 *
 * @see http://arduino.cc/en/Serial/Begin#.Uy4CJ6aKlHY
 * @param speed   baud rate, in standard increments (300..115200)
 * @param config  data frame settings (data length, parity and stop bits)
 * @ingroup setup
 */
void Modbus::begin(long u32speed) {

  switch( u8serno ) {
  case 1:
    port = &Serial1;
    break;

  case 2:
    port = &Serial2;
    break;
    
    case 4:
    port = &Serial4;
    break;
    
    case 5:
    port = &Serial5;
    break;

  case 0:
  default:
    port = &Serial1;
    break;
  }

I want to know which library is better for Modbus Master?

@developer_bt, neither library has been updated in a while and they are not Libraries v2.0 compatible. Iā€™ll try and spend some time today to review them both.

1 Like

@developer_bt, there is already a ModBusMaster library on the IDE. Have you tried it?

@peekay123 Yes I tried to import the the library ModBusMaster in the WEB IDE but does not compile. The same happens with manual input C++/h files.

Processing  /workspace/modbus.ino
make -C ../modules/electron/user-part all
make[1]: Entering directory '/firmware/modules/electron/user-part'
make -C ../../../user 
make[2]: Entering directory '/firmware/user'
Building cpp file: /workspace/modbus.cpp
Invoking: ARM GCC CPP Compiler
mkdir -p ../build/target/user/platform-10-m/workspace/
arm-none-eabi-gcc -DSTM32_DEVICE -DSTM32F2XX -DPLATFORM_THREADING=1 -DPLATFORM_ID=10 -DPLATFORM_NAME=electron -DUSBD_VID_SPARK=0x2B04 -DUSBD_PID_DFU=0xD00A -DUSBD_PID_CDC=0xC00A -DSPARK_PLATFORM -g3 -gdwarf-2 -Os -mcpu=cortex-m3 -mthumb -DINCLUDE_PLATFORM=1 -DPRODUCT_ID=10 -DPRODUCT_FIRMWARE_VERSION=65535 -DUSE_STDPERIPH_DRIVER -DDFU_BUILD_ENABLE -DPARTICLE_NO_ARDUINO_COMPATIBILITY=0 -DSYSTEM_VERSION_STRING=0.6.1 -DRELEASE_BUILD -I./inc -I../wiring/inc -I../system/inc -I../services/inc -I../communication/src -I../hal/inc -I../hal/shared -I/rtos/FreeRTOSv8.2.2/FreeRTOS/Source/include -I/rtos/FreeRTOSv8.2.2/FreeRTOS/Source/portable/GCC/ARM_CM3 -I../hal/src/electron -I../hal/src/stm32f2xx -I../hal/src/stm32 -I../platform/shared/inc -I../platform/MCU/STM32F2xx/STM32_USB_Host_Driver/inc -I../platform/MCU/STM32F2xx/STM32_StdPeriph_Driver/inc -I../platform/MCU/STM32F2xx/STM32_USB_OTG_Driver/inc -I../platform/MCU/STM32F2xx/STM32_USB_Device_Driver/inc -I../platform/MCU/STM32F2xx/SPARK_Firmware_Driver/inc -I../platform/MCU/shared/STM32/inc -I../platform/MCU/STM32F2xx/CMSIS/Include -I../platform/MCU/STM32F2xx/CMSIS/Device/ST/Include -I../dynalib/inc -I/workspace/ -I./libraries -I/workspace/ -I/workspace/ -I/workspace/ -I/workspace/ -I. -MD -MP -MF ../build/target/user/platform-10-m/workspace/modbus.o.d -ffunction-sections -fdata-sections -Wall -Wno-switch -Wno-error=deprecated-declarations -fmessage-length=0 -fno-strict-aliasing -DSPARK=1 -DPARTICLE=1 -DSTART_DFU_FLASHER_SERIAL_SPEED=14400 -DSTART_YMODEM_FLASHER_SERIAL_SPEED=28800 -DSPARK_PLATFORM_NET=UBLOXSARA -fno-builtin-malloc -fno-builtin-free -fno-builtin-realloc  -DLOG_INCLUDE_SOURCE_INFO=1 -DPARTICLE_USER_MODULE -DUSE_THREADING=0 -DUSE_SPI=SPI -DUSE_CS=A2 -DUSE_SPI=SPI -DUSE_CS=A2 -DUSE_THREADING=0 -DUSER_FIRMWARE_IMAGE_SIZE=0x20000 -DUSER_FIRMWARE_IMAGE_LOCATION=0x8080000 -DMODULAR_FIRMWARE=1 -DMODULE_VERSION=4 -DMODULE_FUNCTION=5 -DMODULE_INDEX=1 -DMODULE_DEPENDENCY=4,2,105 -D_WINSOCK_H -D_GNU_SOURCE -DLOG_MODULE_CATEGORY="\"app\""  -fno-exceptions -fno-rtti -fcheck-new -std=gnu++11 -c -o ../build/target/user/platform-10-m/workspace/modbus.o /workspace/modbus.cpp
In file included from /workspace/modbus.cpp:1:0:
/workspace/ModBusMaster.h:80:0: warning: "lowByte" redefined [enabled by default]
 #define lowByte(w)                     ((w) & 0xFF)
 ^
In file included from ../wiring/inc/spark_wiring_arduino.h:36:0,
                 from ./inc/application.h:92,
                 from /workspace/modbus.cpp:1:
../wiring/inc/spark_wiring_arduino_constants.h:100:0: note: this is the location of the previous definition
 #define lowByte(w)   ((uint8_t) ((w) & 0xff))
 ^
In file included from /workspace/modbus.cpp:1:0:
/workspace/ModBusMaster.h:81:0: warning: "highByte" redefined [enabled by default]
 #define highByte(w)                    (((w) >> 8) & 0xFF)
 ^
In file included from ../wiring/inc/spark_wiring_arduino.h:36:0,
                 from ./inc/application.h:92,
                 from /workspace/modbus.cpp:1:
../wiring/inc/spark_wiring_arduino_constants.h:104:0: note: this is the location of the previous definition
 #define highByte(w)  ((uint8_t) ((w) >> 8))
 ^
/workspace/modbus.cpp: In function 'void loop()':
/workspace/modbus.cpp:18:12: warning: variable 'data' set but not used [-Wunused-but-set-variable]
 
            ^

Building cpp file: /workspace/ModBusMaster.cpp
Invoking: ARM GCC CPP Compiler
mkdir -p ../build/target/user/platform-10-m/workspace/
arm-none-eabi-gcc -DSTM32_DEVICE -DSTM32F2XX -DPLATFORM_THREADING=1 -DPLATFORM_ID=10 -DPLATFORM_NAME=electron -DUSBD_VID_SPARK=0x2B04 -DUSBD_PID_DFU=0xD00A -DUSBD_PID_CDC=0xC00A -DSPARK_PLATFORM -g3 -gdwarf-2 -Os -mcpu=cortex-m3 -mthumb -DINCLUDE_PLATFORM=1 -DPRODUCT_ID=10 -DPRODUCT_FIRMWARE_VERSION=65535 -DUSE_STDPERIPH_DRIVER -DDFU_BUILD_ENABLE -DPARTICLE_NO_ARDUINO_COMPATIBILITY=0 -DSYSTEM_VERSION_STRING=0.6.1 -DRELEASE_BUILD -I./inc -I../wiring/inc -I../system/inc -I../services/inc -I../communication/src -I../hal/inc -I../hal/shared -I/rtos/FreeRTOSv8.2.2/FreeRTOS/Source/include -I/rtos/FreeRTOSv8.2.2/FreeRTOS/Source/portable/GCC/ARM_CM3 -I../hal/src/electron -I../hal/src/stm32f2xx -I../hal/src/stm32 -I../platform/shared/inc -I../platform/MCU/STM32F2xx/STM32_USB_Host_Driver/inc -I../platform/MCU/STM32F2xx/STM32_StdPeriph_Driver/inc -I../platform/MCU/STM32F2xx/STM32_USB_OTG_Driver/inc -I../platform/MCU/STM32F2xx/STM32_USB_Device_Driver/inc -I../platform/MCU/STM32F2xx/SPARK_Firmware_Driver/inc -I../platform/MCU/shared/STM32/inc -I../platform/MCU/STM32F2xx/CMSIS/Include -I../platform/MCU/STM32F2xx/CMSIS/Device/ST/Include -I../dynalib/inc -I/workspace/ -I./libraries -I/workspace/ -I/workspace/ -I/workspace/ -I/workspace/ -I. -MD -MP -MF ../build/target/user/platform-10-m/workspace/ModBusMaster.o.d -ffunction-sections -fdata-sections -Wall -Wno-switch -Wno-error=deprecated-declarations -fmessage-length=0 -fno-strict-aliasing -DSPARK=1 -DPARTICLE=1 -DSTART_DFU_FLASHER_SERIAL_SPEED=14400 -DSTART_YMODEM_FLASHER_SERIAL_SPEED=28800 -DSPARK_PLATFORM_NET=UBLOXSARA -fno-builtin-malloc -fno-builtin-free -fno-builtin-realloc  -DLOG_INCLUDE_SOURCE_INFO=1 -DPARTICLE_USER_MODULE -DUSE_THREADING=0 -DUSE_SPI=SPI -DUSE_CS=A2 -DUSE_SPI=SPI -DUSE_CS=A2 -DUSE_THREADING=0 -DUSER_FIRMWARE_IMAGE_SIZE=0x20000 -DUSER_FIRMWARE_IMAGE_LOCATION=0x8080000 -DMODULAR_FIRMWARE=1 -DMODULE_VERSION=4 -DMODULE_FUNCTION=5 -DMODULE_INDEX=1 -DMODULE_DEPENDENCY=4,2,105 -D_WINSOCK_H -D_GNU_SOURCE -DLOG_MODULE_CATEGORY="\"app\""  -fno-exceptions -fno-rtti -fcheck-new -std=gnu++11 -c -o ../build/target/user/platform-10-m/workspace/ModBusMaster.o /workspace/ModBusMaster.cpp
In file included from /workspace/ModBusMaster.cpp:32:0:
/workspace/ModBusMaster.h:80:0: warning: "lowByte" redefined [enabled by default]
 #define lowByte(w)                     ((w) & 0xFF)
 ^
In file included from ../wiring/inc/spark_wiring_arduino.h:36:0,
                 from ./inc/application.h:92,
                 from /workspace/ModBusMaster.h:39,
                 from /workspace/ModBusMaster.cpp:32:
../wiring/inc/spark_wiring_arduino_constants.h:100:0: note: this is the location of the previous definition
 #define lowByte(w)   ((uint8_t) ((w) & 0xff))
 ^
In file included from /workspace/ModBusMaster.cpp:32:0:
/workspace/ModBusMaster.h:81:0: warning: "highByte" redefined [enabled by default]
 #define highByte(w)                    (((w) >> 8) & 0xFF)
 ^
In file included from ../wiring/inc/spark_wiring_arduino.h:36:0,
                 from ./inc/application.h:92,
                 from /workspace/ModBusMaster.h:39,
                 from /workspace/ModBusMaster.cpp:32:
../wiring/inc/spark_wiring_arduino_constants.h:104:0: note: this is the location of the previous definition
 #define highByte(w)  ((uint8_t) ((w) >> 8))
 ^
In file included from ./inc/application.h:92:0,
                 from /workspace/ModBusMaster.h:39,
                 from /workspace/ModBusMaster.cpp:32:
/workspace/ModBusMaster.cpp: In function 'uint16_t makeWord(uint8_t, uint8_t)':
../wiring/inc/spark_wiring_arduino.h:53:19: error: redefinition of 'uint16_t makeWord(uint8_t, uint8_t)'
 #define word(...) makeWord(__VA_ARGS__)
                   ^

/workspace/ModBusMaster.cpp:42:10: note: in expansion of macro 'word'
 uint16_t word(uint8_t high, uint8_t low) {
          ^
../wiring/inc/spark_wiring_arduino.h:47:17: error: 'uint16_t makeWord(uint8_t, uint8_t)' previously defined here
 inline uint16_t makeWord(uint8_t h, uint8_t l) {
                 ^

../build/module.mk:267: recipe for target '../build/target/user/platform-10-m/workspace/ModBusMaster.o' failed
make[2]: Leaving directory '/firmware/user'
make[2]: *** [../build/target/user/platform-10-m/workspace/ModBusMaster.o] Error 1
../../../build/recurse.mk:11: recipe for target 'user' failed
make[1]: Leaving directory '/firmware/modules/electron/user-part'
make[1]: *** [user] Error 2
../build/recurse.mk:11: recipe for target 'modules/electron/user-part' failed
make: *** [modules/electron/user-part] Error 2

ModBus_RS485_Slave library compile without problem.

@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.