Go Down

Topic: Help with ModBus RTU Master-Slave: SimpleModbus [SOLVED] (Read 51 times) previous topic - next topic

JUGmobile

Jul 07, 2013, 09:36 pm Last Edit: Jul 27, 2013, 12:59 pm by JUGmobile Reason: 1
Hello guys!

I'm working on an arduino project that require bulletproof communication between few arduinos (up to 30 or more, up to 100m) in disruptive environment (like near frequency inverter). I have one master device, that is communicating with few slaves. The master can send information-data, to the slaves, or read info-data from them. In project each slave will have few pushbuttons and an display (7-segment or graphic display). When the pushbutton is pressed on slave, the information of that pushbutton has to be transmited to the master. Also master device can send information to the slaves, so slaves can change information on display. Display will show only numbers and maybe few signs. That's about it.

My main goals are:

  • Bulletproof communication

  • Real time response

  • Low price

  • No delays for main code



Real time in my case means up to 1sec to address all slaves and get response from them.

For the communication standard I choosed RS485, which is the best option for my project, I suppose, and have few advantages over CAN, SPI, Ethernet, I2C. On top of that I need a protocol for communication. There is a lot of libraries on the web for arduino that implement RS485 communication into arduino. I'm a little afraid, that these libraries aren't really bulletproof + many of them uses delay function that is not helpful at all for my main code. After some time investigating communication between arduinos I decided to stick with the ModBus protocol. Since ModBus is a little complicated for me and my knowledge I'm asking here for help.

I'm still at the begining phase of programming this. I have found a ModBus library for arduino, called SimpleModbus, that contains master and slave example code. The whole library project can be found here: SimpleModbus.

For my master device I choose Arduino Due, and for the slaves Arduino Leonardo. For RS485 transceiver I choose SN65HVD75D from Texas Instruments, which is working at 3.3V, just like DUE and Leonardo [datasheet].


Current configuration


For the beginning I'm trying to establish communication only between master and one slave device. I haven't connect all the pushbuttons and displays, because firstly I want succesfully establish communication betwen arduinos using ModBus protocol. The hardware is the easiest part of design. Just arduino board, few ICs, breadboard and some wires. The hardware part is working flawlessly (for now  :P ).

So I have connected everything and now I'm stuck with programming. I will go trough most parts of the code and will try to explain what I do understand and what I do not. All the code and libraries are available here:  SimpleModbus

MASTER: SimpleModbusMasterExample

Code: [Select]

#include <SimpleModbusMaster.h>
#define baud 9600
#define timeout 1000
#define polling 200 // the scan rate

// If the packets internal retry register matches
// the set retry count then communication is stopped
// on that packet. To re-enable the packet you must
// set the "connection" variable to true.
#define retry_count 5

// used to toggle the receive/transmit pin on the driver
#define TxEnablePin 2

Few lines of code that defines some parameters. Timeout is time in ms that defines how much is max. time for master to wait, till slave responds. If slave wouldn't respond, master would call next slave.
Polling defines how fast will master call slaves (but I don't know what exactly nr. 200 means).
Retry_count is counter, that defines max. nr. of unsuccessful tryes of calling one slave. If slave don't respond 5 times in row, the master will stop calling him.
TxEnablePin is just a digital output for /RE-DE pins on RS485 transmitter that defines direction (transmit or receive data).

Code: [Select]

// This is the easiest way to create new packets
// Add as many as you want. TOTAL_NO_OF_PACKETS
// is automatically updated.
enum
{
 PACKET1,
 PACKET2,
 // leave this last entry
 TOTAL_NO_OF_PACKETS
};

I'm not 100% sure what this is. I understand part that I can freely add or remove packets. I think this packets contains informations-data of what we want to send to the slave or what we want to read from the slave? Each packet can contain up to 256 bytes of information but serial buffer can contain only 128 bytes of information so this is limited to 128 bytes. But I'm not sure how to handle this packets.

Code: [Select]

// Create an array of Packets to be configured
Packet packets[TOTAL_NO_OF_PACKETS];

// Create a packetPointer to access each packet
// individually. This is not required you can access
// the array explicitly. E.g. packets[PACKET1].id = 2;
// This does become tedious though...
packetPointer packet1 = &packets[PACKET1];
packetPointer packet2 = &packets[PACKET2];

// The data from the PLC will be stored in the regs array
unsigned int regs[10];

Packet pack... is an array of packets. With line packets[1] = 0; I probably write a zero value to the PACKET2. I'm not sure if I'm correct?
packetPointer is just an pointer that points to the corresponding packet.
regs[10] array is, well I don't know what.

Code: [Select]

void setup()
{
 // The modbus packet constructor function will initialize
 // the individual packet with the assigned parameters. You can always do this
 // explicitly by using struct pointers. The first parameter is the address of the
 // packet in question. It is effectively the "this" parameter in Java that points to
 // the address of the passed object. It has the following form:
 // modbus_construct(packet, id, function, address, number of registers, register array)
 
 // read 2 registers starting at address 0  
 modbus_construct(packet1, 2, READ_HOLDING_REGISTERS, 0, 2, regs);
 
 // write the 10 registers to the PLC starting at address 2  
 modbus_construct(packet2, 2, PRESET_MULTIPLE_REGISTERS, 2, 10, regs);
 
 // P.S. the register array entries above can be different arrays
 
 // Initialize communication settings etc...
 modbus_configure(baud, timeout, polling, retry_count, TxEnablePin, packets, TOTAL_NO_OF_PACKETS);
}

Not sure what this is, but if I'm correct, modbus_construct create an map that contains all the informations that will be send to slave, like slave ID, function (receive or transmit data to the slave), what data (packet) to send... I would appreciate if someone could explain this line.
modbus_configure configures modbus setting. Nothing complicated

Code: [Select]

void loop()
{
 modbus_update();
 
 // update the array with the counter data
 regs[2] = packet1->requests;
 regs[3] = packet1->successful_requests;
 regs[4] = packet1->failed_requests;
 regs[5] = packet1->exception_errors;
 regs[6] = packet2->requests;
 regs[7] = packet2->successful_requests;
 regs[8] = packet2->failed_requests;
 regs[9] = packet2->exception_errors;
}

The part that I don't understand at all.
What is modbus_update? What are next lines of code?

I couldn't fit whole post in one post, so the post continue in next post.

JUGmobile

SLAVE: SimpleModbusSlaveExample

Code: [Select]

#include <SimpleModbusSlave.h>

#define  ledPin  13 // onboard led

Nothing special.

Code: [Select]

// Using the enum instruction allows for an easy method for adding and
// removing registers. Doing it this way saves you #defining the size
// of your slaves register array each time you want to add more registers
// and at a glimpse informs you of your slaves register layout.

//////////////// registers of your slave ///////////////////
enum
{    
 // just add or remove registers and your good to go...
 // The first register starts at address 0
 ADC0,    
 ADC1,        
 ADC2,
 ADC3,
 ADC4,
 ADC5,  
 LED_STATE,
 TOTAL_ERRORS,
 // leave this one
 HOLDING_REGS_SIZE
 // total number of registers for function 3 and 16 share the same register array
};

Very similar part of the code like packets in the master code. I do not understand, why nr. of "registers" isn't same as nr. of "packets". Again this "registers" probably contains informations about slave device, informations that we want to send to the master device.

Code: [Select]

unsigned int holdingRegs[HOLDING_REGS_SIZE]; // function 3 and 16 register array

void setup()
{
 /* parameters(long baudrate,
               unsigned char ID,
               unsigned char transmit enable pin,
               unsigned int holding registers size,
               unsigned int* holding register array)
 */
 
 modbus_configure(9600, 1, 2, HOLDING_REGS_SIZE, holdingRegs);    
 
 pinMode(ledPin, OUTPUT);
}

holdingRegs is an array of all "registers". Not sure what to do with this.

Code: [Select]

void loop()
{
 // modbus_update() is the only method used in loop(). It returns the total error
 // count since the slave started. You don't have to use it but it's useful
 // for fault finding by the modbus master.
 
 holdingRegs[TOTAL_ERRORS] = modbus_update();
 
 for (byte i = 0; i < 6; i++)
 {
   holdingRegs[i] = analogRead(i);
   delayMicroseconds(500);    
 }
 
 // read the LED_STATE register value and set the onboard LED
 // function 16 and one whole register is used 0000h == OFF
 // and anything except zero is ON
 digitalWrite(ledPin, holdingRegs[LED_STATE]);
}

Here we write analog values from analog inputs to "registers" ADC0-ADC5?
Here we have again modbus_update. I think this is command to send respond to the master or?


Final words and questions:
I would love to here some answeres, explanations from guys who knows Modbus well. If someone is successfully established modbus connection between arduinos please respond here in this thread. If someone can help me with tip on how to write sketch, please post your solution down in reply.

All I want to do for now is successfully connect 2 arduinos with modbus protocol and send some data from one to another.

This is a long post, so I hope this thread wont die soon. I hope this thread would be in help ti other programmers too. I have searched all the arduino forum and haven't found my answeres so I created a new topic.

Some usefull links for newbies:
Modbus protocol explained
SimpleModbus library

Thank you for your time!
Jakob

JuanB

Hi Jakob,

The library is written following a state machine principal. There are 3 states running in the back ground (not at the same time obviously). Calling modbus_update() checks the 3 states and thus calling this function frequently in your code will result in a quicker turnaround.

To communicate with a slave you will need to establish the id, the function you want to use (reading or writing, function 3 or 16), the starting address you want to read from and the amount of registers you want to read.

The packets you create with the enum instructions are just the index number you will use to explicitly refer to that specific packet. The TOTAL_NUMBER_OF_PACKETS is the number amount for the array of packets namely:
Code: [Select]
Packet packets[TOTAL_NO_OF_PACKETS];

This is effectively the array of packets that will be processed sequentially.

Obviously referring to a single packet you will have to use dot notation or pointer notation i.e.
Code: [Select]
// Create a packetPointer to access each packet
// individually. This is not required you can access
// the array explicitly. E.g. packets[PACKET1].id = 2;
// This does become tedious though...
packetPointer packet1 = &packets[PACKET1];


The regs array is any named unsigned int array. This is the array that will contain the slaves response information you requested (when using function 3) or it could be the array of data you would want to write to the slave. These arrays can be different arrays when assigning them to a packet. I.e. you could use 20 different arrays on 20 different packets.

modbus_constuct() is a function that does the assigning of packet information for you so you don't have to use pointer notation when putting together a packet.

P.s. The master and slave examples in the library folder were not design to communicate with each other. The SimpleModbusMaster example I used to communicate with an LSIS GM7 plc and the SimpleModbusSlave example was the slave to the GM7 plc and various other testing software.

Some more info I extracted from an email I sent to a fellow arduinist:

The library works on a state machine principal. The state machine is updated every call to modbus_update(). I.e. frequent calls in a large program is better. The SM has 3 states 2 of which is timing related. One is the timeout delay which allows the slave to respond within a certain amount of time and the other one is the turn around delay which is basically the polling rate of each packet. The timeout delay needs to be as long as the slowest anticipated response of a slave. As the amount of slaves increase the turn around delay can be decreased since more slaves equal more latency in response. The turn around delay is what I call "a breather delay". For example, if you have one slave and you poll it it takes 100ms to respond, as soon as your master detects a successful response it will poll the slave again but the slave has not yet returned to its ""listening state" which results in a non response which in turn will result in a timeout error. In this instance I would set the timeout to 300-500ms and the polling delay to 100ms. In general modbus is not a fast protocol and few commercial slaves respond so quickly. I have found that 500-1000ms timeout works the best and depending on the distance and amount of slaves a polling delay of 100-200ms.

Let me know if you need any other help.

joerju

I am currently trying to achieve communication with the SimpleModbus library as well. Were you successful?

JUGmobile

Hi joerju! Well I'm still trying. I need more time for this to figure out the process.

Thank you very much JuanB for your answer. I will ask more questions soon :) but firstly I want to try it myself.

Joerju, post your progress here pls.

JuanB

Updated the libraries so parity and byte frame size can be selected to adhere more to wards the modbus standard.

There are also new example codes that will allow communication between an arduino master and arduino slave. Using a potentiometer on the master one can vary an LED's brightness on the slave and also vice versa.

JUGmobile


Updated the libraries so parity and byte frame size can be selected to adhere more to wards the modbus standard.

There are also new example codes that will allow communication between an arduino master and arduino slave. Using a potentiometer on the master one can vary an LED's brightness on the slave and also vice versa.


Superb! With this examples everything become so clear. It is actually simpler to communicate over this modbus protocol than I thought (thanks to you and your libraray). But I still haven't establish communication between my DUE and Leonardo. I had to changed my hardware part a little since I figured out that Leonardo is working on 5V logic not 3.3V (don't know why I thought Leonardo is on 3.3V logic). So now hardware part is working, and one more thing I had to change, I hope I did it correct:

Since Arduino IDE 1.5.2 (which is neccessary for DUE) do not support different parity and stop bits options, I had to make a little changes in libraries. So this is what I changed so far:
Master libraries SimpleModbusMaster.cpp
In modbus_configure I have deleted byteFormat, from this:

Code: [Select]
void modbus_configure(long baud,
unsigned char byteFormat,
unsigned int _timeout,
unsigned int _polling,
unsigned char _retry_count,
unsigned char _TxEnablePin,
Packet* _packets,
unsigned int _total_no_of_packets)
{
Serial.begin(baud, byteFormat);

// Modbus states that a baud rate higher than 19200 must use a fixed 750 us
        ...
        ..
        .
}


to this:

Code: [Select]
void modbus_configure(long baud,

unsigned int _timeout,
unsigned int _polling,
unsigned char _retry_count,
unsigned char _TxEnablePin,
Packet* _packets,
unsigned int _total_no_of_packets)
{
Serial.begin(baud);

// Modbus states that a baud rate higher than 19200 must use a fixed 750 us
        ...
        ..
        .
}


Master libraries SimpleModbusMaster.h
In modbus_configure I have deleted byteFormat, from this:

Code: [Select]
void modbus_configure(long baud,
unsigned char byteFormat,
unsigned int _timeout,
unsigned int _polling,
unsigned char _retry_count,
unsigned char _TxEnablePin,
Packet* _packets,
unsigned int _total_no_of_packets);


to this:

Code: [Select]
void modbus_configure(long baud,
unsigned int _timeout,
unsigned int _polling,
unsigned char _retry_count,
unsigned char _TxEnablePin,
Packet* _packets,
unsigned int _total_no_of_packets);


Slave libraries SimpleModbusSlave.cpp
From this (byteFormat deletion):

Code: [Select]
void modbus_configure(long baud,
unsigned char byteFormat,
unsigned char _slaveID,
                      unsigned char _TxEnablePin,
unsigned int _holdingRegsSize,
                      unsigned int* _regs)
{
  Serial.begin(baud, byteFormat);
slaveID = _slaveID;
  ...
  ..
  .
}   


to this:

Code: [Select]
void modbus_configure(long baud,

unsigned char _slaveID,
                      unsigned char _TxEnablePin,
unsigned int _holdingRegsSize,
                      unsigned int* _regs)
{
  Serial1.begin(baud);
slaveID = _slaveID;
  ...
  ..
  .
}   


And last one: Slave libraries SimpleModbusSlave.h
From this (again byteFormat):

Code: [Select]
void modbus_configure(long baud,
unsigned char byteFormat,
unsigned char _slaveID,
                      unsigned char _TxEnablePin,
                      unsigned int _holdingRegsSize,
                      unsigned int* _regs);


to this:

Code: [Select]
void modbus_configure(long baud,
unsigned char _slaveID,
                      unsigned char _TxEnablePin,
                      unsigned int _holdingRegsSize,
                      unsigned int* _regs);


Also on slave side I had to change every Serial command to Serial1 command, cause Leonardo is using null serial port (Serial.xxx) for communication with computer over terminal and 1st serial port (Serial1.xxx) for communication with other hardware (arduino etc.).
Also I had to delete byteFormat from Master example and from Slave example (in modbus_configure line) to get no errors.

But still with these changes I'm unable to establish communication between arduinos. Due is sending an modbus message to Leonardo every 1 second (probably because timeout is set to 1000 ms), but Leonardo is not responding at all. If I print holdingRegs[PWM_VAL] on Leonardo over null serial port to computer terminal, I get value 0.

I don't know what can cause this problem but I hope not the Arduino 1.5.2 IDE. Any Idea?

JUGmobile

Well, I installed Arduino IDE 1.0.5 and try to establish connection with no success. Then I connect 2 Arduino Leonardos (so 1 Leonardo for master and 1 Leonardo for slave, instead of DUE for master) and get half of communication successful established. Then I tried to program Leonardos (master and slave) with IDE 1.5.2 and it is working the same way as with IDE 1.0.5.
Arduino IDE is not causing problems.

Looks like something is wrong on master (DUE) hardware part. I will try to figure this out but untill I find a solution, I will work with 2 Leonardos.

Here I am for now: I have changed libraries to work with Leonardo (Serial.xxx to Serial1.xxx) and IDE 1.5.2 (no parity and stop bits selection). I have successfuly establish connection in one way: Master->Slave. With potentiometer on master side I can regulate LED connected to PWM pin on slave side. Slave is responding to master request (watched on oscilloscope), but master somekind does not proccess informations that he gets. So I can not regulate LED on master side over potentiometer on slave side. Also master stops sending request to slave after 10 requests sended to slave (when retry_count is set to 5).

Problem is:
1) master send request to slave,
2) slave gets request (physical, measured with oscilloscope),
3) slave process request,
4) slave returns answer to master
5) master gets answer (physical, measured with oscilloscope)
6) master do not process slave answer

After 5 requests (10 actually: PRESET_MULTIPLE_REGISTERS - 5 times and  READ_HOLDING_REGISTERS - 5 times), master stops sending request to slave, due to retry_count which is set to 5. If I set retry count to 5000000, it still stops sending requests.

What can be wrong?

JUGmobile

Nothing new to show :( I tried to use libraries with parity and stop bit options (no changes from original libraries), only thing I have changed is Serial.xxx to Serial1.xxx, so I can work with Leonardo (Leonardo for master and another Leonardo for slave).

Again same problem. Master can control slave, but cannot process answer from slave. Here is picture of communication from oscilloscope:
















    Oscilloscope settings:
    Time - 5ms/div
    RX channel - 2V/div
    TX channel - 2V/div



From picture above we can see that answer from slave is a bit shorter than master request. I think this is all right, or maybe not? But anyway, this is what master gets as an answer from slave, but still does not react on this answer.

JuanB: on what hardware were you testing this library? I realy don't think that hardware can cause this problem, since master can control slave, but who knows.

And many thanks for all help!

JuanB

Hi JUGmobile,
I tested the libraries on the Duemilanove. Imust admit I did not test the arduino master/slave examples with two Duemilanove's since individually they tested fine. Are you using 680Ohm pull up and pull down resistors?

JUGmobile

Hi JuanB,
Well I haven't tryed with pull up/down resistors. Now I connected 680E: A line to Vcc and B line to ground and unfortunately it's still not working.

Have you got an option to test this library on 2 Arduinos? I don't know what can cause this problem, but hardware part looks ok, since everything looks normal when watching signals on osciloscope.

Thank you for your time!
Jakob

JuanB

Hi Jakob,

I have another 328 lying around somewhere, I will hook it up on a bread board and connect them. Give me a few days...

In the mean time, try to remove a packet. Try just requesting a read function (F3) from the slave and see if you can get the LED to vary when turning the slave pot. Then use just F16 to request a write to the slave and see if the master pot varies the slave LED.

Use the two Read sketches together and the two Write sketches together.

SimpleModbusMasterArduinoRead.ino
Code: [Select]

#include <SimpleModbusMaster.h>

#define baud 9600
#define timeout 1000
#define polling 200
#define retry_count 5
#define TxEnablePin 2
#define LED 9

enum
{
  PACKET1,
  TOTAL_NO_OF_PACKETS
};

Packet packets[TOTAL_NO_OF_PACKETS];
packetPointer packet1 = &packets[PACKET1];
unsigned int readRegs[1];

void setup()
{
  modbus_construct(packet1, 1, READ_HOLDING_REGISTERS, 0, 1, readRegs);
  modbus_configure(baud, SERIAL_8N2, timeout, polling, retry_count, TxEnablePin, packets, TOTAL_NO_OF_PACKETS);
  pinMode(LED, OUTPUT);
}

void loop()
{
  modbus_update();
  analogWrite(LED, readRegs[0]/4);
}


SimpleModbusSlaveArduinoRead.ino
Code: [Select]

#include <SimpleModbusSlave.h>

#define  LED 9

enum
{     
  ADC_VAL,           
  HOLDING_REGS_SIZE
};

unsigned int holdingRegs[HOLDING_REGS_SIZE];

void setup()
{
  modbus_configure(9600, SERIAL_8N2, 1, 2, HOLDING_REGS_SIZE, holdingRegs);   
  pinMode(LED, OUTPUT);
}

void loop()
{
  modbus_update();
  holdingRegs[ADC_VAL] = analogRead(A0);
}


SimpleModbusMasterArduinoWrite.ino
Code: [Select]

#include <SimpleModbusMaster.h>

#define baud 9600
#define timeout 1000
#define polling 200
#define retry_count 5
#define TxEnablePin 2
#define LED 9

enum
{
  PACKET1,
  TOTAL_NO_OF_PACKETS
};

Packet packets[TOTAL_NO_OF_PACKETS];
packetPointer packet1 = &packets[PACKET1];
unsigned int writeRegs[1];

void setup()
{
  modbus_construct(packet1, 1, PRESET_MULTIPLE_REGISTERS, 0, 1, writeRegs);
  modbus_configure(baud, SERIAL_8N2, timeout, polling, retry_count, TxEnablePin, packets, TOTAL_NO_OF_PACKETS);
  pinMode(LED, OUTPUT);
}

void loop()
{
  modbus_update();
  writeRegs[0] = analogRead(A0);
}


SimpleModbusSlaveArduinoWrite.ino
Code: [Select]

#include <SimpleModbusSlave.h>

#define  LED 9

enum
{     
  PWM_VAL,       
  HOLDING_REGS_SIZE
};

unsigned int holdingRegs[HOLDING_REGS_SIZE];

void setup()
{
  modbus_configure(9600, SERIAL_8N2, 1, 2, HOLDING_REGS_SIZE, holdingRegs);   
  pinMode(LED, OUTPUT);
}

void loop()
{
  modbus_update(); 
  analogWrite(LED, holdingRegs[PWM_VAL]/4);
}

JuanB

Jakob,

Try a 10k resistor pull-up on the Rx pin to Vcc. Another user has found that noise triggered the Rx receive interrupt on his driver ic.

JuanB

More info,

The SimpleModbusMasterArduino and SimpleModbusSlaveArduino examples were tested by another arduinoist using a Mega as the master and a Micro as the slave with great success.

JUGmobile

Now I can say hurray!

Looks like I had same problems as "another user". I connected 10kE resistor from RX on master side to VCC and now it's working flawlessly! Again thank you very much for all the help!

Have a nice day,
Jakob

Go Up