Modbus communication between Arduino Mega with coriolis flow meter

Hello, I would really appreciate your help with this problem i am facing.

I have a problem to communicate between arduino and micro motion coriolis flow meter R045S series through modbus RTU protocol with RS485. I am using modbusmaster.h library. But the connection modbus always failed. I have tried to change baudrate, parity, and ID slave to solve it. But it still failed to connect the modbus.

Sensor info:
Manual: https://www.emerson.com/documents/automation/installation-manual-r-series-

I am using MAX485 - TTL UART to RS485 Converter Module: https://core-electronics.com.au/ttl-uart-to-rs485-converter-module.html

Here is my code:

#include "REG_PM2200.h"
#include <ModbusMaster.h>
#include <SoftwareSerial.h>
#define baudrate 4800
#define timeout 100
#define polling 100
#define retry_count 10
ModbusMaster node;

#define MAX485_DE      22
#define MAX485_RE_NEG  23

#define ID_meter  0
#define Total_of_Reg  1

#define Reg_E       1101 //Register frequency output pulses per unit

float DATA_METER [Total_of_Reg] ;

uint16_t Reg_addr[1] = {
  Reg_E,
};

void preTransmission()
{
  digitalWrite(MAX485_RE_NEG, 1);
  digitalWrite(MAX485_DE, 1);
}

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

//------------------------------------------------
// Convent 32bit to float
//------------------------------------------------
float HexTofloat(uint32_t x) 
{
  return (*(float*)&x);
}

uint32_t FloatTohex(float x) 
{
  return (*(uint32_t*)&x);
}
//------------------------------------------------

float Read_Meter_float(char addr , uint16_t  REG) 
{
  float i = 0;
  uint8_t result,j;

  uint16_t data[5];
  uint32_t value = 0;
  node.begin(ID_meter,Serial2);
  node.preTransmission(preTransmission);
  node.postTransmission(postTransmission);
  
  result = node.readHoldingRegisters(REG,4); ///< Modbus function 0x03 Read Holding Registers
  delay(500);
  if (result == node.ku8MBSuccess) 
  {
    for (j = 0; j < 5; j++)
    {
      data[j] = (node.getResponseBuffer(j));
    }
    value = data[0];
    value = value << 16;
    value = value + data[1];
    i = HexTofloat(value);
    Serial.println("Connect modbus Ok.");
    return i;
  } else 
  {
    Serial.print("Connect modbus fail. REG >>> "); Serial.println(REG); // Debug
    delay(1000); 
    return 0;
  }
}

void GET_METER() 
{     // Update read all data
  delay(1000);                            
    for (char i = 0; i < Total_of_Reg ; i++)
    {
      DATA_METER [i] = Read_Meter_float(ID_meter, Reg_addr[i]);
    } 
}

void setup() 
{
  Serial.begin(baudrate);
  Serial2.begin(baudrate,SERIAL_8N1);

  Serial.println(F("Test"));
  pinMode(MAX485_RE_NEG, OUTPUT);
  pinMode(MAX485_DE, OUTPUT);
  // Init in receive mode
  digitalWrite(MAX485_RE_NEG, LOW);
  digitalWrite(MAX485_DE, LOW);
}

void loop() 
{
  //float x = Read_Meter_float(ID_meter,Reg_Volt);
  GET_METER();
  Serial.println();
  Serial.print("Data = "); Serial.print(DATA_METER[1]);

}

Thank you very much for your helping!

Here is the sensor

No idea man, but you are registering the callback and intializing the node everytime you make a measurement, put those in setup.
TX1 and RX1 shown in the figure correspond to Serial1, not Serial2.

Ouch ya, thank you. I forget to change the code. But I have tried in Serial1 and Serial2, but it still failed.

This register is a decimal number, most likely wrong.

Can you help me, how to define the register? I see the modbus mapping register from the sensor like this

I can not see the header of the table but I seems they are decimal so it was right (they could also be HEX).
I could not download the manual, the link is broken, just make sure what kind of register is (register, holding register or coil).

It is floating point register

Here is the link for modbus mapping register from the sensor: https://www.emerson.com/documents/automation/modbus-mapping-assignments-for-transmitters-en-65522.pdf

And here is the header of the table

Are you sure that you have the correct baud rate as your code appears to be using 4800 baud. Most modbus devices I see on the forums use 9600 baud.

Are you sure that the modbus address of the device is 0?

Oh yah, I just changed the ID address to 1, and tried to chage the baudrate based on the manual book. I changed to 1200, then 2400, 4800, 9600, 19200, 38400. But it still failed.

#include <ModbusMaster.h>
#include <SoftwareSerial.h>
#define baudrate 9600
#define timeout 100
#define polling 100
#define retry_count 10
ModbusMaster node;

#define MAX485_DE      22
#define MAX485_RE_NEG  23

#define ID_meter  1
#define Total_of_Reg  1

#define Reg_E       1101 //Frequency output pulses per unit

uint16_t Reg_addr[1] = {
  Reg_E,
};

float DATA_METER [Total_of_Reg] ;

void preTransmission()
{
  digitalWrite(MAX485_RE_NEG, 1);
  digitalWrite(MAX485_DE, 1);
}

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

//------------------------------------------------
// Convent 32bit to float
//------------------------------------------------
float HexTofloat(uint32_t x) 
{
  return (*(float*)&x);
}

uint32_t FloatTohex(float x) 
{
  return (*(uint32_t*)&x);
}
//------------------------------------------------

float Read_Meter_float(char addr , uint16_t  REG) 
{
  float i = 0;
  uint8_t result,j;

  uint16_t data[5];
  uint32_t value = 0;
  node.begin(ID_meter,Serial1);
  node.preTransmission(preTransmission);
  node.postTransmission(postTransmission);
  
  result = node.readHoldingRegisters(REG,4); ///< Modbus function 0x03 Read Holding Registers
  delay(500);
  if (result == node.ku8MBSuccess) 
  {
    for (j = 0; j < 5; j++)
    {
      data[j] = (node.getResponseBuffer(j));
    }
    value = data[0];
    value = value << 16;
    value = value + data[1];
    i = HexTofloat(value);
    Serial.println("Connect modbus Ok.");
    return i;
  } else 
  {
    Serial.print("Connect modbus fail. REG >>> "); Serial.println(REG); // Debug
    delay(1000); 
    return 0;
  }
}

void GET_METER() 
{     // Update read all data
  delay(1000);                            
    for (char i = 0; i < Total_of_Reg ; i++)
    {
      DATA_METER [i] = Read_Meter_float(ID_meter, Reg_addr[i]);
    } 
}

void setup() 
{
  Serial.begin(baudrate);
  Serial1.begin(baudrate,SERIAL_8N1);

  Serial.println(F("Test"));
  pinMode(MAX485_RE_NEG, OUTPUT);
  pinMode(MAX485_DE, OUTPUT);
  // Init in receive mode
  digitalWrite(MAX485_RE_NEG, LOW);
  digitalWrite(MAX485_DE, LOW);
}

void loop() 
{
  //float x = Read_Meter_float(ID_meter,Reg_Volt);
  GET_METER();
  Serial.println();
  Serial.print("Data = "); Serial.println(DATA_METER[1]);

}

Also see the advice give to you in post #3.

It is RW so as per the manual it is a holding register. The manual says you must subtract 1 to the address, so it should be 1100.

I've changed the code to Serial1. But it still failed

I tried to change the register code in arduino to 1100, it still failed. I've also change the register to other address, but it still failed. Is the code I've made wrong?

Reading back through the posts, there seems to be guesswork over the baud rate and modbus address required to communicate with the sensor.

I did a quick search for "emerson micro motion software" and the first hit was for "ProLink III Configuration & Service Tool for Micro Motion and Rosemount Flow Meters".

At the bottom of the Features section it says "ProLink III Basic Software is included with each meter" and provides a link to the basic software.

My suggestion would be to get the basic software and a USB-RS485 serial adapter and try to use the software to talk to the sensor. If you get that working, then you can use your Arduino as an RS485 serial monitor. A bit of trial and error should get you the baud rate. Once you know that, you should easily be able to see the modbus address. Once you have that, you can either try your code again, or use the basic software to read the information you want and see which register(s) are being read using your Arduino as the 485 monitor again.

Thank you for your suggestion. Ya, I have to get for the software first, and try to use software to talk to the sensor. Because if I code with arduino, before check from the software, I don't know the baudrate, parity, and ID Slave setting. Thank you for your helping

Hi markd833. I had tried to connect the sensor and Prolink3 software. It's work. I got:

  • baud rate: 38400
  • parity: none
  • stop bit: 1
  • Id address: 4

Then I want to monitor the temperature sensor with arduino, it still failed. The temperature register is 0251. When I check from serial monitor, the address is change to 169. I think, I can not write the register with prefix "0". Here is my code:

#include "REG_PM2200.h"
#include <ModbusMaster.h>
#include <SoftwareSerial.h>
#define baudrate 38400
#define timeout 100
#define polling 100
#define retry_count 10
ModbusMaster node;

#define MAX485_DE      2
#define MAX485_RE_NEG  3

#define ID_meter  4
#define Total_of_Reg  1

#define Reg_E       0251 // Sensor temperature register

uint16_t Reg_addr[1] = {
  Reg_E,
};

float DATA_METER [Total_of_Reg] ;

void preTransmission()
{
  digitalWrite(MAX485_RE_NEG, 1);
  digitalWrite(MAX485_DE, 1);
}

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

//------------------------------------------------
// Convent 32bit to float
//------------------------------------------------
float HexTofloat(uint32_t x) 
{
  return (*(float*)&x);
}

uint32_t FloatTohex(float x) 
{
  return (*(uint32_t*)&x);
}
//------------------------------------------------

float Read_Meter_float(char addr , uint16_t  REG) 
{
  float i = 0;
  uint8_t result,j;

  uint16_t data[5];
  uint32_t value = 0;
  node.begin(ID_meter,Serial);
  node.preTransmission(preTransmission);
  node.postTransmission(postTransmission);
  
  result = node.readHoldingRegisters(REG,4); ///< Modbus function 0x03 Read Holding Registers
  delay(500);
  if (result == node.ku8MBSuccess) 
  {
    for (j = 0; j < 5; j++)
    {
      data[j] = (node.getResponseBuffer(j));
    }
    value = data[0];
    value = value << 16;
    value = value + data[1];
    i = HexTofloat(value);
    Serial.println("Connect modbus Ok.");
    return i;
  } else 
  {
    Serial.print("Connect modbus fail. REG >>> "); Serial.println(REG); // Debug
    delay(1000); 
    return 0;
  }
}

void GET_METER() 
{     // Update read all data
  delay(1000);                            
    for (char i = 0; i < Total_of_Reg ; i++)
    {
      DATA_METER [i] = Read_Meter_float(ID_meter, Reg_addr[i]);
    } 
}

void setup() 
{
  Serial.begin(baudrate, SERIAL_8N1);

  Serial.println(F("Test"));
  pinMode(MAX485_RE_NEG, OUTPUT);
  pinMode(MAX485_DE, OUTPUT);
  // Init in receive mode
  digitalWrite(MAX485_RE_NEG, LOW);
  digitalWrite(MAX485_DE, LOW);
}

void loop() 
{
  //float x = Read_Meter_float(ID_meter,Reg_Volt);
  GET_METER();
  Serial.println();
  Serial.print("Data = "); Serial.println(DATA_METER[0]);

}

Could you help me with this issue? Thank you for your helping.

That's great news that you have been able to talk to the sensor via the manufacturers software. Even better news that you now know for sure that the baud rate is 38400 and the address is 4.

Next observation - back in post #1 your wiring diagram showed you had used TX1 & RX1. Your latest code should therefore be using Serial1.

And, you probably don't need to include SoftwareSerial as you are using a hardware serial port for your modbus.

Oh ya thank you. I forget to delete the SoftwareSerial.

I changed the Serial to TX0 & RX0 in my latest code and wiring

I would stay away from TX0 and RX0. They are used by the on board USB-serial interface and could cause you problems.