Skip to content

Commit

Permalink
Bugfix Modbus RTU Request form, code comments
Browse files Browse the repository at this point in the history
  • Loading branch information
budulinek committed Jan 15, 2024
1 parent 35b8b19 commit b16885c
Show file tree
Hide file tree
Showing 6 changed files with 280 additions and 157 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Allows you to connect your Modbus devices (such as sensors, energy meters, HVAC
- all web interface inputs have proper validation
- factory defaults for user settings can be specified in advanced_settings.h
- settings marked \* are only available if ENABLE_DHCP is defined in the sketch
- settings marked \*\* are only available if ENABLE_EXTRA_DIAG is defined in the sketch
- settings marked \*\* are only available if ENABLE_EXTENDED_WEBUI is defined in the sketch
* advanced settings:
- can be changed in sketch (advanced_settings.h)
- stored in flash memory
Expand Down Expand Up @@ -98,7 +98,7 @@ Connect your Arduino to ethernet and use your web browser to access the web inte
Enjoy :-)
# Settings
- settings marked \* are only available if ENABLE_DHCP is defined in the sketch
- settings marked \*\* are only available if ENABLE_EXTRA_DIAG is defined in the sketch
- settings marked \*\* are only available if ENABLE_EXTENDED_WEBUI is defined in the sketch

## System Info
<img src="pics/modbus1.png" alt="modbus1" style="zoom:100%;" />
Expand Down Expand Up @@ -260,7 +260,7 @@ The number of used sockets is determined (by the Ethernet.h library) based on mi

## Memory

Not everything could fit into the limited flash memory of Arduino Nano / Uno. If you have a microcontroller with more memory (such as Mega), you can enable extra settings in the main sketch by defining ENABLE_DHCP and/or ENABLE_EXTRA_DIAG in advanced settings.
Not everything could fit into the limited flash memory of Arduino Nano / Uno. If you have a microcontroller with more memory (such as Mega), you can enable extra settings in the main sketch by defining ENABLE_DHCP and/or ENABLE_EXTENDED_WEBUI in advanced settings.

# Links and Credits

Expand Down
149 changes: 80 additions & 69 deletions arduino-modbus-rtu-tcp-gateway/01-interfaces.ino
Original file line number Diff line number Diff line change
@@ -1,51 +1,8 @@
/* *******************************************************************
Ethernet and serial interface functions
startSerial()
- starts HW serial interface which we use for RS485 line
charTime(), charTimeOut(), frameDelay()
- calculate Modbus RTU character timeout and inter-frame delay
startEthernet()
- initiates ethernet interface
- if enabled, gets IP from DHCP
- starts all servers (Modbus TCP, UDP, web server)
resetFunc()
- well... resets Arduino
maintainDhcp()
- maintain DHCP lease
maintainUptime()
- maintains up time in case of millis() overflow
maintainCounters(), rollover()
- synchronizes roll-over of data counters to zero
resetStats()
- resets Modbus stats
generateMac()
- generate random MAC using pseudo random generator (faster and than build-in random())
manageSockets()
- closes sockets which are waiting to be closed or which refuse to close
- forwards sockets with data available (webserver or Modbus TCP) for further processing
- disconnects (closes) sockets which are too old / idle for too long
- opens new sockets if needed (and if available)
CreateTrulyRandomSeed()
- seed pseudorandom generator using watch dog timer interrupt (works only on AVR)
- see https://sites.google.com/site/astudyofentropy/project-definition/timer-jitter-entropy-sources/entropy-library/arduino-random-seed
+ preprocessor code for identifying microcontroller board
***************************************************************** */


/**************************************************************************/
/*!
@brief Initiates HW serial interface which we use for the RS485 line.
*/
/**************************************************************************/
void startSerial() {
mySerial.begin((data.config.baud * 100UL), data.config.serialConfig);
#ifdef RS485_CONTROL_PIN
Expand All @@ -54,7 +11,7 @@ void startSerial() {
#endif /* RS485_CONTROL_PIN */
}

// number of bits per character (11 in default Modbus RTU settings)
// Number of bits per character (11 in default Modbus RTU settings)
byte bitsPerChar() {
byte bits =
1 + // start bit
Expand All @@ -64,7 +21,7 @@ byte bitsPerChar() {
return bits;
}

// character timeout in micros
// Character timeout in micros
uint32_t charTimeOut() {
if (data.config.baud <= 192) {
return (15000UL * bitsPerChar()) / data.config.baud; // inter-character time-out should be 1,5T
Expand All @@ -73,7 +30,7 @@ uint32_t charTimeOut() {
}
}

// minimum frame delay in micros
// Minimum frame delay in micros
uint32_t frameDelay() {
if (data.config.baud <= 192) {
return (35000UL * bitsPerChar()) / data.config.baud; // inter-frame delay should be 3,5T
Expand All @@ -82,6 +39,12 @@ uint32_t frameDelay() {
}
}

/**************************************************************************/
/*!
@brief Initiates ethernet interface, if DHCP enabled, gets IP from DHCP,
starts all servers (UDP, web server).
*/
/**************************************************************************/
void startEthernet() {
if (ETH_RESET_PIN != 0) {
pinMode(ETH_RESET_PIN, OUTPUT);
Expand Down Expand Up @@ -112,8 +75,18 @@ void startEthernet() {
#endif
}

/**************************************************************************/
/*!
@brief Resets Arduino (works only on AVR chips).
*/
/**************************************************************************/
void (*resetFunc)(void) = 0; //declare reset function at address 0

/**************************************************************************/
/*!
@brief Checks SPI connection to the W5X00 chip.
*/
/**************************************************************************/
void checkEthernet() {
static byte attempts = 0;
IPAddress tempIP = Ethernet.localIP();
Expand All @@ -128,6 +101,11 @@ void checkEthernet() {
checkEthTimer.sleep(CHECK_ETH_INTERVAL);
}

/**************************************************************************/
/*!
@brief Maintains DHCP lease.
*/
/**************************************************************************/
#ifdef ENABLE_DHCP
void maintainDhcp() {
if (data.config.enableDhcp && dhcpSuccess == true) { // only call maintain if initial DHCP request by startEthernet was successfull
Expand All @@ -136,6 +114,11 @@ void maintainDhcp() {
}
#endif /* ENABLE_DHCP */

/**************************************************************************/
/*!
@brief Maintains uptime in case of millis() overflow.
*/
/**************************************************************************/
#ifdef ENABLE_EXTENDED_WEBUI
void maintainUptime() {
uint32_t milliseconds = millis();
Expand All @@ -152,8 +135,12 @@ void maintainUptime() {
}
#endif /* ENABLE_EXTENDED_WEBUI */

/**************************************************************************/
/*!
@brief Synchronizes roll-over of data counters to zero.
*/
/**************************************************************************/
bool rollover() {
// synchronize roll-over of run time, data counters and modbus stats to zero, at 0xFFFFFF00
const uint32_t ROLLOVER = 0xFFFFFF00;
for (byte i = 0; i < ERROR_LAST; i++) {
if (data.errorCnt[i] > ROLLOVER) {
Expand All @@ -173,7 +160,11 @@ bool rollover() {
return false;
}

// resets counters to 0: data.errorCnt, data.rtuCnt, data.ethCnt
/**************************************************************************/
/*!
@brief Resets error stats, RTU counter and ethernet data counter.
*/
/**************************************************************************/
void resetStats() {
memset(data.errorCnt, 0, sizeof(data.errorCnt));
#ifdef ENABLE_EXTENDED_WEBUI
Expand All @@ -183,7 +174,12 @@ void resetStats() {
#endif /* ENABLE_EXTENDED_WEBUI */
}

// generate new MAC (bytes 0, 1 and 2 are static, bytes 3, 4 and 5 are generated randomly)
/**************************************************************************/
/*!
@brief Generate random MAC using pseudo random generator,
bytes 0, 1 and 2 are static (MAC_START), bytes 3, 4 and 5 are generated randomly
*/
/**************************************************************************/
void generateMac() {
// Marsaglia algorithm from https://github.com/RobTillaart/randomHelpers
seed1 = 36969L * (seed1 & 65535L) + (seed1 >> 16);
Expand All @@ -196,21 +192,29 @@ void generateMac() {
}
}

/**************************************************************************/
/*!
@brief Write (update) data to Arduino EEPROM.
*/
/**************************************************************************/
void updateEeprom() {
eepromTimer.sleep(EEPROM_INTERVAL * 60UL * 60UL * 1000UL); // EEPROM_INTERVAL is in hours, sleep is in milliseconds!
data.eepromWrites++; // we assume that at least some bytes are written to EEPROM during EEPROM.update or EEPROM.put
EEPROM.put(DATA_START, data);
}

#if MAX_SOCK_NUM == 8
uint32_t lastSocketUse[MAX_SOCK_NUM] = { 0, 0, 0, 0, 0, 0, 0, 0 };
byte socketInQueue[MAX_SOCK_NUM] = { 0, 0, 0, 0, 0, 0, 0, 0 };
#elif MAX_SOCK_NUM == 4
uint32_t lastSocketUse[MAX_SOCK_NUM] = { 0, 0, 0, 0 };
byte socketInQueue[MAX_SOCK_NUM] = { 0, 0, 0, 0 };
#endif

// from https://github.com/SapientHetero/Ethernet/blob/master/src/socket.cpp
uint32_t lastSocketUse[MAX_SOCK_NUM];
byte socketInQueue[MAX_SOCK_NUM];
/**************************************************************************/
/*!
@brief Closes sockets which are waiting to be closed or which refuse to close,
forwards sockets with data available for further processing by the webserver,
disconnects (closes) sockets which are too old (idle for too long), opens
new sockets if needed (and if available).
From https://github.com/SapientHetero/Ethernet/blob/master/src/socket.cpp
*/
/**************************************************************************/
void manageSockets() {
uint32_t maxAge = 0; // the 'age' of the socket in a 'disconnectable' state that was last used the longest time ago
byte oldest = MAX_SOCK_NUM; // the socket number of the 'oldest' disconnectable socket
Expand Down Expand Up @@ -308,6 +312,12 @@ void manageSockets() {
// we do not need SPI.beginTransaction(SPI_ETHERNET_SETTINGS) or SPI.endTransaction() ??
}

/**************************************************************************/
/*!
@brief Disconnect or close a socket.
@param s Socket number.
*/
/**************************************************************************/
void disconSocket(byte s) {
if (W5100.readSnSR(s) == SnSR::ESTABLISHED) {
W5100.execCmdSn(s, Sock_DISCON); // Sock_DISCON does not close LISTEN sockets
Expand All @@ -317,7 +327,13 @@ void disconSocket(byte s) {
}
}

// https://sites.google.com/site/astudyofentropy/project-definition/timer-jitter-entropy-sources/entropy-library/arduino-random-seed

/**************************************************************************/
/*!
@brief Seed pseudorandom generator using watch dog timer interrupt (works only on AVR).
See https://sites.google.com/site/astudyofentropy/project-definition/timer-jitter-entropy-sources/entropy-library/arduino-random-seed
*/
/**************************************************************************/
void CreateTrulyRandomSeed() {
seed1 = 0;
nrot = 32; // Must be at least 4, but more increased the uniformity of the produced seeds entropy.
Expand All @@ -344,11 +360,9 @@ ISR(WDT_vect) {
seed1 = seed1 ^ TCNT1L;
}

// Board definitions
// Preprocessor code for identifying microcontroller board
#if defined(TEENSYDUINO)

// --------------- Teensy -----------------

#if defined(__AVR_ATmega32U4__)
#define BOARD F("Teensy 2.0")
#elif defined(__AVR_AT90USB1286__)
Expand All @@ -366,9 +380,7 @@ ISR(WDT_vect) {
#else
#define BOARD F("Unknown Board")
#endif

#else // --------------- Arduino ------------------

#if defined(ARDUINO_AVR_ADK)
#define BOARD F("Arduino Mega Adk")
#elif defined(ARDUINO_AVR_BT) // Bluetooth
Expand Down Expand Up @@ -422,5 +434,4 @@ ISR(WDT_vect) {
#else
#define BOARD F("Unknown Board")
#endif

#endif
27 changes: 25 additions & 2 deletions arduino-modbus-rtu-tcp-gateway/02-modbus-tcp.ino
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ byte masks[8] = { 1, 2, 4, 8, 16, 32, 64, 128 };

uint16_t crc;

/**************************************************************************/
/*!
@brief Receives Modbus UDP (or Modbus RTU over UDP) messages, calls @ref checkRequest()
*/
/**************************************************************************/
void recvUdp() {
uint16_t msgLength = Udp.parsePacket();
if (msgLength) {
Expand Down Expand Up @@ -80,6 +85,11 @@ void recvUdp() {
}
}

/**************************************************************************/
/*!
@brief Receives Modbus TCP (or Modbus RTU over TCP) messages, calls @ref checkRequest()
*/
/**************************************************************************/
void recvTcp(EthernetClient &client) {
uint16_t msgLength = client.available();
#ifdef ENABLE_EXTENDED_WEBUI
Expand Down Expand Up @@ -150,6 +160,19 @@ void scanRequest() {
}
}

/**************************************************************************/
/*!
@brief Checks Modbus TCP/UDP requests (correct MBAP header,
CRC in case of Modbus RTU over TCP/UDP), checks availability of queue,
stores requests into queue or returns an error.
@param inBuffer Modbus TCP/UDP requests
@param msgLength Length of the Modbus TCP/UDP requests
@param remoteIP Remote IP
@param remotePort Remote port
@param requestType UDP or TCP, priority or scan request
@return Modbus error code to be sent back to the recipient.
*/
/**************************************************************************/
byte checkRequest(byte inBuffer[], uint16_t msgLength, const uint32_t remoteIP, const uint16_t remotePort, byte requestType) {
byte addressPos = 6 * !data.config.enableRtuOverTcp; // position of slave address in the incoming TCP/UDP message (0 for Modbus RTU over TCP/UDP and 6 for Modbus RTU over TCP/UDP)
if (data.config.enableRtuOverTcp) { // check CRC for Modbus RTU over TCP/UDP
Expand All @@ -167,12 +190,12 @@ byte checkRequest(byte inBuffer[], uint16_t msgLength, const uint32_t remoteIP,
// check if we have space in request queue
if (queueHeaders.available() < 1 || queueData.available() < msgLength) {
setSlaveStatus(inBuffer[addressPos], SLAVE_ERROR_0A, true, false);
return 0x0A; // return modbus error 0x0A (Gateway Overloaded)
return 0x0A; // return Modbus error code 10 (Gateway Overloaded)
}
// allow only one request to non responding slaves
if (getSlaveStatus(inBuffer[addressPos], SLAVE_ERROR_0B_QUEUE)) {
data.errorCnt[SLAVE_ERROR_0B]++;
return 0x0B; // return modbus error 11 (Gateway Target Device Failed to Respond)
return 0x0B; // return Modbus error code 11 (Gateway Target Device Failed to Respond)
} else if (getSlaveStatus(inBuffer[addressPos], SLAVE_ERROR_0B)) {
setSlaveStatus(inBuffer[addressPos], SLAVE_ERROR_0B_QUEUE, true, false);
} else {
Expand Down
Loading

0 comments on commit b16885c

Please sign in to comment.