Tuesday 25 March 2014

Another Modbus slave example

I've also tried to work on a easy to understand example, which could cover most of the Arduino I/O:
The comments in the code are quite explicit regarding to the I/O mapping. The code looks like this:

/**
 *  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
 *
 * pin maping:
 * 2 - digital input
 * 3 - digital input
 * 4 - digital input
 * 5 - digital input
 * 6 - digital output
 * 7 - digital output
 * 8 - digital output
 * 9 - digital output
 * 10 - analog output
 * 11 - analog output
 * 14 - analog input
 * 15 - analog input
 */
#define ID   3

Modbus slave(ID, 0, 0);
boolean led;
int8_t state = 0;
unsigned long tempus;
// data array for modbus network sharing
uint16_t au16data[9];


void setup() {
  // define i/o
  pinMode(2, INPUT);
  pinMode(3, INPUT);
  pinMode(4, INPUT);
  pinMode(5, INPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
  pinMode(13, OUTPUT);

  digitalWrite(6, LOW );
  digitalWrite(7, LOW );
  digitalWrite(8, LOW );
  digitalWrite(9, LOW );
  digitalWrite(13, HIGH );
  analogWrite(10, 0 );
  analogWrite(11, 0 );

  // start communication
  slave.begin( 19200 );
  tempus = millis() + 100;
  digitalWrite(13, HIGH );
}


void loop() {
  // poll messages
  // blink led pin on each valid message
  state = slave.poll( au16data, 9 );
  if (state > 4) {
    tempus = millis() + 50;
    digitalWrite(13, HIGH);
  }
  if (millis() > tempus) digitalWrite(13, LOW );

  // get digital inputs -> au16data[0]
  bitWrite( au16data[0], 0, digitalRead( 2 ));
  bitWrite( au16data[0], 1, digitalRead( 3 ));
  bitWrite( au16data[0], 2, digitalRead( 4 ));
  bitWrite( au16data[0], 3, digitalRead( 5 ));

  // set digital outputs -> au16data[1]
  digitalWrite( 6, bitRead( au16data[1], 0 ));
  digitalWrite( 7, bitRead( au16data[1], 1 ));
  digitalWrite( 8, bitRead( au16data[1], 2 ));
  digitalWrite( 9, bitRead( au16data[1], 3 ));

  // set analog outputs
  analogWrite( 10, au16data[2] );
  analogWrite( 11, au16data[3] );

  // read analog inputs
  au16data[4] = analogRead( 0 );
  au16data[5] = analogRead( 1 );

  // diagnose communication
  au16data[6] = slave.getInCnt();
  au16data[7] = slave.getOutCnt();
  au16data[8] = slave.getErrCnt();
} 
 
 
The Arduino sketch is fully available here. The old link is still available, but this example is fully implemented in the "Advanced Slave" example, included in my last release of the Library.

As an sketch summary,
  • pin 13 is used to show valid messages and it is lit for 50ms after getting a valid message;
  • this same feature could be used to implement a communication watchdog, which could disable digital outputs when there are no incoming queries from the master;
  • pins from 2 to 5 are digital inputs and are available in bits 0-3 of word au16data[0];
  • something similar for pins from 6 to 9 and word au16data[1];
  • the same pattern repeats for analog outputs and analog inputs;
  • as an addition, here appear the diagnostics words of Modbus library.
I've checked the communication link with QModbus. QModbus settings are:
  • Serial port: dev/ttyUSB1 (yes, I'm running a Linux Mint Box)
  • Serial port settings: 19200 baud, 8N1
  • Slave ID: 3 
Modbus function 3 allows to access to the whole memory map, while functions 15 and 16 should be reserved to write coils (digital outputs) and registers (analog outputs). Anyway there is no restriction to write words 0 or 4, but the loop() section will overwrite these values. 







28 comments:

  1. Ya the this post is very helpfull. I wanted to how to assign slave address, andif i wanted to read value of analog port Ao how can read it here

    ReplyDelete
    Replies
    1. This example is focused on the Arduino I/O, but it could be anything. The au16data array is not refered to any particular field and this allows to link it to infinite choices.

      Delete
    2. I have 2 analog Pins to read value , which assigned to address au16data[0] and au16data[1].
      How to assign value date/mm/yr hh/mm/sec to address . and read the address.I tried to assign floating value latitude13.08 but i read only 13. How to add the floating value.

      Delete
    3. Dear Ajit,
      1) The problem with the time is quite common and you could split it into several words, such as:
      word 1.L = day in BCD (01..31)
      word 1.H = month in BCD (01..12)
      word 2 = year in BCD (0001..9999)
      word 3.L = minute in BCD (00..59)
      word 3.H = hour in BCD (00..23)
      word 4.H = seconds in BCD (00..59)

      2) Something similar for the latitude, but consider to write it as degrees, minutes and seconds:

      word 5 = latitude in degrees
      word 6.H = minutes (00..59)
      word 6.L = seconds (00..59)

      Your 13.08º becomes 13 degrees plus 3 minutes and 0 seconds. I guess that this may be enough accurate for most applications. These conversions are quite simple, isn't it?

      3) These are different errors:

      - INVALID CRC means that the slave has generated a wrong CRC field. The only way to check it properly consists on getting the outcoming message from your Modbus Master and the answer coming from the Arduino.

      - I/O ERROR DID NOT RECEIVE DATA FROM SLAVE means that the Modbus Master has sent a query message and there has been no answer from the Arduino. It is difficult to guess why it has happened so.

      I did several tests:
      - between two Arduinos with RS-485, where one is the Master and the second one is an Slave. They were linked for more than 2 hours without any error.
      - between an Arduino Duemilianove as Modbus Slave and QModbus (as Master).
      - between an Arduino Mini-pro and an ABB AC500 PLC.

      Regards,

      Delete
    4. If I do correctly understand you, you only need to refer to address 5 in irder to read Latitude degrees or addresses from 5 to 6 to read the full Latitude.

      Delete
    5. https://www.sparkfun.com/products/11959
      i have purchased the above product now. I have succefully uploaded the code . I am getting slave through expection Invalid CRC implimented

      Delete
    6. I don't give this sort of support. :-)
      As I have browsed, it's an RS485 shield, so just look at the wiring between your Master and this slave:
      - if both are using RS485
      - if terminals A(-), B(+) and GND are wired together
      - if the cable is long, remember to put 120 ohm terminal resistors on both sides between A and B
      Before starting to work with RS485, I would try USB and mount a virtual port on the PC and run a Modbus Master program. Afterwards you can start with RS485.

      Delete
    7. Refer to this topic:
      http://forum.arduino.cc/index.php?topic=213432.msg1587070#msg1587070

      Delete
  2. I have finally made it work.I have few question here.
    1) Is it possible to change address of slave. What is no i can changed it to, where to change it.
    2)How to assign value in word format in your code.
    3) i have connected an potentiometer output to A0 port even through it is not varying i found values get updated in modbus master.

    My code:

    #define ID 3

    Modbus slave(ID, 0, 0);
    boolean led;
    int8_t state = 0;
    unsigned long tempus;
    // data array for modbus network sharing
    uint16_t au16data[9];

    float latitude=13.08;
    void setup() {
    // define i/o
    pinMode(2, INPUT);
    pinMode(3, INPUT);
    pinMode(4, INPUT);
    pinMode(5, INPUT);
    pinMode(6, OUTPUT);
    pinMode(7, OUTPUT);
    pinMode(8, OUTPUT);
    pinMode(9, OUTPUT);
    pinMode(10, OUTPUT);
    pinMode(11, OUTPUT);
    pinMode(13, OUTPUT);

    digitalWrite(6, LOW );
    digitalWrite(7, LOW );
    digitalWrite(8, LOW );
    digitalWrite(9, LOW );
    digitalWrite(13, HIGH );


    // start communication
    slave.begin( 9600 );
    tempus = millis() + 100;
    digitalWrite(13, HIGH );
    }


    void loop() {
    // poll messages
    // blink led pin on each valid message
    state = slave.poll( au16data, 9 );
    if (state > 4) {
    tempus = millis() + 50;
    digitalWrite(13, HIGH);
    }
    if (millis() > tempus) digitalWrite(13, LOW );


    // read analog inputs
    au16data[0] = analogRead(3);
    au16data[1]=latitude;

    // diagnose communication
    au16data[6] = slave.getInCnt();
    au16data[7] = slave.getOutCnt();
    au16data[8] = slave.getErrCnt();
    }

    ReplyDelete
  3. Dear Ajit,
    Regarding to these 3 points:
    1) The slave address is changed with the ID definition. Anyway I think that I wrote a method called "setID", which can be called anytime to this value. For example, you could associate it to a DIP switch.
    2) Just refer to Arduino Rerence available here:
    http://arduino.cc/en/Reference/HomePage
    You shall find how to work with words and fill their low and high bytes or how to split them into two bytes.
    3) Have you really measured the voltage at your analog input 3? Maybe your potentiometer is wiring somewhere else or not supplied with 5 V.
    Good Luck!

    ReplyDelete
  4. How to call write function code 6 & 15 in your example code.Suppose i want to write au16data[6] using function code 6 . How could i write .Can share small example.

    ReplyDelete
    Replies
    1. There is nothing to do in the code itself. All this must be done in the Master, who throws all the queries. The Slave just answers the incoming queries. If it doesn't understand them, then it throws an exception.

      With QModbus, just try:


      - to write coils between 16 and 19 with function 15 (for instance, put 0x1001);
      - to write coil 18 with 0x00 or 0xff, so it would become a 0 or a 1.

      Delete
  5. Hi Where can i download the library?

    ReplyDelete
    Replies
    1. There is a link to a dropbox .zip file in this same post. The library is written as a project module. It is fully functional, but I'm still moving it to a .h file.

      Regards

      Delete
    2. The dropbox link appears to be down, could you please publish it again?

      Delete
    3. Yes, here it is:
      http://arduino-experience.blogspot.com.es/2014/07/broken-link-to-my-modbus-slave-example.html
      You will see that this is a simplier sketch. You can extend it to behave as this post, if you copy the contents of setup() and loop() instances.
      I hope this helps you!

      Delete
  6. How can i assign slave ID here.i am using 8:1 mux & 16 PIN Dip switch. depend on status of digital value calcualte device ID. Serially i checked, device variable changes with postion of DIp switch. when i check in qmodbus.it wont work. Let me know how can i resolve this. or let me know we should configure slave ID for indivuall device in code itself/
    #include
    #define Slave_ID 1
    uint16_t au16data[16] = {
    3, 1415, 9265, 4, 2, 7182, 28182, 8, 0, 0, 0, 0, 0, 0, 1, -1 };

    Modbus slave(Slave_ID,0,0); // this is slave @1 and RS-232 or USB-FTDI

    void setup() {
    slave.begin( 9600 ); // baud-rate at 19200
    }

    void loop() {
    slave.poll( au16data, 16 );
    }

    int ID_Check() {
    int ID_value;
    for(int row=0;row<8;row++)
    {
    digitalWrite(SO_enable,array[row][0]);
    digitalWrite(S1_enable,array[row][1]);
    digitalWrite(S2_enable,array[row][2]);
    Status_Out[row]=digitalRead(Output_Read);

    }

    ID_value = 1 * Status_Out[0] + 2 * Status_Out[1] + 4 * Status_Out[2] + 8 * Status_Out[3] + 16 * Status_Out[4] + 32 * Status_Out[5] + 64 * Status_Out[6] + 128 * Status_Out[7];
    return(ID_value);
    }

    I would like to assign the return value to slave ID. Let me Know how can i assign it. i tried to call function in setup & assign value. The Serially i check slave ID changes as DIp switch changes

    ReplyDelete
  7. Dear Ajit,
    The library provides two methods to change the slave address (other than 0) and to read it. These are. "setID"and "getID". I haven't still tried them.
    Just implement in the loop section an statement to check if the slave ID has changed since the last time like this:

    If (ID_value <> oldID) {
    slave.setId( ID_value);
    oldID = ID_value; }

    ReplyDelete
    Replies
    1. yes suby i tried Set ID function calling in setup function before

      int oldID =1;
      int Device_ID=1;
      void setup
      {
      int Device_ID=ID_Check();
      if (Device_ID>oldID )
      {
      slave.setId( Device_ID);
      oldID = Device_ID;
      }
      Modbus slave(Device_ID,0,0);
      }

      Delete
    2. I'm afraid that you missunderstood the way to implement these methods. The "setup" section is only called once during the initialisation of your program. You need to refresh your node address while working. Thus you must call them in the "loop":

      void loop {
      int Device_ID=ID_Check();
      if (Device_ID>oldID )
      {
      slave.setId( Device_ID);
      oldID = Device_ID;
      }
      slave.poll( au16data, 16 );
      }

      Delete
  8. Hai I am Manjunath for 3 week am searching for mod-bus library and finally got success by using your library.
    My arduino with Rs485 is communicating well. com LED also blink continuously,

    Here my Question is how can I read particular holding register say for example 40157 is frequency parameter and all Register floating point data from 40101 to 40125 from my Energy meter to arduino and show at soft serial port say pin(10,11). to Serial terminal(Hercules)

    These are the
    Arduino - Duemilenove -->(Master)
    Energy Meter - EN8400 Navigator made by Elmearsure. --> (Slave)
    Protocol - Mod bus RTU via RS485.
    Com port - 9600 8 bit no-parity 1- stop bit.
    Devie ID - 4.

    ReplyDelete
    Replies
    1. Hello Manjunath,

      I'm afraid that my library isn't completely compatible with the software serial library. You must be careful because any "delay" instance interferes whenever there is a Modbus pooling. This is particularly critical with the Modbus slave, where you can miss some telegram.

      In your case, you are using it as a Master. Therefore you must make sure that you aren't using the software serial port while there is a pending query at the Modbus side.

      You need to perform a au16reg[] "serial.print" to your Hercules terminal. Have you tried to make "serial.print" for a set of values?

      Cheers,

      Delete
  9. Hey guys!
    am trying to build a Seriall communiation between a few Arduinos by using RS485 Serial communication and Modbus RTU Protocoll! I am using an Arduino Uno as a Master and 4 Arduino Uno as Salves.
    I was searching on internet to find a proper Library for my Modbus rtu Communication, undortunately, i couldn't find any! Could someone help me out?

    Cheers,
    Walkieee

    ReplyDelete
  10. Hai SUBY,

    really it is great support froom you can .

    i have copied the code and it is working fine with arduino port but with rs 485 it is not working fine

    i have used rs485 to usb convertor and connected pin 18,19 to convertor .

    can you give me details of wiring .

    ReplyDelete
  11. sorry i have forgetten to mention i am using arduino mega 2560

    ReplyDelete
  12. Hi, How can I change the address registers, for example my holding registers should start at 4000 and coil register should start at 3001, Is there a way to do it.

    ReplyDelete
  13. Your code is very useful and as a newbie to MODbus most manuals that i have read don't use codes to explain the how to configure master and slave for arduino. i have found videos explaining basics, but as we all do it and cut and paste to build a code to create one. I hope i wont ask for too much, but if i have question may I ask for your assistance?

    ReplyDelete