Modbus RTU over Arduino

Satriyo Jati Pamungkas
7 min readApr 17, 2022

Last week ago I received a challenging task from my colleague to convert modbus RTU addresses from a sensor to the modbus RTU addresses that was configured by default on Autonics PLC. This task was accomplished by using arduino as a communication relay. The modbus addresses on sensor was mapped by arduino so that it matches with standard modbus addresses on famous slave product of Autonics PLC : TK Series. The program was made and sent into the arduino to perform the task. I choose the Arduino Nano since it doesn’t take up much space and it is enough to perform such task.

The system is comprised of Arduino Nano, two MAX485 modules, two LEDs indicator, and MP1584 DC-DC boost converter .

The modbus RTU protocol itself used an RS485 which specify the electrical interface and physical layer for point-to-point communication of electrical devices. The arduino controller doesn’t support any RS485 pins hence in this project, I use MAX485 module to perform a conversion from TTL level voltage into RS485 level voltage or vice versa.

The sensor was IMU sensor. It is manufactured by WitMotion Company which is based in China. The sensor can measure 3-axis angle, angular velocity as well as acceleration. Moreover, it uses RS485 communication protocol and using 24 VDC power.

WITMOTION IMU SENSOR

Modbus protocol employ master/slave structure. For more information about modbus protocol, you might have to visit this website as it is good resource to learn :

https://www.se.com/us/en/faqs/FA168406/#:~:text=Modbus%20is%20a%20serial%20communication,lines%20between%20electronic%20devices.

This will give you a brief introduction about modbus protocol. Here in this post I assume you are familiar with this protocol.

First thing first is that one require to find list of components that constitute the converter:

  1. Arduino Nano
  2. MP1584 DC to DC converter
  3. Two MAX485 modules
  4. two LEDs for inditor
  5. WitMotion SINDT-485 IMU sensor

The Sensor is a slave/server in this scheme. On the other hand, arduino is a master/client. It seems simple since many libraries on the internet offer solutions to this problem. Nevertheless, it turns to be complicated as arduino also acts as a slave of Autonics Master. I carry out some research on google and discover that I prefer to use external libraries to enable modbus on arduino rather than the standard library provided by arduino.

Since the project require me to only use one axis data, I just have to query one register from the sensor which is X-axis angle data. The table below shows some modbus registers in the sensor :

Register list on the SINDT sensor

As you can see the xAngle is located on 0x3D address. I obtain this table from sensor datasheet. This document also provides you some examples about modbus message format used by the sensor. One important information to consider is the function code that’s being used to read register. As you can see below, it uses function code 0x03 which indicates that it performs read holding register function.

I include two non-arduino standard modbus libraries to my program:

https://github.com/4-20ma/ModbusMaster : this library enables your arduino board to be a master and make queries to the corresponding slaves.

https://github.com/smarmengol/Modbus-Master-Slave-for-Arduino : this repo is required to make the arduino as a slave for PLC master which in my case is an Autonics PLC. The arduino will share the measured data from the sensor to modbus network.

This is the program I’ve wrote :

#include <ModbusMaster.h>
#include <SoftwareSerial.h> //using Software Serial, not Hardware (limited to brate 19200)
#include <ModbusRtu.h> //Arduino slave

I use software serial as I don’t want to use default serial pins to peform modbus communication as well as debugging. Moreover, it might cause error if you use the default serial pin to perform multiple task. In addition, I use default serial pins(pin 0,TX, pin 1, RX) just to communicate with Autonics PLC thereafter.

#define MAX485_DE      3  //control pin first MAX485  
#define MAX485_RE_NEG 2 //control pin first MAX485
#define UPPER 6 //LED indicator
#define LOWER 7
#define TXEN 10

Since I use two Max485, each MAX485 uses different control pins (RE, DE). The first MAX485 is used to perform communication from sensor to arduino. The second one is used to send data to the PLC. Since I use ModbusMaster library, It requires to make two pin definitions as control pins. However, by the definition I’ve found on ModbusRtu library, It is only required to define one control pin which is pin 10 on my Nano. Two others pin (6 and 7) are used to toggle LED indicators.

short xAngle = 0;
uint8_t result;
int16_t modbus_array[102];

I define xAngle variable to store signed 16 bit integer data from sensor. On arduino this type of varible is called short . You can also int16_tuse if you want to. The result variable is just for debugging only. It stores the return value of querying data function from sensor. If it’s success, it has value 0 , non-zero otherwise. The modbus_array[] array is the data which are shared in the network. Why do I need 102 contagious address if I only query one axis data in the network? The answer lies on how Autonics configure one of their famous product TK series. The table below shows us modbus protocol of TK series:

As you can see 300001 to 300100 are reserved. So at least I could use address offset higher than 300100 to be the address which is available from the arduino. Although the library itself doesn’t distinguish between 300101 or 400101. It only cares about the register and not the function codes.

unsigned long previousMillis = 0;
const long interval = 10;

//Creating modbus master object
ModbusMaster node;
//Creating modbus slave
SoftwareSerial myserial(4,5); //RX, TX for master arduino - slave sensor//Creating modbus slave object using software serial, Slave ID : 1, control pin second MAX485 : 10 (RE & DE connected)
Modbus bus(20,Serial,TXEN);
void preTransmission()
{
myserial.flush();
digitalWrite(MAX485_RE_NEG, 1);
digitalWrite(MAX485_DE, 1);
}
void postTransmission()
{
delay(10);
digitalWrite(MAX485_RE_NEG, 0);
digitalWrite(MAX485_DE, 0);
}
void processdata(short angle)
{
if(angle >= 2){
digitalWrite(UPPER,HIGH);
digitalWrite(LOWER,LOW);
}else if (angle <= -2){
digitalWrite(UPPER,LOW);
digitalWrite(LOWER,HIGH);
}else{
digitalWrite(UPPER,LOW);
digitalWrite(LOWER,LOW);
}

processdata(short angle) is the function to classify so that if the angle value between -2 to 2 degree it does nothing, otherwise it toggles LEDs. The LEDs indicate whether your device tilt upward or downward. On the horizontal position the x Angle is 0, it increases when tilted up or rotate upto 180 degrees then it goes to negative values upto -179 degrees and return 0 in a full rotation.

void setup() {
// put your setup code here, to run once:
//pinMode(10,OUTPUT);
pinMode(MAX485_RE_NEG, OUTPUT);
pinMode(MAX485_DE, OUTPUT);
// Init in receive mode
digitalWrite(MAX485_RE_NEG, 0);
digitalWrite(MAX485_DE, 0);
//baudrate for master arduino - slave sensor
myserial.begin(19200);
//Baudrate for default serial ( debugging only)
Serial.begin(19200, SERIAL_8E1);
bus.start();
// Sensor Modbus slave ID is 0x50, I use "myserial" as a serial communication to first MAX485
node.begin(0x50, myserial);

// Callbacks allow us to configure the RS485 transceiver correctly
node.preTransmission(preTransmission);
node.postTransmission(postTransmission);
//Start slave at baudrate 19200
//myserial2.begin(19200);

}
void loop() {
// put your main code here, to run repeatedly:
//16-bit Sensor data on starting register address 0x3D, two contagious address requested
//Below how I begin requesting a query to slave(sensor) , it returns 0 if success, error code otherwise
//poll every 100ms
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval)
{
previousMillis = currentMillis;
//DO something
modbus_array[100] = xAngle;
bus.poll(modbus_array,14);
}
//Querying data from slave
result = node.readHoldingRegisters(0x3D, 1);
if ( result == node.ku8MBSuccess)
{
//Sensor conversion to degree, it uses short(int16_t) type to represent negative value
xAngle = ((short)node.getResponseBuffer(0))/32768.0*180.0;
processdata(xAngle);
//Serial.println(xAngle);
}
}

This is the frustating part. I almost spent for a week to troubleshoot timeouts problem. From the hardware to software to PC. It turned out that this was software problem. The first I thought it was my hardware, it might be true because I had changed my Nano and then it worked. But the real problem is the software instead.

I will post it on my github repo. As I’ve mentioned. I still have an issue when the sensor is absent the node.readHoldingRegisters() takeover the program counter time and it introduces delay hence poll() always provides timeout to Master device (PLC or PC ).

https://github.com/SatriyoJati it still empty though, but I’ll upload the code or projects I’ll do in advance.

The first time I connected to the sensor and used QModbus as a Master on PC, i worked as I expected although the scan rate was only 1000ms. You can make it faster by increasing the scan rate. I also connected it with Autonics PLC and it worked as well.

--

--

Satriyo Jati Pamungkas

I am an electrical engineering student who love learning about electronics, programming, signal processing stuffs, and productivity