New Modbus Master-Slave library

Hi all!

I've been working on a new implementation for a Modbus Master-Slave library. My purpose is to implement both on a same class and just make it able to handle most of the Serial port calls. For those interested in this work, it is here:

and the library is here:

It still needs to be converted to the Arduino library format. The Slave seems to be almost complete, but not the Master, which still lacks functions 1, 2 and 15. I have also seen that this needs some extra work, particularly when having more than a Modbus port on the same board.

Keep in touch!

Hi,

I am a bit of a newbie to the Arduino community and am currently working or a project involving Modbus control for linear actuators.

I read thru your code and saw provisions for RS-232, 485 and USB. Is that all via the normal serial pins port 0 and 1 as defined in the comments?

How do I add this library and access the commands?

Thanks.

Bookmarked
+1

Hi dgtldan,

I am a bit of a newbie to the Arduino community and am currently working or a project involving Modbus control for linear actuators.

First you should try to answer one question:

  • which role is your Arduino going to perform in your network? Is it going to send orders and ask for states to the order network members? Is it going to play the same role as the other members?
    As other industrial networks, MODBUS relies on one master and one or several slave devices. If your Arduino is going to control these other devices, then it would be a master. Otherwise, there should be a PLC or a PC requesting queries to your Arduino and the other devices.
    As its name tells, the Modbus allows to configure and run either a Master or an Slave. This is just done when declaring the Modbus object that would be associated to a Serial port.

I read thru your code and saw provisions for RS-232, 485 and USB.

RS-232 and USB may behave the same way. There may be some USB driver start-up delay in the Leornado's, so I should look at its USB implementation for the Leornado's. Anyway it should be quite similar to RS-232. If you use USB-FTDI, then it is the same thing as RS-232.
RS-485 is a bit different, because it needs an additional pin to set the RS-485 transceiver to send mode. As long as a telegram is being sent, this pin must be kept active. This is internally written in the library when sending messages.
Again the communication mode is chosen when declaring the Modbus object.

How do I add this library and access the commands?

First an object must be declared. For instance,

Modbus com(1,0,0);

The constructor parameters are:

  • u8id: node address 0=master, 1..247=slave
  • u8serno: serial port used 0..3 (for Duemilia's and Uno's, it is always 0)
  • u8txenpin pin for txen RS-485 (leave it at 0 for USB-FTDI or RS-232)
    Beside the object constructor, it is important to declare an array with the data exchange area that would be used in the network. This would be the values that would be read or written.

In your setup(),

void setup() {
  com.begin(19200);
  com.setTimeOut( 2000 ); // <---- only for master, useless otherwise
  u32wait = millis() + 1000; 
}

Here the driver needs to start its linked serial port as Serial.begin(19200). You can set any baudrate.
There may be an issue here regarding to the byte-frame implementation. Older Arduinos didn't allow other byte-frame formats other than 8N1 and I've seen that HardwareSerial library now allows others like 7E2 or 7E1.
Regarding to the loop(), here there are significant differences between the master and the slave:

Modbus Slave:

void loop() {
com.poll( dm, 10 );
}

This shall allow to refresh the dm array from the network. Beside this, our code can write a particular value according to an analog input (i.e. dm[3] = analogRead(0) ) or read its value and send it through a PWM output.

Modbus Master:

void loop() {
  switch( u8state ) {
  case 0: 
    if (millis() > u32wait) u8state++;
    break;
  case 1: 
    telegram.u8id = 1;
    telegram.u8fct = 3;
    telegram.u16RegAdd = 1;
    telegram.u16CoilsNo = 4;
    telegram.au16reg = dm;

    com.query( telegram );
    u8state++;
    break;
  case 2:
    com.poll();
    if (com.getState() == COM_IDLE) {
      u8state = 0;
      u32wait = millis() + 100; 
    }
    break;
  }
}

Here there is another player: the telegram variable. The library defines a modbus_t structure, which is a Modbus master telegram. Most equipment manufacturers allow to define a Modbus Master query as here:

typedef struct {
  uint8_t u8id;  // slave number to access 
  uint8_t u8fct; // Modbus function to implement
  uint16_t u16RegAdd; // slave register to access
  uint16_t u16CoilsNo; // coils or words to transfer
  uint16_t *au16reg; // pointer to the network exchange area inside master
} 
modbus_t;

The idea is to make an small finite-state machine that shall query a message and then wait for an answer. If there is no answer, restart after a maximum time.

Most of the work is done, but there are some missing points. In the Master, the next functions are missing:

  • FCT 1 & 2 are not working. A message is sent but there is no code to process it into the network exchange area;
  • FCT 15 is not working. It doesn't generate a query message.

I'm looking forward your comments.

Best Regards,

Hi,

I am looking to use the Arduino as a MODBUS master to control a couple of MODBUS slaved actuators or two . Currently I am sending serial commands to the actuators via hyperterminal and can get them to move based on the manufacturer's specified codes. This is via RS-232 from the PC and I have a 232 to 485 converter in between the PC and the actuator which I like to eliminate as well.

Mostly I'd be using variables, derived from external inputs, to write continuously changing data relative to position, acceleration, and speed of the actuator in Function code 10 registers. Here is an ASCII representation:
Query : 01 10 9900 0007 0E 0000 1388 0000 000A 0000 2710 001E 47[CR][LF] Response : 01 10 9900 0007 4F[CR][LF]
and RTU
Query : 01 10 9900 0007 0E 0000 1388 0000 000A 0000 2710 001E 50CF Response : 01 10 9900 0007 AF57

From what I see in your code, there is a modbus_t structure which will construct the query and format with CRC. How does it deal with number of registers and bytes of data that vary between various commands? Maybe I am missing that part as I looked thru the code.

I noticed in your code you can specify three serial ports, but not sure how you define pins to them or change them to 485 since I am under the impression that this requires an interface chip. I like to use RS-485 from serial pins other than pin 0 and 1.

Thanks for your patience, I am wrapping my head around the whole thing!

I am looking to use the Arduino as a MODBUS master to control a couple of MODBUS slaved actuators or two . Currently I am sending serial commands to the actuators via hyperterminal and can get them to move based on the manufacturer's specified codes. This is via RS-232 from the PC and I have a 232 to 485 converter in between the PC and the actuator which I like to eliminate as well.

That's nice to hear and means that you have already tested your connection. All you need is to put it in your Arduino. XD

From what I see in your code, there is a modbus_t structure which will construct the query and format with CRC. How does it deal with number of registers and bytes of data that vary between various commands? Maybe I am missing that part as I looked thru the code.

The modbus_t structure emulates what most industrial PLCs do:

  • it identifies the slave that shall be accessed. It was "1" in your query. This is telegram.u8id in my example;
  • it points to the first slave register to be accessed. It was 0x9900 in your query. This is telegram.u16RegAdd;
  • it specifies how many registers or coils shall take place. It was 0x0007 in your query. This is telegram.u16CoilsNo;
  • it declares which kind of access shall be done (read registers, read coils and so on). It was 0x10. This is telegram.u8fct;
  • and finally it points to a memory link in order to transfer memory from or to the network. You have written several values in your query. This is telegram.au16reg and all these values would be somewhere of this pointer.

The CRC is automatically appended to the outcoming telegram before any transmission.

All the values you have added in the query message would have been borrowed from the pointer dm in my example. Previously you should have filled them somewhere else in the code.

Whenever com.query( telegram ) is executed, the Modbus object fills all the fields of the Modbus telegram as you manually do, appends memory if needed (because of function 0x10 or 0x06), adds the CRC and transfers it through Arduino UART.

After executing the com.query( telegram ), there must be a wait state in order to get and answer from the slave. Sometimes this answer also includes real-time data from your slave (i.e. actual position or status).

I noticed in your code you can specify three serial ports, but not sure how you define pins to them or change them to 485 since I am under the impression that this requires an interface chip. I like to use RS-485 from serial pins other than pin 0 and 1.

I'm afraid that there is a misunderstanding here. The code refers to Serial, Serial1, Serial2 and Serial3, which are available in Arduino Mega. It also supports Arduino Duemilia or Uno (328P based), Leornado and even other microcontrollers like ATMEGA1284P. The Arduino Duemilia or Uno only has Serial and this means that this library shall use your pins 0 and 1 for the RS-485 port.

how you define pins to them or change them to 485 since I am under the impression that this requires an interface chip

RS-485 needs an interface chip which can be: ST485, MAX483 or SN75LBC184P. This chip needs to tie pins 2 and 3 in order to set it to receive or transmit mode. This mode must be directly controlled from the Arduino with one of your digital outputs and must be defined when creating the Modbus object. In order to allow you to upload code to the Arduino, it is particularly useful to put a pull-up resistor between this line and VCC.

Hello,

Ok, I understand the need for the RS-485 interface chip, just wasn't clear on it. As for additional serial ports, I've been reading a few ways to use other pins for additional serial ports which use the <SoftwareSerial.h> library. Here is the example I came across.
http://arduino-info.wikispaces.com/SoftwareSerialRS485Example
Was wondering if this is something you had in your library. If this is not suitable, I'll switch to a Mega.

Any estimate when your library will be available?

Thanks.

Hi,
The SoftwareSerial cannot be used here. If you need an addition serial port, it would be better to move to a bigger microcontroller.
The library can be used as a file inside your sketch. As I wrote, there are a couple of missing functions for the master. The last step would be to make it an Arduino Library and provide a set of examples and good documentation. I haven't too much free time for these things... maybe one month.
Best Regards,

Hello suby, thank for you sharing

I open Modbus.ino copy all your code,but it can't be compile .Error massage show up

core.a(main.cpp.o): In function main': C:\Users\weley\Desktop\arduino-1.0.5-r2\hardware\arduino\cores\arduino/main.cpp:11: undefined reference to setup'
C:\Users\weley\Desktop\arduino-1.0.5-r2\hardware\arduino\cores\arduino/main.cpp:14: undefined reference to `loop'

Thank you again for your reply in another post . :slight_smile:

Hello platoxii,

These things happen even in the best families. :smiley:
Just try this example. I attach an screenshot to let you show how I test it when it is an slave.

BR

libmodbus_slave_example-140210a.zip (6.45 KB)

Hello suby,

Thank you made a example for me I really appreciate your help. But i am still in trouble :frowning: .Your code work very well in USB port


But when I want to connect arduino via RS232 it's still no response.


Modbus slave(1,0,0); In your code the third parameter is 0 so I decide connect it via RS232 and don't change anything.

http://i.imgur.com/nm5COl9.jpg?1 this is my wiring .As far as I know I need a converter to convert TTL to RS232 so I buy

a max232 module.

RX --> RX , TX --> TX , 5V -->VCC, GND-->GND . By the way I tried RX-->TX , TX -->RX wiring it's still not work.

And then I decide to connect it via RS485 so I change this line

Modbus slave(1,0,2);

And I use the same wiring and fail again.



Sorry I am too stupid to make this thing work :disappointed_relieved: I can't figure out which part of my procedure is wrong.

Thank you again.

Hello Platoxii,

But when I want to connect arduino via RS232 it's still no response.

I understand that you are using a MAX232 or MAX202 for the RS-232 interface, isn't it? I cannot determine what wiring are you using from the photograph. :fearful:

Then the wiring between it and the Arduino should be:

Arduino pin MAX232
RXD 0 ---------------------- 12 R1OUT
TXD 1 ----------------------- 11 T1IN
GND ------------------------ 15 GND
VCC ------------------------- 16 VCC

You should look at the wiring between the MAX232 and your USB-RS232 interface:

MAX232 USB-RS232
T1OUT 14 --------------------- 2 RXD
R1IN 13 ----------------------- 3 TXD
GND 16 ----------------------- 5 GND

Maybe your wiring is a bit different, but should be similar to this. Please check it before testing your communication.

And then I decide to connect it via RS485 so I change this line
Modbus slave(1,0,2);
And I use the same wiring and fail again.

Regarding to your RS-485 connection, please exchange the green and the blue wires, so

  • T/R+ is tied to MAX485's pin 7 B
  • T/R- is tied to MAX485's pin 6 A

If you are using transmission speed lower than 57600 and very short distances (< 1m), there is no need to use termination resistors.

I encourage you to read thoroughly some manuals to understand the MAX232 and the MAX485. It's the only way to learn these things.

Best Regards,

As far as I know I need a converter to convert TTL to RS232 so I buy

a max232 module.

RX --> RX , TX --> TX , 5V -->VCC, GND-->GND . By the way I tried RX-->TX , TX -->RX wiring it's still not work.

I've missed this. The last try seems correct:

  • Arduino RXD is an INPUT and needs to be connected to an output coming from your interface;
  • Same thing for TXD, which is an OUTPUT.

Instead of executing a Modbus example, just try this simple sketch:

void setup() {
 Serial.begin(9600);
}

void loop() {
 Serial.print("hello world!");
 delay(1000);
}

Try it either USB and RS-232. You can use the Arduino Serial terminal as an output in your PC. You should get a "hello world!" message each second. If you don't succeed it with RS-232, there may be something wrong. Maybe your RS232-TTL board. :sleeping:

Hello,suby

Yes, I wrote a simple program to test read write via RS232 before .It's work very well.

void setup() {

    Serial.begin(9600); // start serial communication at 9600bps
}

void loop() {
  Serial.println("hihihihi");
  delay(1000);
  char ser_char;
 
  if( Serial.available() > 0) { // if data is available to read
     ser_char = Serial.read(); // read it and store it in 'ser_char'
     Serial.write(ser_char);
     
    
    }
}

By the way , RX-->RX , TX-->TX wiring can read write data but RX-->TX, TX---> RX wiring can't read write data.

I start to think my arduino board is counterfeit. :disappointed_relieved:

Good news!!It's work not no response but it's seem receive invalid data. :roll_eyes:


And my MAX232 module Transfer Led start to blink


Thanks a million.

Good news!!!!!! I finally did it . It both work very well via RS232 and RS485 you are genius.

I've taken so much of your time, I don't know how to say what I feel inside. :*

My last question. If I want command coil status 00008 0--->1 and let arduino pin8 start to output.

What should I do.

I add one line in void setup but not work .I guess I should add digital.Write (address 00008,HIGH)if read 00008 =1 somewhere but I don't know how.

void loop()  {
pinMode(8,OUTPUT);
}

Thanks a million.

Just take my Modbus example and type

DigitalPin(8,OUTPUT);

In setup(). Go to loop() and add

DigitalWrite(8, bitRead( dm[0], 8 ));

Sry , suby

I add those lines but compile error show up "dm was not declare in this scope"

Sorry! Instead of "dm", replace "dm" with "au16data":

DigitalWrite(8, bitRead( au16data[0], 8 ));

Hello ,suby

It's work!!Thank you again for everything you're done. :slight_smile:

hello suby

Nice project!! I follow this post and work very well

But can you teach me how to add DI/DO AI/AO in this sketch ?

THX~