Skip to content

Commit cd864da

Browse files
authored
Merge pull request #22 from budulinek/dev
Dev
2 parents 4bdf86b + 1cbbbe2 commit cd864da

File tree

9 files changed

+455
-419
lines changed

9 files changed

+455
-419
lines changed

README.md

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ Change settings of your Arduino-based Modbus RTU to Modbus TCP/UDP gateway via w
3030
- send Modbus request and recieve Modbus response
3131
- scan Modbus slaves on RS485 interface
3232
- queue (buffer) status
33-
- error counts
33+
- counters ("RTU Data", "Ethernet Data", "Modbus Statistics") are periodically saved to EEPROM (every 6 hours)
34+
- unsigned longs are used, rollover of counters is synchronized
3435
- content of the Modbus Status page is updated in the background (fetch API), javascript alert is shown if connection is lost
3536
* optimized TCP socket management (web interface and Modbus TCP):
3637
- gateway always listens for new web and Modbus TCP connections
@@ -45,10 +46,11 @@ Change settings of your Arduino-based Modbus RTU to Modbus TCP/UDP gateway via w
4546
- stored in EEPROM
4647
- retained during firmware upgrade (only in case of major version change, Arduino loads factory defaults)
4748
- all web interface inputs have proper validation
49+
- factory defaults for user settings can be specified in advanced_settings.h
4850
- settings marked \* are only available if ENABLE_DHCP is defined in the sketch
4951
- settings marked \*\* are only available if ENABLE_EXTRA_DIAG is defined in the sketch
5052
* advanced settings:
51-
- can be changed in sketch (see the initial section of arduino-modbus-rtu-tcp-gateway.ino)
53+
- can be changed in sketch (advanced_settings.h)
5254
- stored in flash memory
5355

5456
<img src="/pics/modbus1.png" alt="01" style="zoom:100%;" />
@@ -57,6 +59,10 @@ Change settings of your Arduino-based Modbus RTU to Modbus TCP/UDP gateway via w
5759

5860
**Reboot**.
5961

62+
**EEPROM Health**. Keeps track of EEPROM write cycles (this counter is persistent, never cleared during factory resets). Replace your Arduino once you reach 100 000 write cycles (with 6 hours EEPROM_INTERVAL you have more than 50 years lifespan).
63+
64+
**Ethernet Sockets**. Max number of usable sockets. See Limitations bellow. One socket is reserved for Modbus UDP, remaining sockets are shared between Modbus TCP and WebUI.
65+
6066
**Generate New MAC**. Generate new MAC address. First 3 bytes are fixed 90:A2:DA, remaining 3 bytes are true random.
6167

6268
<img src="/pics/modbus2.png" alt="02" style="zoom:100%;" />
@@ -73,7 +79,7 @@ Change settings of your Arduino-based Modbus RTU to Modbus TCP/UDP gateway via w
7379

7480
**Requests Queue**. Monitors internal request queue (buffer). The limits for bytes and for the number of requests stored in the queue can be configured in advanced settings.
7581

76-
**Modbus Statistics**. Counters for various errors. Insigned longs are used, rollover of counters is synchronized:
82+
**Modbus Statistics**.
7783
* **Slave Responded**. Slave responded with a valid Modbus RTU response within response timeout.
7884
* **Slave Responded with Error (Codes 1~8)**. Slave responded, but with an error. For the list of error codes see https://en.wikipedia.org/wiki/Modbus#Exception_responses.
7985
* **Gateway Overloaded (Code 10)**. Request queue is full (either the number of bytes stored or the number of requests stored). Request was dropped and the gateway responded with an error code 10.
@@ -148,21 +154,27 @@ Get the hardware (cheap clones from China are sufficient) and connect together:
148154
* **Arduino Nano, Uno or Mega** (and possibly other). On Mega you have to configure Serial in advanced settings in the sketch.
149155
* **W5100, W5200 or W5500 based Ethernet shield**. The ubiquitous W5100 shield for Uno/Mega is sufficient. If available, I recommend W5500 Ethernet Shield. !!! ENC28J60 will not work !!!
150156
* **TTL to RS485 module**:
151-
- with hardware automatic flow control (recommended, available on Aliexpress)<br>
152-
Arduino <-> Module<br>
153-
Tx1 <-> Tx<br>
154-
Rx0 <-> Rx
155-
- with flow controlled by pin (such as MAX485 module)<br>
156-
Arduino <-> MAX485<br>
157-
Tx1 <-> DI<br>
158-
Rx0 <-> RO<br>
157+
- recommended: TTL to RS485 module with hardware automatic flow control (available on Aliexpress)<br>
158+
Arduino <-> Module
159+
Tx1 <-> Tx
160+
Rx0 <-> Rx<br>
161+
162+
- not recommended: MAX485 module with flow controlled by pin (works but is vulnerable to burn outs)<br>
163+
Arduino <-> MAX485
164+
Tx1 <-> DI
165+
Rx0 <-> RO
159166
Pin 6 <-> DE,RE
160167

161-
Here is my setup:
168+
Here is my HW setup:
162169
Terminal shield + Arduino Nano + W5500 ethernet shield (RobotDyn) + TTL to RS485 module (HW automatic flow control)
163170
<img src="/pics/HW.jpg" alt="01" style="zoom:100%;" />
164171

165-
Download this repository (all *.ino files) and open arduino-modbus-rtu-tcp-gateway.ino in Arduino IDE. Download all required libraries (they are available in "library manager"). If you want, you can check the default factory settings (can be later changed via web interface) and advanced settings (can only be changed in the sketch). Compile and upload your program to Arduino. Connect your Arduino to ethernet, connect your Modbus RTU slaves to MAX485 module. Use your web browser to access the web interface on default IP http://192.168.1.254 Enjoy :-)
172+
You can either:
173+
- ** Download and flash my pre-compiled firmware** from "Releases".
174+
- **Compile your own firmware**. Download this repository (all *.ino files) and open arduino-modbus-rtu-tcp-gateway.ino in Arduino IDE. Download all required libraries (they are available in "library manager"). If you want, you can check the default factory settings (can be later changed via web interface) and advanced settings (can only be changed in the sketch). Compile and upload your program to Arduino.
175+
176+
Connect your Arduino to ethernet and use your web browser to access the web interface on default IP: http://192.168.1.254
177+
Enjoy :-)
166178

167179
## Where can I learn more about Modbus protocols?
168180

@@ -199,7 +211,7 @@ The number of used sockets is determined (by the Ethernet.h library) based on mi
199211

200212
#### Memory
201213

202-
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 the sketch.
214+
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.
203215

204216
## Version history
205217

arduino-modbus-rtu-tcp-gateway/01-interfaces.ino

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -54,29 +54,29 @@ void startSerial() {
5454
#endif /* RS485_CONTROL_PIN */
5555
}
5656

57-
// time to send 1 character over serial in microseconds
58-
unsigned long charTime() {
59-
byte bits = // number of bits per character (11 in default Modbus RTU settings)
57+
// number of bits per character (11 in default Modbus RTU settings)
58+
byte bitsPerChar() {
59+
byte bits =
6060
1 + // start bit
6161
(((localConfig.serialConfig & 0x06) >> 1) + 5) + // data bits
6262
(((localConfig.serialConfig & 0x08) >> 3) + 1); // stop bits
6363
if (((localConfig.serialConfig & 0x30) >> 4) > 1) bits += 1; // parity bit (if present)
64-
return (bits * 10000UL) / (unsigned long)localConfig.baud;
64+
return bits;
6565
}
6666

67-
// character timeout
67+
// character timeout in micros
6868
unsigned long charTimeOut() {
6969
if (localConfig.baud <= 192) {
70-
return 1.5 * charTime(); // inter-character time-out should be 1,5T
70+
return (15000UL * bitsPerChar()) / localConfig.baud; // inter-character time-out should be 1,5T
7171
} else {
7272
return 750;
7373
}
7474
}
7575

76-
// minimum frame delay
76+
// minimum frame delay in micros
7777
unsigned long frameDelay() {
7878
if (localConfig.baud <= 192) {
79-
return 3.5 * charTime(); // inter-frame delay should be 3,5T
79+
return (35000UL * bitsPerChar()) / localConfig.baud; // inter-frame delay should be 3,5T
8080
} else {
8181
return 1750; // 1750 μs
8282
}
@@ -88,17 +88,17 @@ void startEthernet() {
8888
digitalWrite(ETH_RESET_PIN, LOW);
8989
delay(25);
9090
digitalWrite(ETH_RESET_PIN, HIGH);
91-
delay(500);
91+
delay(ETH_RESET_DELAY);
9292
}
9393
byte mac[6];
9494
memcpy(mac, MAC_START, 3); // set first 3 bytes
9595
memcpy(mac + 3, localConfig.macEnd, 3); // set last 3 bytes
9696
#ifdef ENABLE_DHCP
97-
if (extraConfig.enableDhcp) {
97+
if (localConfig.enableDhcp) {
9898
dhcpSuccess = Ethernet.begin(mac);
9999
}
100-
if (!extraConfig.enableDhcp || dhcpSuccess == false) {
101-
Ethernet.begin(mac, localConfig.ip, extraConfig.dns, localConfig.gateway, localConfig.subnet);
100+
if (!localConfig.enableDhcp || dhcpSuccess == false) {
101+
Ethernet.begin(mac, localConfig.ip, localConfig.dns, localConfig.gateway, localConfig.subnet);
102102
}
103103
#else /* ENABLE_DHCP */
104104
Ethernet.begin(mac, localConfig.ip, {}, localConfig.gateway, localConfig.subnet); // No DNS
@@ -119,7 +119,7 @@ void (*resetFunc)(void) = 0; //declare reset function at address 0
119119

120120
#ifdef ENABLE_DHCP
121121
void maintainDhcp() {
122-
if (extraConfig.enableDhcp && dhcpSuccess == true) { // only call maintain if initial DHCP request by startEthernet was successfull
122+
if (localConfig.enableDhcp && dhcpSuccess == true) { // only call maintain if initial DHCP request by startEthernet was successfull
123123
uint8_t maintainResult = Ethernet.maintain();
124124
if (maintainResult == 1 || maintainResult == 3) { // renew failed or rebind failed
125125
dhcpSuccess = false;
@@ -148,37 +148,32 @@ void maintainUptime() {
148148
bool rollover() {
149149
// synchronize roll-over of run time, data counters and modbus stats to zero, at 0xFFFFFF00
150150
const unsigned long ROLLOVER = 0xFFFFFF00;
151-
for (byte i = 0; i < SLAVE_ERROR_0B_QUEUE; i++) { // there is no counter for SLAVE_ERROR_0B_QUEUE
151+
for (byte i = 0; i < ERROR_LAST; i++) {
152152
if (errorCount[i] > ROLLOVER) {
153153
return true;
154154
}
155155
}
156-
if (errorTcpCount > ROLLOVER || errorRtuCount > ROLLOVER || errorTimeoutCount > ROLLOVER) {
157-
return true;
158-
}
159156
#ifdef ENABLE_EXTRA_DIAG
160157
if (seconds > ROLLOVER) {
161158
return true;
162159
}
163-
if (serialTxCount > ROLLOVER || serialRxCount > ROLLOVER || ethTxCount > ROLLOVER || ethRxCount > ROLLOVER) {
164-
return true;
160+
for (byte i = 0; i < DATA_LAST; i++) {
161+
if (rtuCount[i] > ROLLOVER || ethCount[i] > ROLLOVER) {
162+
return true;
163+
}
165164
}
166165
#endif /* ENABLE_EXTRA_DIAG */
167166
return false;
168167
}
169168

170169
void resetStats() {
171170
memset(errorCount, 0, sizeof(errorCount));
172-
errorTcpCount = 0;
173-
errorRtuCount = 0;
174-
errorTimeoutCount = 0;
175171
#ifdef ENABLE_EXTRA_DIAG
176172
remaining_seconds = -(millis() / 1000);
177-
ethRxCount = 0;
178-
ethTxCount = 0;
179-
serialRxCount = 0;
180-
serialTxCount = 0;
173+
memset(rtuCount, 0, sizeof(rtuCount));
174+
memset(ethCount, 0, sizeof(ethCount));
181175
#endif /* ENABLE_EXTRA_DIAG */
176+
updateEeprom();
182177
}
183178

184179
void generateMac() {
@@ -192,13 +187,31 @@ void generateMac() {
192187
}
193188
}
194189

195-
190+
void updateEeprom() {
191+
eepromTimer.sleep(EEPROM_INTERVAL * 60UL * 60UL * 1000UL); // EEPROM_INTERVAL is in hours, sleep is in milliseconds!
192+
eepromWrites++; // we assume that at least some bytes are written to EEPROM during EEPROM.update or EEPROM.put
193+
int address = CONFIG_START;
194+
EEPROM.put(address, eepromWrites);
195+
address += sizeof(eepromWrites);
196+
EEPROM.put(address, VERSION[0]);
197+
address += 1;
198+
EEPROM.put(address, localConfig);
199+
address += sizeof(localConfig);
200+
EEPROM.put(address, errorCount);
201+
address += sizeof(errorCount);
202+
#ifdef ENABLE_EXTRA_DIAG
203+
EEPROM.put(address, rtuCount);
204+
address += sizeof(rtuCount);
205+
EEPROM.put(address, ethCount);
206+
address += sizeof(ethCount);
207+
#endif /* ENABLE_EXTRA_DIAG */
208+
}
196209

197210
#if MAX_SOCK_NUM == 8
198-
unsigned long lastSocketUse[MAX_SOCK_NUM] = { 0, 0, 0, 0, 0, 0, 0, 0 }; // +rs 03Feb2019 - records last interaction involving each socket to enable detecting sockets unused for longest time period
211+
unsigned long lastSocketUse[MAX_SOCK_NUM] = { 0, 0, 0, 0, 0, 0, 0, 0 };
199212
byte socketInQueue[MAX_SOCK_NUM] = { 0, 0, 0, 0, 0, 0, 0, 0 };
200213
#elif MAX_SOCK_NUM == 4
201-
unsigned long lastSocketUse[MAX_SOCK_NUM] = { 0, 0, 0, 0 }; // +rs 03Feb2019 - records last interaction involving each socket to enable detecting sockets unused for longest time period
214+
unsigned long lastSocketUse[MAX_SOCK_NUM] = { 0, 0, 0, 0 };
202215
byte socketInQueue[MAX_SOCK_NUM] = { 0, 0, 0, 0 };
203216
#endif
204217

@@ -356,7 +369,7 @@ ISR(WDT_vect) {
356369
#elif defined(__MK66FX1M0__)
357370
#define BOARD F("Teensy 3.6")
358371
#else
359-
#define BOARD F("Unknown board")
372+
#define BOARD F("Unknown Board")
360373
#endif
361374

362375
#else // --------------- Arduino ------------------
@@ -412,7 +425,7 @@ ISR(WDT_vect) {
412425
#elif defined(ARDUINO_ARC32_TOOLS)
413426
#define BOARD F("Arduino 101")
414427
#else
415-
#define BOARD F("Unknown board")
428+
#define BOARD F("Unknown Board")
416429
#endif
417430

418431
#endif

arduino-modbus-rtu-tcp-gateway/02-modbus-tcp.ino

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ void recvUdp() {
4343
unsigned int msgLength = Udp.parsePacket();
4444
if (msgLength) {
4545
#ifdef ENABLE_EXTRA_DIAG
46-
ethRxCount += msgLength;
46+
ethCount[DATA_RX] += msgLength;
4747
#endif /* ENABLE_EXTRA_DIAG */
4848
byte inBuffer[MODBUS_SIZE + 4]; // Modbus TCP frame is 4 bytes longer than Modbus RTU frame
4949
// Modbus TCP/UDP frame: [0][1] transaction ID, [2][3] protocol ID, [4][5] length and [6] unit ID (address)..... no CRC
@@ -72,8 +72,8 @@ void recvUdp() {
7272
}
7373
Udp.endPacket();
7474
#ifdef ENABLE_EXTRA_DIAG
75-
ethTxCount += 5;
76-
if (!localConfig.enableRtuOverTcp) ethTxCount += 4;
75+
ethCount[DATA_TX] += 5;
76+
if (!localConfig.enableRtuOverTcp) ethCount[DATA_TX] += 4;
7777
#endif /* ENABLE_EXTRA_DIAG */
7878
}
7979
}
@@ -82,7 +82,7 @@ void recvUdp() {
8282
void recvTcp(EthernetClient &client) {
8383
unsigned int msgLength = client.available();
8484
#ifdef ENABLE_EXTRA_DIAG
85-
ethRxCount += msgLength;
85+
ethCount[DATA_RX] += msgLength;
8686
#endif /* ENABLE_EXTRA_DIAG */
8787
byte inBuffer[MODBUS_SIZE + 4]; // Modbus TCP frame is 4 bytes longer than Modbus RTU frame
8888
// Modbus TCP/UDP frame: [0][1] transaction ID, [2][3] protocol ID, [4][5] length and [6] unit ID (address).....
@@ -113,8 +113,8 @@ void recvTcp(EthernetClient &client) {
113113
}
114114
client.write(outBuffer, i);
115115
#ifdef ENABLE_EXTRA_DIAG
116-
ethTxCount += 5;
117-
if (!localConfig.enableRtuOverTcp) ethTxCount += 4;
116+
ethCount[DATA_TX] += 5;
117+
if (!localConfig.enableRtuOverTcp) ethCount[DATA_TX] += 4;
118118
#endif /* ENABLE_EXTRA_DIAG */
119119
}
120120
}
@@ -152,12 +152,12 @@ byte checkRequest(byte inBuffer[], unsigned int msgLength, const uint32_t remote
152152
byte addressPos = 6 * !localConfig.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)
153153
if (localConfig.enableRtuOverTcp) { // check CRC for Modbus RTU over TCP/UDP
154154
if (checkCRC(inBuffer, msgLength) == false) {
155-
errorTcpCount++;
155+
errorCount[ERROR_TCP]++;
156156
return 0; // drop request and do not return any error code
157157
}
158158
} else { // check MBAP header structure for Modbus TCP/UDP
159159
if (inBuffer[2] != 0x00 || inBuffer[3] != 0x00 || inBuffer[4] != 0x00 || inBuffer[5] != msgLength - 6) {
160-
errorTcpCount++;
160+
errorCount[ERROR_TCP]++;
161161
return 0; // drop request and do not return any error code
162162
}
163163
}
@@ -215,29 +215,29 @@ void deleteRequest() // delete request from queue
215215
}
216216

217217
void clearQueue() {
218-
queueHeaders.clear(); // <- eats memory!
218+
queueHeaders.clear();
219219
queueData.clear();
220220
scanReqInQueue = false;
221221
priorityReqInQueue = false;
222222
memset(socketInQueue, 0, sizeof(socketInQueue));
223-
memset(stat[SLAVE_ERROR_0B_QUEUE], 0, sizeof(stat[SLAVE_ERROR_0B_QUEUE]));
224-
sendTimer.sleep(0);
223+
memset(slaveStatus[SLAVE_ERROR_0B_QUEUE], 0, sizeof(slaveStatus[SLAVE_ERROR_0B_QUEUE]));
224+
sendMicroTimer.sleep(0);
225225
}
226226

227227
bool getSlaveStatus(const uint8_t slave, const byte status) {
228228
if (slave >= MAX_SLAVES) return false; // error
229-
return (stat[status][slave / 8] & masks[slave & 7]) > 0;
229+
return (slaveStatus[status][slave / 8] & masks[slave & 7]) > 0;
230230
}
231231

232232
void setSlaveStatus(const uint8_t slave, byte status, const bool value, const bool isScan) {
233-
if (slave >= MAX_SLAVES) return; // error
233+
if (slave >= MAX_SLAVES || status > SLAVE_ERROR_0B_QUEUE) return; // error
234234
if (value == 0) {
235-
stat[status][slave / 8] &= ~masks[slave & 7];
235+
slaveStatus[status][slave / 8] &= ~masks[slave & 7];
236236
} else {
237-
for (byte i = 0; i < SLAVE_ERROR_LAST; i++) {
238-
stat[i][slave / 8] &= ~masks[slave & 7]; // set all other flags to false
237+
for (byte i = 0; i <= SLAVE_ERROR_0B_QUEUE; i++) {
238+
slaveStatus[i][slave / 8] &= ~masks[slave & 7]; // set all other flags to false, SLAVE_ERROR_0B_QUEUE is the last slave status
239239
}
240-
stat[status][slave / 8] |= masks[slave & 7];
240+
slaveStatus[status][slave / 8] |= masks[slave & 7];
241241
if (status != SLAVE_ERROR_0B_QUEUE && isScan == false) errorCount[status]++; // there is no counter for SLAVE_ERROR_0B_QUEUE, ignor scans in statistics
242242
}
243243
}

0 commit comments

Comments
 (0)