Forum Rule: Always post complete source code & details to reproduce any issue!
Page 1 of 2 1 2 LastLast
Results 1 to 25 of 37

Thread: Modbus RTU

  1. #1
    Junior Member
    Join Date
    Aug 2016
    Posts
    12

    Modbus RTU

    Hi, i am trying to setup Teensy as ModbusRTU Slave. Using >this< library i can set up registers up to 250?.. But if i want to set up a higher range of registers, for example register 300 teensy 3.1 freezes. I have to push the reset button to flash it again.. Somone knows a fix or has an other (working) Library?

  2. #2
    Senior Member Epyon's Avatar
    Join Date
    Apr 2013
    Location
    Belgium
    Posts
    443
    Looks like a buffer overflow of some sorts?

    I have tested a lot of Modbus libraries and found that this one performs the most satisfactory. To get it to work on Teensy you just need to comment out some AVR registers in the ModbusRTU.h file.

  3. #3
    Junior Member
    Join Date
    Aug 2016
    Posts
    12
    Hi thanks, i have tried the example but it doesent compile...

    Code:
    #include <ModbusRtu.h>
    uint16_t au16data[16] = {
      3, 1415, 9265, 4, 2, 7182, 28182, 8, 0, 0, 0, 0, 0, 0, 1, 4 };
    Modbus slave(1,1,0); 
    void setup() {
      slave.begin( 115200, SERIAL_8N1 ); 
    }
    void loop() {
      slave.poll( au16data, 16 );
    }
    It says:
    Code:
    It says
    
    In file included from C:\Users\o5i\Desktop\sketch_sep13a\sketch_sep13a.ino:10:0:
    
    C:\Users\o5i\Documents\Arduino\libraries\Modbus-Master-Slave-for-Arduino-master/ModbusRtu.h: In member function 'void Modbus::begin(long int)':
    
    C:\Users\o5i\Documents\Arduino\libraries\Modbus-Master-Slave-for-Arduino-master/ModbusRtu.h:316:14: error: cannot convert 'usb_serial_class*' to 'HardwareSerial*' in assignment
    
             port = &Serial;
    
                  ^
    
    C:\Users\o5i\Documents\Arduino\libraries\Modbus-Master-Slave-for-Arduino-master/ModbusRtu.h: In member function 'void Modbus::begin(long int, uint8_t)':
    
    C:\Users\o5i\Documents\Arduino\libraries\Modbus-Master-Slave-for-Arduino-master/ModbusRtu.h:399:14: error: cannot convert 'usb_serial_class*' to 'HardwareSerial*' in assignment
    
             port = &Serial;
    
                  ^
    
    C:\Users\o5i\Documents\Arduino\libraries\Modbus-Master-Slave-for-Arduino-master/ModbusRtu.h: In member function 'void Modbus::sendTxBuffer()':
    
    C:\Users\o5i\Documents\Arduino\libraries\Modbus-Master-Slave-for-Arduino-master/ModbusRtu.h:924:13: error: 'UCSR0A' was not declared in this scope
    
                 UCSR0A=UCSR0A |(1 << TXC0);
    
                 ^
    
    C:\Users\o5i\Documents\Arduino\libraries\Modbus-Master-Slave-for-Arduino-master/ModbusRtu.h:924:34: error: 'TXC0' was not declared in this scope
    
                 UCSR0A=UCSR0A |(1 << TXC0);
    
                                      ^
    
    C:\Users\o5i\Documents\Arduino\libraries\Modbus-Master-Slave-for-Arduino-master/ModbusRtu.h:960:22: error: 'UCSR0A' was not declared in this scope
    
                 while (!(UCSR0A & (1 << TXC0)));
    
                          ^
    
    C:\Users\o5i\Documents\Arduino\libraries\Modbus-Master-Slave-for-Arduino-master/ModbusRtu.h:960:37: error: 'TXC0' was not declared in this scope
    
                 while (!(UCSR0A & (1 << TXC0)));
    
                                         ^
    
    C:\Users\o5i\Documents\Arduino\libraries\Modbus-Master-Slave-for-Arduino-master/ModbusRtu.h:891:13: warning: unused variable 'i' [-Wunused-variable]
    
         uint8_t i = 0;
    
                 ^
    
    C:\Users\o5i\Documents\Arduino\libraries\Modbus-Master-Slave-for-Arduino-master/ModbusRtu.h: In member function 'void Modbus::get_FC1()':
    
    C:\Users\o5i\Documents\Arduino\libraries\Modbus-Master-Slave-for-Arduino-master/ModbusRtu.h:1152:21: warning: unused variable 'i' [-Wunused-variable]
    
         uint8_t u8byte, i;
    
                         ^
    
    C:\Users\o5i\Documents\Arduino\libraries\Modbus-Master-Slave-for-Arduino-master/ModbusRtu.h: In member function 'int8_t Modbus::process_FC16(uint16_t*, uint8_t)':
    
    C:\Users\o5i\Documents\Arduino\libraries\Modbus-Master-Slave-for-Arduino-master/ModbusRtu.h:1393:13: warning: unused variable 'u8func' [-Wunused-variable]
    
         uint8_t u8func = au8Buffer[ FUNC ];  // get the original FUNC code
    
                 ^
    
    Bibliothek Modbus-Master-Slave-for-Arduino-master im Ordner: C:\Users\o5i\Documents\Arduino\libraries\Modbus-Master-Slave-for-Arduino-master (legacy) wird verwendet
    Bibliothek SoftwareSerial in Version 1.0 im Ordner: C:\Users\o5i\Desktop\Micros\arduino-1.6.9\hardware\teensy\avr\libraries\SoftwareSerial  wird verwendet
    Fehler beim Kompilieren für das Board Teensy 3.2 / 3.1.

    I use teensy 3.1, it looks like the lib doesent like the serial?

    Can you send me the modified file?
    Last edited by o5i; 09-13-2016 at 08:10 PM.

  4. #4
    Senior Member Epyon's Avatar
    Join Date
    Apr 2013
    Location
    Belgium
    Posts
    443
    Like I said, you need to change some AVR registers to equivalent Teensy counterparts. Look for void Modbus::begin at line #254 and replace it by this:

    Code:
    void Modbus::begin(long u32speed) {
    
      switch( u8serno ) {
      case 1:
        port = &Serial1;
        port->begin(u32speed);
        break;
      case 2:
        port = &Serial2;
        port->begin(u32speed);
        break;
      case 3:
        port = &Serial3;
        port->begin(u32speed);
        break;
      case 0:
      default:
        port = &Serial1;
        break;
      }
      //port->begin(u32speed);
    
      if (u8txenpin > 1) { // pin 0 & pin 1 are reserved for RX/TX
        // return RS485 transceiver to transmit mode
        pinMode(u8txenpin, OUTPUT);
        digitalWrite(u8txenpin, LOW);
      }
    
      port->flush();
      u8lastRec = u8BufferSize = 0;
      u16InCnt = u16OutCnt = u16errCnt = 0;
    }
    And the same a bit lower for
    Code:
    void Modbus::begin(long u32speed,uint8_t u8config) {
    
      switch( u8serno ) {
      case 1:
        port = &Serial1;
        port->begin(u32speed, u8config);
        break;
      case 2:
        port = &Serial2;
        port->begin(u32speed, u8config);
        break;
      case 3:
        port = &Serial2;
        port->begin(u32speed, u8config);
        break;
      case 0:
      default:
        port = &Serial1;
        break;
      }
    
      //port->begin(u32speed, u8config);
      if (u8txenpin > 1) { // pin 0 & pin 1 are reserved for RX/TX
        // return RS485 transceiver to transmit mode
        pinMode(u8txenpin, OUTPUT);
        digitalWrite(u8txenpin, LOW);
      }
    
      port->flush();
      u8lastRec = u8BufferSize = 0;
      u16InCnt = u16OutCnt = u16errCnt = 0;
    }
    I was planning to write some examples on my blog, but haven't got around to it.

  5. #5
    Junior Member
    Join Date
    Aug 2016
    Posts
    12
    Hi, the file looks litlebit different, probably there was an update?

    Code:
    /**
     * @file 	ModbusRtu.h
     * @version     1.21
     * @date        2016.02.21
     * @author 	Samuel Marco i Armengol
     * @contact     sammarcoarmengol@gmail.com
     * @contribution Helium6072
     *
     * @description
     *  Arduino library for communicating with Modbus devices
     *  over RS232/USB/485 via RTU protocol.
     *
     *  Further information:
     *  http://modbus.org/
     *  http://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf
     *
     * @license
     *  This library is free software; you can redistribute it and/or
     *  modify it under the terms of the GNU Lesser General Public
     *  License as published by the Free Software Foundation; version
     *  2.1 of the License.
     *
     *  This library is distributed in the hope that it will be useful,
     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     *  Lesser General Public License for more details.
     *
     *  You should have received a copy of the GNU Lesser General Public
     *  License along with this library; if not, write to the Free Software
     *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     *
     * @defgroup setup Modbus Object Instantiation/Initialization
     * @defgroup loop Modbus Object Management
     * @defgroup buffer Modbus Buffer Management
     * @defgroup discrete Modbus Function Codes for Discrete Coils/Inputs
     * @defgroup register Modbus Function Codes for Holding/Input Registers
     *
     */
    
    #include <inttypes.h>
    #include "Arduino.h"
    #include "Print.h"
    #include <SoftwareSerial.h>
    
    /**
     * @struct modbus_t
     * @brief
     * Master query structure:
     * This includes all the necessary fields to make the Master generate a Modbus query.
     * A Master may keep several of these structures and send them cyclically or
     * use them according to program needs.
     */
    typedef struct
    {
        uint8_t u8id;          /*!< Slave address between 1 and 247. 0 means broadcast */
        uint8_t u8fct;         /*!< Function code: 1, 2, 3, 4, 5, 6, 15 or 16 */
        uint16_t u16RegAdd;    /*!< Address of the first register to access at slave/s */
        uint16_t u16CoilsNo;   /*!< Number of coils or registers to access */
        uint16_t *au16reg;     /*!< Pointer to memory image in master */
    }
    modbus_t;
    
    enum
    {
        RESPONSE_SIZE = 6,
        EXCEPTION_SIZE = 3,
        CHECKSUM_SIZE = 2
    };
    
    /**
     * @enum MESSAGE
     * @brief
     * Indexes to telegram frame positions
     */
    enum MESSAGE
    {
        ID                             = 0, //!< ID field
        FUNC, //!< Function code position
        ADD_HI, //!< Address high byte
        ADD_LO, //!< Address low byte
        NB_HI, //!< Number of coils or registers high byte
        NB_LO, //!< Number of coils or registers low byte
        BYTE_CNT  //!< byte counter
    };
    
    /**
     * @enum MB_FC
     * @brief
     * Modbus function codes summary.
     * These are the implement function codes either for Master or for Slave.
     *
     * @see also fctsupported
     * @see also modbus_t
     */
    enum MB_FC
    {
        MB_FC_NONE                     = 0,   /*!< null operator */
        MB_FC_READ_COILS               = 1,	/*!< FCT=1 -> read coils or digital outputs */
        MB_FC_READ_DISCRETE_INPUT      = 2,	/*!< FCT=2 -> read digital inputs */
        MB_FC_READ_REGISTERS           = 3,	/*!< FCT=3 -> read registers or analog outputs */
        MB_FC_READ_INPUT_REGISTER      = 4,	/*!< FCT=4 -> read analog inputs */
        MB_FC_WRITE_COIL               = 5,	/*!< FCT=5 -> write single coil or output */
        MB_FC_WRITE_REGISTER           = 6,	/*!< FCT=6 -> write single register */
        MB_FC_WRITE_MULTIPLE_COILS     = 15,	/*!< FCT=15 -> write multiple coils or outputs */
        MB_FC_WRITE_MULTIPLE_REGISTERS = 16	/*!< FCT=16 -> write multiple registers */
    };
    
    enum COM_STATES
    {
        COM_IDLE                     = 0,
        COM_WAITING                  = 1
    
    };
    
    enum ERR_LIST
    {
        ERR_NOT_MASTER                = -1,
        ERR_POLLING                   = -2,
        ERR_BUFF_OVERFLOW             = -3,
        ERR_BAD_CRC                   = -4,
        ERR_EXCEPTION                 = -5
    };
    
    enum
    {
        NO_REPLY = 255,
        EXC_FUNC_CODE = 1,
        EXC_ADDR_RANGE = 2,
        EXC_REGS_QUANT = 3,
        EXC_EXECUTE = 4
    };
    
    const unsigned char fctsupported[] =
    {
        MB_FC_READ_COILS,
        MB_FC_READ_DISCRETE_INPUT,
        MB_FC_READ_REGISTERS,
        MB_FC_READ_INPUT_REGISTER,
        MB_FC_WRITE_COIL,
        MB_FC_WRITE_REGISTER,
        MB_FC_WRITE_MULTIPLE_COILS,
        MB_FC_WRITE_MULTIPLE_REGISTERS
    };
    
    #define T35  5
    #define  MAX_BUFFER  64	//!< maximum size for the communication buffer in bytes
    
    /**
     * @class Modbus
     * @brief
     * Arduino class library for communicating with Modbus devices over
     * USB/RS232/485 (via RTU protocol).
     */
    class Modbus
    {
    private:
        HardwareSerial *port; //!< Pointer to Serial class object
        SoftwareSerial *softPort; //!< Pointer to SoftwareSerial class object
        uint8_t u8id; //!< 0=master, 1..247=slave number
        uint8_t u8serno; //!< serial port: 0-Serial, 1..3-Serial1..Serial3; 4: use software serial
        uint8_t u8txenpin; //!< flow control pin: 0=USB or RS-232 mode, >0=RS-485 mode
        uint8_t u8state;
        uint8_t u8lastError;
        uint8_t au8Buffer[MAX_BUFFER];
        uint8_t u8BufferSize;
        uint8_t u8lastRec;
        uint16_t *au16regs;
        uint16_t u16InCnt, u16OutCnt, u16errCnt;
        uint16_t u16timeOut;
        uint32_t u32time, u32timeOut;
        uint8_t u8regsize;
    
        void init(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin);
    	void init(uint8_t u8id);
        void sendTxBuffer();
        int8_t getRxBuffer();
        uint16_t calcCRC(uint8_t u8length);
        uint8_t validateAnswer();
        uint8_t validateRequest();
        void get_FC1();
        void get_FC3();
        int8_t process_FC1( uint16_t *regs, uint8_t u8size );
        int8_t process_FC3( uint16_t *regs, uint8_t u8size );
        int8_t process_FC5( uint16_t *regs, uint8_t u8size );
        int8_t process_FC6( uint16_t *regs, uint8_t u8size );
        int8_t process_FC15( uint16_t *regs, uint8_t u8size );
        int8_t process_FC16( uint16_t *regs, uint8_t u8size );
        void buildException( uint8_t u8exception ); // build exception message
    
    public:
        Modbus();
        Modbus(uint8_t u8id, uint8_t u8serno);
        Modbus(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin);
        Modbus(uint8_t u8id);
        void begin(long u32speed);
        void begin(SoftwareSerial *sPort, long u32speed);
        void begin(long u32speed, uint8_t u8config);
        void begin();
        void setTimeOut( uint16_t u16timeout); //!<write communication watch-dog timer
        uint16_t getTimeOut(); //!<get communication watch-dog timer value
        boolean getTimeOutState(); //!<get communication watch-dog timer state
        int8_t query( modbus_t telegram ); //!<only for master
        int8_t poll(); //!<cyclic poll for master
        int8_t poll( uint16_t *regs, uint8_t u8size ); //!<cyclic poll for slave
        uint16_t getInCnt(); //!<number of incoming messages
        uint16_t getOutCnt(); //!<number of outcoming messages
        uint16_t getErrCnt(); //!<error counter
        uint8_t getID(); //!<get slave ID between 1 and 247
        uint8_t getState();
        uint8_t getLastError(); //!<get last error message
        void setID( uint8_t u8id ); //!<write new ID for the slave
        void end(); //!<finish any communication and release serial communication port
    };
    
    /* _____PUBLIC FUNCTIONS_____________________________________________________ */
    
    /**
     * @brief
     * Default Constructor for Master through Serial
     *
     * @ingroup setup
     */
    Modbus::Modbus()
    {
        init(0, 0, 0);
    }
    
    /**
     * @brief
     * Full constructor for a Master/Slave through USB/RS232C
     *
     * @param u8id   node address 0=master, 1..247=slave
     * @param u8serno  serial port used 0..3
     * @ingroup setup
     * @overload Modbus::Modbus(uint8_t u8id, uint8_t u8serno)
     * @overload Modbus::Modbus(uint8_t u8id)
     * @overload Modbus::Modbus()
     */
    Modbus::Modbus(uint8_t u8id, uint8_t u8serno)
    {
        init(u8id, u8serno, 0);
    }
    
    /**
     * @brief
     * Full constructor for a Master/Slave through USB/RS232C/RS485
     * It needs a pin for flow control only for RS485 mode
     *
     * @param u8id   node address 0=master, 1..247=slave
     * @param u8serno  serial port used 0..3
     * @param u8txenpin pin for txen RS-485 (=0 means USB/RS232C mode)
     * @ingroup setup
     * @overload Modbus::Modbus(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin)
     * @overload Modbus::Modbus(uint8_t u8id)
     * @overload Modbus::Modbus()
     */
    Modbus::Modbus(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin)
    {
        init(u8id, u8serno, u8txenpin);
    }
    
    /**
     * @brief
     * Constructor for a Master/Slave through USB/RS232C via software serial
     * This constructor only specifies u8id (node address) and should be only
     * used if you want to use software serial instead of hardware serial.
     * If you use this constructor you have to begin ModBus object by
     * using "void Modbus::begin(SoftwareSerial *softPort, long u32speed)".
     *
     * @param u8id   node address 0=master, 1..247=slave
     * @ingroup setup
     * @overload Modbus::Modbus(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin)
     * @overload Modbus::Modbus(uint8_t u8id, uint8_t u8serno)
     * @overload Modbus::Modbus()
     */
    Modbus::Modbus(uint8_t u8id)
    {
        init(u8id);
    }
    
    /**
     * @brief
     * Initialize class object.
     *
     * Sets up the serial port using specified baud rate.
     * Call once class has been instantiated, typically within setup().
     *
     * @see http://arduino.cc/en/Serial/Begin#.Uy4CJ6aKlHY
     * @param speed   baud rate, in standard increments (300..115200)
     * @ingroup setup
     */
    void Modbus::begin(long u32speed)
    {
    
        switch( u8serno )
        {
    #if defined(UBRR1H)
        case 1:
            port = &Serial1;
            break;
    #endif
    
    #if defined(UBRR2H)
        case 2:
            port = &Serial2;
            break;
    #endif
    
    #if defined(UBRR3H)
        case 3:
            port = &Serial3;
            break;
    #endif
        case 0:
        default:
            port = &Serial;
            break;
        }
    
        port->begin(u32speed);
        if (u8txenpin > 1)   // pin 0 & pin 1 are reserved for RX/TX
        {
            // return RS485 transceiver to transmit mode
            pinMode(u8txenpin, OUTPUT);
            digitalWrite(u8txenpin, LOW);
        }
    
        while(port->read() >= 0);
        u8lastRec = u8BufferSize = 0;
        u16InCnt = u16OutCnt = u16errCnt = 0;
    }
    
    /**
     * @brief
     * Initialize class object.
     *
     * Sets up the software serial port using specified baud rate and SoftwareSerial object.
     * Call once class has been instantiated, typically within setup().
     *
     * @param speed   *softPort, pointer to SoftwareSerial class object
     * @param speed   baud rate, in standard increments (300..115200)
     * @ingroup setup
     */
    void Modbus::begin(SoftwareSerial *sPort, long u32speed)
    {
    
        softPort=sPort;
    
        softPort->begin(u32speed);
    
        if (u8txenpin > 1)   // pin 0 & pin 1 are reserved for RX/TX
        {
            // return RS485 transceiver to transmit mode
            pinMode(u8txenpin, OUTPUT);
            digitalWrite(u8txenpin, LOW);
        }
    
        while(softPort->read() >= 0);
        u8lastRec = u8BufferSize = 0;
        u16InCnt = u16OutCnt = u16errCnt = 0;
    }
    
    /**
     * @brief
     * Initialize class object.
     *
     * Sets up the serial port using specified baud rate.
     * Call once class has been instantiated, typically within setup().
     *
     * @see http://arduino.cc/en/Serial/Begin#.Uy4CJ6aKlHY
     * @param speed   baud rate, in standard increments (300..115200)
     * @param config  data frame settings (data length, parity and stop bits)
     * @ingroup setup
     */
    void Modbus::begin(long u32speed,uint8_t u8config)
    {
    
        switch( u8serno )
        {
    #if defined(UBRR1H)
        case 1:
            port = &Serial1;
            break;
    #endif
    
    #if defined(UBRR2H)
        case 2:
            port = &Serial2;
            break;
    #endif
    
    #if defined(UBRR3H)
        case 3:
            port = &Serial3;
            break;
    #endif
        case 0:
        default:
            port = &Serial;
            break;
        }
    
        port->begin(u32speed, u8config);
        if (u8txenpin > 1)   // pin 0 & pin 1 are reserved for RX/TX
        {
            // return RS485 transceiver to transmit mode
            pinMode(u8txenpin, OUTPUT);
            digitalWrite(u8txenpin, LOW);
        }
    
        while(port->read() >= 0);
        u8lastRec = u8BufferSize = 0;
        u16InCnt = u16OutCnt = u16errCnt = 0;
    }
    
    /**
     * @brief
     * Initialize default class object.
     *
     * Sets up the serial port using 19200 baud.
     * Call once class has been instantiated, typically within setup().
     *
     * @overload Modbus::begin(uint16_t u16BaudRate)
     * @ingroup setup
     */
    void Modbus::begin()
    {
        begin(19200);
    }
    
    /**
     * @brief
     * Method to write a new slave ID address
     *
     * @param 	u8id	new slave address between 1 and 247
     * @ingroup setup
     */
    void Modbus::setID( uint8_t u8id)
    {
        if (( u8id != 0) && (u8id <= 247))
        {
            this->u8id = u8id;
        }
    }
    
    /**
     * @brief
     * Method to read current slave ID address
     *
     * @return u8id	current slave address between 1 and 247
     * @ingroup setup
     */
    uint8_t Modbus::getID()
    {
        return this->u8id;
    }
    
    /**
     * @brief
     * Initialize time-out parameter
     *
     * Call once class has been instantiated, typically within setup().
     * The time-out timer is reset each time that there is a successful communication
     * between Master and Slave. It works for both.
     *
     * @param time-out value (ms)
     * @ingroup setup
     */
    void Modbus::setTimeOut( uint16_t u16timeOut)
    {
        this->u16timeOut = u16timeOut;
    }
    
    /**
     * @brief
     * Return communication Watchdog state.
     * It could be usefull to reset outputs if the watchdog is fired.
     *
     * @return TRUE if millis() > u32timeOut
     * @ingroup loop
     */
    boolean Modbus::getTimeOutState()
    {
        return (millis() > u32timeOut);
    }
    
    /**
     * @brief
     * Get input messages counter value
     * This can be useful to diagnose communication
     *
     * @return input messages counter
     * @ingroup buffer
     */
    uint16_t Modbus::getInCnt()
    {
        return u16InCnt;
    }
    
    /**
     * @brief
     * Get transmitted messages counter value
     * This can be useful to diagnose communication
     *
     * @return transmitted messages counter
     * @ingroup buffer
     */
    uint16_t Modbus::getOutCnt()
    {
        return u16OutCnt;
    }
    
    /**
     * @brief
     * Get errors counter value
     * This can be useful to diagnose communication
     *
     * @return errors counter
     * @ingroup buffer
     */
    uint16_t Modbus::getErrCnt()
    {
        return u16errCnt;
    }
    
    /**
     * Get modbus master state
     *
     * @return = 0 IDLE, = 1 WAITING FOR ANSWER
     * @ingroup buffer
     */
    uint8_t Modbus::getState()
    {
        return u8state;
    }
    
    /**
     * Get the last error in the protocol processor
     *
     * @returnreturn   NO_REPLY = 255      Time-out
     * @return   EXC_FUNC_CODE = 1   Function code not available
     * @return   EXC_ADDR_RANGE = 2  Address beyond available space for Modbus registers
     * @return   EXC_REGS_QUANT = 3  Coils or registers number beyond the available space
     * @ingroup buffer
     */
    uint8_t Modbus::getLastError()
    {
        return u8lastError;
    }
    
    /**
     * @brief
     * *** Only Modbus Master ***
     * Generate a query to an slave with a modbus_t telegram structure
     * The Master must be in COM_IDLE mode. After it, its state would be COM_WAITING.
     * This method has to be called only in loop() section.
     *
     * @see modbus_t
     * @param modbus_t  modbus telegram structure (id, fct, ...)
     * @ingroup loop
     * @todo finish function 15
     */
    int8_t Modbus::query( modbus_t telegram )
    {
        uint8_t u8regsno, u8bytesno;
        if (u8id!=0) return -2;
        if (u8state != COM_IDLE) return -1;
    
        if ((telegram.u8id==0) || (telegram.u8id>247)) return -3;
    
        au16regs = telegram.au16reg;
    
        // telegram header
        au8Buffer[ ID ]         = telegram.u8id;
        au8Buffer[ FUNC ]       = telegram.u8fct;
        au8Buffer[ ADD_HI ]     = highByte(telegram.u16RegAdd );
        au8Buffer[ ADD_LO ]     = lowByte( telegram.u16RegAdd );
    
        switch( telegram.u8fct )
        {
        case MB_FC_READ_COILS:
        case MB_FC_READ_DISCRETE_INPUT:
        case MB_FC_READ_REGISTERS:
        case MB_FC_READ_INPUT_REGISTER:
            au8Buffer[ NB_HI ]      = highByte(telegram.u16CoilsNo );
            au8Buffer[ NB_LO ]      = lowByte( telegram.u16CoilsNo );
            u8BufferSize = 6;
            break;
        case MB_FC_WRITE_COIL:
            au8Buffer[ NB_HI ]      = ((au16regs[0] > 0) ? 0xff : 0);
            au8Buffer[ NB_LO ]      = 0;
            u8BufferSize = 6;
            break;
        case MB_FC_WRITE_REGISTER:
            au8Buffer[ NB_HI ]      = highByte(au16regs[0]);
            au8Buffer[ NB_LO ]      = lowByte(au16regs[0]);
            u8BufferSize = 6;
            break;
        case MB_FC_WRITE_MULTIPLE_COILS: // TODO: implement "sending coils"
            u8regsno = telegram.u16CoilsNo / 16;
            u8bytesno = u8regsno * 2;
            if ((telegram.u16CoilsNo % 16) != 0)
            {
                u8bytesno++;
                u8regsno++;
            }
    
            au8Buffer[ NB_HI ]      = highByte(telegram.u16CoilsNo );
            au8Buffer[ NB_LO ]      = lowByte( telegram.u16CoilsNo );
            au8Buffer[ NB_LO+1 ]    = u8bytesno;
            u8BufferSize = 7;
    
            u8regsno = u8bytesno = 0; // now auxiliary registers
            for (uint16_t i = 0; i < telegram.u16CoilsNo; i++)
            {
    
    
            }
            break;
    
        case MB_FC_WRITE_MULTIPLE_REGISTERS:
            au8Buffer[ NB_HI ]      = highByte(telegram.u16CoilsNo );
            au8Buffer[ NB_LO ]      = lowByte( telegram.u16CoilsNo );
            au8Buffer[ NB_LO+1 ]    = (uint8_t) ( telegram.u16CoilsNo * 2 );
            u8BufferSize = 7;
    
            for (uint16_t i=0; i< telegram.u16CoilsNo; i++)
            {
                au8Buffer[ u8BufferSize ] = highByte( au16regs[ i ] );
                u8BufferSize++;
                au8Buffer[ u8BufferSize ] = lowByte( au16regs[ i ] );
                u8BufferSize++;
            }
            break;
        }
    
        sendTxBuffer();
        u8state = COM_WAITING;
        return 0;
    }
    
    /**
     * @brief *** Only for Modbus Master ***
     * This method checks if there is any incoming answer if pending.
     * If there is no answer, it would change Master state to COM_IDLE.
     * This method must be called only at loop section.
     * Avoid any delay() function.
     *
     * Any incoming data would be redirected to au16regs pointer,
     * as defined in its modbus_t query telegram.
     *
     * @params	nothing
     * @return errors counter
     * @ingroup loop
     */
    int8_t Modbus::poll()
    {
        // check if there is any incoming frame
    	uint8_t u8current;
        if(u8serno<4)
            u8current = port->available();
        else
            u8current = softPort->available();
    
        if (millis() > u32timeOut)
        {
            u8state = COM_IDLE;
            u8lastError = NO_REPLY;
            u16errCnt++;
            return 0;
        }
    
        if (u8current == 0) return 0;
    
        // check T35 after frame end or still no frame end
        if (u8current != u8lastRec)
        {
            u8lastRec = u8current;
            u32time = millis() + T35;
            return 0;
        }
        if (millis() < u32time) return 0;
    
        // transfer Serial buffer frame to auBuffer
        u8lastRec = 0;
        int8_t i8state = getRxBuffer();
        if (i8state < 7)
        {
            u8state = COM_IDLE;
            u16errCnt++;
            return i8state;
        }
    
        // validate message: id, CRC, FCT, exception
        uint8_t u8exception = validateAnswer();
        if (u8exception != 0)
        {
            u8state = COM_IDLE;
            return u8exception;
        }
    
        // process answer
        switch( au8Buffer[ FUNC ] )
        {
        case MB_FC_READ_COILS:
        case MB_FC_READ_DISCRETE_INPUT:
            // call get_FC1 to transfer the incoming message to au16regs buffer
            get_FC1( );
            break;
        case MB_FC_READ_INPUT_REGISTER:
        case MB_FC_READ_REGISTERS :
            // call get_FC3 to transfer the incoming message to au16regs buffer
            get_FC3( );
            break;
        case MB_FC_WRITE_COIL:
        case MB_FC_WRITE_REGISTER :
        case MB_FC_WRITE_MULTIPLE_COILS:
        case MB_FC_WRITE_MULTIPLE_REGISTERS :
            // nothing to do
            break;
        default:
            break;
        }
        u8state = COM_IDLE;
        return u8BufferSize;
    }
    
    /**
     * @brief
     * *** Only for Modbus Slave ***
     * This method checks if there is any incoming query
     * Afterwards, it would shoot a validation routine plus a register query
     * Avoid any delay() function !!!!
     * After a successful frame between the Master and the Slave, the time-out timer is reset.
     *
     * @param *regs  register table for communication exchange
     * @param u8size  size of the register table
     * @return 0 if no query, 1..4 if communication error, >4 if correct query processed
     * @ingroup loop
     */
    int8_t Modbus::poll( uint16_t *regs, uint8_t u8size )
    {
    
        au16regs = regs;
        u8regsize = u8size;
    	uint8_t u8current;
    
    
        // check if there is any incoming frame
        if(u8serno<4)
            u8current = port->available();
        else
            u8current = softPort->available();
    
        if (u8current == 0) return 0;
    
        // check T35 after frame end or still no frame end
        if (u8current != u8lastRec)
        {
            u8lastRec = u8current;
            u32time = millis() + T35;
            return 0;
        }
        if (millis() < u32time) return 0;
    
        u8lastRec = 0;
        int8_t i8state = getRxBuffer();
        u8lastError = i8state;
        if (i8state < 7) return i8state;
    
        // check slave id
        if (au8Buffer[ ID ] != u8id) return 0;
    
        // validate message: CRC, FCT, address and size
        uint8_t u8exception = validateRequest();
        if (u8exception > 0)
        {
            if (u8exception != NO_REPLY)
            {
                buildException( u8exception );
                sendTxBuffer();
            }
            u8lastError = u8exception;
            return u8exception;
        }
    
        u32timeOut = millis() + long(u16timeOut);
        u8lastError = 0;
    
        // process message
        switch( au8Buffer[ FUNC ] )
        {
        case MB_FC_READ_COILS:
        case MB_FC_READ_DISCRETE_INPUT:
            return process_FC1( regs, u8size );
            break;
        case MB_FC_READ_INPUT_REGISTER:
        case MB_FC_READ_REGISTERS :
            return process_FC3( regs, u8size );
            break;
        case MB_FC_WRITE_COIL:
            return process_FC5( regs, u8size );
            break;
        case MB_FC_WRITE_REGISTER :
            return process_FC6( regs, u8size );
            break;
        case MB_FC_WRITE_MULTIPLE_COILS:
            return process_FC15( regs, u8size );
            break;
        case MB_FC_WRITE_MULTIPLE_REGISTERS :
            return process_FC16( regs, u8size );
            break;
        default:
            break;
        }
        return i8state;
    }
    
    /* _____PRIVATE FUNCTIONS_____________________________________________________ */
    
    void Modbus::init(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin)
    {
        this->u8id = u8id;
        this->u8serno = (u8serno > 3) ? 0 : u8serno;
        this->u8txenpin = u8txenpin;
        this->u16timeOut = 1000;
    }
    
    void Modbus::init(uint8_t u8id)
    {
        this->u8id = u8id;
        this->u8serno = 4;
        this->u8txenpin = 0;
        this->u16timeOut = 1000;
    }
    
    /**
     * @brief
     * This method moves Serial buffer data to the Modbus au8Buffer.
     *
     * @return buffer size if OK, ERR_BUFF_OVERFLOW if u8BufferSize >= MAX_BUFFER
     * @ingroup buffer
     */
    int8_t Modbus::getRxBuffer()
    {
        boolean bBuffOverflow = false;
    
        if (u8txenpin > 1) digitalWrite( u8txenpin, LOW );
    
        u8BufferSize = 0;
        if(u8serno<4)
            while ( port->available() )
            {
                au8Buffer[ u8BufferSize ] = port->read();
                u8BufferSize ++;
    
                if (u8BufferSize >= MAX_BUFFER) bBuffOverflow = true;
            }
        else
            while ( softPort->available() )
            {
                au8Buffer[ u8BufferSize ] = softPort->read();
                u8BufferSize ++;
    
                if (u8BufferSize >= MAX_BUFFER) bBuffOverflow = true;
            }
        u16InCnt++;
    
        if (bBuffOverflow)
        {
            u16errCnt++;
            return ERR_BUFF_OVERFLOW;
        }
        return u8BufferSize;
    }
    
    /**
     * @brief
     * This method transmits au8Buffer to Serial line.
     * Only if u8txenpin != 0, there is a flow handling in order to keep
     * the RS485 transceiver in output state as long as the message is being sent.
     * This is done with UCSRxA register.
     * The CRC is appended to the buffer before starting to send it.
     *
     * @param nothing
     * @return nothing
     * @ingroup buffer
     */
    void Modbus::sendTxBuffer()
    {
        uint8_t i = 0;
    
        // append CRC to message
        uint16_t u16crc = calcCRC( u8BufferSize );
        au8Buffer[ u8BufferSize ] = u16crc >> 8;
        u8BufferSize++;
        au8Buffer[ u8BufferSize ] = u16crc & 0x00ff;
        u8BufferSize++;
    
        // set RS485 transceiver to transmit mode
        if (u8txenpin > 1)
        {
            switch( u8serno )
            {
    #if defined(UBRR1H)
            case 1:
                UCSR1A=UCSR1A |(1 << TXC1);
                break;
    #endif
    
    #if defined(UBRR2H)
            case 2:
                UCSR2A=UCSR2A |(1 << TXC2);
                break;
    #endif
    
    #if defined(UBRR3H)
            case 3:
                UCSR3A=UCSR3A |(1 << TXC3);
                break;
    #endif
            case 0:
            default:
                UCSR0A=UCSR0A |(1 << TXC0);
                break;
            }
            digitalWrite( u8txenpin, HIGH );
        }
    
        // transfer buffer to serial line
        if(u8serno<4)
            port->write( au8Buffer, u8BufferSize );
        else
            softPort->write( au8Buffer, u8BufferSize );
    
        // keep RS485 transceiver in transmit mode as long as sending
        if (u8txenpin > 1)
        {
            switch( u8serno )
            {
    #if defined(UBRR1H)
            case 1:
                while (!(UCSR1A & (1 << TXC1)));
                break;
    #endif
    
    #if defined(UBRR2H)
            case 2:
                while (!(UCSR2A & (1 << TXC2)));
                break;
    #endif
    
    #if defined(UBRR3H)
            case 3:
                while (!(UCSR3A & (1 << TXC3)));
                break;
    #endif
            case 0:
            default:
                while (!(UCSR0A & (1 << TXC0)));
                break;
            }
    
            // return RS485 transceiver to receive mode
            digitalWrite( u8txenpin, LOW );
        }
        if(u8serno<4)
            while(port->read() >= 0);
        else
            while(softPort->read() >= 0);
    
        u8BufferSize = 0;
    
        // set time-out for master
        u32timeOut = millis() + (unsigned long) u16timeOut;
    
        // increase message counter
        u16OutCnt++;
    }
    
    /**
     * @brief
     * This method calculates CRC
     *
     * @return uint16_t calculated CRC value for the message
     * @ingroup buffer
     */
    uint16_t Modbus::calcCRC(uint8_t u8length)
    {
        unsigned int temp, temp2, flag;
        temp = 0xFFFF;
        for (unsigned char i = 0; i < u8length; i++)
        {
            temp = temp ^ au8Buffer[i];
            for (unsigned char j = 1; j <= 8; j++)
            {
                flag = temp & 0x0001;
                temp >>=1;
                if (flag)
                    temp ^= 0xA001;
            }
        }
        // Reverse byte order.
        temp2 = temp >> 8;
        temp = (temp << 8) | temp2;
        temp &= 0xFFFF;
        // the returned value is already swapped
        // crcLo byte is first & crcHi byte is last
        return temp;
    }
    
    /**
     * @brief
     * This method validates slave incoming messages
     *
     * @return 0 if OK, EXCEPTION if anything fails
     * @ingroup buffer
     */
    uint8_t Modbus::validateRequest()
    {
        // check message crc vs calculated crc
        uint16_t u16MsgCRC =
            ((au8Buffer[u8BufferSize - 2] << 8)
             | au8Buffer[u8BufferSize - 1]); // combine the crc Low & High bytes
        if ( calcCRC( u8BufferSize-2 ) != u16MsgCRC )
        {
            u16errCnt ++;
            return NO_REPLY;
        }
    
        // check fct code
        boolean isSupported = false;
        for (uint8_t i = 0; i< sizeof( fctsupported ); i++)
        {
            if (fctsupported[i] == au8Buffer[FUNC])
            {
                isSupported = 1;
                break;
            }
        }
        if (!isSupported)
        {
            u16errCnt ++;
            return EXC_FUNC_CODE;
        }
    
        // check start address & nb range
        uint16_t u16regs = 0;
        uint8_t u8regs;
        switch ( au8Buffer[ FUNC ] )
        {
        case MB_FC_READ_COILS:
        case MB_FC_READ_DISCRETE_INPUT:
        case MB_FC_WRITE_MULTIPLE_COILS:
            u16regs = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ]) / 16;
            u16regs += word( au8Buffer[ NB_HI ], au8Buffer[ NB_LO ]) /16;
            u8regs = (uint8_t) u16regs;
            if (u8regs > u8regsize) return EXC_ADDR_RANGE;
            break;
        case MB_FC_WRITE_COIL:
            u16regs = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ]) / 16;
            u8regs = (uint8_t) u16regs;
            if (u8regs > u8regsize) return EXC_ADDR_RANGE;
            break;
        case MB_FC_WRITE_REGISTER :
            u16regs = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ]);
            u8regs = (uint8_t) u16regs;
            if (u8regs > u8regsize) return EXC_ADDR_RANGE;
            break;
        case MB_FC_READ_REGISTERS :
        case MB_FC_READ_INPUT_REGISTER :
        case MB_FC_WRITE_MULTIPLE_REGISTERS :
            u16regs = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ]);
            u16regs += word( au8Buffer[ NB_HI ], au8Buffer[ NB_LO ]);
            u8regs = (uint8_t) u16regs;
            if (u8regs > u8regsize) return EXC_ADDR_RANGE;
            break;
        }
        return 0; // OK, no exception code thrown
    }
    
    /**
     * @brief
     * This method validates master incoming messages
     *
     * @return 0 if OK, EXCEPTION if anything fails
     * @ingroup buffer
     */
    uint8_t Modbus::validateAnswer()
    {
        // check message crc vs calculated crc
        uint16_t u16MsgCRC =
            ((au8Buffer[u8BufferSize - 2] << 8)
             | au8Buffer[u8BufferSize - 1]); // combine the crc Low & High bytes
        if ( calcCRC( u8BufferSize-2 ) != u16MsgCRC )
        {
            u16errCnt ++;
            return NO_REPLY;
        }
    
        // check exception
        if ((au8Buffer[ FUNC ] & 0x80) != 0)
        {
            u16errCnt ++;
            return ERR_EXCEPTION;
        }
    
        // check fct code
        boolean isSupported = false;
        for (uint8_t i = 0; i< sizeof( fctsupported ); i++)
        {
            if (fctsupported[i] == au8Buffer[FUNC])
            {
                isSupported = 1;
                break;
            }
        }
        if (!isSupported)
        {
            u16errCnt ++;
            return EXC_FUNC_CODE;
        }
    
        return 0; // OK, no exception code thrown
    }
    
    /**
     * @brief
     * This method builds an exception message
     *
     * @ingroup buffer
     */
    void Modbus::buildException( uint8_t u8exception )
    {
        uint8_t u8func = au8Buffer[ FUNC ];  // get the original FUNC code
    
        au8Buffer[ ID ]      = u8id;
        au8Buffer[ FUNC ]    = u8func + 0x80;
        au8Buffer[ 2 ]       = u8exception;
        u8BufferSize         = EXCEPTION_SIZE;
    }
    
    /**
     * This method processes functions 1 & 2 (for master)
     * This method puts the slave answer into master data buffer
     *
     * @ingroup register
     * TODO: finish its implementation
     */
    void Modbus::get_FC1()
    {
        uint8_t u8byte, i;
        u8byte = 0;
    
        //  for (i=0; i< au8Buffer[ 2 ] /2; i++) {
        //    au16regs[ i ] = word(
        //    au8Buffer[ u8byte ],
        //    au8Buffer[ u8byte +1 ]);
        //    u8byte += 2;
        //  }
    }
    
    /**
     * This method processes functions 3 & 4 (for master)
     * This method puts the slave answer into master data buffer
     *
     * @ingroup register
     */
    void Modbus::get_FC3()
    {
        uint8_t u8byte, i;
        u8byte = 3;
    
        for (i=0; i< au8Buffer[ 2 ] /2; i++)
        {
            au16regs[ i ] = word(
                                au8Buffer[ u8byte ],
                                au8Buffer[ u8byte +1 ]);
            u8byte += 2;
        }
    }
    
    /**
     * @brief
     * This method processes functions 1 & 2
     * This method reads a bit array and transfers it to the master
     *
     * @return u8BufferSize Response to master length
     * @ingroup discrete
     */
    int8_t Modbus::process_FC1( uint16_t *regs, uint8_t u8size )
    {
        uint8_t u8currentRegister, u8currentBit, u8bytesno, u8bitsno;
        uint8_t u8CopyBufferSize;
        uint16_t u16currentCoil, u16coil;
    
        // get the first and last coil from the message
        uint16_t u16StartCoil = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ] );
        uint16_t u16Coilno = word( au8Buffer[ NB_HI ], au8Buffer[ NB_LO ] );
    
        // put the number of bytes in the outcoming message
        u8bytesno = (uint8_t) (u16Coilno / 8);
        if (u16Coilno % 8 != 0) u8bytesno ++;
        au8Buffer[ ADD_HI ]  = u8bytesno;
        u8BufferSize         = ADD_LO;
    
        // read each coil from the register map and put its value inside the outcoming message
        u8bitsno = 0;
    
        for (u16currentCoil = 0; u16currentCoil < u16Coilno; u16currentCoil++)
        {
            u16coil = u16StartCoil + u16currentCoil;
            u8currentRegister = (uint8_t) (u16coil / 16);
            u8currentBit = (uint8_t) (u16coil % 16);
    
            bitWrite(
                au8Buffer[ u8BufferSize ],
                u8bitsno,
                bitRead( regs[ u8currentRegister ], u8currentBit ) );
            u8bitsno ++;
    
            if (u8bitsno > 7)
            {
                u8bitsno = 0;
                u8BufferSize++;
            }
        }
    
        // send outcoming message
        if (u16Coilno % 8 != 0) u8BufferSize ++;
        u8CopyBufferSize = u8BufferSize +2;
        sendTxBuffer();
        return u8CopyBufferSize;
    }
    
    /**
     * @brief
     * This method processes functions 3 & 4
     * This method reads a word array and transfers it to the master
     *
     * @return u8BufferSize Response to master length
     * @ingroup register
     */
    int8_t Modbus::process_FC3( uint16_t *regs, uint8_t u8size )
    {
    
        uint8_t u8StartAdd = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ] );
        uint8_t u8regsno = word( au8Buffer[ NB_HI ], au8Buffer[ NB_LO ] );
        uint8_t u8CopyBufferSize;
        uint8_t i;
    
        au8Buffer[ 2 ]       = u8regsno * 2;
        u8BufferSize         = 3;
    
        for (i = u8StartAdd; i < u8StartAdd + u8regsno; i++)
        {
            au8Buffer[ u8BufferSize ] = highByte(regs[i]);
            u8BufferSize++;
            au8Buffer[ u8BufferSize ] = lowByte(regs[i]);
            u8BufferSize++;
        }
        u8CopyBufferSize = u8BufferSize +2;
        sendTxBuffer();
    
        return u8CopyBufferSize;
    }
    
    /**
     * @brief
     * This method processes function 5
     * This method writes a value assigned by the master to a single bit
     *
     * @return u8BufferSize Response to master length
     * @ingroup discrete
     */
    int8_t Modbus::process_FC5( uint16_t *regs, uint8_t u8size )
    {
        uint8_t u8currentRegister, u8currentBit;
        uint8_t u8CopyBufferSize;
        uint16_t u16coil = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ] );
    
        // point to the register and its bit
        u8currentRegister = (uint8_t) (u16coil / 16);
        u8currentBit = (uint8_t) (u16coil % 16);
    
        // write to coil
        bitWrite(
            regs[ u8currentRegister ],
            u8currentBit,
            au8Buffer[ NB_HI ] == 0xff );
    
    
        // send answer to master
        u8BufferSize = 6;
        u8CopyBufferSize = u8BufferSize +2;
        sendTxBuffer();
    
        return u8CopyBufferSize;
    }
    
    /**
     * @brief
     * This method processes function 6
     * This method writes a value assigned by the master to a single word
     *
     * @return u8BufferSize Response to master length
     * @ingroup register
     */
    int8_t Modbus::process_FC6( uint16_t *regs, uint8_t u8size )
    {
    
        uint8_t u8add = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ] );
        uint8_t u8CopyBufferSize;
        uint16_t u16val = word( au8Buffer[ NB_HI ], au8Buffer[ NB_LO ] );
    
        regs[ u8add ] = u16val;
    
        // keep the same header
        u8BufferSize         = RESPONSE_SIZE;
    
        u8CopyBufferSize = u8BufferSize +2;
        sendTxBuffer();
    
        return u8CopyBufferSize;
    }
    
    /**
     * @brief
     * This method processes function 15
     * This method writes a bit array assigned by the master
     *
     * @return u8BufferSize Response to master length
     * @ingroup discrete
     */
    int8_t Modbus::process_FC15( uint16_t *regs, uint8_t u8size )
    {
        uint8_t u8currentRegister, u8currentBit, u8frameByte, u8bitsno;
        uint8_t u8CopyBufferSize;
        uint16_t u16currentCoil, u16coil;
        boolean bTemp;
    
        // get the first and last coil from the message
        uint16_t u16StartCoil = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ] );
        uint16_t u16Coilno = word( au8Buffer[ NB_HI ], au8Buffer[ NB_LO ] );
    
    
        // read each coil from the register map and put its value inside the outcoming message
        u8bitsno = 0;
        u8frameByte = 7;
        for (u16currentCoil = 0; u16currentCoil < u16Coilno; u16currentCoil++)
        {
    
            u16coil = u16StartCoil + u16currentCoil;
            u8currentRegister = (uint8_t) (u16coil / 16);
            u8currentBit = (uint8_t) (u16coil % 16);
    
            bTemp = bitRead(
                        au8Buffer[ u8frameByte ],
                        u8bitsno );
    
            bitWrite(
                regs[ u8currentRegister ],
                u8currentBit,
                bTemp );
    
            u8bitsno ++;
    
            if (u8bitsno > 7)
            {
                u8bitsno = 0;
                u8frameByte++;
            }
        }
    
        // send outcoming message
        // it's just a copy of the incomping frame until 6th byte
        u8BufferSize         = 6;
        u8CopyBufferSize = u8BufferSize +2;
        sendTxBuffer();
        return u8CopyBufferSize;
    }
    
    /**
     * @brief
     * This method processes function 16
     * This method writes a word array assigned by the master
     *
     * @return u8BufferSize Response to master length
     * @ingroup register
     */
    int8_t Modbus::process_FC16( uint16_t *regs, uint8_t u8size )
    {
        uint8_t u8func = au8Buffer[ FUNC ];  // get the original FUNC code
        uint8_t u8StartAdd = au8Buffer[ ADD_HI ] << 8 | au8Buffer[ ADD_LO ];
        uint8_t u8regsno = au8Buffer[ NB_HI ] << 8 | au8Buffer[ NB_LO ];
        uint8_t u8CopyBufferSize;
        uint8_t i;
        uint16_t temp;
    
        // build header
        au8Buffer[ NB_HI ]   = 0;
        au8Buffer[ NB_LO ]   = u8regsno;
        u8BufferSize         = RESPONSE_SIZE;
    
        // write registers
        for (i = 0; i < u8regsno; i++)
        {
            temp = word(
                       au8Buffer[ (BYTE_CNT + 1) + i * 2 ],
                       au8Buffer[ (BYTE_CNT + 2) + i * 2 ]);
    
            regs[ u8StartAdd + i ] = temp;
        }
        u8CopyBufferSize = u8BufferSize +2;
        sendTxBuffer();
    
        return u8CopyBufferSize;
    }

  6. #6
    Senior Member Epyon's Avatar
    Join Date
    Apr 2013
    Location
    Belgium
    Posts
    443
    It's the same file, I just made some edits for my specific application that removed some lines. The code to be changed that I posted above can be found in your file at lines #292 and #344. Just look for the function names and edit that piece of code.

  7. #7
    Junior Member
    Join Date
    Aug 2016
    Posts
    12
    Hi i did the modifications but it doesent work:
    Code:
    n file included from C:\Users\Perger\AppData\Local\Temp\arduino_modified_sketch_175523\sketch_sep14a.ino:3:0:
    
    C:\Users\Perger\Documents\Arduino\libraries\Modbus-Master-Slave-for-Arduino-master/ModbusRtumod.h: In member function 'void Modbus::sendTxBuffer()':
    
    C:\Users\Perger\Documents\Arduino\libraries\Modbus-Master-Slave-for-Arduino-master/ModbusRtumod.h:912:13: error: 'UCSR0A' was not declared in this scope
    
                 UCSR0A=UCSR0A |(1 << TXC0);
    
                 ^
    
    C:\Users\Perger\Documents\Arduino\libraries\Modbus-Master-Slave-for-Arduino-master/ModbusRtumod.h:912:34: error: 'TXC0' was not declared in this scope
    
                 UCSR0A=UCSR0A |(1 << TXC0);
    
                                      ^
    
    C:\Users\Perger\Documents\Arduino\libraries\Modbus-Master-Slave-for-Arduino-master/ModbusRtumod.h:948:22: error: 'UCSR0A' was not declared in this scope
    
                 while (!(UCSR0A & (1 << TXC0)));
    
                          ^
    
    C:\Users\Perger\Documents\Arduino\libraries\Modbus-Master-Slave-for-Arduino-master/ModbusRtumod.h:948:37: error: 'TXC0' was not declared in this scope
    
                 while (!(UCSR0A & (1 << TXC0)));
    
                                         ^
    You want to share your file whit me?

  8. #8
    Junior Member
    Join Date
    Sep 2017
    Posts
    1

    Question ModbusRtu.h on a Teensy++ 2.0 (AT90USB1286)

    Quote Originally Posted by Epyon View Post
    Looks like a buffer overflow of some sorts?

    I have tested a lot of Modbus libraries and found that this one performs the most satisfactory. To get it to work on Teensy you just need to comment out some AVR registers in the ModbusRTU.h file.
    Hi Epyon,

    and did you try to run it on a Teensy++ 2.0 (AT90USB1286)?

    Regards,
    Luís

  9. #9
    Senior Member Epyon's Avatar
    Join Date
    Apr 2013
    Location
    Belgium
    Posts
    443
    Quote Originally Posted by LuisMorais View Post
    Hi Epyon,

    and did you try to run it on a Teensy++ 2.0 (AT90USB1286)?

    Regards,
    Luís
    No, I also don't have any 2.0 hardware. But because it is based on AVR, you can probably use the standard version of the library with minimal editting.

  10. #10
    Senior Member Ben's Avatar
    Join Date
    Jul 2013
    Location
    Germany
    Posts
    401
    I'm looking for a good Modbus-library as well. It seems the one Epyon linked is hard-coded for a specific Arduino board as it expects exactly four hardware serial ports.
    From my limited understanding it would be better if the constructor or begin() were changed to take a HardwareSerial or Stream object by reference. Also the TX-Enable management for RS485 could be changed from digitalWrite() to transmitterEnable(pin) when possible.
    I'm not sure if I'm up to that task, seems like a big change in the library...

    EDIT:
    I changed the Lib to use a HardwareSerial object instead of hard-coded Serial ports and commented out SoftwareSerial support. transmitterEnable() is not yet implemented, still using digitalWrite(). This is completely untested, but at least it compiles with an empty sketch with #include <ModbusRtu.h>.

    Here's the modified ModbusRtu.h
    I have no idea if I did this right, might be complete garbage :/
    I'll put a fork to Github as soon as I can, really need to go to bed now

    Code:
    /**
     * @file 	ModbusRtu.h
     * @version     1.21
     * @date        2016.02.21
     * @author 	Samuel Marco i Armengol
     * @contact     sammarcoarmengol@gmail.com
     * @contribution Helium6072
     *
     * @description
     *  Arduino library for communicating with Modbus devices
     *  over RS232/USB/485 via RTU protocol.
     *
     *  Further information:
     *  http://modbus.org/
     *  http://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf
     *
     * @license
     *  This library is free software; you can redistribute it and/or
     *  modify it under the terms of the GNU Lesser General Public
     *  License as published by the Free Software Foundation; version
     *  2.1 of the License.
     *
     *  This library is distributed in the hope that it will be useful,
     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     *  Lesser General Public License for more details.
     *
     *  You should have received a copy of the GNU Lesser General Public
     *  License along with this library; if not, write to the Free Software
     *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     *
     * @defgroup setup Modbus Object Instantiation/Initialization
     * @defgroup loop Modbus Object Management
     * @defgroup buffer Modbus Buffer Management
     * @defgroup discrete Modbus Function Codes for Discrete Coils/Inputs
     * @defgroup register Modbus Function Codes for Holding/Input Registers
     *
     */
    
    #include <inttypes.h>
    #include "Arduino.h"
    #include "Print.h"
    #include <SoftwareSerial.h>
    
    /**
     * @struct modbus_t
     * @brief
     * Master query structure:
     * This includes all the necessary fields to make the Master generate a Modbus query.
     * A Master may keep several of these structures and send them cyclically or
     * use them according to program needs.
     */
    typedef struct
    {
        uint8_t u8id;          /*!< Slave address between 1 and 247. 0 means broadcast */
        uint8_t u8fct;         /*!< Function code: 1, 2, 3, 4, 5, 6, 15 or 16 */
        uint16_t u16RegAdd;    /*!< Address of the first register to access at slave/s */
        uint16_t u16CoilsNo;   /*!< Number of coils or registers to access */
        uint16_t *au16reg;     /*!< Pointer to memory image in master */
    }
    modbus_t;
    
    enum
    {
        RESPONSE_SIZE = 6,
        EXCEPTION_SIZE = 3,
        CHECKSUM_SIZE = 2
    };
    
    /**
     * @enum MESSAGE
     * @brief
     * Indexes to telegram frame positions
     */
    enum MESSAGE
    {
        ID                             = 0, //!< ID field
        FUNC, //!< Function code position
        ADD_HI, //!< Address high byte
        ADD_LO, //!< Address low byte
        NB_HI, //!< Number of coils or registers high byte
        NB_LO, //!< Number of coils or registers low byte
        BYTE_CNT  //!< byte counter
    };
    
    /**
     * @enum MB_FC
     * @brief
     * Modbus function codes summary.
     * These are the implement function codes either for Master or for Slave.
     *
     * @see also fctsupported
     * @see also modbus_t
     */
    enum MB_FC
    {
        MB_FC_NONE                     = 0,   /*!< null operator */
        MB_FC_READ_COILS               = 1,	/*!< FCT=1 -> read coils or digital outputs */
        MB_FC_READ_DISCRETE_INPUT      = 2,	/*!< FCT=2 -> read digital inputs */
        MB_FC_READ_REGISTERS           = 3,	/*!< FCT=3 -> read registers or analog outputs */
        MB_FC_READ_INPUT_REGISTER      = 4,	/*!< FCT=4 -> read analog inputs */
        MB_FC_WRITE_COIL               = 5,	/*!< FCT=5 -> write single coil or output */
        MB_FC_WRITE_REGISTER           = 6,	/*!< FCT=6 -> write single register */
        MB_FC_WRITE_MULTIPLE_COILS     = 15,	/*!< FCT=15 -> write multiple coils or outputs */
        MB_FC_WRITE_MULTIPLE_REGISTERS = 16	/*!< FCT=16 -> write multiple registers */
    };
    
    enum COM_STATES
    {
        COM_IDLE                     = 0,
        COM_WAITING                  = 1
    
    };
    
    enum ERR_LIST
    {
        ERR_NOT_MASTER                = -1,
        ERR_POLLING                   = -2,
        ERR_BUFF_OVERFLOW             = -3,
        ERR_BAD_CRC                   = -4,
        ERR_EXCEPTION                 = -5
    };
    
    enum
    {
        NO_REPLY = 255,
        EXC_FUNC_CODE = 1,
        EXC_ADDR_RANGE = 2,
        EXC_REGS_QUANT = 3,
        EXC_EXECUTE = 4
    };
    
    const unsigned char fctsupported[] =
    {
        MB_FC_READ_COILS,
        MB_FC_READ_DISCRETE_INPUT,
        MB_FC_READ_REGISTERS,
        MB_FC_READ_INPUT_REGISTER,
        MB_FC_WRITE_COIL,
        MB_FC_WRITE_REGISTER,
        MB_FC_WRITE_MULTIPLE_COILS,
        MB_FC_WRITE_MULTIPLE_REGISTERS
    };
    
    #define T35  5
    #define  MAX_BUFFER  64	//!< maximum size for the communication buffer in bytes
    
    /**
     * @class Modbus
     * @brief
     * Arduino class library for communicating with Modbus devices over
     * USB/RS232/485 (via RTU protocol).
     */
    class Modbus
    {
    private:
        //HardwareSerial *port; //!< Pointer to Serial class object
        //SoftwareSerial *softPort; //!< Pointer to SoftwareSerial class object
        uint8_t u8id; //!< 0=master, 1..247=slave number
        //uint8_t u8serno; //!< serial port: 0-Serial, 1..3-Serial1..Serial3; 4: use software serial
    	HardwareSerial _port;
        uint8_t u8txenpin; //!< flow control pin: 0=USB or RS-232 mode, >0=RS-485 mode
        uint8_t u8state;
        uint8_t u8lastError;
        uint8_t au8Buffer[MAX_BUFFER];
        uint8_t u8BufferSize;
        uint8_t u8lastRec;
        uint16_t *au16regs;
        uint16_t u16InCnt, u16OutCnt, u16errCnt;
        uint16_t u16timeOut;
        uint32_t u32time, u32timeOut;
        uint8_t u8regsize;
    
        //void init(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin);
    	void init(uint8_t u8id, HardwareSerial &_port, uint8_t u8txenpin);
    	void init(uint8_t u8id);
        void sendTxBuffer();
        int8_t getRxBuffer();
        uint16_t calcCRC(uint8_t u8length);
        uint8_t validateAnswer();
        uint8_t validateRequest();
        void get_FC1();
        void get_FC3();
        int8_t process_FC1( uint16_t *regs, uint8_t u8size );
        int8_t process_FC3( uint16_t *regs, uint8_t u8size );
        int8_t process_FC5( uint16_t *regs, uint8_t u8size );
        int8_t process_FC6( uint16_t *regs, uint8_t u8size );
        int8_t process_FC15( uint16_t *regs, uint8_t u8size );
        int8_t process_FC16( uint16_t *regs, uint8_t u8size );
        void buildException( uint8_t u8exception ); // build exception message
    
    public:
        Modbus();
        //Modbus(uint8_t u8id, uint8_t u8serno);
    	Modbus(uint8_t u8id, HardwareSerial &_port);
        //Modbus(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin);
    	Modbus(uint8_t u8id, HardwareSerial &_port, uint8_t u8txenpin);
        Modbus(uint8_t u8id);
        void begin(long u32speed);
        //void begin(SoftwareSerial *sPort, long u32speed);
        void begin(long u32speed, uint8_t u8config);
        void begin();
        void setTimeOut( uint16_t u16timeout); //!<write communication watch-dog timer
        uint16_t getTimeOut(); //!<get communication watch-dog timer value
        boolean getTimeOutState(); //!<get communication watch-dog timer state
        int8_t query( modbus_t telegram ); //!<only for master
        int8_t poll(); //!<cyclic poll for master
        int8_t poll( uint16_t *regs, uint8_t u8size ); //!<cyclic poll for slave
        uint16_t getInCnt(); //!<number of incoming messages
        uint16_t getOutCnt(); //!<number of outcoming messages
        uint16_t getErrCnt(); //!<error counter
        uint8_t getID(); //!<get slave ID between 1 and 247
        uint8_t getState();
        uint8_t getLastError(); //!<get last error message
        void setID( uint8_t u8id ); //!<write new ID for the slave
        void end(); //!<finish any communication and release serial communication port
    };
    
    /* _____PUBLIC FUNCTIONS_____________________________________________________ */
    
    /**
     * @brief
     * Default Constructor for Master through Serial
     *
     * @ingroup setup
     */
    Modbus::Modbus()
    {
        init(0, Serial1, 0);
    }
    
    /**
     * @brief
     * Full constructor for a Master/Slave through USB/RS232C
     *
     * @param u8id   node address 0=master, 1..247=slave
     * @param u8serno  serial port used 0..3
     * @ingroup setup
     * @overload Modbus::Modbus(uint8_t u8id, uint8_t u8serno)
     * @overload Modbus::Modbus(uint8_t u8id)
     * @overload Modbus::Modbus()
     */
    /*
    Modbus::Modbus(uint8_t u8id, uint8_t u8serno)
    {
        init(u8id, u8serno, 0);
    }
    */
    Modbus::Modbus(uint8_t u8id, HardwareSerial &_port)
    {
        init(u8id, _port, 0);
    }
    
    /**
     * @brief
     * Full constructor for a Master/Slave through USB/RS232C/RS485
     * It needs a pin for flow control only for RS485 mode
     *
     * @param u8id   node address 0=master, 1..247=slave
     * @param u8serno  serial port used 0..3
     * @param u8txenpin pin for txen RS-485 (=0 means USB/RS232C mode)
     * @ingroup setup
     * @overload Modbus::Modbus(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin)
     * @overload Modbus::Modbus(uint8_t u8id)
     * @overload Modbus::Modbus()
     */
     /*
    Modbus::Modbus(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin)
    {
        init(u8id, u8serno, u8txenpin);
    }
    */
    Modbus::Modbus(uint8_t u8id, HardwareSerial &_port, uint8_t u8txenpin)
    {
        init(u8id, _port, u8txenpin);
    }
    
    /**
     * @brief
     * Constructor for a Master/Slave through USB/RS232C via software serial
     * This constructor only specifies u8id (node address) and should be only
     * used if you want to use software serial instead of hardware serial.
     * If you use this constructor you have to begin ModBus object by
     * using "void Modbus::begin(SoftwareSerial *softPort, long u32speed)".
     *
     * @param u8id   node address 0=master, 1..247=slave
     * @ingroup setup
     * @overload Modbus::Modbus(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin)
     * @overload Modbus::Modbus(uint8_t u8id, uint8_t u8serno)
     * @overload Modbus::Modbus()
     */
    Modbus::Modbus(uint8_t u8id)
    {
        init(u8id);
    }
    
    /**
     * @brief
     * Initialize class object.
     *
     * Sets up the serial port using specified baud rate.
     * Call once class has been instantiated, typically within setup().
     *
     * @see http://arduino.cc/en/Serial/Begin#.Uy4CJ6aKlHY
     * @param speed   baud rate, in standard increments (300..115200)
     * @ingroup setup
     */
    /*void Modbus::begin(long u32speed)
    {
    
    	
        switch( u8serno )
        {
    #if defined(UBRR1H)
        case 1:
            port = &Serial1;
            break;
    #endif
    
    #if defined(UBRR2H)
        case 2:
            port = &Serial2;
            break;
    #endif
    
    #if defined(UBRR3H)
        case 3:
            port = &Serial3;
            break;
    #endif
        case 0:
        default:
            port = &Serial;
            break;
        }
    
        port->begin(u32speed);
        if (u8txenpin > 1)   // pin 0 & pin 1 are reserved for RX/TX
        {
            // return RS485 transceiver to transmit mode
            pinMode(u8txenpin, OUTPUT);
            digitalWrite(u8txenpin, LOW);
        }
    
        while(port->read() >= 0);
        u8lastRec = u8BufferSize = 0;
        u16InCnt = u16OutCnt = u16errCnt = 0;
    }
    */
    void Modbus::begin(long u32speed)
    {
    	_port.begin(u32speed);
    	//return RS485 transceiver to transmit mode
    	pinMode(u8txenpin, OUTPUT);
        digitalWrite(u8txenpin, LOW);
    	while(_port.read() >= 0);
        u8lastRec = u8BufferSize = 0;
        u16InCnt = u16OutCnt = u16errCnt = 0;
    }
    
    /**
     * @brief
     * Initialize class object.
     *
     * Sets up the software serial port using specified baud rate and SoftwareSerial object.
     * Call once class has been instantiated, typically within setup().
     *
     * @param speed   *softPort, pointer to SoftwareSerial class object
     * @param speed   baud rate, in standard increments (300..115200)
     * @ingroup setup
     */
    /*
    void Modbus::begin(SoftwareSerial *sPort, long u32speed)
    {
    
        softPort=sPort;
    
        softPort->begin(u32speed);
    
        if (u8txenpin > 1)   // pin 0 & pin 1 are reserved for RX/TX
        {
            // return RS485 transceiver to transmit mode
            pinMode(u8txenpin, OUTPUT);
            digitalWrite(u8txenpin, LOW);
        }
    
        while(softPort->read() >= 0);
        u8lastRec = u8BufferSize = 0;
        u16InCnt = u16OutCnt = u16errCnt = 0;
    }
    */
    
    /**
     * @brief
     * Initialize class object.
     *
     * Sets up the serial port using specified baud rate.
     * Call once class has been instantiated, typically within setup().
     *
     * @see http://arduino.cc/en/Serial/Begin#.Uy4CJ6aKlHY
     * @param speed   baud rate, in standard increments (300..115200)
     * @param config  data frame settings (data length, parity and stop bits)
     * @ingroup setup
     */
    /*void Modbus::begin(long u32speed,uint8_t u8config)
    {
    
        switch( u8serno )
        {
    #if defined(UBRR1H)
        case 1:
            port = &Serial1;
            break;
    #endif
    
    #if defined(UBRR2H)
        case 2:
            port = &Serial2;
            break;
    #endif
    
    #if defined(UBRR3H)
        case 3:
            port = &Serial3;
            break;
    #endif
        case 0:
        default:
            port = &Serial;
            break;
        }
    
        port->begin(u32speed, u8config);
        if (u8txenpin > 1)   // pin 0 & pin 1 are reserved for RX/TX
        {
            // return RS485 transceiver to transmit mode
            pinMode(u8txenpin, OUTPUT);
            digitalWrite(u8txenpin, LOW);
        }
    
        while(port->read() >= 0);
        u8lastRec = u8BufferSize = 0;
        u16InCnt = u16OutCnt = u16errCnt = 0;
    }*/
    void Modbus::begin(long u32speed, uint8_t u8config)
    {
    	_port.begin(u32speed, u8config);
    	//return RS485 transceiver to transmit mode
    	pinMode(u8txenpin, OUTPUT);
        digitalWrite(u8txenpin, LOW);
    	while(_port.read() >= 0);
        u8lastRec = u8BufferSize = 0;
        u16InCnt = u16OutCnt = u16errCnt = 0;
    }
    
    /**
     * @brief
     * Initialize default class object.
     *
     * Sets up the serial port using 19200 baud.
     * Call once class has been instantiated, typically within setup().
     *
     * @overload Modbus::begin(uint16_t u16BaudRate)
     * @ingroup setup
     */
    void Modbus::begin()
    {
        begin(19200);
    }
    
    /**
     * @brief
     * Method to write a new slave ID address
     *
     * @param 	u8id	new slave address between 1 and 247
     * @ingroup setup
     */
    void Modbus::setID( uint8_t u8id)
    {
        if (( u8id != 0) && (u8id <= 247))
        {
            this->u8id = u8id;
        }
    }
    
    /**
     * @brief
     * Method to read current slave ID address
     *
     * @return u8id	current slave address between 1 and 247
     * @ingroup setup
     */
    uint8_t Modbus::getID()
    {
        return this->u8id;
    }
    
    /**
     * @brief
     * Initialize time-out parameter
     *
     * Call once class has been instantiated, typically within setup().
     * The time-out timer is reset each time that there is a successful communication
     * between Master and Slave. It works for both.
     *
     * @param time-out value (ms)
     * @ingroup setup
     */
    void Modbus::setTimeOut( uint16_t u16timeOut)
    {
        this->u16timeOut = u16timeOut;
    }
    
    /**
     * @brief
     * Return communication Watchdog state.
     * It could be usefull to reset outputs if the watchdog is fired.
     *
     * @return TRUE if millis() > u32timeOut
     * @ingroup loop
     */
    boolean Modbus::getTimeOutState()
    {
        return (millis() > u32timeOut);
    }
    
    /**
     * @brief
     * Get input messages counter value
     * This can be useful to diagnose communication
     *
     * @return input messages counter
     * @ingroup buffer
     */
    uint16_t Modbus::getInCnt()
    {
        return u16InCnt;
    }
    
    /**
     * @brief
     * Get transmitted messages counter value
     * This can be useful to diagnose communication
     *
     * @return transmitted messages counter
     * @ingroup buffer
     */
    uint16_t Modbus::getOutCnt()
    {
        return u16OutCnt;
    }
    
    /**
     * @brief
     * Get errors counter value
     * This can be useful to diagnose communication
     *
     * @return errors counter
     * @ingroup buffer
     */
    uint16_t Modbus::getErrCnt()
    {
        return u16errCnt;
    }
    
    /**
     * Get modbus master state
     *
     * @return = 0 IDLE, = 1 WAITING FOR ANSWER
     * @ingroup buffer
     */
    uint8_t Modbus::getState()
    {
        return u8state;
    }
    
    /**
     * Get the last error in the protocol processor
     *
     * @returnreturn   NO_REPLY = 255      Time-out
     * @return   EXC_FUNC_CODE = 1   Function code not available
     * @return   EXC_ADDR_RANGE = 2  Address beyond available space for Modbus registers
     * @return   EXC_REGS_QUANT = 3  Coils or registers number beyond the available space
     * @ingroup buffer
     */
    uint8_t Modbus::getLastError()
    {
        return u8lastError;
    }
    
    /**
     * @brief
     * *** Only Modbus Master ***
     * Generate a query to an slave with a modbus_t telegram structure
     * The Master must be in COM_IDLE mode. After it, its state would be COM_WAITING.
     * This method has to be called only in loop() section.
     *
     * @see modbus_t
     * @param modbus_t  modbus telegram structure (id, fct, ...)
     * @ingroup loop
     * @todo finish function 15
     */
    int8_t Modbus::query( modbus_t telegram )
    {
        uint8_t u8regsno, u8bytesno;
        if (u8id!=0) return -2;
        if (u8state != COM_IDLE) return -1;
    
        if ((telegram.u8id==0) || (telegram.u8id>247)) return -3;
    
        au16regs = telegram.au16reg;
    
        // telegram header
        au8Buffer[ ID ]         = telegram.u8id;
        au8Buffer[ FUNC ]       = telegram.u8fct;
        au8Buffer[ ADD_HI ]     = highByte(telegram.u16RegAdd );
        au8Buffer[ ADD_LO ]     = lowByte( telegram.u16RegAdd );
    
        switch( telegram.u8fct )
        {
        case MB_FC_READ_COILS:
        case MB_FC_READ_DISCRETE_INPUT:
        case MB_FC_READ_REGISTERS:
        case MB_FC_READ_INPUT_REGISTER:
            au8Buffer[ NB_HI ]      = highByte(telegram.u16CoilsNo );
            au8Buffer[ NB_LO ]      = lowByte( telegram.u16CoilsNo );
            u8BufferSize = 6;
            break;
        case MB_FC_WRITE_COIL:
            au8Buffer[ NB_HI ]      = ((au16regs[0] > 0) ? 0xff : 0);
            au8Buffer[ NB_LO ]      = 0;
            u8BufferSize = 6;
            break;
        case MB_FC_WRITE_REGISTER:
            au8Buffer[ NB_HI ]      = highByte(au16regs[0]);
            au8Buffer[ NB_LO ]      = lowByte(au16regs[0]);
            u8BufferSize = 6;
            break;
        case MB_FC_WRITE_MULTIPLE_COILS: // TODO: implement "sending coils"
            u8regsno = telegram.u16CoilsNo / 16;
            u8bytesno = u8regsno * 2;
            if ((telegram.u16CoilsNo % 16) != 0)
            {
                u8bytesno++;
                u8regsno++;
            }
    
            au8Buffer[ NB_HI ]      = highByte(telegram.u16CoilsNo );
            au8Buffer[ NB_LO ]      = lowByte( telegram.u16CoilsNo );
            au8Buffer[ NB_LO+1 ]    = u8bytesno;
            u8BufferSize = 7;
    
            u8regsno = u8bytesno = 0; // now auxiliary registers
            for (uint16_t i = 0; i < telegram.u16CoilsNo; i++)
            {
    
    
            }
            break;
    
        case MB_FC_WRITE_MULTIPLE_REGISTERS:
            au8Buffer[ NB_HI ]      = highByte(telegram.u16CoilsNo );
            au8Buffer[ NB_LO ]      = lowByte( telegram.u16CoilsNo );
            au8Buffer[ NB_LO+1 ]    = (uint8_t) ( telegram.u16CoilsNo * 2 );
            u8BufferSize = 7;
    
            for (uint16_t i=0; i< telegram.u16CoilsNo; i++)
            {
                au8Buffer[ u8BufferSize ] = highByte( au16regs[ i ] );
                u8BufferSize++;
                au8Buffer[ u8BufferSize ] = lowByte( au16regs[ i ] );
                u8BufferSize++;
            }
            break;
        }
    
        sendTxBuffer();
        u8state = COM_WAITING;
        return 0;
    }
    
    /**
     * @brief *** Only for Modbus Master ***
     * This method checks if there is any incoming answer if pending.
     * If there is no answer, it would change Master state to COM_IDLE.
     * This method must be called only at loop section.
     * Avoid any delay() function.
     *
     * Any incoming data would be redirected to au16regs pointer,
     * as defined in its modbus_t query telegram.
     *
     * @params	nothing
     * @return errors counter
     * @ingroup loop
     */
    int8_t Modbus::poll()
    {
        // check if there is any incoming frame
    	uint8_t u8current;
        /*
    	if(u8serno<4)
            u8current = port->available();
        else
            u8current = softPort->available();
    	*/
    	u8current = _port.available();
    
        if (millis() > u32timeOut)
        {
            u8state = COM_IDLE;
            u8lastError = NO_REPLY;
            u16errCnt++;
            return 0;
        }
    
        if (u8current == 0) return 0;
    
        // check T35 after frame end or still no frame end
        if (u8current != u8lastRec)
        {
            u8lastRec = u8current;
            u32time = millis() + T35;
            return 0;
        }
        if (millis() < u32time) return 0;
    
        // transfer Serial buffer frame to auBuffer
        u8lastRec = 0;
        int8_t i8state = getRxBuffer();
        if (i8state < 7)
        {
            u8state = COM_IDLE;
            u16errCnt++;
            return i8state;
        }
    
        // validate message: id, CRC, FCT, exception
        uint8_t u8exception = validateAnswer();
        if (u8exception != 0)
        {
            u8state = COM_IDLE;
            return u8exception;
        }
    
        // process answer
        switch( au8Buffer[ FUNC ] )
        {
        case MB_FC_READ_COILS:
        case MB_FC_READ_DISCRETE_INPUT:
            // call get_FC1 to transfer the incoming message to au16regs buffer
            get_FC1( );
            break;
        case MB_FC_READ_INPUT_REGISTER:
        case MB_FC_READ_REGISTERS :
            // call get_FC3 to transfer the incoming message to au16regs buffer
            get_FC3( );
            break;
        case MB_FC_WRITE_COIL:
        case MB_FC_WRITE_REGISTER :
        case MB_FC_WRITE_MULTIPLE_COILS:
        case MB_FC_WRITE_MULTIPLE_REGISTERS :
            // nothing to do
            break;
        default:
            break;
        }
        u8state = COM_IDLE;
        return u8BufferSize;
    }
    
    /**
     * @brief
     * *** Only for Modbus Slave ***
     * This method checks if there is any incoming query
     * Afterwards, it would shoot a validation routine plus a register query
     * Avoid any delay() function !!!!
     * After a successful frame between the Master and the Slave, the time-out timer is reset.
     *
     * @param *regs  register table for communication exchange
     * @param u8size  size of the register table
     * @return 0 if no query, 1..4 if communication error, >4 if correct query processed
     * @ingroup loop
     */
    int8_t Modbus::poll( uint16_t *regs, uint8_t u8size )
    {
    
        au16regs = regs;
        u8regsize = u8size;
    	uint8_t u8current;
    
    
        // check if there is any incoming frame
        /*
    	if(u8serno<4)
            u8current = port->available();
        else
            u8current = softPort->available();
    	*/
    	u8current = _port.available();
    
        if (u8current == 0) return 0;
    
        // check T35 after frame end or still no frame end
        if (u8current != u8lastRec)
        {
            u8lastRec = u8current;
            u32time = millis() + T35;
            return 0;
        }
        if (millis() < u32time) return 0;
    
        u8lastRec = 0;
        int8_t i8state = getRxBuffer();
        u8lastError = i8state;
        if (i8state < 7) return i8state;
    
        // check slave id
        if (au8Buffer[ ID ] != u8id) return 0;
    
        // validate message: CRC, FCT, address and size
        uint8_t u8exception = validateRequest();
        if (u8exception > 0)
        {
            if (u8exception != NO_REPLY)
            {
                buildException( u8exception );
                sendTxBuffer();
            }
            u8lastError = u8exception;
            return u8exception;
        }
    
        u32timeOut = millis() + long(u16timeOut);
        u8lastError = 0;
    
        // process message
        switch( au8Buffer[ FUNC ] )
        {
        case MB_FC_READ_COILS:
        case MB_FC_READ_DISCRETE_INPUT:
            return process_FC1( regs, u8size );
            break;
        case MB_FC_READ_INPUT_REGISTER:
        case MB_FC_READ_REGISTERS :
            return process_FC3( regs, u8size );
            break;
        case MB_FC_WRITE_COIL:
            return process_FC5( regs, u8size );
            break;
        case MB_FC_WRITE_REGISTER :
            return process_FC6( regs, u8size );
            break;
        case MB_FC_WRITE_MULTIPLE_COILS:
            return process_FC15( regs, u8size );
            break;
        case MB_FC_WRITE_MULTIPLE_REGISTERS :
            return process_FC16( regs, u8size );
            break;
        default:
            break;
        }
        return i8state;
    }
    
    /* _____PRIVATE FUNCTIONS_____________________________________________________ */
    
    void Modbus::init(uint8_t u8id, HardwareSerial &_port, uint8_t u8txenpin)
    {
        this->u8id = u8id;
        //this->u8serno = (u8serno > 3) ? 0 : u8serno;
    	this->_port = _port;
        this->u8txenpin = u8txenpin;
        this->u16timeOut = 1000;
    }
    
    void Modbus::init(uint8_t u8id)
    {
        this->u8id = u8id;
        //this->u8serno = 4;
    	this->_port = Serial1;
        this->u8txenpin = 0;
        this->u16timeOut = 1000;
    }
    
    /**
     * @brief
     * This method moves Serial buffer data to the Modbus au8Buffer.
     *
     * @return buffer size if OK, ERR_BUFF_OVERFLOW if u8BufferSize >= MAX_BUFFER
     * @ingroup buffer
     */
    int8_t Modbus::getRxBuffer()
    {
        boolean bBuffOverflow = false;
    
        //if (u8txenpin > 1) digitalWrite( u8txenpin, LOW );
    	digitalWrite(u8txenpin, LOW);
    
        u8BufferSize = 0;
    	/*
        if(u8serno<4)
            while ( port->available() )
            {
                au8Buffer[ u8BufferSize ] = port->read();
                u8BufferSize ++;
    
                if (u8BufferSize >= MAX_BUFFER) bBuffOverflow = true;
            }
        else
            while ( softPort->available() )
            {
                au8Buffer[ u8BufferSize ] = softPort->read();
                u8BufferSize ++;
    
                if (u8BufferSize >= MAX_BUFFER) bBuffOverflow = true;
            }
    	*/
    	while (_port.available())
    	{
    		au8Buffer[ u8BufferSize ] = _port.read();
            u8BufferSize ++;
    		if (u8BufferSize >= MAX_BUFFER) bBuffOverflow = true;
    	}
        u16InCnt++;
    
        if (bBuffOverflow)
        {
            u16errCnt++;
            return ERR_BUFF_OVERFLOW;
        }
        return u8BufferSize;
    }
    
    /**
     * @brief
     * This method transmits au8Buffer to Serial line.
     * Only if u8txenpin != 0, there is a flow handling in order to keep
     * the RS485 transceiver in output state as long as the message is being sent.
     * This is done with UCSRxA register.
     * The CRC is appended to the buffer before starting to send it.
     *
     * @param nothing
     * @return nothing
     * @ingroup buffer
     */
    void Modbus::sendTxBuffer()
    {
        uint8_t i = 0;
    
        // append CRC to message
        uint16_t u16crc = calcCRC( u8BufferSize );
        au8Buffer[ u8BufferSize ] = u16crc >> 8;
        u8BufferSize++;
        au8Buffer[ u8BufferSize ] = u16crc & 0x00ff;
        u8BufferSize++;
    
        // set RS485 transceiver to transmit mode
        /*if (u8txenpin > 1)
        {
            switch( u8serno )
            {
    #if defined(UBRR1H)
            case 1:
                UCSR1A=UCSR1A |(1 << TXC1);
                break;
    #endif
    
    #if defined(UBRR2H)
            case 2:
                UCSR2A=UCSR2A |(1 << TXC2);
                break;
    #endif
    
    #if defined(UBRR3H)
            case 3:
                UCSR3A=UCSR3A |(1 << TXC3);
                break;
    #endif
            case 0:
            default:
                UCSR0A=UCSR0A |(1 << TXC0);
                break;
            }
            digitalWrite( u8txenpin, HIGH );
        }
    	*/
    	digitalWrite( u8txenpin, HIGH );
    
        // transfer buffer to serial line
        /*
    	if(u8serno<4)
            port->write( au8Buffer, u8BufferSize );
        else
            softPort->write( au8Buffer, u8BufferSize );
    	*/
    	_port.write(au8Buffer, u8BufferSize);
    
        // keep RS485 transceiver in transmit mode as long as sending
    	/*
        if (u8txenpin > 1)
        {
            switch( u8serno )
            {
    #if defined(UBRR1H)
            case 1:
                while (!(UCSR1A & (1 << TXC1)));
                break;
    #endif
    
    #if defined(UBRR2H)
            case 2:
                while (!(UCSR2A & (1 << TXC2)));
                break;
    #endif
    
    #if defined(UBRR3H)
            case 3:
                while (!(UCSR3A & (1 << TXC3)));
                break;
    #endif
            case 0:
            default:
                while (!(UCSR0A & (1 << TXC0)));
                break;
            }
    
            // return RS485 transceiver to receive mode
            digitalWrite( u8txenpin, LOW );
        }*/
    	_port.flush(); //Wait until send complete
    	digitalWrite( u8txenpin, LOW );
    
        /*
    	if(u8serno<4)
            while(port->read() >= 0);
        else
            while(softPort->read() >= 0);
    	*/
    	while(_port.read()>=0);
    
        u8BufferSize = 0;
    
        // set time-out for master
        u32timeOut = millis() + (unsigned long) u16timeOut;
    
        // increase message counter
        u16OutCnt++;
    }
    
    /**
     * @brief
     * This method calculates CRC
     *
     * @return uint16_t calculated CRC value for the message
     * @ingroup buffer
     */
    uint16_t Modbus::calcCRC(uint8_t u8length)
    {
        unsigned int temp, temp2, flag;
        temp = 0xFFFF;
        for (unsigned char i = 0; i < u8length; i++)
        {
            temp = temp ^ au8Buffer[i];
            for (unsigned char j = 1; j <= 8; j++)
            {
                flag = temp & 0x0001;
                temp >>=1;
                if (flag)
                    temp ^= 0xA001;
            }
        }
        // Reverse byte order.
        temp2 = temp >> 8;
        temp = (temp << 8) | temp2;
        temp &= 0xFFFF;
        // the returned value is already swapped
        // crcLo byte is first & crcHi byte is last
        return temp;
    }
    
    /**
     * @brief
     * This method validates slave incoming messages
     *
     * @return 0 if OK, EXCEPTION if anything fails
     * @ingroup buffer
     */
    uint8_t Modbus::validateRequest()
    {
        // check message crc vs calculated crc
        uint16_t u16MsgCRC =
            ((au8Buffer[u8BufferSize - 2] << 8)
             | au8Buffer[u8BufferSize - 1]); // combine the crc Low & High bytes
        if ( calcCRC( u8BufferSize-2 ) != u16MsgCRC )
        {
            u16errCnt ++;
            return NO_REPLY;
        }
    
        // check fct code
        boolean isSupported = false;
        for (uint8_t i = 0; i< sizeof( fctsupported ); i++)
        {
            if (fctsupported[i] == au8Buffer[FUNC])
            {
                isSupported = 1;
                break;
            }
        }
        if (!isSupported)
        {
            u16errCnt ++;
            return EXC_FUNC_CODE;
        }
    
        // check start address & nb range
        uint16_t u16regs = 0;
        uint8_t u8regs;
        switch ( au8Buffer[ FUNC ] )
        {
        case MB_FC_READ_COILS:
        case MB_FC_READ_DISCRETE_INPUT:
        case MB_FC_WRITE_MULTIPLE_COILS:
            u16regs = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ]) / 16;
            u16regs += word( au8Buffer[ NB_HI ], au8Buffer[ NB_LO ]) /16;
            u8regs = (uint8_t) u16regs;
            if (u8regs > u8regsize) return EXC_ADDR_RANGE;
            break;
        case MB_FC_WRITE_COIL:
            u16regs = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ]) / 16;
            u8regs = (uint8_t) u16regs;
            if (u8regs > u8regsize) return EXC_ADDR_RANGE;
            break;
        case MB_FC_WRITE_REGISTER :
            u16regs = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ]);
            u8regs = (uint8_t) u16regs;
            if (u8regs > u8regsize) return EXC_ADDR_RANGE;
            break;
        case MB_FC_READ_REGISTERS :
        case MB_FC_READ_INPUT_REGISTER :
        case MB_FC_WRITE_MULTIPLE_REGISTERS :
            u16regs = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ]);
            u16regs += word( au8Buffer[ NB_HI ], au8Buffer[ NB_LO ]);
            u8regs = (uint8_t) u16regs;
            if (u8regs > u8regsize) return EXC_ADDR_RANGE;
            break;
        }
        return 0; // OK, no exception code thrown
    }
    
    /**
     * @brief
     * This method validates master incoming messages
     *
     * @return 0 if OK, EXCEPTION if anything fails
     * @ingroup buffer
     */
    uint8_t Modbus::validateAnswer()
    {
        // check message crc vs calculated crc
        uint16_t u16MsgCRC =
            ((au8Buffer[u8BufferSize - 2] << 8)
             | au8Buffer[u8BufferSize - 1]); // combine the crc Low & High bytes
        if ( calcCRC( u8BufferSize-2 ) != u16MsgCRC )
        {
            u16errCnt ++;
            return NO_REPLY;
        }
    
        // check exception
        if ((au8Buffer[ FUNC ] & 0x80) != 0)
        {
            u16errCnt ++;
            return ERR_EXCEPTION;
        }
    
        // check fct code
        boolean isSupported = false;
        for (uint8_t i = 0; i< sizeof( fctsupported ); i++)
        {
            if (fctsupported[i] == au8Buffer[FUNC])
            {
                isSupported = 1;
                break;
            }
        }
        if (!isSupported)
        {
            u16errCnt ++;
            return EXC_FUNC_CODE;
        }
    
        return 0; // OK, no exception code thrown
    }
    
    /**
     * @brief
     * This method builds an exception message
     *
     * @ingroup buffer
     */
    void Modbus::buildException( uint8_t u8exception )
    {
        uint8_t u8func = au8Buffer[ FUNC ];  // get the original FUNC code
    
        au8Buffer[ ID ]      = u8id;
        au8Buffer[ FUNC ]    = u8func + 0x80;
        au8Buffer[ 2 ]       = u8exception;
        u8BufferSize         = EXCEPTION_SIZE;
    }
    
    /**
     * This method processes functions 1 & 2 (for master)
     * This method puts the slave answer into master data buffer
     *
     * @ingroup register
     * TODO: finish its implementation
     */
    void Modbus::get_FC1()
    {
        uint8_t u8byte, i;
        u8byte = 0;
    
        //  for (i=0; i< au8Buffer[ 2 ] /2; i++) {
        //    au16regs[ i ] = word(
        //    au8Buffer[ u8byte ],
        //    au8Buffer[ u8byte +1 ]);
        //    u8byte += 2;
        //  }
    }
    
    /**
     * This method processes functions 3 & 4 (for master)
     * This method puts the slave answer into master data buffer
     *
     * @ingroup register
     */
    void Modbus::get_FC3()
    {
        uint8_t u8byte, i;
        u8byte = 3;
    
        for (i=0; i< au8Buffer[ 2 ] /2; i++)
        {
            au16regs[ i ] = word(
                                au8Buffer[ u8byte ],
                                au8Buffer[ u8byte +1 ]);
            u8byte += 2;
        }
    }
    
    /**
     * @brief
     * This method processes functions 1 & 2
     * This method reads a bit array and transfers it to the master
     *
     * @return u8BufferSize Response to master length
     * @ingroup discrete
     */
    int8_t Modbus::process_FC1( uint16_t *regs, uint8_t u8size )
    {
        uint8_t u8currentRegister, u8currentBit, u8bytesno, u8bitsno;
        uint8_t u8CopyBufferSize;
        uint16_t u16currentCoil, u16coil;
    
        // get the first and last coil from the message
        uint16_t u16StartCoil = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ] );
        uint16_t u16Coilno = word( au8Buffer[ NB_HI ], au8Buffer[ NB_LO ] );
    
        // put the number of bytes in the outcoming message
        u8bytesno = (uint8_t) (u16Coilno / 8);
        if (u16Coilno % 8 != 0) u8bytesno ++;
        au8Buffer[ ADD_HI ]  = u8bytesno;
        u8BufferSize         = ADD_LO;
    
        // read each coil from the register map and put its value inside the outcoming message
        u8bitsno = 0;
    
        for (u16currentCoil = 0; u16currentCoil < u16Coilno; u16currentCoil++)
        {
            u16coil = u16StartCoil + u16currentCoil;
            u8currentRegister = (uint8_t) (u16coil / 16);
            u8currentBit = (uint8_t) (u16coil % 16);
    
            bitWrite(
                au8Buffer[ u8BufferSize ],
                u8bitsno,
                bitRead( regs[ u8currentRegister ], u8currentBit ) );
            u8bitsno ++;
    
            if (u8bitsno > 7)
            {
                u8bitsno = 0;
                u8BufferSize++;
            }
        }
    
        // send outcoming message
        if (u16Coilno % 8 != 0) u8BufferSize ++;
        u8CopyBufferSize = u8BufferSize +2;
        sendTxBuffer();
        return u8CopyBufferSize;
    }
    
    /**
     * @brief
     * This method processes functions 3 & 4
     * This method reads a word array and transfers it to the master
     *
     * @return u8BufferSize Response to master length
     * @ingroup register
     */
    int8_t Modbus::process_FC3( uint16_t *regs, uint8_t u8size )
    {
    
        uint8_t u8StartAdd = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ] );
        uint8_t u8regsno = word( au8Buffer[ NB_HI ], au8Buffer[ NB_LO ] );
        uint8_t u8CopyBufferSize;
        uint8_t i;
    
        au8Buffer[ 2 ]       = u8regsno * 2;
        u8BufferSize         = 3;
    
        for (i = u8StartAdd; i < u8StartAdd + u8regsno; i++)
        {
            au8Buffer[ u8BufferSize ] = highByte(regs[i]);
            u8BufferSize++;
            au8Buffer[ u8BufferSize ] = lowByte(regs[i]);
            u8BufferSize++;
        }
        u8CopyBufferSize = u8BufferSize +2;
        sendTxBuffer();
    
        return u8CopyBufferSize;
    }
    
    /**
     * @brief
     * This method processes function 5
     * This method writes a value assigned by the master to a single bit
     *
     * @return u8BufferSize Response to master length
     * @ingroup discrete
     */
    int8_t Modbus::process_FC5( uint16_t *regs, uint8_t u8size )
    {
        uint8_t u8currentRegister, u8currentBit;
        uint8_t u8CopyBufferSize;
        uint16_t u16coil = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ] );
    
        // point to the register and its bit
        u8currentRegister = (uint8_t) (u16coil / 16);
        u8currentBit = (uint8_t) (u16coil % 16);
    
        // write to coil
        bitWrite(
            regs[ u8currentRegister ],
            u8currentBit,
            au8Buffer[ NB_HI ] == 0xff );
    
    
        // send answer to master
        u8BufferSize = 6;
        u8CopyBufferSize = u8BufferSize +2;
        sendTxBuffer();
    
        return u8CopyBufferSize;
    }
    
    /**
     * @brief
     * This method processes function 6
     * This method writes a value assigned by the master to a single word
     *
     * @return u8BufferSize Response to master length
     * @ingroup register
     */
    int8_t Modbus::process_FC6( uint16_t *regs, uint8_t u8size )
    {
    
        uint8_t u8add = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ] );
        uint8_t u8CopyBufferSize;
        uint16_t u16val = word( au8Buffer[ NB_HI ], au8Buffer[ NB_LO ] );
    
        regs[ u8add ] = u16val;
    
        // keep the same header
        u8BufferSize         = RESPONSE_SIZE;
    
        u8CopyBufferSize = u8BufferSize +2;
        sendTxBuffer();
    
        return u8CopyBufferSize;
    }
    
    /**
     * @brief
     * This method processes function 15
     * This method writes a bit array assigned by the master
     *
     * @return u8BufferSize Response to master length
     * @ingroup discrete
     */
    int8_t Modbus::process_FC15( uint16_t *regs, uint8_t u8size )
    {
        uint8_t u8currentRegister, u8currentBit, u8frameByte, u8bitsno;
        uint8_t u8CopyBufferSize;
        uint16_t u16currentCoil, u16coil;
        boolean bTemp;
    
        // get the first and last coil from the message
        uint16_t u16StartCoil = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ] );
        uint16_t u16Coilno = word( au8Buffer[ NB_HI ], au8Buffer[ NB_LO ] );
    
    
        // read each coil from the register map and put its value inside the outcoming message
        u8bitsno = 0;
        u8frameByte = 7;
        for (u16currentCoil = 0; u16currentCoil < u16Coilno; u16currentCoil++)
        {
    
            u16coil = u16StartCoil + u16currentCoil;
            u8currentRegister = (uint8_t) (u16coil / 16);
            u8currentBit = (uint8_t) (u16coil % 16);
    
            bTemp = bitRead(
                        au8Buffer[ u8frameByte ],
                        u8bitsno );
    
            bitWrite(
                regs[ u8currentRegister ],
                u8currentBit,
                bTemp );
    
            u8bitsno ++;
    
            if (u8bitsno > 7)
            {
                u8bitsno = 0;
                u8frameByte++;
            }
        }
    
        // send outcoming message
        // it's just a copy of the incomping frame until 6th byte
        u8BufferSize         = 6;
        u8CopyBufferSize = u8BufferSize +2;
        sendTxBuffer();
        return u8CopyBufferSize;
    }
    
    /**
     * @brief
     * This method processes function 16
     * This method writes a word array assigned by the master
     *
     * @return u8BufferSize Response to master length
     * @ingroup register
     */
    int8_t Modbus::process_FC16( uint16_t *regs, uint8_t u8size )
    {
        uint8_t u8func = au8Buffer[ FUNC ];  // get the original FUNC code
        uint8_t u8StartAdd = au8Buffer[ ADD_HI ] << 8 | au8Buffer[ ADD_LO ];
        uint8_t u8regsno = au8Buffer[ NB_HI ] << 8 | au8Buffer[ NB_LO ];
        uint8_t u8CopyBufferSize;
        uint8_t i;
        uint16_t temp;
    
        // build header
        au8Buffer[ NB_HI ]   = 0;
        au8Buffer[ NB_LO ]   = u8regsno;
        u8BufferSize         = RESPONSE_SIZE;
    
        // write registers
        for (i = 0; i < u8regsno; i++)
        {
            temp = word(
                       au8Buffer[ (BYTE_CNT + 1) + i * 2 ],
                       au8Buffer[ (BYTE_CNT + 2) + i * 2 ]);
    
            regs[ u8StartAdd + i ] = temp;
        }
        u8CopyBufferSize = u8BufferSize +2;
        sendTxBuffer();
    
        return u8CopyBufferSize;
    }
    Last edited by Ben; 01-14-2018 at 09:44 PM. Reason: add modified file

  11. #11
    Senior Member Epyon's Avatar
    Join Date
    Apr 2013
    Location
    Belgium
    Posts
    443
    I actually developed a more hardware agnostic version of the library for internal use at my workplace. I was thinking about putting it on Github, but never got around cleaning up the code. So I'm willing to cooperate on this, but my free time is however limited these days .

  12. #12
    Senior Member Ben's Avatar
    Join Date
    Jul 2013
    Location
    Germany
    Posts
    401
    Sounds good, would you paste your code here so I can have a look?

  13. #13
    Senior Member Epyon's Avatar
    Join Date
    Apr 2013
    Location
    Belgium
    Posts
    443
    The problem is that it's still integrated with proprietary code which I need to clean up first. I'm abroad for work till next week, I'll look into it afterwards.

    My ultimate goal was a merged Modbus TCP/RTU library with the same API. Eventually I settled for two libs with the same API because that just worked and I needed to get on with things . It would also be nice to completely rewrite it but under MIT license.

  14. #14
    Senior Member Ben's Avatar
    Join Date
    Jul 2013
    Location
    Germany
    Posts
    401
    Hmm that might be a too high goal for my programming skills, I'd be happy with the RTU mode working on Teensies. I'll try to test the code I posted in #10 when I have the time and maybe reintroduce SoftwareSerial support so it's backwards compatible and I can do a pull request. My main problem is that I had to choose HardwareSerial instead of a Stream object because there is no Stream::begin(). I don't care for my application though, I'll be using hardware ports. But anyway, many thanks for your offer!

  15. #15
    Senior Member
    Join Date
    Dec 2016
    Location
    Montreal, Canada
    Posts
    2,710
    if you use hardwareserial, your using stream class as hardwareserial is derived from the base class stream. regardless, you can include both as well:

    class whatever : public HardwareSerial, public Stream {

    its called multiple inheritance

  16. #16
    Senior Member Ben's Avatar
    Join Date
    Jul 2013
    Location
    Germany
    Posts
    401
    Here's a version that works for creating Modbus Masters and Slaves with a Teensy using hardware serial ports:

    Code:
    /**
     * @file 	ModbusRtu.h
     * @version     1.21
     * @date        2016.02.21
     * @author 	Samuel Marco i Armengol
     * @contact     sammarcoarmengol@gmail.com
     * @contribution Helium6072
     *
     * @description
     *  Arduino library for communicating with Modbus devices
     *  over RS232/USB/485 via RTU protocol.
     *
     *  Further information:
     *  http://modbus.org/
     *  http://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf
     *
     * @license
     *  This library is free software; you can redistribute it and/or
     *  modify it under the terms of the GNU Lesser General Public
     *  License as published by the Free Software Foundation; version
     *  2.1 of the License.
     *
     *  This library is distributed in the hope that it will be useful,
     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     *  Lesser General Public License for more details.
     *
     *  You should have received a copy of the GNU Lesser General Public
     *  License along with this library; if not, write to the Free Software
     *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     *
     * @defgroup setup Modbus Object Instantiation/Initialization
     * @defgroup loop Modbus Object Management
     * @defgroup buffer Modbus Buffer Management
     * @defgroup discrete Modbus Function Codes for Discrete Coils/Inputs
     * @defgroup register Modbus Function Codes for Holding/Input Registers
     *
     */
    
    #include <inttypes.h>
    #include "Arduino.h"
    #include "Print.h"
    #include <SoftwareSerial.h>
    
    /**
     * @struct modbus_t
     * @brief
     * Master query structure:
     * This includes all the necessary fields to make the Master generate a Modbus query.
     * A Master may keep several of these structures and send them cyclically or
     * use them according to program needs.
     */
    typedef struct
    {
        uint8_t u8id;          /*!< Slave address between 1 and 247. 0 means broadcast */
        uint8_t u8fct;         /*!< Function code: 1, 2, 3, 4, 5, 6, 15 or 16 */
        uint16_t u16RegAdd;    /*!< Address of the first register to access at slave/s */
        uint16_t u16CoilsNo;   /*!< Number of coils or registers to access */
        uint16_t *au16reg;     /*!< Pointer to memory image in master */
    }
    modbus_t;
    
    enum
    {
        RESPONSE_SIZE = 6,
        EXCEPTION_SIZE = 3,
        CHECKSUM_SIZE = 2
    };
    
    /**
     * @enum MESSAGE
     * @brief
     * Indexes to telegram frame positions
     */
    enum MESSAGE
    {
        ID                             = 0, //!< ID field
        FUNC, //!< Function code position
        ADD_HI, //!< Address high byte
        ADD_LO, //!< Address low byte
        NB_HI, //!< Number of coils or registers high byte
        NB_LO, //!< Number of coils or registers low byte
        BYTE_CNT  //!< byte counter
    };
    
    /**
     * @enum MB_FC
     * @brief
     * Modbus function codes summary.
     * These are the implement function codes either for Master or for Slave.
     *
     * @see also fctsupported
     * @see also modbus_t
     */
    enum MB_FC
    {
        MB_FC_NONE                     = 0,   /*!< null operator */
        MB_FC_READ_COILS               = 1,	/*!< FCT=1 -> read coils or digital outputs */
        MB_FC_READ_DISCRETE_INPUT      = 2,	/*!< FCT=2 -> read digital inputs */
        MB_FC_READ_REGISTERS           = 3,	/*!< FCT=3 -> read registers or analog outputs */
        MB_FC_READ_INPUT_REGISTER      = 4,	/*!< FCT=4 -> read analog inputs */
        MB_FC_WRITE_COIL               = 5,	/*!< FCT=5 -> write single coil or output */
        MB_FC_WRITE_REGISTER           = 6,	/*!< FCT=6 -> write single register */
        MB_FC_WRITE_MULTIPLE_COILS     = 15,	/*!< FCT=15 -> write multiple coils or outputs */
        MB_FC_WRITE_MULTIPLE_REGISTERS = 16	/*!< FCT=16 -> write multiple registers */
    };
    
    enum COM_STATES
    {
        COM_IDLE                     = 0,
        COM_WAITING                  = 1
    
    };
    
    enum ERR_LIST
    {
        ERR_NOT_MASTER                = -1,
        ERR_POLLING                   = -2,
        ERR_BUFF_OVERFLOW             = -3,
        ERR_BAD_CRC                   = -4,
        ERR_EXCEPTION                 = -5
    };
    
    enum
    {
        NO_REPLY = 255,
        EXC_FUNC_CODE = 1,
        EXC_ADDR_RANGE = 2,
        EXC_REGS_QUANT = 3,
        EXC_EXECUTE = 4
    };
    
    const unsigned char fctsupported[] =
    {
        MB_FC_READ_COILS,
        MB_FC_READ_DISCRETE_INPUT,
        MB_FC_READ_REGISTERS,
        MB_FC_READ_INPUT_REGISTER,
        MB_FC_WRITE_COIL,
        MB_FC_WRITE_REGISTER,
        MB_FC_WRITE_MULTIPLE_COILS,
        MB_FC_WRITE_MULTIPLE_REGISTERS
    };
    
    #define T35  5
    #define  MAX_BUFFER  64	//!< maximum size for the communication buffer in bytes
    
    /**
     * @class Modbus
     * @brief
     * Arduino class library for communicating with Modbus devices over
     * USB/RS232/485 (via RTU protocol).
     */
    class Modbus
    {
    private:
        //HardwareSerial *port; //!< Pointer to Serial class object
        //SoftwareSerial *softPort; //!< Pointer to SoftwareSerial class object
        uint8_t u8id; //!< 0=master, 1..247=slave number
        //uint8_t u8serno; //!< serial port: 0-Serial, 1..3-Serial1..Serial3; 4: use software serial
    	HardwareSerial _port;
        uint8_t u8txenpin; //!< flow control pin: 0=USB or RS-232 mode, >0=RS-485 mode
        uint8_t u8state;
        uint8_t u8lastError;
        uint8_t au8Buffer[MAX_BUFFER];
        uint8_t u8BufferSize;
        uint8_t u8lastRec;
        uint16_t *au16regs;
        uint16_t u16InCnt, u16OutCnt, u16errCnt;
        uint16_t u16timeOut;
        uint32_t u32time, u32timeOut;
        uint8_t u8regsize;
    
        //void init(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin);
    	void init(uint8_t u8id, HardwareSerial &_port, uint8_t u8txenpin);
    	void init(uint8_t u8id);
        void sendTxBuffer();
        int8_t getRxBuffer();
        uint16_t calcCRC(uint8_t u8length);
        uint8_t validateAnswer();
        uint8_t validateRequest();
        void get_FC1();
        void get_FC3();
        int8_t process_FC1( uint16_t *regs, uint8_t u8size );
        int8_t process_FC3( uint16_t *regs, uint8_t u8size );
        int8_t process_FC5( uint16_t *regs, uint8_t u8size );
        int8_t process_FC6( uint16_t *regs, uint8_t u8size );
        int8_t process_FC15( uint16_t *regs, uint8_t u8size );
        int8_t process_FC16( uint16_t *regs, uint8_t u8size );
        void buildException( uint8_t u8exception ); // build exception message
    
    public:
        Modbus();
        //Modbus(uint8_t u8id, uint8_t u8serno);
    	Modbus(uint8_t u8id, HardwareSerial &_port);
        //Modbus(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin);
    	Modbus(uint8_t u8id, HardwareSerial &_port, uint8_t u8txenpin);
        Modbus(uint8_t u8id);
        void begin(long u32speed);
        //void begin(SoftwareSerial *sPort, long u32speed);
        void begin(long u32speed, uint8_t u8config);
        void begin();
        void setTimeOut( uint16_t u16timeout); //!<write communication watch-dog timer
        uint16_t getTimeOut(); //!<get communication watch-dog timer value
        boolean getTimeOutState(); //!<get communication watch-dog timer state
        int8_t query( modbus_t telegram ); //!<only for master
        int8_t poll(); //!<cyclic poll for master
        int8_t poll( uint16_t *regs, uint8_t u8size ); //!<cyclic poll for slave
        uint16_t getInCnt(); //!<number of incoming messages
        uint16_t getOutCnt(); //!<number of outcoming messages
        uint16_t getErrCnt(); //!<error counter
        uint8_t getID(); //!<get slave ID between 1 and 247
        uint8_t getState();
        uint8_t getLastError(); //!<get last error message
        void setID( uint8_t u8id ); //!<write new ID for the slave
        void end(); //!<finish any communication and release serial communication port
    };
    
    /* _____PUBLIC FUNCTIONS_____________________________________________________ */
    
    /**
     * @brief
     * Default Constructor for Master through Serial
     *
     * @ingroup setup
     */
    Modbus::Modbus()
    {
        init(0, Serial1, 0);
    }
    
    /**
     * @brief
     * Full constructor for a Master/Slave through USB/RS232C
     *
     * @param u8id   node address 0=master, 1..247=slave
     * @param u8serno  serial port used 0..3
     * @ingroup setup
     * @overload Modbus::Modbus(uint8_t u8id, uint8_t u8serno)
     * @overload Modbus::Modbus(uint8_t u8id)
     * @overload Modbus::Modbus()
     */
    /*
    Modbus::Modbus(uint8_t u8id, uint8_t u8serno)
    {
        init(u8id, u8serno, 0);
    }
    */
    Modbus::Modbus(uint8_t u8id, HardwareSerial &_port)
    {
        init(u8id, _port, 0);
    }
    
    /**
     * @brief
     * Full constructor for a Master/Slave through USB/RS232C/RS485
     * It needs a pin for flow control only for RS485 mode
     *
     * @param u8id   node address 0=master, 1..247=slave
     * @param u8serno  serial port used 0..3
     * @param u8txenpin pin for txen RS-485 (=0 means USB/RS232C mode)
     * @ingroup setup
     * @overload Modbus::Modbus(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin)
     * @overload Modbus::Modbus(uint8_t u8id)
     * @overload Modbus::Modbus()
     */
     /*
    Modbus::Modbus(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin)
    {
        init(u8id, u8serno, u8txenpin);
    }
    */
    Modbus::Modbus(uint8_t u8id, HardwareSerial &_port, uint8_t u8txenpin)
    {
        init(u8id, _port, u8txenpin);
    }
    
    /**
     * @brief
     * Constructor for a Master/Slave through USB/RS232C via software serial
     * This constructor only specifies u8id (node address) and should be only
     * used if you want to use software serial instead of hardware serial.
     * If you use this constructor you have to begin ModBus object by
     * using "void Modbus::begin(SoftwareSerial *softPort, long u32speed)".
     *
     * @param u8id   node address 0=master, 1..247=slave
     * @ingroup setup
     * @overload Modbus::Modbus(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin)
     * @overload Modbus::Modbus(uint8_t u8id, uint8_t u8serno)
     * @overload Modbus::Modbus()
     */
    Modbus::Modbus(uint8_t u8id)
    {
        init(u8id);
    }
    
    /**
     * @brief
     * Initialize class object.
     *
     * Sets up the serial port using specified baud rate.
     * Call once class has been instantiated, typically within setup().
     *
     * @see http://arduino.cc/en/Serial/Begin#.Uy4CJ6aKlHY
     * @param speed   baud rate, in standard increments (300..115200)
     * @ingroup setup
     */
    /*void Modbus::begin(long u32speed)
    {
    
    	
        switch( u8serno )
        {
    #if defined(UBRR1H)
        case 1:
            port = &Serial1;
            break;
    #endif
    
    #if defined(UBRR2H)
        case 2:
            port = &Serial2;
            break;
    #endif
    
    #if defined(UBRR3H)
        case 3:
            port = &Serial3;
            break;
    #endif
        case 0:
        default:
            port = &Serial;
            break;
        }
    
        port->begin(u32speed);
        if (u8txenpin > 1)   // pin 0 & pin 1 are reserved for RX/TX
        {
            // return RS485 transceiver to transmit mode
            pinMode(u8txenpin, OUTPUT);
            digitalWrite(u8txenpin, LOW);
        }
    
        while(port->read() >= 0);
        u8lastRec = u8BufferSize = 0;
        u16InCnt = u16OutCnt = u16errCnt = 0;
    }
    */
    void Modbus::begin(long u32speed)
    {
    	_port.begin(u32speed);
    	//return RS485 transceiver to transmit mode
    	pinMode(u8txenpin, OUTPUT);
        digitalWrite(u8txenpin, LOW);
    	while(_port.read() >= 0);
        u8lastRec = u8BufferSize = 0;
        u16InCnt = u16OutCnt = u16errCnt = 0;
    }
    
    /**
     * @brief
     * Initialize class object.
     *
     * Sets up the software serial port using specified baud rate and SoftwareSerial object.
     * Call once class has been instantiated, typically within setup().
     *
     * @param speed   *softPort, pointer to SoftwareSerial class object
     * @param speed   baud rate, in standard increments (300..115200)
     * @ingroup setup
     */
    /*
    void Modbus::begin(SoftwareSerial *sPort, long u32speed)
    {
    
        softPort=sPort;
    
        softPort->begin(u32speed);
    
        if (u8txenpin > 1)   // pin 0 & pin 1 are reserved for RX/TX
        {
            // return RS485 transceiver to transmit mode
            pinMode(u8txenpin, OUTPUT);
            digitalWrite(u8txenpin, LOW);
        }
    
        while(softPort->read() >= 0);
        u8lastRec = u8BufferSize = 0;
        u16InCnt = u16OutCnt = u16errCnt = 0;
    }
    */
    
    /**
     * @brief
     * Initialize class object.
     *
     * Sets up the serial port using specified baud rate.
     * Call once class has been instantiated, typically within setup().
     *
     * @see http://arduino.cc/en/Serial/Begin#.Uy4CJ6aKlHY
     * @param speed   baud rate, in standard increments (300..115200)
     * @param config  data frame settings (data length, parity and stop bits)
     * @ingroup setup
     */
    /*void Modbus::begin(long u32speed,uint8_t u8config)
    {
    
        switch( u8serno )
        {
    #if defined(UBRR1H)
        case 1:
            port = &Serial1;
            break;
    #endif
    
    #if defined(UBRR2H)
        case 2:
            port = &Serial2;
            break;
    #endif
    
    #if defined(UBRR3H)
        case 3:
            port = &Serial3;
            break;
    #endif
        case 0:
        default:
            port = &Serial;
            break;
        }
    
        port->begin(u32speed, u8config);
        if (u8txenpin > 1)   // pin 0 & pin 1 are reserved for RX/TX
        {
            // return RS485 transceiver to transmit mode
            pinMode(u8txenpin, OUTPUT);
            digitalWrite(u8txenpin, LOW);
        }
    
        while(port->read() >= 0);
        u8lastRec = u8BufferSize = 0;
        u16InCnt = u16OutCnt = u16errCnt = 0;
    }*/
    void Modbus::begin(long u32speed, uint8_t u8config)
    {
    	_port.begin(u32speed, u8config);
    	//return RS485 transceiver to transmit mode
    	pinMode(u8txenpin, OUTPUT);
        digitalWrite(u8txenpin, LOW);
    	while(_port.read() >= 0);
        u8lastRec = u8BufferSize = 0;
        u16InCnt = u16OutCnt = u16errCnt = 0;
    }
    
    /**
     * @brief
     * Initialize default class object.
     *
     * Sets up the serial port using 19200 baud.
     * Call once class has been instantiated, typically within setup().
     *
     * @overload Modbus::begin(uint16_t u16BaudRate)
     * @ingroup setup
     */
    void Modbus::begin()
    {
        begin(19200);
    }
    
    /**
     * @brief
     * Method to write a new slave ID address
     *
     * @param 	u8id	new slave address between 1 and 247
     * @ingroup setup
     */
    void Modbus::setID( uint8_t u8id)
    {
        if (( u8id != 0) && (u8id <= 247))
        {
            this->u8id = u8id;
        }
    }
    
    /**
     * @brief
     * Method to read current slave ID address
     *
     * @return u8id	current slave address between 1 and 247
     * @ingroup setup
     */
    uint8_t Modbus::getID()
    {
        return this->u8id;
    }
    
    /**
     * @brief
     * Initialize time-out parameter
     *
     * Call once class has been instantiated, typically within setup().
     * The time-out timer is reset each time that there is a successful communication
     * between Master and Slave. It works for both.
     *
     * @param time-out value (ms)
     * @ingroup setup
     */
    void Modbus::setTimeOut( uint16_t u16timeOut)
    {
        this->u16timeOut = u16timeOut;
    }
    
    /**
     * @brief
     * Return communication Watchdog state.
     * It could be usefull to reset outputs if the watchdog is fired.
     *
     * @return TRUE if millis() > u32timeOut
     * @ingroup loop
     */
    boolean Modbus::getTimeOutState()
    {
        return (millis() > u32timeOut);
    }
    
    /**
     * @brief
     * Get input messages counter value
     * This can be useful to diagnose communication
     *
     * @return input messages counter
     * @ingroup buffer
     */
    uint16_t Modbus::getInCnt()
    {
        return u16InCnt;
    }
    
    /**
     * @brief
     * Get transmitted messages counter value
     * This can be useful to diagnose communication
     *
     * @return transmitted messages counter
     * @ingroup buffer
     */
    uint16_t Modbus::getOutCnt()
    {
        return u16OutCnt;
    }
    
    /**
     * @brief
     * Get errors counter value
     * This can be useful to diagnose communication
     *
     * @return errors counter
     * @ingroup buffer
     */
    uint16_t Modbus::getErrCnt()
    {
        return u16errCnt;
    }
    
    /**
     * Get modbus master state
     *
     * @return = 0 IDLE, = 1 WAITING FOR ANSWER
     * @ingroup buffer
     */
    uint8_t Modbus::getState()
    {
        return u8state;
    }
    
    /**
     * Get the last error in the protocol processor
     *
     * @returnreturn   NO_REPLY = 255      Time-out
     * @return   EXC_FUNC_CODE = 1   Function code not available
     * @return   EXC_ADDR_RANGE = 2  Address beyond available space for Modbus registers
     * @return   EXC_REGS_QUANT = 3  Coils or registers number beyond the available space
     * @ingroup buffer
     */
    uint8_t Modbus::getLastError()
    {
        return u8lastError;
    }
    
    /**
     * @brief
     * *** Only Modbus Master ***
     * Generate a query to an slave with a modbus_t telegram structure
     * The Master must be in COM_IDLE mode. After it, its state would be COM_WAITING.
     * This method has to be called only in loop() section.
     *
     * @see modbus_t
     * @param modbus_t  modbus telegram structure (id, fct, ...)
     * @ingroup loop
     * @todo finish function 15
     */
    int8_t Modbus::query( modbus_t telegram )
    {
        uint8_t u8regsno, u8bytesno;
        if (u8id!=0) return -2;
        if (u8state != COM_IDLE) return -1;
    
        if ((telegram.u8id==0) || (telegram.u8id>247)) return -3;
    
        au16regs = telegram.au16reg;
    
        // telegram header
        au8Buffer[ ID ]         = telegram.u8id;
        au8Buffer[ FUNC ]       = telegram.u8fct;
        au8Buffer[ ADD_HI ]     = highByte(telegram.u16RegAdd );
        au8Buffer[ ADD_LO ]     = lowByte( telegram.u16RegAdd );
    
        switch( telegram.u8fct )
        {
        case MB_FC_READ_COILS:
        case MB_FC_READ_DISCRETE_INPUT:
        case MB_FC_READ_REGISTERS:
        case MB_FC_READ_INPUT_REGISTER:
            au8Buffer[ NB_HI ]      = highByte(telegram.u16CoilsNo );
            au8Buffer[ NB_LO ]      = lowByte( telegram.u16CoilsNo );
            u8BufferSize = 6;
            break;
        case MB_FC_WRITE_COIL:
            au8Buffer[ NB_HI ]      = ((au16regs[0] > 0) ? 0xff : 0);
            au8Buffer[ NB_LO ]      = 0;
            u8BufferSize = 6;
            break;
        case MB_FC_WRITE_REGISTER:
            au8Buffer[ NB_HI ]      = highByte(au16regs[0]);
            au8Buffer[ NB_LO ]      = lowByte(au16regs[0]);
            u8BufferSize = 6;
            break;
        case MB_FC_WRITE_MULTIPLE_COILS: // TODO: implement "sending coils"
            u8regsno = telegram.u16CoilsNo / 16;
            u8bytesno = u8regsno * 2;
            if ((telegram.u16CoilsNo % 16) != 0)
            {
                u8bytesno++;
                u8regsno++;
            }
    
            au8Buffer[ NB_HI ]      = highByte(telegram.u16CoilsNo );
            au8Buffer[ NB_LO ]      = lowByte( telegram.u16CoilsNo );
            au8Buffer[ NB_LO+1 ]    = u8bytesno;
            u8BufferSize = 7;
    
            u8regsno = u8bytesno = 0; // now auxiliary registers
            for (uint16_t i = 0; i < telegram.u16CoilsNo; i++)
            {
    
    
            }
            break;
    
        case MB_FC_WRITE_MULTIPLE_REGISTERS:
            au8Buffer[ NB_HI ]      = highByte(telegram.u16CoilsNo );
            au8Buffer[ NB_LO ]      = lowByte( telegram.u16CoilsNo );
            au8Buffer[ NB_LO+1 ]    = (uint8_t) ( telegram.u16CoilsNo * 2 );
            u8BufferSize = 7;
    
            for (uint16_t i=0; i< telegram.u16CoilsNo; i++)
            {
                au8Buffer[ u8BufferSize ] = highByte( au16regs[ i ] );
                u8BufferSize++;
                au8Buffer[ u8BufferSize ] = lowByte( au16regs[ i ] );
                u8BufferSize++;
            }
            break;
        }
    
        sendTxBuffer();
        u8state = COM_WAITING;
        return 0;
    }
    
    /**
     * @brief *** Only for Modbus Master ***
     * This method checks if there is any incoming answer if pending.
     * If there is no answer, it would change Master state to COM_IDLE.
     * This method must be called only at loop section.
     * Avoid any delay() function.
     *
     * Any incoming data would be redirected to au16regs pointer,
     * as defined in its modbus_t query telegram.
     *
     * @params	nothing
     * @return errors counter
     * @ingroup loop
     */
    int8_t Modbus::poll()
    {
        // check if there is any incoming frame
    	uint8_t u8current;
        /*
    	if(u8serno<4)
            u8current = port->available();
        else
            u8current = softPort->available();
    	*/
    	u8current = _port.available();
    
        if (millis() > u32timeOut)
        {
            u8state = COM_IDLE;
            u8lastError = NO_REPLY;
            u16errCnt++;
            return 0;
        }
    
        if (u8current == 0) return 0;
    
        // check T35 after frame end or still no frame end
        if (u8current != u8lastRec)
        {
            u8lastRec = u8current;
            u32time = millis() + T35;
            return 0;
        }
        if (millis() < u32time) return 0;
    
        // transfer Serial buffer frame to auBuffer
        u8lastRec = 0;
        int8_t i8state = getRxBuffer();
        if (i8state < 7)
        {
            u8state = COM_IDLE;
            u16errCnt++;
            return i8state;
        }
    
        // validate message: id, CRC, FCT, exception
        uint8_t u8exception = validateAnswer();
        if (u8exception != 0)
        {
            u8state = COM_IDLE;
            return u8exception;
        }
    
        // process answer
        switch( au8Buffer[ FUNC ] )
        {
        case MB_FC_READ_COILS:
        case MB_FC_READ_DISCRETE_INPUT:
            // call get_FC1 to transfer the incoming message to au16regs buffer
            get_FC1( );
            break;
        case MB_FC_READ_INPUT_REGISTER:
        case MB_FC_READ_REGISTERS :
            // call get_FC3 to transfer the incoming message to au16regs buffer
            get_FC3( );
            break;
        case MB_FC_WRITE_COIL:
        case MB_FC_WRITE_REGISTER :
        case MB_FC_WRITE_MULTIPLE_COILS:
        case MB_FC_WRITE_MULTIPLE_REGISTERS :
            // nothing to do
            break;
        default:
            break;
        }
        u8state = COM_IDLE;
        return u8BufferSize;
    }
    
    /**
     * @brief
     * *** Only for Modbus Slave ***
     * This method checks if there is any incoming query
     * Afterwards, it would shoot a validation routine plus a register query
     * Avoid any delay() function !!!!
     * After a successful frame between the Master and the Slave, the time-out timer is reset.
     *
     * @param *regs  register table for communication exchange
     * @param u8size  size of the register table
     * @return 0 if no query, 1..4 if communication error, >4 if correct query processed
     * @ingroup loop
     */
    int8_t Modbus::poll( uint16_t *regs, uint8_t u8size )
    {
    
        au16regs = regs;
        u8regsize = u8size;
    	uint8_t u8current;
    
    
        // check if there is any incoming frame
        /*
    	if(u8serno<4)
            u8current = port->available();
        else
            u8current = softPort->available();
    	*/
    	u8current = _port.available();
    
        if (u8current == 0) return 0;
    
        // check T35 after frame end or still no frame end
        if (u8current != u8lastRec)
        {
            u8lastRec = u8current;
            u32time = millis() + T35;
            return 0;
        }
        if (millis() < u32time) return 0;
    
        u8lastRec = 0;
        int8_t i8state = getRxBuffer();
        u8lastError = i8state;
        if (i8state < 7) return i8state;
    
        // check slave id
        if (au8Buffer[ ID ] != u8id) return 0;
    
        // validate message: CRC, FCT, address and size
        uint8_t u8exception = validateRequest();
        if (u8exception > 0)
        {
            if (u8exception != NO_REPLY)
            {
                buildException( u8exception );
                sendTxBuffer();
            }
            u8lastError = u8exception;
            return u8exception;
        }
    
        u32timeOut = millis() + long(u16timeOut);
        u8lastError = 0;
    
        // process message
        switch( au8Buffer[ FUNC ] )
        {
        case MB_FC_READ_COILS:
        case MB_FC_READ_DISCRETE_INPUT:
            return process_FC1( regs, u8size );
            break;
        case MB_FC_READ_INPUT_REGISTER:
        case MB_FC_READ_REGISTERS :
            return process_FC3( regs, u8size );
            break;
        case MB_FC_WRITE_COIL:
            return process_FC5( regs, u8size );
            break;
        case MB_FC_WRITE_REGISTER :
            return process_FC6( regs, u8size );
            break;
        case MB_FC_WRITE_MULTIPLE_COILS:
            return process_FC15( regs, u8size );
            break;
        case MB_FC_WRITE_MULTIPLE_REGISTERS :
            return process_FC16( regs, u8size );
            break;
        default:
            break;
        }
        return i8state;
    }
    
    /* _____PRIVATE FUNCTIONS_____________________________________________________ */
    
    void Modbus::init(uint8_t u8id, HardwareSerial &_port, uint8_t u8txenpin)
    {
        this->u8id = u8id;
        //this->u8serno = (u8serno > 3) ? 0 : u8serno;
    	this->_port = _port;
        this->u8txenpin = u8txenpin;
        this->u16timeOut = 1000;
    }
    
    void Modbus::init(uint8_t u8id)
    {
        this->u8id = u8id;
        //this->u8serno = 4;
    	this->_port = Serial1;
        this->u8txenpin = 0;
        this->u16timeOut = 1000;
    }
    
    /**
     * @brief
     * This method moves Serial buffer data to the Modbus au8Buffer.
     *
     * @return buffer size if OK, ERR_BUFF_OVERFLOW if u8BufferSize >= MAX_BUFFER
     * @ingroup buffer
     */
    int8_t Modbus::getRxBuffer()
    {
        boolean bBuffOverflow = false;
    
        //if (u8txenpin > 1) digitalWrite( u8txenpin, LOW );
    	digitalWrite(u8txenpin, LOW);
    
        u8BufferSize = 0;
    	/*
        if(u8serno<4)
            while ( port->available() )
            {
                au8Buffer[ u8BufferSize ] = port->read();
                u8BufferSize ++;
    
                if (u8BufferSize >= MAX_BUFFER) bBuffOverflow = true;
            }
        else
            while ( softPort->available() )
            {
                au8Buffer[ u8BufferSize ] = softPort->read();
                u8BufferSize ++;
    
                if (u8BufferSize >= MAX_BUFFER) bBuffOverflow = true;
            }
    	*/
    	while (_port.available())
    	{
    		au8Buffer[ u8BufferSize ] = _port.read();
            u8BufferSize ++;
    		if (u8BufferSize >= MAX_BUFFER) bBuffOverflow = true;
    	}
        u16InCnt++;
    
        if (bBuffOverflow)
        {
            u16errCnt++;
            return ERR_BUFF_OVERFLOW;
        }
        return u8BufferSize;
    }
    
    /**
     * @brief
     * This method transmits au8Buffer to Serial line.
     * Only if u8txenpin != 0, there is a flow handling in order to keep
     * the RS485 transceiver in output state as long as the message is being sent.
     * This is done with UCSRxA register.
     * The CRC is appended to the buffer before starting to send it.
     *
     * @param nothing
     * @return nothing
     * @ingroup buffer
     */
    void Modbus::sendTxBuffer()
    {
        // uint8_t i = 0;
    
        // append CRC to message
        uint16_t u16crc = calcCRC( u8BufferSize );
        au8Buffer[ u8BufferSize ] = u16crc >> 8;
        u8BufferSize++;
        au8Buffer[ u8BufferSize ] = u16crc & 0x00ff;
        u8BufferSize++;
    
        // set RS485 transceiver to transmit mode
        /*if (u8txenpin > 1)
        {
            switch( u8serno )
            {
    #if defined(UBRR1H)
            case 1:
                UCSR1A=UCSR1A |(1 << TXC1);
                break;
    #endif
    
    #if defined(UBRR2H)
            case 2:
                UCSR2A=UCSR2A |(1 << TXC2);
                break;
    #endif
    
    #if defined(UBRR3H)
            case 3:
                UCSR3A=UCSR3A |(1 << TXC3);
                break;
    #endif
            case 0:
            default:
                UCSR0A=UCSR0A |(1 << TXC0);
                break;
            }
            digitalWrite( u8txenpin, HIGH );
        }
    	*/
    	digitalWrite( u8txenpin, HIGH );
    
        // transfer buffer to serial line
        /*
    	if(u8serno<4)
            port->write( au8Buffer, u8BufferSize );
        else
            softPort->write( au8Buffer, u8BufferSize );
    	*/
    	//pinMode(LED_BUILTIN, OUTPUT);digitalWrite(LED_BUILTIN, HIGH);
    	_port.write(au8Buffer, u8BufferSize);
    
        // keep RS485 transceiver in transmit mode as long as sending
    	/*
        if (u8txenpin > 1)
        {
            switch( u8serno )
            {
    #if defined(UBRR1H)
            case 1:
                while (!(UCSR1A & (1 << TXC1)));
                break;
    #endif
    
    #if defined(UBRR2H)
            case 2:
                while (!(UCSR2A & (1 << TXC2)));
                break;
    #endif
    
    #if defined(UBRR3H)
            case 3:
                while (!(UCSR3A & (1 << TXC3)));
                break;
    #endif
            case 0:
            default:
                while (!(UCSR0A & (1 << TXC0)));
                break;
            }
    
            // return RS485 transceiver to receive mode
            digitalWrite( u8txenpin, LOW );
        }*/
    	_port.flush(); //Wait until send complete
    	//digitalWrite(LED_BUILTIN, LOW);
    	digitalWrite( u8txenpin, LOW );
    
        /*
    	if(u8serno<4)
            while(port->read() >= 0);
        else
            while(softPort->read() >= 0);
    	*/
    	while(_port.read()>=0);
    
        u8BufferSize = 0;
    
        // set time-out for master
        u32timeOut = millis() + (unsigned long) u16timeOut;
    
        // increase message counter
        u16OutCnt++;
    }
    
    /**
     * @brief
     * This method calculates CRC
     *
     * @return uint16_t calculated CRC value for the message
     * @ingroup buffer
     */
    uint16_t Modbus::calcCRC(uint8_t u8length)
    {
        unsigned int temp, temp2, flag;
        temp = 0xFFFF;
        for (unsigned char i = 0; i < u8length; i++)
        {
            temp = temp ^ au8Buffer[i];
            for (unsigned char j = 1; j <= 8; j++)
            {
                flag = temp & 0x0001;
                temp >>=1;
                if (flag)
                    temp ^= 0xA001;
            }
        }
        // Reverse byte order.
        temp2 = temp >> 8;
        temp = (temp << 8) | temp2;
        temp &= 0xFFFF;
        // the returned value is already swapped
        // crcLo byte is first & crcHi byte is last
        return temp;
    }
    
    /**
     * @brief
     * This method validates slave incoming messages
     *
     * @return 0 if OK, EXCEPTION if anything fails
     * @ingroup buffer
     */
    uint8_t Modbus::validateRequest()
    {
        // check message crc vs calculated crc
        uint16_t u16MsgCRC =
            ((au8Buffer[u8BufferSize - 2] << 8)
             | au8Buffer[u8BufferSize - 1]); // combine the crc Low & High bytes
        if ( calcCRC( u8BufferSize-2 ) != u16MsgCRC )
        {
            u16errCnt ++;
            return NO_REPLY;
        }
    
        // check fct code
        boolean isSupported = false;
        for (uint8_t i = 0; i< sizeof( fctsupported ); i++)
        {
            if (fctsupported[i] == au8Buffer[FUNC])
            {
                isSupported = 1;
                break;
            }
        }
        if (!isSupported)
        {
            u16errCnt ++;
            return EXC_FUNC_CODE;
        }
    
        // check start address & nb range
        uint16_t u16regs = 0;
        uint8_t u8regs;
        switch ( au8Buffer[ FUNC ] )
        {
        case MB_FC_READ_COILS:
        case MB_FC_READ_DISCRETE_INPUT:
        case MB_FC_WRITE_MULTIPLE_COILS:
            u16regs = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ]) / 16;
            u16regs += word( au8Buffer[ NB_HI ], au8Buffer[ NB_LO ]) /16;
            u8regs = (uint8_t) u16regs;
            if (u8regs > u8regsize) return EXC_ADDR_RANGE;
            break;
        case MB_FC_WRITE_COIL:
            u16regs = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ]) / 16;
            u8regs = (uint8_t) u16regs;
            if (u8regs > u8regsize) return EXC_ADDR_RANGE;
            break;
        case MB_FC_WRITE_REGISTER :
            u16regs = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ]);
            u8regs = (uint8_t) u16regs;
            if (u8regs > u8regsize) return EXC_ADDR_RANGE;
            break;
        case MB_FC_READ_REGISTERS :
        case MB_FC_READ_INPUT_REGISTER :
        case MB_FC_WRITE_MULTIPLE_REGISTERS :
            u16regs = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ]);
            u16regs += word( au8Buffer[ NB_HI ], au8Buffer[ NB_LO ]);
            u8regs = (uint8_t) u16regs;
            if (u8regs > u8regsize) return EXC_ADDR_RANGE;
            break;
        }
        return 0; // OK, no exception code thrown
    }
    
    /**
     * @brief
     * This method validates master incoming messages
     *
     * @return 0 if OK, EXCEPTION if anything fails
     * @ingroup buffer
     */
    uint8_t Modbus::validateAnswer()
    {
        // check message crc vs calculated crc
        uint16_t u16MsgCRC =
            ((au8Buffer[u8BufferSize - 2] << 8)
             | au8Buffer[u8BufferSize - 1]); // combine the crc Low & High bytes
        if ( calcCRC( u8BufferSize-2 ) != u16MsgCRC )
        {
            u16errCnt ++;
            return NO_REPLY;
        }
    
        // check exception
        if ((au8Buffer[ FUNC ] & 0x80) != 0)
        {
            u16errCnt ++;
            return ERR_EXCEPTION;
        }
    
        // check fct code
        boolean isSupported = false;
        for (uint8_t i = 0; i< sizeof( fctsupported ); i++)
        {
            if (fctsupported[i] == au8Buffer[FUNC])
            {
                isSupported = 1;
                break;
            }
        }
        if (!isSupported)
        {
            u16errCnt ++;
            return EXC_FUNC_CODE;
        }
    
        return 0; // OK, no exception code thrown
    }
    
    /**
     * @brief
     * This method builds an exception message
     *
     * @ingroup buffer
     */
    void Modbus::buildException( uint8_t u8exception )
    {
        uint8_t u8func = au8Buffer[ FUNC ];  // get the original FUNC code
    
        au8Buffer[ ID ]      = u8id;
        au8Buffer[ FUNC ]    = u8func + 0x80;
        au8Buffer[ 2 ]       = u8exception;
        u8BufferSize         = EXCEPTION_SIZE;
    }
    
    /**
     * This method processes functions 1 & 2 (for master)
     * This method puts the slave answer into master data buffer
     *
     * @ingroup register
     * TODO: finish its implementation
     */
    void Modbus::get_FC1()
    {
        /*
    	uint8_t u8byte, i;
        u8byte = 0;
    	*/
    	
        //  for (i=0; i< au8Buffer[ 2 ] /2; i++) {
        //    au16regs[ i ] = word(
        //    au8Buffer[ u8byte ],
        //    au8Buffer[ u8byte +1 ]);
        //    u8byte += 2;
        //  }
    }
    
    /**
     * This method processes functions 3 & 4 (for master)
     * This method puts the slave answer into master data buffer
     *
     * @ingroup register
     */
    void Modbus::get_FC3()
    {
        uint8_t u8byte, i;
        u8byte = 3;
    
        for (i=0; i< au8Buffer[ 2 ] /2; i++)
        {
            au16regs[ i ] = word(
                                au8Buffer[ u8byte ],
                                au8Buffer[ u8byte +1 ]);
            u8byte += 2;
        }
    }
    
    /**
     * @brief
     * This method processes functions 1 & 2
     * This method reads a bit array and transfers it to the master
     *
     * @return u8BufferSize Response to master length
     * @ingroup discrete
     */
    int8_t Modbus::process_FC1( uint16_t *regs, uint8_t u8size )
    {
        uint8_t u8currentRegister, u8currentBit, u8bytesno, u8bitsno;
        uint8_t u8CopyBufferSize;
        uint16_t u16currentCoil, u16coil;
    
        // get the first and last coil from the message
        uint16_t u16StartCoil = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ] );
        uint16_t u16Coilno = word( au8Buffer[ NB_HI ], au8Buffer[ NB_LO ] );
    
        // put the number of bytes in the outcoming message
        u8bytesno = (uint8_t) (u16Coilno / 8);
        if (u16Coilno % 8 != 0) u8bytesno ++;
        au8Buffer[ ADD_HI ]  = u8bytesno;
        u8BufferSize         = ADD_LO;
    
        // read each coil from the register map and put its value inside the outcoming message
        u8bitsno = 0;
    
        for (u16currentCoil = 0; u16currentCoil < u16Coilno; u16currentCoil++)
        {
            u16coil = u16StartCoil + u16currentCoil;
            u8currentRegister = (uint8_t) (u16coil / 16);
            u8currentBit = (uint8_t) (u16coil % 16);
    
            bitWrite(
                au8Buffer[ u8BufferSize ],
                u8bitsno,
                bitRead( regs[ u8currentRegister ], u8currentBit ) );
            u8bitsno ++;
    
            if (u8bitsno > 7)
            {
                u8bitsno = 0;
                u8BufferSize++;
            }
        }
    
        // send outcoming message
        if (u16Coilno % 8 != 0) u8BufferSize ++;
        u8CopyBufferSize = u8BufferSize +2;
        sendTxBuffer();
        return u8CopyBufferSize;
    }
    
    /**
     * @brief
     * This method processes functions 3 & 4
     * This method reads a word array and transfers it to the master
     *
     * @return u8BufferSize Response to master length
     * @ingroup register
     */
    int8_t Modbus::process_FC3( uint16_t *regs, uint8_t u8size )
    {
    
        uint8_t u8StartAdd = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ] );
        uint8_t u8regsno = word( au8Buffer[ NB_HI ], au8Buffer[ NB_LO ] );
        uint8_t u8CopyBufferSize;
        uint8_t i;
    
        au8Buffer[ 2 ]       = u8regsno * 2;
        u8BufferSize         = 3;
    
        for (i = u8StartAdd; i < u8StartAdd + u8regsno; i++)
        {
            au8Buffer[ u8BufferSize ] = highByte(regs[i]);
            u8BufferSize++;
            au8Buffer[ u8BufferSize ] = lowByte(regs[i]);
            u8BufferSize++;
        }
        u8CopyBufferSize = u8BufferSize +2;
        sendTxBuffer();
    
        return u8CopyBufferSize;
    }
    
    /**
     * @brief
     * This method processes function 5
     * This method writes a value assigned by the master to a single bit
     *
     * @return u8BufferSize Response to master length
     * @ingroup discrete
     */
    int8_t Modbus::process_FC5( uint16_t *regs, uint8_t u8size )
    {
        uint8_t u8currentRegister, u8currentBit;
        uint8_t u8CopyBufferSize;
        uint16_t u16coil = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ] );
    
        // point to the register and its bit
        u8currentRegister = (uint8_t) (u16coil / 16);
        u8currentBit = (uint8_t) (u16coil % 16);
    
        // write to coil
        bitWrite(
            regs[ u8currentRegister ],
            u8currentBit,
            au8Buffer[ NB_HI ] == 0xff );
    
    
        // send answer to master
        u8BufferSize = 6;
        u8CopyBufferSize = u8BufferSize +2;
        sendTxBuffer();
    
        return u8CopyBufferSize;
    }
    
    /**
     * @brief
     * This method processes function 6
     * This method writes a value assigned by the master to a single word
     *
     * @return u8BufferSize Response to master length
     * @ingroup register
     */
    int8_t Modbus::process_FC6( uint16_t *regs, uint8_t u8size )
    {
    
        uint8_t u8add = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ] );
        uint8_t u8CopyBufferSize;
        uint16_t u16val = word( au8Buffer[ NB_HI ], au8Buffer[ NB_LO ] );
    
        regs[ u8add ] = u16val;
    
        // keep the same header
        u8BufferSize         = RESPONSE_SIZE;
    
        u8CopyBufferSize = u8BufferSize +2;
        sendTxBuffer();
    
        return u8CopyBufferSize;
    }
    
    /**
     * @brief
     * This method processes function 15
     * This method writes a bit array assigned by the master
     *
     * @return u8BufferSize Response to master length
     * @ingroup discrete
     */
    int8_t Modbus::process_FC15( uint16_t *regs, uint8_t u8size )
    {
        uint8_t u8currentRegister, u8currentBit, u8frameByte, u8bitsno;
        uint8_t u8CopyBufferSize;
        uint16_t u16currentCoil, u16coil;
        boolean bTemp;
    
        // get the first and last coil from the message
        uint16_t u16StartCoil = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ] );
        uint16_t u16Coilno = word( au8Buffer[ NB_HI ], au8Buffer[ NB_LO ] );
    
    
        // read each coil from the register map and put its value inside the outcoming message
        u8bitsno = 0;
        u8frameByte = 7;
        for (u16currentCoil = 0; u16currentCoil < u16Coilno; u16currentCoil++)
        {
    
            u16coil = u16StartCoil + u16currentCoil;
            u8currentRegister = (uint8_t) (u16coil / 16);
            u8currentBit = (uint8_t) (u16coil % 16);
    
            bTemp = bitRead(
                        au8Buffer[ u8frameByte ],
                        u8bitsno );
    
            bitWrite(
                regs[ u8currentRegister ],
                u8currentBit,
                bTemp );
    
            u8bitsno ++;
    
            if (u8bitsno > 7)
            {
                u8bitsno = 0;
                u8frameByte++;
            }
        }
    
        // send outcoming message
        // it's just a copy of the incomping frame until 6th byte
        u8BufferSize         = 6;
        u8CopyBufferSize = u8BufferSize +2;
        sendTxBuffer();
        return u8CopyBufferSize;
    }
    
    /**
     * @brief
     * This method processes function 16
     * This method writes a word array assigned by the master
     *
     * @return u8BufferSize Response to master length
     * @ingroup register
     */
    int8_t Modbus::process_FC16( uint16_t *regs, uint8_t u8size )
    {
        //uint8_t u8func = au8Buffer[ FUNC ];  // get the original FUNC code
        uint8_t u8StartAdd = au8Buffer[ ADD_HI ] << 8 | au8Buffer[ ADD_LO ];
        uint8_t u8regsno = au8Buffer[ NB_HI ] << 8 | au8Buffer[ NB_LO ];
        uint8_t u8CopyBufferSize;
        uint8_t i;
        uint16_t temp;
    
        // build header
        au8Buffer[ NB_HI ]   = 0;
        au8Buffer[ NB_LO ]   = u8regsno;
        u8BufferSize         = RESPONSE_SIZE;
    
        // write registers
        for (i = 0; i < u8regsno; i++)
        {
            temp = word(
                       au8Buffer[ (BYTE_CNT + 1) + i * 2 ],
                       au8Buffer[ (BYTE_CNT + 2) + i * 2 ]);
    
            regs[ u8StartAdd + i ] = temp;
        }
        u8CopyBufferSize = u8BufferSize +2;
        sendTxBuffer();
    
        return u8CopyBufferSize;
    }
    I testes with a Teensy 3.1 as slave with this code:

    Code:
    #include <ModbusRtu.h>
    
    uint16_t slaveData[16];
    Modbus slave(5, Serial1, 2); //Bus ID, port, RX-pin
    
    elapsedMillis sinceRead = 0;
    elapsedMillis ledOffTime = 0;
    uint8_t ledState = 0;
    uint8_t address = 0;
    
    void setup() {
      //Serial.begin(9600);
      slave.begin(115200);
      pinMode(LED_BUILTIN, OUTPUT);
      digitalWrite(LED_BUILTIN, LOW);
    }
    
    void loop() {
      slave.poll(slaveData, 16);
      if (sinceRead > 100) {
        sinceRead -= 100;
        slaveData[address] = analogRead(A0);
        address++;
        if (address > 15) address = 0;
        ledFlash (1);
      }
      if (Serial1.available()>=1) ledFlash(100);
    
      if (ledOffTime > 1000) {
        ledOffTime = 1000;
        digitalWrite(LED_BUILTIN, LOW);
        ledState = 0;
      }
    }
    
    void ledFlash(uint32_t duration) {
      digitalWrite(LED_BUILTIN, HIGH);
      if (ledState == 0 && ledOffTime >= duration) {
        ledOffTime -= duration;
      }
      ledState = 1;
      return;
    }
    And a Teensy LC as a master using this Sketch:
    Code:
    #include <ModbusRtu.h>
    
    uint16_t masterData[8];
    Modbus master(0,Serial1,2);
    modbus_t telegram;
    
    elapsedMillis sinceSerial;
    elapsedMillis sinceUpdate;
    elapsedMillis ledOffTime = 0;
    uint8_t ledState = 0;
    
    void setup() {
      Serial.begin(9600);
      delay(500);
      master.begin(115200);
      master.setTimeOut(50);
    
      telegram.u8id = 5; // slave address
      telegram.u8fct = 3; // function code (this one is registers read)
      telegram.u16RegAdd = 1; // start address in slave (1 == array index 0)
      telegram.u16CoilsNo = 8; // number of elements (coils or registers) to read
      telegram.au16reg = masterData; // pointer to a memory array in the Arduino
    
      sinceSerial = 0;
      sinceUpdate = 0;
    
      pinMode(LED_BUILTIN, OUTPUT);
    }
    
    void loop() {
      if (master.getState() != COM_IDLE) {
        master.poll();
      }
      
      if (sinceUpdate > 100) {
        sinceUpdate -= 100;
        master.query( telegram ); // send query (only once)
      }
    
      if (sinceSerial > 100) {
        sinceSerial -= 100;
        Serial.print("time = ");Serial.println(millis());
        for (int i=0; i<8; i++) {
          Serial.print("masterData[");Serial.print(i);
          Serial.print("] = ");Serial.println(masterData[i]);
        }
        Serial.print("master.getState() = ");Serial.println(master.getState());
        Serial.print("master.getErrCnt() = ");Serial.println(master.getErrCnt());
        Serial.println("");
      }
    
      if (Serial1.available()>=1) ledFlash(10);
    
      if (ledOffTime > 1000) {
        ledOffTime = 1000;
        digitalWrite(LED_BUILTIN, LOW);
        ledState = 0;
      }
    }
    
    void ledFlash(uint32_t duration) {
      digitalWrite(LED_BUILTIN, HIGH);
      if (ledState == 0 && ledOffTime >= duration) {
        ledOffTime -= duration;
      }
      ledState = 1;
      return;
    }
    Click image for larger version. 

Name:	zeroErrors.jpg 
Views:	39 
Size:	74.7 KB 
ID:	12648

    The Teensies were connected over their Serial1 ports (and 5V and GND).
    Polling 10 times a second over night worked well with zero communication errors. I first tried at a lower baud rate of 9600, which caused the slave to not answer some requests by the master. I guess the issue is in the code responsible for detecting the end of each message, there is a constant timeout value that imho should be a function of baud rate.

    Ben

  17. #17
    Senior Member Ben's Avatar
    Join Date
    Jul 2013
    Location
    Germany
    Posts
    401
    I'm considering a rewrite with some improvements like proper timeout handling and allowing to configure more parameters at runtime (such as slave address and baud rate) so these can be fetched e.g. from EEPROM on startup. With the current library as a working example I'm semi-confident I can get this done, especially since I found this thread.

    The original library expects a uint16_t-array provided by the user sketch to read and write to. Is this "the Arduino way" or is there another, preferred technique? IMHO even expecting the user to declare an array of this "odd type" is not very "arduino-ish".

    It's also odd that the original library only uses a single uint16_t array, I would expect individual arrays for "coils" (1-bit outputs, read and write), "discrete inputs" (1-bit inputs, read only), "input registers" (16-bit analog inputs, read only) and "holding registers" (16 bit multiple purpose registers, read and write), as defined by the modbus standard

    Ben

  18. #18
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    18,153
    Here's my first attempt to clean up this library, in a way that won't interfere with its support for any other boards.

    https://github.com/PaulStoffregen/Mo...ve-for-Arduino

    Hopefully they will be willing to merge these changes back into their copy.

    Please let me know if this works for you? I'll wait for your confirmation before submitting a pull request back to the original code...

  19. #19
    Senior Member Ben's Avatar
    Join Date
    Jul 2013
    Location
    Germany
    Posts
    401
    Will do, give me a few days.

  20. #20
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    18,153
    I took another quick look at this library. The RS485 transmit enable was definitely not working, since it has very AVR specific code.

    Here's another small fix, so it uses Teensy's special HardwareSerial transmitterEnable(pin) feature.

    When you test this, please make sure to grab the latest code, especially if you'll be using a RS485 chip.


    So far, my only testing has been looking at waveforms on my oscilloscope. Please let me know when you've had a chance to test with real Modbus devices? I'll hold off submitting a pull request until it's been tested further.

    Click image for larger version. 

Name:	file.png 
Views:	40 
Size:	32.0 KB 
ID:	12684
    (click for full size)

  21. #21
    Senior Member Ben's Avatar
    Join Date
    Jul 2013
    Location
    Germany
    Posts
    401
    Thanks Paul. Unfortunately I don't own any modbus devices. My use case is a diy sensor bus in my home, with a sensor node in each room for temperature, humidity, light level etc. The node will also function as my door bell. (Teensy 3.2 with a Prop Shield "witout"). I'll build both those sensors and the master myself, so I can only test the library against itself and I can test your modifications against mine. I could ask our universities department of automation technology if they have any modbus devices I can borrow, but realistically I won't have time for that before mid february.

    But then again, the library didn't meet the official modbus requirements and specifications even before your and my modifications. Timeout handling is done wrong and from what I understand all read/write functions act on the same piece of memory where there should be four seperate ones (digital in, digital out, 16b-in and 16b general purpose). So failing to work with commercial units wouldn't mean your modifications were wrong...

  22. #22
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    18,153
    Are you using RS485 transceivers?

  23. #23
    Senior Member Ben's Avatar
    Join Date
    Jul 2013
    Location
    Germany
    Posts
    401
    not for testing, but I'll use RS485 half duplex. That's actually defined and required by the modbus standard. I have transceiver ICs in the mail and will spin a quick test board once they arrive. I have access to our in house pcb etching lab, so turnaround times are quick.

  24. #24
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    18,153
    Fixing this library's existing limitations and bugs isn't my goal. Honestly, it's been a while since I've seen a library with this much AVR-centric and generally poor quality code. Most Arduino libs have improved quite a lot in recent years, but these older ones are still with us.

    Please let me know when you feel it's working as well on Teensy as it does on other boards. I'll try sending a pull request. But perhaps this lib is now abandoned by its original authors? We'll see....

  25. #25
    Junior Member
    Join Date
    Jan 2018
    Posts
    3
    Really interesting thread!!

    I was looking for somebody to produce a sketch for an Arduino Zero/M0 that supports Modbus RTU and I thought the library you guys were discussing (https://github.com/smarmengol/Modbus...ve-for-Arduino) would be a good basis. Problem is the Zero/M0 suffers the same issues you guys are experiencing. The actual board I'm working with can be found here https://robotdyn.com/samd21-m0.html although I am happy to move to the Teensy - Freescale version - if it has solid Modbus support.

    In my case either target board should only act as a ModBus RTU slave and because the board is acting as a ModBus slave the ModBus master (Raspberry Pi 3B) needs to acquire data from various registers as defined in the ModBus specification i.e. digital outputs should be found in coils 1 - 9999 analogue outputs should be found in registers 40001 - 49999 etc., all of the digital inputs 0-7 can be stored in one coil 10001 and 8 digital outputs can be found in coil 00001 and Analogue inputs found in registers 30001-3xxxx. I assumed the library under discussion did his otherwise in my opinion it is not Modbus.

    Again in my case comms should preferably communicate over a serial line using TTL signals (pins 8 & 10 on the RPI 3 to PB03 and PA27 on the Zero; both are at 3v3) but RS485 is also important to me, and the comms parameters should be as fast as is reliably acceptable so good timing is important. In tests using an FTDI USB to TTL converter I have comms running at 115,200 / 8/ N/ 1 reliably against other ModBus devices on my SCADA system

    Videos are posted on YouTube at https://www.youtube.com/watch?v=SkcBzkr_Z34 and here https://www.youtube.com/watch?v=JZEXuZbwVbo&t=805s they will give some background about what I am doing and how my SCADA system is set-up to communicate over Modbus. you can also go to www.linklaser.com for more background.

    If anybody (including Epyon / PaulStoffgren) is interested in working on this to produce a non-specific-board centric solution I can provide a microSD image for the RPI 3 with the complete SCADA software. You will need to sign a simple NDA to get that. The copy becomes yours and you can use it once in one personal / commercial project - you are not allowed to make copies of the image. Incidentally I have also produced an I/O simulator board with the same form factor as the Uno to physically test the I/O. I'll watch this thread to see how things progress

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •