Sunday 5 October 2014

A little more advanced Modbus Master

Advanced Modbus Master code

 This is a little more advanced Modbus Master:

/**
 *  Modbus master example 2:
 *  The purpose of this example is to query several sets of data
 *  from an external Modbus slave device. 
 *  The link media can be USB or RS232.
 *
 *  Recommended Modbus slave: 
 *  diagslave http://www.modbusdriver.com/diagslave.html
 *
 *  In a Linux box, run 
 *  "./diagslave /dev/ttyUSB0 -b 19200 -d 8 -s 1 -p none -m rtu -a 1"
 *  This is:
 *   serial port /dev/ttyUSB0 at 19200 baud 8N1
 *  RTU mode and address @1
 */

#include <ModbusRtu.h>

uint16_t au16data[16]; //!< data array for modbus network sharing
uint8_t u8state; //!< machine state
uint8_t u8query; //!< pointer to message query

/**
 *  Modbus object declaration
 *  u8id : node id = 0 for master, = 1..247 for slave
 *  u8serno : serial port (use 0 for Serial)
 *  u8txenpin : 0 for RS-232 and USB-FTDI 
 *               or any pin number > 1 for RS-485
 */
Modbus master(0,0,0); // this is master and RS-232 or USB-FTDI

/**
 * This is an structe which contains a query to an slave device
 */
modbus_t telegram[2];

unsigned long u32wait;

void setup() {
  // telegram 0: read registers
  telegram[0].u8id = 1; // slave address
  telegram[0].u8fct = 3; // function code (this one is registers read)
  telegram[0].u16RegAdd = 0; // start address in slave
  telegram[0].u16CoilsNo = 4; // number of elements (coils or registers) to read
  telegram[0].au16reg = au16data; // pointer to a memory array in the Arduino

  // telegram 1: write a single register
  telegram[1].u8id = 1; // slave address
  telegram[1].u8fct = 6; // function code (this one is write a single register)
  telegram[1].u16RegAdd = 4; // start address in slave
  telegram[1].u16CoilsNo = 1; // number of elements (coils or registers) to read
  telegram[1].au16reg = au16data+4; // pointer to a memory array in the Arduino
 
  master.begin( 19200 ); // baud-rate at 19200
  master.setTimeOut( 5000 ); // if there is no answer in 5000 ms, roll over
  u32wait = millis() + 1000;
  u8state = u8query = 0; 
}

void loop() {
  switch( u8state ) {
  case 0: 
    if (millis() > u32wait) u8state++; // wait state
    break;
  case 1: 
    master.query( telegram[u8query] ); // send query (only once)
    u8state++;
 u8query++;
 if (u8query > 2) u8query = 0;
    break;
  case 2:
    master.poll(); // check incoming messages
    if (master.getState() == COM_IDLE) {
      u8state = 0;
      u32wait = millis() + 1000; 
    }
    break;
  }
  au16data[4] = analogRead( 0 );
} 
  

This code is already included in the last release of my Library, available at Github.

Now there are two queries: 0 and 1. 0 makes the same as before, while 1 adds a single-register write. This is done through the state-machine implemented in loop, which sends both queries through u8query.

Modbus RTU is not an IP-based protocol. Serial protocols require to send messages one after another and wait for their answer before sending a new message. This is done here.

Only for debug purposes, the u32wait time has been set to 1000 ms.

Understanding the code

In setup(), the master is initialised:
  • a set of queries through telegram are written;
  • the serial port is started with master.begin(19200) at 19200 baud;
  • the u8query index is reset;
  • the u8state machine state register is reset.

Modbus_t variables

telegram is an structure array based on modbus_t type. It contains the queries to the Modbus slaves. Things to define here:
  • slave id and register to access
  • what to do (read or write) and how many fields
  • where is this information linked
The .au16reg field contains a pointer to an integer array. It allows to link the Modbus protocol to the rest of the Arduino program. It could be an LCD or digital I/O or a set of devices plugged to I2C.

The loop section runs the machine state and links any process data to the integer array au16data.

The machine state

This library need that a machine state to be implemented in software in the loop section. This is for flexibility of the code.

The machine state is driven by u8state byte. It consists on 3 states:
  • 0: it is a wait phase;
  • 1: a query is sent through master.query( telegram[ u8query ] );
  • 2: the poll section waits for an answer or just gets out if there is a time-out event.
The time-out event is defined by master.setTimeOut( 5000 ) in setup(). This means that the application allows up to 5 s to wait for an answer.

Debugging the code

Debugging with Multiway

Multiway is a worthy protocol analiser written by Omron Corporation in France. It supports several industrial protocols and Modbus is between them.


Multiway has been set as

COM4
8-bit, 1-bit stop, none
Modbus slave number 1

The Master is reading registers from 0 to 3 and writes on register 4.

Link to the library upgrade

Google Drive
GitHub

25 comments:

  1. Yes, you can if the question is related to the library.

    ReplyDelete
  2. Thanks for answer bro.

    Sure, well, i'm new in this topic of arduino and obviously in modbus, at the end of the code, the au16data[4] = analogRead( 0 );. What that means?

    I'm Trying to communicate an arduino UNO to a Power Meter PM9C by schneider, it works with rs485, i have to read data and save it in a SD, but the first step is read the data.

    ReplyDelete
    Replies
    1. Dear Marco,
      Regarding to your questions,
      1) analogRead is fully explained here:
      https://www.arduino.cc/en/Reference/AnalogRead
      2) As I previously replied you, I only ask questions related to the library itself, not regarding any particular software or hardware configuration.
      I wish you good luck with your project.
      Cheers

      Delete
    2. Suby

      "The .au16reg field contains a pointer to an integer array. It allows to link the Modbus protocol to the rest of the Arduino program. It could be an LCD or digital I/O or a set of devices plugged to I2C."

      Then how can show on soft serial port instead of LCD.

      Delete
  3. suby,
    can you help me regarding your library, that how can show the read holding registers modbus data in string format at soft serial. from 40101 to 40125, my slave id is 4 9600 8b N.

    ReplyDelete
  4. Suby

    Please dont mind I have asking so many questions. at different forum locations .
    kindly answer my question How can different Holding registers data on soft serial port
    I am waiting for your valuable reply.

    Regards,
    Manjunath

    ReplyDelete
  5. Hi, I want to know... ok you send a query by master.query(telegram) and them you wait for the answer but how i can take the answer and print it on the serial monitor...?

    ReplyDelete
    Replies
    1. The answer is encapsulated and you cannot print it. Your only choice is to print the registers contents.

      Delete
  6. an other question is in wich pins should be linked the max485 pins to communication

    ReplyDelete
    Replies
    1. You define it in the Modbus object declaration. It's the third argument. Here there was no pin declared.

      Delete
  7. Hi,
    I can't find a informtion for function codes 1 and 2. Coul you help me with this? Thanks in advance!

    ReplyDelete
    Replies
    1. Hi,
      I haven't written these codes for the Master. Only for the Slave. I've seen some posts in the GitHub regarding to their implementation, but I haven't checked them.
      Best Regards,

      Delete
  8. Hi, seems to be a good example, do you have a spheme or pin´s connection?

    Thank you in advance :)

    ReplyDelete
  9. Hii, seems to be a good example! Do you have a connceting scheme with pins?

    Thanks in advance

    ReplyDelete
    Replies
    1. Hi Elena,
      I haven't any scheme with pins. The reason is that the libray allows to use any Hardware Serial port. I mean that the used pins are those assigned for the Hardware UARTs.

      Therefore you should refer to the Arduino Reference:
      https://www.arduino.cc/reference/en/language/functions/communication/serial/

      The only "but" is that this library adds support for RS-485 flow control. This is done with a dedicated digital pin through the library itself. This pin could be any except those already used for the Serial UART or external program functions.

      I hope this helps a little bit!

      Delete
  10. hello sir ,i I'm not getting how to operate my one arduino uno as slave and another as master,I want to design my system ,which will follow modbus protocol ,i.e One Arduino will send data continuously on his rs485 port,but another arduino must have send some packet to first arduino,then n then only communication must establish.....what should i do with programming..

    ReplyDelete
  11. Hi,

    I did this:

    https://arduino-experience.blogspot.com.es/2014/01/modbus-master-slave-library.html?showComment=1511209245594

    There are two Arduinos linked through RS-485. There is a Master and an Slave. Both have a display which shows the telegram counter.

    ReplyDelete
  12. Hi suby, You mentioned that the information is encapsulated, but the registers could be printed to serial monitor. How did you print the registers content?, Sorry I'm not so expert. Thanks

    ReplyDelete
    Replies
    1. Hola Carlos,
      Sigo en inglés.
      You can print these registers either through another serial port (hardware or software) or through a display. You can even use a Modbus simulator in your pc.
      I hope this helps.

      Delete
  13. sir if i want to add any value in 5 registers simuntaneously what changes have i to do in code ?
    i tried by adding telegram[2].u16RegAdd = 4; its showing values continuesly in two registors but when i add telegram[3].u16RegAdd = 4; its showing me nothing....plz help me out

    ReplyDelete
  14. Hi suby,
    I'm working on a project for solar inverter monitoring using arduino. Is this library applicable for it?

    ReplyDelete
    Replies
    1. Dear Subash,
      This library can be used anywhere under your responsibility as any Freeware code.
      Best Regards

      Delete
  15. hi suby,
    you mentioned print these au16data register.

    can you give snippets.
    or convert to any variable name.
    as i am not good in pointers.

    ReplyDelete
    Replies
    1. Dear Suresh,
      You could use this array as an intermediate memory (for convenience) and assign any index to a variable. For instance,

      iDigitalIn := au16data[0];
      iDigitalOut := au16data[1];

      Delete
  16. This comment has been removed by the author.

    ReplyDelete