From 91c63699637ad326afb81e787411092d03af4c44 Mon Sep 17 00:00:00 2001 From: Alex Tingle Date: Mon, 3 Jun 2019 21:12:48 +0100 Subject: [PATCH 01/10] Software Serial now conditionally compiled. New macro USE_SOFTWARE_SERIAL may be set to 0 (default, hardware) or 1 (software) to select software/hardware serial implementation at compile time. --- ModbusRtu.h | 160 ++++++++++++++++++++-------------------------------- 1 file changed, 60 insertions(+), 100 deletions(-) diff --git a/ModbusRtu.h b/ModbusRtu.h index 06417f3..c0e7779 100644 --- a/ModbusRtu.h +++ b/ModbusRtu.h @@ -41,7 +41,17 @@ #include #include "Arduino.h" #include "Print.h" -#include + +// Define USE_SOFTWARE_SERIAL as 1, in order to use SoftwareSerial. +// (You cannot use both hardware and software Serial implementations +// at the same time.) +#if !defined(USE_SOFTWARE_SERIAL) +# define USE_SOFTWARE_SERIAL 0 +#endif + +#if (USE_SOFTWARE_SERIAL!=0) +# include +#endif /** * @struct modbus_t @@ -155,8 +165,11 @@ const unsigned char fctsupported[] = class Modbus { private: +#if (USE_SOFTWARE_SERIAL==0) HardwareSerial *port; //!< Pointer to Serial class object - SoftwareSerial *softPort; //!< Pointer to SoftwareSerial class object +#else + SoftwareSerial *port; //!< Pointer to SoftwareSerial class object +#endif 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 @@ -172,7 +185,9 @@ class Modbus uint8_t u8regsize; void init(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin); - void init(uint8_t u8id); +#if (USE_SOFTWARE_SERIAL!=0) + void init(uint8_t u8id); +#endif void sendTxBuffer(); int8_t getRxBuffer(); uint16_t calcCRC(uint8_t u8length); @@ -192,12 +207,14 @@ class Modbus Modbus(); Modbus(uint8_t u8id, uint8_t u8serno); Modbus(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin); +#if (USE_SOFTWARE_SERIAL!=0) 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(SoftwareSerial *sPort, long u32speed, uint8_t u8txenpin); +#else + void begin(long u32speed); void begin(); +#endif void setTimeOut( uint16_t u16timeOut); //!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 @@ -347,9 +316,9 @@ void Modbus::begin(long u32speed) void Modbus::begin(SoftwareSerial *sPort, long u32speed) { - softPort=sPort; + port=sPort; - softPort->begin(u32speed); + port->begin(u32speed); if (u8txenpin > 1) // pin 0 & pin 1 are reserved for RX/TX { @@ -358,11 +327,12 @@ 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. @@ -379,9 +349,9 @@ void Modbus::begin(SoftwareSerial *sPort, long u32speed, uint8_t u8txenpin) { this->u8txenpin=u8txenpin; - softPort=sPort; + port = sPort; - softPort->begin(u32speed); + port->begin(u32speed); if (u8txenpin > 1) // pin 0 & pin 1 are reserved for RX/TX { @@ -390,11 +360,15 @@ void Modbus::begin(SoftwareSerial *sPort, long u32speed, uint8_t u8txenpin) digitalWrite(u8txenpin, LOW); } - while(softPort->read() >= 0); + while(port->read() >= 0); u8lastRec = u8BufferSize = 0; u16InCnt = u16OutCnt = u16errCnt = 0; } + +#else // USE_SOFTWARE_SERIAL==0: + + /** * @brief * Initialize class object. @@ -404,10 +378,9 @@ void Modbus::begin(SoftwareSerial *sPort, long u32speed, uint8_t u8txenpin) * * @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) +void Modbus::begin(long u32speed) { switch( u8serno ) @@ -435,7 +408,7 @@ void Modbus::begin(SoftwareSerial *sPort, long u32speed, uint8_t u8txenpin) break; } - port->begin(u32speed, u8config); + port->begin(u32speed); if (u8txenpin > 1) // pin 0 & pin 1 are reserved for RX/TX { // return RS485 transceiver to transmit mode @@ -446,7 +419,7 @@ void Modbus::begin(SoftwareSerial *sPort, long u32speed, uint8_t u8txenpin) while(port->read() >= 0); u8lastRec = u8BufferSize = 0; u16InCnt = u16OutCnt = u16errCnt = 0; -} */ +} /** * @brief @@ -463,6 +436,9 @@ void Modbus::begin() begin(19200); } +#endif // USE_SOFTWARE_SERIAL + + /** * @brief * Method to write a new slave ID address @@ -714,10 +690,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 +777,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; @@ -884,6 +854,8 @@ void Modbus::init(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin) this->u32overTime = 0; } + +#if (USE_SOFTWARE_SERIAL!=0) void Modbus::init(uint8_t u8id) { this->u8id = u8id; @@ -892,6 +864,8 @@ void Modbus::init(uint8_t u8id) this->u16timeOut = 1000; this->u32overTime = 0; } +#endif + /** * @brief @@ -907,22 +881,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) @@ -961,26 +926,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) { +#if (USE_SOFTWARE_SERIAL==0) // must wait transmission end before changing pin state // soft serial does not need it since it is blocking - if (u8serno < 4) - port->flush(); + port->flush(); +#endif // 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; From 3346c6b2b31a7156934ba695706ccfb5c4f733cc Mon Sep 17 00:00:00 2001 From: Alex Tingle Date: Mon, 3 Jun 2019 21:18:44 +0100 Subject: [PATCH 02/10] Update example to set USE_SOFTWARE_SERIAL. --- .../software_serial_simple_master.ino | 3 +++ 1 file changed, 3 insertions(+) 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..1d3c241 100644 --- a/examples/software_serial_simple_master/software_serial_simple_master.ino +++ b/examples/software_serial_simple_master/software_serial_simple_master.ino @@ -41,6 +41,9 @@ This example code is in the public domain. */ +// Must set this macro to 1 *before* including ModbusRtu.h header. +#define USE_SOFTWARE_SERIAL 1 + #include #include From 43a41592debe6c2ca709110788a6593014d55636 Mon Sep 17 00:00:00 2001 From: Alex Tingle Date: Tue, 4 Jun 2019 19:34:01 +0100 Subject: [PATCH 03/10] Eliminate reduntant ctor/begin/init methods. Replace overloaded methods with default parameter values. Eliminate duplicate code by having initialisation methods call each other, rather than replicate code. Preserves all possible ways that users might be contructing a working ModbusRtu object. --- ModbusRtu.h | 211 +++++++++++++--------------------------------------- 1 file changed, 52 insertions(+), 159 deletions(-) diff --git a/ModbusRtu.h b/ModbusRtu.h index c0e7779..c9ed247 100644 --- a/ModbusRtu.h +++ b/ModbusRtu.h @@ -171,7 +171,6 @@ class Modbus SoftwareSerial *port; //!< Pointer to SoftwareSerial class object #endif 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; @@ -184,10 +183,6 @@ class Modbus uint32_t u32time, u32timeOut, u32overTime; uint8_t u8regsize; - void init(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin); -#if (USE_SOFTWARE_SERIAL!=0) - void init(uint8_t u8id); -#endif void sendTxBuffer(); int8_t getRxBuffer(); uint16_t calcCRC(uint8_t u8length); @@ -204,17 +199,12 @@ 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=0, uint8_t u8serno=0, uint8_t u8txenpin=0); #if (USE_SOFTWARE_SERIAL!=0) - Modbus(uint8_t u8id); void begin(SoftwareSerial *sPort, long u32speed); void begin(SoftwareSerial *sPort, long u32speed, uint8_t u8txenpin); -#else - void begin(long u32speed); - void begin(); #endif + void begin(long u32speed = 19200); void setTimeOut( uint16_t u16timeOut); //!u8id = u8id; + this->u8txenpin = u8txenpin; + this->u16timeOut = 1000; + this->u32overTime = 0; #if (USE_SOFTWARE_SERIAL!=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); + (void)u8serno; // Suppress warning about unused parameter. + +#else + + 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; + } + +#endif // USE_SOFTWARE_SERIAL } +#if (USE_SOFTWARE_SERIAL!=0) + /** * @brief * Initialize class object. @@ -315,21 +301,8 @@ Modbus::Modbus(uint8_t u8id) */ void Modbus::begin(SoftwareSerial *sPort, long u32speed) { - - port=sPort; - - 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; + port = sPort; + begin(u32speed); } @@ -347,26 +320,11 @@ void Modbus::begin(SoftwareSerial *sPort, long u32speed) */ void Modbus::begin(SoftwareSerial *sPort, long u32speed, uint8_t u8txenpin) { - - this->u8txenpin=u8txenpin; - port = sPort; - - 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; + this->u8txenpin = u8txenpin; + begin(sPort, u32speed); } - -#else // USE_SOFTWARE_SERIAL==0: +#endif // USE_SOFTWARE_SERIAL /** @@ -377,37 +335,11 @@ void Modbus::begin(SoftwareSerial *sPort, long u32speed, uint8_t u8txenpin) * 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 speed baud rate, in standard increments (300..115200). Default=19200 * @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 { @@ -421,23 +353,6 @@ void Modbus::begin(long u32speed) 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); -} - -#endif // USE_SOFTWARE_SERIAL - /** * @brief @@ -845,28 +760,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; -} - - -#if (USE_SOFTWARE_SERIAL!=0) -void Modbus::init(uint8_t u8id) -{ - this->u8id = u8id; - this->u8serno = 4; - this->u8txenpin = 0; - this->u16timeOut = 1000; - this->u32overTime = 0; -} -#endif - - /** * @brief * This method moves Serial buffer data to the Modbus au8Buffer. @@ -918,7 +811,7 @@ void Modbus::sendTxBuffer() u8BufferSize++; au8Buffer[ u8BufferSize ] = u16crc & 0x00ff; u8BufferSize++; - + if (u8txenpin > 1) { // set RS485 transceiver to transmit mode From 3d8e5f31920f96261e2228edc95c1818a9242c20 Mon Sep 17 00:00:00 2001 From: Alex Tingle Date: Tue, 4 Jun 2019 23:40:34 +0100 Subject: [PATCH 04/10] Vastly simpler solution. Eliminate any dependence upon SoftwareSerial class. ModbusRtu now just refers to the base class Stream, which is the parent of both HardwareSerial and SoftwareSerial. The only function that is not in the base class is begin(). Ideally, users will call this function on their port object directly, and then call the new ModbusRtu::start() function. I've added in a couple of template begin() methods that enable existing code to work without modification. These should be deprecated, to encourage users to begin() their port objects directly. This approach also fixes Issue #44 - "how to setup Serial settings: parity, stop bits". --- ModbusRtu.h | 160 +++++++++++------- .../software_serial_simple_master.ino | 11 +- 2 files changed, 102 insertions(+), 69 deletions(-) diff --git a/ModbusRtu.h b/ModbusRtu.h index c9ed247..fdd9d36 100644 --- a/ModbusRtu.h +++ b/ModbusRtu.h @@ -40,18 +40,7 @@ #include #include "Arduino.h" -#include "Print.h" -// Define USE_SOFTWARE_SERIAL as 1, in order to use SoftwareSerial. -// (You cannot use both hardware and software Serial implementations -// at the same time.) -#if !defined(USE_SOFTWARE_SERIAL) -# define USE_SOFTWARE_SERIAL 0 -#endif - -#if (USE_SOFTWARE_SERIAL!=0) -# include -#endif /** * @struct modbus_t @@ -165,13 +154,9 @@ const unsigned char fctsupported[] = class Modbus { private: -#if (USE_SOFTWARE_SERIAL==0) - HardwareSerial *port; //!< Pointer to Serial class object -#else - SoftwareSerial *port; //!< Pointer to SoftwareSerial class object -#endif + Stream *port; //!< Pointer to Stream class object (Either HardwareSerial or SoftwareSerial) uint8_t u8id; //!< 0=master, 1..247=slave number - 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]; @@ -199,12 +184,20 @@ class Modbus void buildException( uint8_t u8exception ); // build exception message public: + template + Modbus(uint8_t u8id, T_Stream& port, uint8_t u8txenpin); + Modbus(uint8_t u8id=0, uint8_t u8serno=0, uint8_t u8txenpin=0); -#if (USE_SOFTWARE_SERIAL!=0) - void begin(SoftwareSerial *sPort, long u32speed); - void begin(SoftwareSerial *sPort, long u32speed, uint8_t u8txenpin); -#endif + void start(); + + template + void begin(T_Stream* port_, long u32speed_); + + template + void begin(T_Stream* port_, long u32speed_, uint8_t u8txenpin_); + void begin(long u32speed = 19200); + void setTimeOut( uint16_t u16timeOut); //! +Modbus::Modbus(uint8_t u8id, T_Stream& port, uint8_t u8txenpin) +{ + this->port = &port; + this->u8id = u8id; + this->u8txenpin = u8txenpin; + this->u16timeOut = 1000; + this->u32overTime = 0; +} + + /** * @brief * Constructor for a Master/Slave. @@ -240,9 +267,7 @@ class Modbus * @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, uint8_t u8serno, uint8_t u8txenpin) - * @overload Modbus::Modbus(uint8_t u8id) - * @overload Modbus::Modbus() + * @overload Modbus::Modbus(uint8_t u8id, T_Stream& port, uint8_t u8txenpin) */ Modbus::Modbus(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin) { @@ -251,12 +276,6 @@ Modbus::Modbus(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin) this->u16timeOut = 1000; this->u32overTime = 0; -#if (USE_SOFTWARE_SERIAL!=0) - - (void)u8serno; // Suppress warning about unused parameter. - -#else - switch( u8serno ) { #if defined(UBRR1H) @@ -281,57 +300,82 @@ Modbus::Modbus(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin) port = &Serial; break; } - -#endif // USE_SOFTWARE_SERIAL } -#if (USE_SOFTWARE_SERIAL!=0) +/** + * @brief + * Start-up class object. + * + * 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.) + * + * @ingroup setup + */ +void Modbus::start() +{ + 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. + * Install a serial port into ModbusRtu, 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(). * - * @param sPort *softPort, pointer to SoftwareSerial class object - * @param u32speed baud rate, in standard increments (300..115200) + * @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) +template +void Modbus::begin(T_Stream* install_port, long u32speed) { - port = sPort; - begin(u32speed); + port = install_port; + install_port->begin(u32speed); + start(); } /** * @brief - * Initialize class object. + * Install a serial port into ModbusRtu, 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(). * - * @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) + * @param u8txenpin pin for txen RS-485 (=0 means USB/RS232C mode) * @ingroup setup */ -void Modbus::begin(SoftwareSerial *sPort, long u32speed, uint8_t u8txenpin) +template +void Modbus::begin(T_Stream* install_port, long u32speed, uint8_t u8txenpin) { this->u8txenpin = u8txenpin; - begin(sPort, u32speed); + this->port = install_port; + install_port->begin(u32speed); + start(); } -#endif // USE_SOFTWARE_SERIAL - /** * @brief - * Initialize class object. + * begin() hardware serial port and start ModbusRtu. * - * Sets up the serial port using specified baud rate. + * Sets up the HardwareSerial port using specified baud rate. * Call once class has been instantiated, typically within setup(). * * @see http://arduino.cc/en/Serial/Begin#.Uy4CJ6aKlHY @@ -340,17 +384,9 @@ void Modbus::begin(SoftwareSerial *sPort, long u32speed, uint8_t u8txenpin) */ void Modbus::begin(long u32speed) { - 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; + // !!Can ONLY do this if port ACTUALLY IS a HardwareSerial object!! + static_cast(port)->begin(u32speed); + start(); } @@ -823,11 +859,11 @@ void Modbus::sendTxBuffer() if (u8txenpin > 1) { -#if (USE_SOFTWARE_SERIAL==0) // must wait transmission end before changing pin state // soft serial does not need it since it is blocking + // ...but the implementation in SoftwareSerial does nothing + // anyway, so no harm in calling it. port->flush(); -#endif // return RS485 transceiver to receive mode volatile uint32_t u32overTimeCountDown = u32overTime; while ( u32overTimeCountDown-- > 0); 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 1d3c241..346f84f 100644 --- a/examples/software_serial_simple_master/software_serial_simple_master.ino +++ b/examples/software_serial_simple_master/software_serial_simple_master.ino @@ -41,9 +41,6 @@ This example code is in the public domain. */ -// Must set this macro to 1 *before* including ModbusRtu.h header. -#define USE_SOFTWARE_SERIAL 1 - #include #include @@ -51,6 +48,8 @@ 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 @@ -58,7 +57,7 @@ uint8_t u8state; * 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 @@ -67,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 + master.start(); // start the ModBus object. master.setTimeOut( 2000 ); // if there is no answer in 2000 ms, roll over u32wait = millis() + 1000; u8state = 0; From cb023681cb4a5837d89d3d939b3b58494a8e6509 Mon Sep 17 00:00:00 2001 From: Alex Tingle Date: Wed, 5 Jun 2019 10:17:14 +0100 Subject: [PATCH 05/10] Deprecated backwards compatibility functions. This will create a warning in users' build output, which will encourage them to switch to the new, simpler constructor. Changed the documentation to describe how to upgrade. --- ModbusRtu.h | 56 +++++++++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/ModbusRtu.h b/ModbusRtu.h index fdd9d36..da4aa89 100644 --- a/ModbusRtu.h +++ b/ModbusRtu.h @@ -187,17 +187,7 @@ class Modbus template Modbus(uint8_t u8id, T_Stream& port, uint8_t u8txenpin); - Modbus(uint8_t u8id=0, uint8_t u8serno=0, uint8_t u8txenpin=0); void start(); - - template - void begin(T_Stream* port_, long u32speed_); - - template - void begin(T_Stream* port_, long u32speed_, uint8_t u8txenpin_); - - void begin(long u32speed = 19200); - 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_____________________________________________________ */ @@ -253,15 +260,10 @@ Modbus::Modbus(uint8_t u8id, T_Stream& port, uint8_t u8txenpin) /** * @brief - * Constructor for a Master/Slave. + * DEPRECATED constructor for a Master/Slave. * - * For hardware serial through USB/RS232C/RS485 set u8serno to 0..3. - * It needs a pin for flow control only for RS485 mode - * - * For software serial through RS232C/RS485 u8serno is ignored. - * It needs a pin for flow control only for RS485 mode - * If you use software serial you have to begin ModBus object by - * using "void Modbus::begin(SoftwareSerial *softPort, long u32speed)". + * THIS CONSTRUCTOR IS ONLY PROVIDED FOR BACKWARDS COMPATIBILITY. + * USE Modbus(uint8_t, T_Stream&, uint8_t) INSTEAD. * * @param u8id node address 0=master, 1..247=slave * @param u8serno serial port used 0..3 (ignored for software serial) @@ -331,10 +333,10 @@ void Modbus::start() /** * @brief - * Install a serial port into ModbusRtu, begin() it, and start ModbusRtu. + * 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 install_port pointer to SoftwareSerial or HardwareSerial class object * @param u32speed baud rate, in standard increments (300..115200) @@ -351,10 +353,10 @@ void Modbus::begin(T_Stream* install_port, long u32speed) /** * @brief - * Install a serial port into ModbusRtu, begin() it, and start ModbusRtu. + * 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 install_port pointer to SoftwareSerial or HardwareSerial class object * @param u32speed baud rate, in standard increments (300..115200) @@ -373,10 +375,10 @@ void Modbus::begin(T_Stream* install_port, long u32speed, uint8_t u8txenpin) /** * @brief - * begin() hardware serial port and start ModbusRtu. + * DEPRECATED. begin() hardware serial port and start ModbusRtu. * - * Sets up the HardwareSerial 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). Default=19200 From b9350634509dbf71881203de030e5730622b053a Mon Sep 17 00:00:00 2001 From: Alex Tingle Date: Wed, 5 Jun 2019 10:24:51 +0100 Subject: [PATCH 06/10] Made constructor param #3 optional. --- ModbusRtu.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModbusRtu.h b/ModbusRtu.h index da4aa89..bbcc34c 100644 --- a/ModbusRtu.h +++ b/ModbusRtu.h @@ -185,7 +185,7 @@ class Modbus public: template - Modbus(uint8_t u8id, T_Stream& port, uint8_t u8txenpin); + Modbus(uint8_t u8id, T_Stream& port, uint8_t u8txenpin =0); void start(); void setTimeOut( uint16_t u16timeOut); //! Date: Wed, 5 Jun 2019 10:28:35 +0100 Subject: [PATCH 07/10] Updated examples to use new constructor. --- examples/RS485_slave/RS485_slave.ino | 7 ++++--- examples/advanced_master/advanced_master.ino | 9 +++++---- examples/advanced_slave/advanced_slave.ino | 5 +++-- examples/simple_master/simple_master.ino | 7 ++++--- examples/simple_slave/simple_slave.ino | 7 ++++--- examples/simple_slave2/simple_slave2.ino | 7 ++++--- .../software_serial_simple_master.ino | 4 ++-- 7 files changed, 26 insertions(+), 20 deletions(-) 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 346f84f..2e5c532 100644 --- a/examples/software_serial_simple_master/software_serial_simple_master.ino +++ b/examples/software_serial_simple_master/software_serial_simple_master.ino @@ -53,7 +53,7 @@ SoftwareSerial mySerial(3, 5);//Create a SoftwareSerial object so that we can us /** * 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 */ @@ -67,7 +67,7 @@ modbus_t telegram; unsigned long u32wait; void setup() { - Serial.begin(9600);//use the hardware serial if you want to connect to your computer via usb cable, etc. + 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; From d98f14ff1b915b20d797b0b49738e199256aca7d Mon Sep 17 00:00:00 2001 From: Alex Tingle Date: Wed, 5 Jun 2019 10:59:28 +0100 Subject: [PATCH 08/10] Remove unnecessary template. --- ModbusRtu.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ModbusRtu.h b/ModbusRtu.h index bbcc34c..1584005 100644 --- a/ModbusRtu.h +++ b/ModbusRtu.h @@ -184,8 +184,7 @@ class Modbus void buildException( uint8_t u8exception ); // build exception message public: - template - Modbus(uint8_t u8id, T_Stream& port, uint8_t u8txenpin =0); + Modbus(uint8_t u8id, Stream& port, uint8_t u8txenpin =0); void start(); void setTimeOut( uint16_t u16timeOut); //! -Modbus::Modbus(uint8_t u8id, T_Stream& port, uint8_t u8txenpin) +Modbus::Modbus(uint8_t u8id, Stream& port, uint8_t u8txenpin) { this->port = &port; this->u8id = u8id; From e4999a92b2c5e3e0aad3887c3c448be0302fc9f5 Mon Sep 17 00:00:00 2001 From: Alex Tingle Date: Wed, 12 Jun 2019 22:13:04 +0100 Subject: [PATCH 09/10] Test harness. 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". --- test/echo/echo.ino | 421 +++++++++++++++++++++++++++++++++++++++ test/echo/src/loopback.h | 102 ++++++++++ 2 files changed, 523 insertions(+) create mode 100644 test/echo/echo.ino create mode 100644 test/echo/src/loopback.h 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; + } +}; + From 08bae2c4ccf4fa1c205ff10d09a49569c4934f0e Mon Sep 17 00:00:00 2001 From: Alex Tingle Date: Wed, 12 Jun 2019 22:16:27 +0100 Subject: [PATCH 10/10] Bug fixes - clear old data in comms buffers. Modbus::get_FC1() - the code used to preserve old data in the high byte of au16regs words. So, if a single coil is returned from the slave, au16regs[0]'s high byte was left with junk in it. Modbus::process_FC1() - make sure that the data returned to the master is clean. Unused bits should be zeroed, according to the spec. --- ModbusRtu.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ModbusRtu.h b/ModbusRtu.h index 1584005..5ff4df1 100644 --- a/ModbusRtu.h +++ b/ModbusRtu.h @@ -1061,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]); } } @@ -1116,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;