diff --git a/ModbusRtu.h b/ModbusRtu.h index 06417f3..5ff4df1 100644 --- a/ModbusRtu.h +++ b/ModbusRtu.h @@ -40,8 +40,7 @@ #include #include "Arduino.h" -#include "Print.h" -#include + /** * @struct modbus_t @@ -155,11 +154,9 @@ const unsigned char fctsupported[] = class Modbus { private: - HardwareSerial *port; //!< Pointer to Serial class object - SoftwareSerial *softPort; //!< Pointer to SoftwareSerial class object + Stream *port; //!< Pointer to Stream class object (Either HardwareSerial or SoftwareSerial) 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 u8txenpin; //!< flow control pin: 0=USB or RS-232 mode, >1=RS-485 mode uint8_t u8state; uint8_t u8lastError; uint8_t au8Buffer[MAX_BUFFER]; @@ -171,8 +168,6 @@ class Modbus uint32_t u32time, u32timeOut, u32overTime; 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); @@ -189,15 +184,9 @@ class Modbus 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(SoftwareSerial *sPort, long u32speed, uint8_t u8txenpin); - //void begin(long u32speed, uint8_t u8config); - void begin(); + Modbus(uint8_t u8id, Stream& port, uint8_t u8txenpin =0); + + void start(); void setTimeOut( uint16_t u16timeOut); //! + void begin(T_Stream* port_, long u32speed_) __attribute__((deprecated)); + + // Deprecated: Use "start()" instead. + template + void begin(T_Stream* port_, long u32speed_, uint8_t u8txenpin_) __attribute__((deprecated)); + + // Deprecated: Use "start()" instead. + void begin(long u32speed = 19200) __attribute__((deprecated)); }; /* _____PUBLIC FUNCTIONS_____________________________________________________ */ /** * @brief - * Default Constructor for Master through Serial + * Constructor for a Master/Slave. * - * @ingroup setup - */ -Modbus::Modbus() -{ - init(0, 0, 0); -} - -/** - * @brief - * Full constructor for a Master/Slave through USB/RS232C + * For hardware serial through USB/RS232C/RS485 set port to Serial, Serial1, + * Serial2, or Serial3. (Numbered hardware serial ports are only available on + * some boards.) * - * @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 + * For software serial through RS232C/RS485 set port to a SoftwareSerial object + * that you have already constructed. + * + * ModbusRtu needs a pin for flow control only for RS485 mode. Pins 0 and 1 + * cannot be used. + * + * First call begin() on your serial port, and then start up ModbusRtu by + * calling start(). You can choose the line speed and other port parameters + * by passing the appropriate values to the port's begin() function. * * @param u8id node address 0=master, 1..247=slave - * @param u8serno serial port used 0..3 + * @param port serial port used * @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) +Modbus::Modbus(uint8_t u8id, Stream& port, uint8_t u8txenpin) { - init(u8id, u8serno, u8txenpin); + this->port = &port; + this->u8id = u8id; + this->u8txenpin = u8txenpin; + this->u16timeOut = 1000; + this->u32overTime = 0; } -/** - * @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. + * DEPRECATED constructor for a Master/Slave. * - * Sets up the serial port using specified baud rate. - * Call once class has been instantiated, typically within setup(). + * THIS CONSTRUCTOR IS ONLY PROVIDED FOR BACKWARDS COMPATIBILITY. + * USE Modbus(uint8_t, T_Stream&, uint8_t) INSTEAD. * - * @see http://arduino.cc/en/Serial/Begin#.Uy4CJ6aKlHY - * @param speed baud rate, in standard increments (300..115200) + * @param u8id node address 0=master, 1..247=slave + * @param u8serno serial port used 0..3 (ignored for software serial) + * @param u8txenpin pin for txen RS-485 (=0 means USB/RS232C mode) * @ingroup setup + * @overload Modbus::Modbus(uint8_t u8id, T_Stream& port, uint8_t u8txenpin) */ -void Modbus::begin(long u32speed) +Modbus::Modbus(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin) { + this->u8id = u8id; + this->u8txenpin = u8txenpin; + this->u16timeOut = 1000; + this->u32overTime = 0; switch( u8serno ) { @@ -319,38 +300,22 @@ void Modbus::begin(long u32speed) 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. + * Start-up class object. * - * Sets up the software serial port using specified baud rate and SoftwareSerial object. - * Call once class has been instantiated, typically within setup(). + * Call this AFTER calling begin() on the serial port, typically within setup(). + * + * (If you call this function, then you should NOT call any of + * ModbusRtu's own begin() functions.) * - * @param sPort *softPort, pointer to SoftwareSerial class object - * @param u32speed baud rate, in standard increments (300..115200) * @ingroup setup */ -void Modbus::begin(SoftwareSerial *sPort, long u32speed) +void Modbus::start() { - - softPort=sPort; - - softPort->begin(u32speed); - if (u8txenpin > 1) // pin 0 & pin 1 are reserved for RX/TX { // return RS485 transceiver to transmit mode @@ -358,111 +323,73 @@ void Modbus::begin(SoftwareSerial *sPort, long u32speed) digitalWrite(u8txenpin, LOW); } - while(softPort->read() >= 0); + while(port->read() >= 0); u8lastRec = u8BufferSize = 0; u16InCnt = u16OutCnt = u16errCnt = 0; } + /** * @brief - * Initialize class object. + * DEPRECATED Install a serial port, begin() it, and start ModbusRtu. * - * Sets up the software serial port using specified baud rate and SoftwareSerial object. - * Call once class has been instantiated, typically within setup(). + * ONLY PROVIDED FOR BACKWARDS COMPATIBILITY. + * USE Serial.begin(); FOLLOWED BY Modbus.start() INSTEAD. * - * @param *sPort pointer to SoftwareSerial class object - * @param u32speed baud rate, in standard increments (300..115200) - * @param u8txenpin pin for txen RS-485 (=0 means USB/RS232C mode) + * @param install_port pointer to SoftwareSerial or HardwareSerial class object + * @param u32speed baud rate, in standard increments (300..115200) * @ingroup setup */ -void Modbus::begin(SoftwareSerial *sPort, long u32speed, uint8_t u8txenpin) +template +void Modbus::begin(T_Stream* install_port, long u32speed) { - - this->u8txenpin=u8txenpin; - 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; + port = install_port; + install_port->begin(u32speed); + start(); } + /** * @brief - * Initialize class object. + * DEPRECATED. Install a serial port, begin() it, and start ModbusRtu. * - * Sets up the serial port using specified baud rate. - * Call once class has been instantiated, typically within setup(). + * ONLY PROVIDED FOR BACKWARDS COMPATIBILITY. + * USE Serial.begin(); FOLLOWED BY Modbus.start() INSTEAD. * - * @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) + * @param install_port pointer to SoftwareSerial or HardwareSerial class object + * @param u32speed baud rate, in standard increments (300..115200) + * @param u8txenpin pin for txen RS-485 (=0 means USB/RS232C mode) * @ingroup setup */ -/* void Modbus::begin(long u32speed,uint8_t u8config) +template +void Modbus::begin(T_Stream* install_port, long u32speed, uint8_t u8txenpin) { + this->u8txenpin = u8txenpin; + this->port = install_port; + install_port->begin(u32speed); + start(); +} - 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. + * DEPRECATED. begin() hardware serial port and start ModbusRtu. * - * Sets up the serial port using 19200 baud. - * Call once class has been instantiated, typically within setup(). + * ONLY PROVIDED FOR BACKWARDS COMPATIBILITY. + * USE Serial.begin(); FOLLOWED BY Modbus.start() INSTEAD. * - * @overload Modbus::begin(uint16_t u16BaudRate) + * @see http://arduino.cc/en/Serial/Begin#.Uy4CJ6aKlHY + * @param speed baud rate, in standard increments (300..115200). Default=19200 * @ingroup setup */ -void Modbus::begin() +void Modbus::begin(long u32speed) { - begin(19200); + // !!Can ONLY do this if port ACTUALLY IS a HardwareSerial object!! + static_cast(port)->begin(u32speed); + start(); } + /** * @brief * Method to write a new slave ID address @@ -714,10 +641,7 @@ 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 ((unsigned long)(millis() -u32timeOut) > (unsigned long)u16timeOut) { @@ -804,10 +728,7 @@ int8_t Modbus::poll( uint16_t *regs, uint8_t u8size ) // 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; @@ -875,24 +796,6 @@ int8_t Modbus::poll( uint16_t *regs, uint8_t u8size ) /* _____PRIVATE FUNCTIONS_____________________________________________________ */ -void Modbus::init(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin) -{ - this->u8id = u8id; - this->u8serno = u8serno; - this->u8txenpin = u8txenpin; - this->u16timeOut = 1000; - this->u32overTime = 0; -} - -void Modbus::init(uint8_t u8id) -{ - this->u8id = u8id; - this->u8serno = 4; - this->u8txenpin = 0; - this->u16timeOut = 1000; - this->u32overTime = 0; -} - /** * @brief * This method moves Serial buffer data to the Modbus au8Buffer. @@ -907,22 +810,13 @@ int8_t Modbus::getRxBuffer() 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 ++; + while ( port->available() ) + { + au8Buffer[ u8BufferSize ] = port->read(); + u8BufferSize ++; - if (u8BufferSize >= MAX_BUFFER) bBuffOverflow = true; - } + if (u8BufferSize >= MAX_BUFFER) bBuffOverflow = true; + } u16InCnt++; if (bBuffOverflow) @@ -953,7 +847,7 @@ void Modbus::sendTxBuffer() u8BufferSize++; au8Buffer[ u8BufferSize ] = u16crc & 0x00ff; u8BufferSize++; - + if (u8txenpin > 1) { // set RS485 transceiver to transmit mode @@ -961,26 +855,21 @@ void Modbus::sendTxBuffer() } // transfer buffer to serial line - if (u8serno < 4) - port->write( au8Buffer, u8BufferSize ); - else - softPort->write( au8Buffer, u8BufferSize ); + port->write( au8Buffer, u8BufferSize ); if (u8txenpin > 1) { // must wait transmission end before changing pin state // soft serial does not need it since it is blocking - if (u8serno < 4) - port->flush(); + // ...but the implementation in SoftwareSerial does nothing + // anyway, so no harm in calling it. + port->flush(); // return RS485 transceiver to receive mode volatile uint32_t u32overTimeCountDown = u32overTime; while ( u32overTimeCountDown-- > 0); digitalWrite( u8txenpin, LOW ); } - if(u8serno<4) - while(port->read() >= 0); - else - while(softPort->read() >= 0); + while(port->read() >= 0); u8BufferSize = 0; @@ -1172,8 +1061,7 @@ void Modbus::get_FC1() } else { - - au16regs[i/2]= word(highByte(au16regs[i/2]), au8Buffer[i+u8byte]); + au16regs[i/2]= word(0, au8Buffer[i+u8byte]); } } @@ -1227,6 +1115,9 @@ int8_t Modbus::process_FC1( uint16_t *regs, uint8_t /*u8size*/ ) // read each coil from the register map and put its value inside the outcoming message u8bitsno = 0; + // Clear all data bits in outgoing message. + memset( au8Buffer+u8BufferSize, 0, MAX_BUFFER-u8BufferSize ); + for (u16currentCoil = 0; u16currentCoil < u16Coilno; u16currentCoil++) { u16coil = u16StartCoil + u16currentCoil; diff --git a/examples/RS485_slave/RS485_slave.ino b/examples/RS485_slave/RS485_slave.ino index 89eb1d9..5d6ba8b 100644 --- a/examples/RS485_slave/RS485_slave.ino +++ b/examples/RS485_slave/RS485_slave.ino @@ -19,14 +19,15 @@ uint16_t au16data[16] = { /** * Modbus object declaration * u8id : node id = 0 for master, = 1..247 for slave - * u8serno : serial port (use 0 for Serial) + * port : serial port * u8txenpin : 0 for RS-232 and USB-FTDI * or any pin number > 1 for RS-485 */ -Modbus slave(1,0,TXEN); // this is slave @1 and RS-485 +Modbus slave(1,Serial,TXEN); // this is slave @1 and RS-485 void setup() { - slave.begin( 19200 ); // baud-rate at 19200 + Serial.begin( 19200 ); // baud-rate at 19200 + slave.start(); } void loop() { diff --git a/examples/advanced_master/advanced_master.ino b/examples/advanced_master/advanced_master.ino index 484949b..d7101e2 100644 --- a/examples/advanced_master/advanced_master.ino +++ b/examples/advanced_master/advanced_master.ino @@ -23,11 +23,11 @@ uint8_t u8query; //!< pointer to message query /** * Modbus object declaration * u8id : node id = 0 for master, = 1..247 for slave - * u8serno : serial port (use 0 for Serial) + * port : serial port * u8txenpin : 0 for RS-232 and USB-FTDI * or any pin number > 1 for RS-485 */ -Modbus master(0,0,0); // this is master and RS-232 or USB-FTDI +Modbus master(0,Serial,0); // this is master and RS-232 or USB-FTDI /** * This is an structe which contains a query to an slave device @@ -50,8 +50,9 @@ void setup() { telegram[1].u16RegAdd = 4; // start address in slave telegram[1].u16CoilsNo = 1; // number of elements (coils or registers) to read telegram[1].au16reg = au16data+4; // pointer to a memory array in the Arduino - - master.begin( 19200 ); // baud-rate at 19200 + + Serial.begin( 19200 ); // baud-rate at 19200 + master.start(); master.setTimeOut( 5000 ); // if there is no answer in 5000 ms, roll over u32wait = millis() + 1000; u8state = u8query = 0; diff --git a/examples/advanced_slave/advanced_slave.ino b/examples/advanced_slave/advanced_slave.ino index 3b83522..3fe3171 100644 --- a/examples/advanced_slave/advanced_slave.ino +++ b/examples/advanced_slave/advanced_slave.ino @@ -14,7 +14,7 @@ #define ID 1 //Crear instancia -Modbus slave(ID, 0, 0); //ID del nodo. 0 para el master, 1-247 para esclavo +Modbus slave(ID, Serial, 0); //ID del nodo. 0 para el master, 1-247 para esclavo //Puerto serie (0 = TX: 1 - RX: 0) //Protocolo serie. 0 para RS-232 + USB (default), cualquier pin mayor a 1 para RS-485 boolean led; @@ -29,7 +29,8 @@ uint16_t au16data[9]; //La tabla de registros que se desea compartir por la red void setup() { io_setup(); //configura las entradas y salidas - slave.begin(19200); //Abre la comunicación como esclavo + Serial.begin(19200); //Abre la comunicación como esclavo + slave.start(); tempus = millis() + 100; //Guarda el tiempo actual + 100ms digitalWrite(13, HIGH ); //Prende el led del pin 13 (el de la placa) } diff --git a/examples/simple_master/simple_master.ino b/examples/simple_master/simple_master.ino index 947dda8..8b09176 100644 --- a/examples/simple_master/simple_master.ino +++ b/examples/simple_master/simple_master.ino @@ -23,11 +23,11 @@ uint8_t u8state; /** * Modbus object declaration * u8id : node id = 0 for master, = 1..247 for slave - * u8serno : serial port (use 0 for Serial) + * port : serial port * u8txenpin : 0 for RS-232 and USB-FTDI * or any pin number > 1 for RS-485 */ -Modbus master(0,0,0); // this is master and RS-232 or USB-FTDI +Modbus master(0,Serial,0); // this is master and RS-232 or USB-FTDI /** * This is an structe which contains a query to an slave device @@ -37,7 +37,8 @@ modbus_t telegram; unsigned long u32wait; void setup() { - master.begin( 19200 ); // baud-rate at 19200 + Serial.begin( 19200 ); // baud-rate at 19200 + master.start(); master.setTimeOut( 2000 ); // if there is no answer in 2000 ms, roll over u32wait = millis() + 1000; u8state = 0; diff --git a/examples/simple_slave/simple_slave.ino b/examples/simple_slave/simple_slave.ino index e12c5e5..ebe69ea 100644 --- a/examples/simple_slave/simple_slave.ino +++ b/examples/simple_slave/simple_slave.ino @@ -16,14 +16,15 @@ uint16_t au16data[16] = { /** * Modbus object declaration * u8id : node id = 0 for master, = 1..247 for slave - * u8serno : serial port (use 0 for Serial) + * port : serial port * u8txenpin : 0 for RS-232 and USB-FTDI * or any pin number > 1 for RS-485 */ -Modbus slave(1,0,0); // this is slave @1 and RS-232 or USB-FTDI +Modbus slave(1,Serial,0); // this is slave @1 and RS-232 or USB-FTDI void setup() { - slave.begin( 19200 ); // baud-rate at 19200 + Serial.begin( 19200 ); // baud-rate at 19200 + slave.start(); } void loop() { diff --git a/examples/simple_slave2/simple_slave2.ino b/examples/simple_slave2/simple_slave2.ino index 27e7c6e..245fcd3 100644 --- a/examples/simple_slave2/simple_slave2.ino +++ b/examples/simple_slave2/simple_slave2.ino @@ -16,14 +16,15 @@ uint16_t au16data[16] = { /** * Modbus object declaration * u8id : node id = 0 for master, = 1..247 for slave - * u8serno : serial port (use 0 for Serial) + * port : serial port * u8txenpin : 0 for RS-232 and USB-FTDI * or any pin number > 1 for RS-485 */ -Modbus slave(1,0,0); // this is slave @1 and RS-232 or USB-FTDI +Modbus slave(1,Serial,0); // this is slave @1 and RS-232 or USB-FTDI void setup() { - slave.begin( 19200, SERIAL_8E1 ); // 19200 baud, 8-bits, even, 1-bit stop + Serial.begin( 19200, SERIAL_8E1 ); // 19200 baud, 8-bits, even, 1-bit stop + slave.start(); } void loop() { diff --git a/examples/software_serial_simple_master/software_serial_simple_master.ino b/examples/software_serial_simple_master/software_serial_simple_master.ino index 4944a80..2e5c532 100644 --- a/examples/software_serial_simple_master/software_serial_simple_master.ino +++ b/examples/software_serial_simple_master/software_serial_simple_master.ino @@ -48,14 +48,16 @@ uint16_t au16data[16]; uint8_t u8state; +SoftwareSerial mySerial(3, 5);//Create a SoftwareSerial object so that we can use software serial. Search "software serial" on Arduino.cc to find out more details. + /** * Modbus object declaration * u8id : node id = 0 for master, = 1..247 for slave - * u8serno : serial port (use 0 for Serial) + * port : serial port * u8txenpin : 0 for RS-232 and USB-FTDI * or any pin number > 1 for RS-485 */ -Modbus master(0); // this is master and RS-232 or USB-FTDI via software serial +Modbus master(0, mySerial); // this is master and RS-232 or USB-FTDI via software serial /** * This is an structe which contains a query to an slave device @@ -64,11 +66,9 @@ modbus_t telegram; unsigned long u32wait; -SoftwareSerial mySerial(3, 5);//Create a SoftwareSerial object so that we can use software serial. Search "software serial" on Arduino.cc to find out more details. - void setup() { - Serial.begin(9600);//use the hardware serial if you want to connect to your computer via usb cable, etc. - master.begin( &mySerial, 9600 ); // begin the ModBus object. The first parameter is the address of your SoftwareSerial address. Do not forget the "&". 9600 means baud-rate at 9600 + mySerial.begin(9600);//use the hardware serial if you want to connect to your computer via usb cable, etc. + master.start(); // start the ModBus object. master.setTimeOut( 2000 ); // if there is no answer in 2000 ms, roll over u32wait = millis() + 1000; u8state = 0; diff --git a/test/echo/echo.ino b/test/echo/echo.ino new file mode 100644 index 0000000..c56a6f8 --- /dev/null +++ b/test/echo/echo.ino @@ -0,0 +1,421 @@ +/** + * Uses a loopback stream to set up communication between a master and slave on the + * same machine. Exercises various MODBUS functions, and tests that the results + * are correct. + * + * Test failures are reported to Serial, labelled "FAIL". + */ + +#include +#include "src/loopback.h" + +// Set to 1, to report PASSes as well as FAILures. +// Set to higher numbers for increasing levels of detail. +#define VERBOSE_RESULTS 0 + +// +// Master + +const uint16_t master_data_count = 16; +uint16_t master_data[master_data_count+1]; +modbus_t telegram; +int8_t master_poll_result; + +Loopback master_stream(MAX_BUFFER+1); +Modbus master(0,master_stream,0); + + +// +// Slave + +const uint8_t slave_id = 1; +const uint16_t slave_data_count = 9; +uint16_t slave_data[slave_data_count+1]; ///< Include extra OOB register +int8_t slave_poll_result; + +Loopback slave_stream(MAX_BUFFER+1); +Modbus slave(slave_id,slave_stream,0); + + +/** Calculate MODBUS CRC of data. */ +uint16_t calcCRC(const void* data, uint8_t len) +{ + const uint8_t* bytes = static_cast(data); + unsigned int temp, temp2, flag; + temp = 0xFFFF; + for (unsigned char i = 0; i < len; i++) + { + temp = temp ^ bytes[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; +} + +void report(const char* type, uint16_t addr, uint16_t val, uint16_t expected) +{ + Serial.print(type); + Serial.print(F(": ")); + Serial.print(addr); + Serial.print(F("=")); + Serial.print(val); + Serial.print(F(" ?")); + Serial.println(expected); +} +void pass(const char* type, uint16_t addr, uint16_t val, uint16_t expected) +{ + Serial.print(F("PASS ")); + report(type, addr, val, expected); +} +void fail(const char* type, uint16_t addr, uint16_t val, uint16_t expected) +{ + Serial.print(F("FAIL ")); + report(type, addr, val, expected); +} + +void test_equal(const char* type, uint16_t addr, uint16_t val, uint16_t expected) +{ + if(val!=expected) + fail(type, addr, val, expected); +#if defined(VERBOSE_RESULTS) && (VERBOSE_RESULTS>=1) + else + pass(type, addr, val, expected); +#endif +} + +uint16_t addr2word(uint16_t addr) +{ + return calcCRC(&addr, sizeof(addr)); +} + +bool addr2bool(uint16_t addr) +{ + uint16_t index = addr / 16; + uint16_t crc = calcCRC(&index, sizeof(index)); + return bitRead(crc, addr % 16); +} + +void init_master() +{ + telegram.u8id = slave_id; + telegram.u8fct = 0; + telegram.u16RegAdd = 0; + telegram.u16CoilsNo = 0; + telegram.au16reg = master_data; // pointer to a memory array in the Arduino +} + +void init_slave() +{ + // Also initialise the *extra* register, at the end of the array. + for(size_t i=0; i<=slave_data_count; ++i) + { + slave_data[i] = addr2word(i); + } +} + + +/** Poll both master and slave. */ +void poll() +{ +#if defined(VERBOSE_RESULTS) && (VERBOSE_RESULTS>=4) + master_stream.print_status(Serial, "master"); + slave_stream.print_status(Serial, "slave"); +#endif + + slave_poll_result = slave.poll(slave_data, sizeof(slave_data)); +#if defined(VERBOSE_RESULTS) && (VERBOSE_RESULTS>=3) + master_stream.print_status(Serial, "master"); + slave_stream.print_status(Serial, "slave"); +#endif + + master_poll_result = master.poll(); +#if defined(VERBOSE_RESULTS) && (VERBOSE_RESULTS>=3) + master_stream.print_status(Serial, "master"); + slave_stream.print_status(Serial, "slave"); +#endif + +#if defined(VERBOSE_RESULTS) && (VERBOSE_RESULTS>=2) + Serial.print(F(" poll: slave=")); Serial.print(slave_poll_result); + Serial.print(F(" master=")); Serial.print(master_poll_result); + Serial.print(F(" st:")); Serial.print(master.getState()); + Serial.print(F(" out:")); Serial.print(master.getOutCnt()); + Serial.print(F(" in:")); Serial.print(master.getInCnt()); + Serial.print(F(" err:")); Serial.print(master.getErrCnt()); + Serial.print(F(" last:")); Serial.print(master.getLastError()); + Serial.println(""); +#endif +} + + +// +// HOLDING REGISTERS + +uint16_t read_holding_register(uint16_t addr) +{ + telegram.u8fct = MB_FC_READ_REGISTERS; + telegram.u16RegAdd = addr; + telegram.u16CoilsNo = 1; + master.query( telegram ); + while(master.getState()==COM_WAITING) + { + poll(); + } + return telegram.au16reg[0]; +} + +void write_holding_register(uint16_t addr, uint16_t val) +{ + telegram.u8fct = MB_FC_WRITE_REGISTER; + telegram.u16RegAdd = addr; + telegram.u16CoilsNo = 0; + telegram.au16reg[0] = val; + master.query( telegram ); + while(master.getState()==COM_WAITING) + { + poll(); + } +} + +/** Holding registers are read/write. */ +void test_holding_register(uint16_t reg_addr) +{ + // First check the starting value. + uint16_t val = read_holding_register(reg_addr); + uint16_t expected = addr2word(reg_addr); + test_equal("test_holding_register, starting", reg_addr, val, expected); + + // Test write & read-back. + uint16_t new_val = addr2word(~expected+321); + + // Write + write_holding_register(reg_addr, new_val); + test_equal("test_holding_register, write", reg_addr, slave_data[reg_addr], new_val); + + // Read + val = read_holding_register(reg_addr); + test_equal("test_holding_register, read", reg_addr, val, new_val); +} + +void test_oob_holding_register() +{ + uint16_t errcnt0 = master.getErrCnt(); + + // Currently FAILS, because the slave does not check its bounds. + uint16_t val = read_holding_register(slave_data_count); + test_equal( + "test_oob_holding_register, non-existant", + slave_data_count, + master.getErrCnt(), + errcnt0+1 + ); + + // Check that the slave does not read beyond the bounds of its data array. + test_equal( + "test_oob_holding_register, OOB read check", + slave_data_count, + val == slave_data[slave_data_count], + 0 ///< Should be false + ); + + // Check that the slave will not write to arbitrary memory. + uint16_t new_val = 0xBAD; + write_holding_register(slave_data_count, new_val); + test_equal( + "test_oob_holding_register, OOB write check", + slave_data_count, + new_val == slave_data[slave_data_count], + 0 ///< Should be false + ); +} + +void test_holding_registers() +{ + uint16_t errcnt0 = master.getErrCnt(); + for(uint16_t reg_addr=0; reg_addr sizeof(test_data)) + run = sizeof(test_data); + memcpy((void*)begin, (void*)test_data, run); + begin += run; + } +} + + +/** Read/write multiple registers. */ +void test_multiple_registers() +{ + const uint16_t errcnt0 = master.getErrCnt(); + const uint16_t max_num = min(slave_data_count, master_data_count); + for(uint16_t num=2; num +#include + + +class Loopback: public Stream +{ +private: + uint8_t* const buffer; + const uint8_t size; ///< Number of bytes in buffer. + uint8_t head; + uint8_t tail; + Loopback* other; + +public: + Loopback(const uint8_t size_=64): + buffer( new uint8_t[size_] ), + size( size_ ), + head( 0 ), + tail( 0 ), + other( NULL ) + {} + + void connect(Loopback& obj) + { + other = &obj; + obj.other = this; + } + + size_t queue_length() const + { + int result = head; + result -= tail; + if(result < 0) + result += size; + return result; + } + + void print_status(Stream& outstream, const char* label = "?") const + { + size_t len = queue_length(); + outstream.print(F(" Loopback \"")); + outstream.print(label); + outstream.print(F("\": ")); + outstream.print(len); + outstream.print(F(" bytes")); + if(len) + { + outstream.print(F(", next byte = ")); + outstream.print(buffer[tail], HEX); + } + outstream.println(""); + } + +public: + // Print + using Print::write; + + virtual size_t write(uint8_t c) + { + uint8_t i = (head+1) % size; + if(i!=tail) + { + buffer[head] = c; + head = i; + return 1; + } + return 0; + } + + virtual int availableForWrite() + { + return ((head+1) % size != tail); + } + + // Stream + virtual int available() + { + return (other && other->head != other->tail); + } + + virtual int read() + { + if( available() ) + { + int c = other->buffer[ other->tail++ ]; + other->tail %= other->size; + return c; + } + return -1; + } + + virtual int peek() + { + if( available() ) + return other->buffer[ other->tail ]; + else + return -1; + } +}; +